/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: * * Statistics Report Generator * * Copyright (c) 2011 Mark Watkins <jedimark@users.sourceforge.net> * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the Linux * distribution for more details. */ #include <QApplication> #include <cmath> #include "mainwindow.h" #include "statistics.h" #include "SleepLib/schema.h" extern MainWindow *mainwin; Statistics::Statistics(QObject *parent) : QObject(parent) { } QString htmlHeader() { QString address = PROFILE.user->address(); address.replace("\n", "<br/>"); // "a:link,a:visited { color: '#000020'; text-decoration: none; font-weight: bold;}" // "a:hover { background-color: inherit; color: red; text-decoration:none; font-weight: bold; }" return QString("<html><head>" "</head>" "<style type='text/css'>" "<!--h1,p,a,td,body { font-family: 'FreeSans', 'Sans Serif' } --/>" "p,a,td,body { font-size: 14px }" "</style>" "<link rel='stylesheet' type='text/css' href='qrc:/docs/tooltips.css' />" "<script type='text/javascript'>" "function ChangeColor(tableRow, highLight)" "{ tableRow.style.backgroundColor = highLight; }" "function Go(url) { throw(url); }" "</script>" "</head>" "<body leftmargin=0 topmargin=0 rightmargin=0>" "<div align=center><table cellpadding=3 cellspacing=0 border=0 width=100%>" //"<tr>" //"<td bgcolor='red' width=100% colspan=3 align=center><font color='yellow'><b>"+QObject::tr("Warning:")+"</b> "+ // #ifdef UNSTABLE_BUILD // QObject::tr("This is an unstable build so expect the possibility things will go wrong.")+"<br/>"+ // QObject::tr("Please report bugs you find here to SleepyHead's developer mailing list.")+ // #else // QObject::tr("This is a beta software and some functionality may not work as intended yet.")+"<br/>"+ // QObject::tr("Please report any bugs you find to SleepyHead's SourceForge page.")+ // #endif //"</font></td>" //"</tr><tr>" "<td>" + QString(QObject::tr("Name: %1, %2")).arg(PROFILE.user->lastName()).arg( PROFILE.user->firstName()) + "<br/>" + QString(QObject::tr("DOB: %1")).arg(PROFILE.user->DOB().toString()) + "<br/>" + QString(QObject::tr("Phone: %1")).arg(PROFILE.user->phone()) + "<br/>" + QString(QObject::tr("Email: %1")).arg(PROFILE.user->email()) + "<br/><br/>" + QObject::tr("Address:") + "<br/>" + address + "</td>" "<td align='right'>" "<h1>" + STR_TR_SleepyHead + "</h1><br/>" "<font size='+3'>" + QObject::tr("Usage Statistics") + "</font>" "</td>" "<td align='right' width=170px><img src='qrc:/icons/bob-v3.0.png' height=140px><br/>" "</td></tr></table>" "</div>" "<hr/>"); } QString htmlFooter() { return "<hr/><div align=center><font size='-1'><i>" + QString(QObject::tr("This report was generated by a pre-release version of SleepyHead (%1), <b>and has not been approved in any way for compliance or medical diagnostic purposes</b>.")).arg( FullVersionString) + "<br/><br/>" + QObject::tr("SleepyHead is free open-source software available from http://sourceforge.net/projects/SleepyHead") + "</i></i></div>" "</body></html>"; } QString formatTime(float time) { int hours = time; int seconds = time * 3600.0; int minutes = (seconds / 60) % 60; seconds %= 60; return QString().sprintf("%02i:%02i", hours, minutes); //,seconds); } EventDataType calcAHI(QDate start, QDate end) { EventDataType val = (p_profile->calcCount(CPAP_Obstructive, MT_CPAP, start, end) + p_profile->calcCount(CPAP_Hypopnea, MT_CPAP, start, end) + p_profile->calcCount(CPAP_ClearAirway, MT_CPAP, start, end) + p_profile->calcCount(CPAP_Apnea, MT_CPAP, start, end)); if (PROFILE.general->calculateRDI()) { val += p_profile->calcCount(CPAP_RERA, MT_CPAP, start, end); } EventDataType hours = p_profile->calcHours(MT_CPAP, start, end); if (hours > 0) { val /= hours; } else { val = 0; } return val; } EventDataType calcFL(QDate start, QDate end) { EventDataType val = (p_profile->calcCount(CPAP_FlowLimit, MT_CPAP, start, end)); EventDataType hours = p_profile->calcHours(MT_CPAP, start, end); if (hours > 0) { val /= hours; } else { val = 0; } return val; } struct RXChange { RXChange() { highlight = 0; machine = nullptr; } RXChange(const RXChange ©) { first = copy.first; last = copy.last; days = copy.days; ahi = copy.ahi; fl = copy.fl; mode = copy.mode; min = copy.min; max = copy.max; ps = copy.ps; pshi = copy.pshi; maxipap = copy.maxipap; machine = copy.machine; per1 = copy.per1; per2 = copy.per2; highlight = copy.highlight; weighted = copy.weighted; prelief = copy.prelief; prelset = copy.prelset; } QDate first; QDate last; int days; EventDataType ahi; EventDataType fl; CPAPMode mode; EventDataType min; EventDataType max; EventDataType ps; EventDataType pshi; EventDataType maxipap; EventDataType per1; EventDataType per2; EventDataType weighted; PRTypes prelief; Machine *machine; short prelset; short highlight; }; enum RXSortMode { RX_first, RX_last, RX_days, RX_ahi, RX_mode, RX_min, RX_max, RX_ps, RX_pshi, RX_maxipap, RX_per1, RX_per2, RX_weighted }; RXSortMode RXsort = RX_first; bool RXorder = false; bool operator<(const RXChange &c1, const RXChange &c2) { const RXChange *comp1 = &c1; const RXChange *comp2 = &c2; if (RXorder) { switch (RXsort) { case RX_ahi: return comp1->ahi < comp2->ahi; case RX_days: return comp1->days < comp2->days; case RX_first: return comp1->first < comp2->first; case RX_last: return comp1->last < comp2->last; case RX_mode: return comp1->mode < comp2->mode; case RX_min: return comp1->min < comp2->min; case RX_max: return comp1->max < comp2->max; case RX_ps: return comp1->ps < comp2->ps; case RX_pshi: return comp1->pshi < comp2->pshi; case RX_maxipap: return comp1->maxipap < comp2->maxipap; case RX_per1: return comp1->per1 < comp2->per1; case RX_per2: return comp1->per2 < comp2->per2; case RX_weighted: return comp1->weighted < comp2->weighted; }; } else { switch (RXsort) { case RX_ahi: return comp1->ahi > comp2->ahi; case RX_days: return comp1->days > comp2->days; case RX_first: return comp1->first > comp2->first; case RX_last: return comp1->last > comp2->last; case RX_mode: return comp1->mode > comp2->mode; case RX_min: return comp1->min > comp2->min; case RX_max: return comp1->max > comp2->max; case RX_ps: return comp1->ps > comp2->ps; case RX_pshi: return comp1->pshi > comp2->pshi; case RX_maxipap: return comp1->maxipap > comp2->maxipap; case RX_per1: return comp1->per1 > comp2->per1; case RX_per2: return comp1->per2 > comp2->per2; case RX_weighted: return comp1->weighted > comp2->weighted; }; } return true; } bool RXSort(const RXChange *comp1, const RXChange *comp2) { if (RXorder) { switch (RXsort) { case RX_ahi: return comp1->ahi < comp2->ahi; case RX_days: return comp1->days < comp2->days; case RX_first: return comp1->first < comp2->first; case RX_last: return comp1->last < comp2->last; case RX_mode: return comp1->mode < comp2->mode; case RX_min: return comp1->min < comp2->min; case RX_max: return comp1->max < comp2->max; case RX_ps: return comp1->ps < comp2->ps; case RX_pshi: return comp1->pshi < comp2->pshi; case RX_maxipap: return comp1->maxipap < comp2->maxipap; case RX_per1: return comp1->per1 < comp2->per1; case RX_per2: return comp1->per2 < comp2->per2; case RX_weighted: return comp1->weighted < comp2->weighted; }; } else { switch (RXsort) { case RX_ahi: return comp1->ahi > comp2->ahi; case RX_days: return comp1->days > comp2->days; case RX_first: return comp1->first > comp2->first; case RX_last: return comp1->last > comp2->last; case RX_mode: return comp1->mode > comp2->mode; case RX_min: return comp1->min > comp2->min; case RX_max: return comp1->max > comp2->max; case RX_ps: return comp1->ps > comp2->ps; case RX_pshi: return comp1->pshi > comp2->pshi; case RX_maxipap: return comp1->maxipap > comp2->maxipap; case RX_per1: return comp1->per1 > comp2->per1; case RX_per2: return comp1->per2 > comp2->per2; case RX_weighted: return comp1->weighted > comp2->weighted; }; } return true; } struct UsageData { UsageData() { ahi = 0; hours = 0; } UsageData(QDate d, EventDataType v, EventDataType h) { date = d; ahi = v; hours = h; } UsageData(const UsageData ©) { date = copy.date; ahi = copy.ahi; hours = copy.hours; } QDate date; EventDataType ahi; EventDataType hours; }; bool operator <(const UsageData &c1, const UsageData &c2) { if (c1.ahi < c2.ahi) { return true; } if ((c1.ahi == c2.ahi) && (c1.date > c2.date)) { return true; } return false; //return c1.value < c2.value; } QString Statistics::GenerateHTML() { QString html = htmlHeader(); QDate lastcpap = p_profile->LastGoodDay(MT_CPAP); QDate firstcpap = p_profile->FirstGoodDay(MT_CPAP); QDate cpapweek = lastcpap.addDays(-7); QDate cpapmonth = lastcpap.addDays(-30); QDate cpap6month = lastcpap.addMonths(-6); QDate cpapyear = lastcpap.addYears(-12); if (cpapweek < firstcpap) { cpapweek = firstcpap; } if (cpapmonth < firstcpap) { cpapmonth = firstcpap; } if (cpap6month < firstcpap) { cpap6month = firstcpap; } if (cpapyear < firstcpap) { cpapyear = firstcpap; } QList<Machine *> cpap_machines = PROFILE.GetMachines(MT_CPAP); QList<Machine *> oximeters = PROFILE.GetMachines(MT_OXIMETER); QList<Machine *> mach; mach.append(cpap_machines); mach.append(oximeters); if (mach.size() == 0) { html += "<table cellpadding=2 cellspacing=0 border=0 width=100% height=60%>"; html += "<tr><td align=center><h1>" + tr("Please Import Some Data") + "</h1><i>" + tr("SleepyHead is pretty much useless without it.") + "</i><br/><p>" + tr("It might be a good idea to check preferences first,</br>as there are some options that affect import.") + "</p><p>" + tr("First import can take a few minutes.") + "</p></td></tr></table>"; html += htmlFooter(); return html; } int cpapdays = PROFILE.countDays(MT_CPAP, firstcpap, lastcpap); int cpapweekdays = PROFILE.countDays(MT_CPAP, cpapweek, lastcpap); int cpapmonthdays = PROFILE.countDays(MT_CPAP, cpapmonth, lastcpap); int cpapyeardays = PROFILE.countDays(MT_CPAP, cpapyear, lastcpap); int cpap6monthdays = PROFILE.countDays(MT_CPAP, cpap6month, lastcpap); CPAPMode cpapmode = (CPAPMode)(int)p_profile->calcSettingsMax(CPAP_Mode, MT_CPAP, firstcpap, lastcpap); float percentile = PROFILE.general->prefCalcPercentile() / 100.0; // int mididx=PROFILE.general->prefCalcMiddle(); // SummaryType ST_mid; // if (mididx==0) ST_mid=ST_PERC; // if (mididx==1) ST_mid=ST_WAVG; // if (mididx==2) ST_mid=ST_AVG; QString ahitxt; if (PROFILE.general->calculateRDI()) { ahitxt = STR_TR_RDI; } else { ahitxt = STR_TR_AHI; } int decimals = 2; html += "<div align=center>"; html += QString("<table cellpadding=2 cellspacing=0 border=1 width=90%>"); if (cpapdays == 0) { html += "<tr><td colspan=6 align=center>" + tr("No CPAP Machine Data Imported") + "</td></tr>"; } else { html += QString("<tr><td colspan=6 align=center><b>") + tr("CPAP Statistics as of") + QString(" %1</b></td></tr>").arg(lastcpap.toString(Qt::SystemLocaleLongDate)); if (cpap_machines.size() > 0) { // html+=QString("<tr><td colspan=6 align=center><b>%1</b></td></tr>").arg(tr("CPAP Statistics")); if (!cpapdays) { html += QString("<tr><td colspan=6 align=center><b>%1</b></td></tr>").arg( tr("No CPAP data available.")); } else if (cpapdays == 1) { html += QString("<tr><td colspan=6 align=center>%1</td></tr>").arg(QString( tr("%1 day of CPAP Data, on %2.")).arg(cpapdays).arg(firstcpap.toString( Qt::SystemLocaleShortDate))); } else { html += QString("<tr><td colspan=6 align=center>%1</td></tr>").arg(QString( tr("%1 days of CPAP Data, between %2 and %3")).arg(cpapdays).arg(firstcpap.toString( Qt::SystemLocaleShortDate)).arg(lastcpap.toString(Qt::SystemLocaleShortDate))); } html += QString("<tr><td><b>%1</b></td><td><b>%2</b></td><td><b>%3</b></td><td><b>%4</b></td><td><b>%5</b></td><td><b>%6</td></tr>") .arg(tr("Details")).arg(tr("Most Recent")).arg(tr("Last 7 Days")).arg(tr("Last 30 Days")).arg( tr("Last 6 months")).arg(tr("Last Year")); html += QString("<tr><td>%1</td><td>%2</td><td>%3</td><td>%4</td><td>%5</td><td>%6</td></tr>") .arg(ahitxt) .arg(calcAHI(lastcpap, lastcpap), 0, 'f', decimals) .arg(calcAHI(cpapweek, lastcpap), 0, 'f', decimals) .arg(calcAHI(cpapmonth, lastcpap), 0, 'f', decimals) .arg(calcAHI(cpap6month, lastcpap), 0, 'f', decimals) .arg(calcAHI(cpapyear, lastcpap), 0, 'f', decimals); if (PROFILE.calcCount(CPAP_RERA, MT_CPAP, cpapyear, lastcpap)) { html += QString("<tr><td>%1</td><td>%2</td><td>%3</td><td>%4</td><td>%5</td><td>%6</td></tr>") .arg(tr("RERA Index")) .arg(PROFILE.calcCount(CPAP_RERA, MT_CPAP, lastcpap, lastcpap) / PROFILE.calcHours(MT_CPAP, lastcpap, lastcpap), 0, 'f', decimals) .arg(PROFILE.calcCount(CPAP_RERA, MT_CPAP, cpapweek, lastcpap) / PROFILE.calcHours(MT_CPAP, cpapweek, lastcpap), 0, 'f', decimals) .arg(PROFILE.calcCount(CPAP_RERA, MT_CPAP, cpapmonth, lastcpap) / PROFILE.calcHours(MT_CPAP, cpapmonth, lastcpap), 0, 'f', decimals) .arg(PROFILE.calcCount(CPAP_RERA, MT_CPAP, cpap6month, lastcpap) / PROFILE.calcHours(MT_CPAP, cpap6month, lastcpap), 0, 'f', decimals) .arg(PROFILE.calcCount(CPAP_RERA, MT_CPAP, cpapyear, lastcpap) / PROFILE.calcHours(MT_CPAP, cpapyear, lastcpap), 0, 'f', decimals); } if (PROFILE.calcCount(CPAP_FlowLimit, MT_CPAP, cpapyear, lastcpap)) { html += QString("<tr><td>%1</td><td>%2</td><td>%3</td><td>%4</td><td>%5</td><td>%6</td></tr>") .arg(tr("Flow Limit Index")) .arg(PROFILE.calcCount(CPAP_FlowLimit, MT_CPAP, lastcpap, lastcpap) / PROFILE.calcHours(MT_CPAP, lastcpap, lastcpap), 0, 'f', decimals) .arg(PROFILE.calcCount(CPAP_FlowLimit, MT_CPAP, cpapweek, lastcpap) / PROFILE.calcHours(MT_CPAP, cpapweek, lastcpap), 0, 'f', decimals) .arg(PROFILE.calcCount(CPAP_FlowLimit, MT_CPAP, cpapmonth, lastcpap) / PROFILE.calcHours(MT_CPAP, cpapmonth, lastcpap), 0, 'f', decimals) .arg(PROFILE.calcCount(CPAP_FlowLimit, MT_CPAP, cpap6month, lastcpap) / PROFILE.calcHours(MT_CPAP, cpap6month, lastcpap), 0, 'f', decimals) .arg(PROFILE.calcCount(CPAP_FlowLimit, MT_CPAP, cpapyear, lastcpap) / PROFILE.calcHours(MT_CPAP, cpapyear, lastcpap), 0, 'f', decimals); } html += QString("<tr><td>%1</td><td>%2</td><td>%3</td><td>%4</td><td>%5</td><td>%6</td></tr>") .arg(tr("Hours per Night")) .arg(formatTime(p_profile->calcHours(MT_CPAP))) .arg(formatTime(p_profile->calcHours(MT_CPAP, cpapweek, lastcpap) / float(cpapweekdays))) .arg(formatTime(p_profile->calcHours(MT_CPAP, cpapmonth, lastcpap) / float(cpapmonthdays))) .arg(formatTime(p_profile->calcHours(MT_CPAP, cpap6month, lastcpap) / float(cpap6monthdays))) .arg(formatTime(p_profile->calcHours(MT_CPAP, cpapyear, lastcpap) / float(cpapyeardays))); if (cpapmode >= MODE_BIPAP) { html += QString("<tr><td>%1</td><td>%2</td><td>%3</td><td>%4</td><td>%5</td><td>%6</td></tr>") .arg(tr("Min EPAP")) .arg(p_profile->calcMin(CPAP_EPAP, MT_CPAP), 0, 'f', decimals) .arg(p_profile->calcMin(CPAP_EPAP, MT_CPAP, cpapweek, lastcpap), 0, 'f', decimals) .arg(p_profile->calcMin(CPAP_EPAP, MT_CPAP, cpapmonth, lastcpap), 0, 'f', decimals) .arg(p_profile->calcMin(CPAP_EPAP, MT_CPAP, cpap6month, lastcpap), 0, 'f', decimals) .arg(p_profile->calcMin(CPAP_EPAP, MT_CPAP, cpapyear, lastcpap), 0, 'f', decimals); html += QString("<tr><td>%1</td><td>%2</td><td>%3</td><td>%4</td><td>%5</td><td>%6</td></tr>") .arg(QString("%1% " + STR_TR_EPAP).arg(percentile * 100.0, 0, 'f', 0)) .arg(p_profile->calcPercentile(CPAP_EPAP, percentile, MT_CPAP), 0, 'f', decimals) .arg(p_profile->calcPercentile(CPAP_EPAP, percentile, MT_CPAP, cpapweek, lastcpap), 0, 'f', decimals) .arg(p_profile->calcPercentile(CPAP_EPAP, percentile, MT_CPAP, cpapmonth, lastcpap), 0, 'f', decimals) .arg(p_profile->calcPercentile(CPAP_EPAP, percentile, MT_CPAP, cpap6month, lastcpap), 0, 'f', decimals) .arg(p_profile->calcPercentile(CPAP_EPAP, percentile, MT_CPAP, cpapyear, lastcpap), 0, 'f', decimals); html += QString("<tr><td>%1</td><td>%2</td><td>%3</td><td>%4</td><td>%5</td><td>%6</td></tr>") .arg(tr("Max IPAP")) .arg(p_profile->calcMax(CPAP_IPAP, MT_CPAP), 0, 'f', decimals) .arg(p_profile->calcMax(CPAP_IPAP, MT_CPAP, cpapweek, lastcpap), 0, 'f', decimals) .arg(p_profile->calcMax(CPAP_IPAP, MT_CPAP, cpapmonth, lastcpap), 0, 'f', decimals) .arg(p_profile->calcMax(CPAP_IPAP, MT_CPAP, cpap6month, lastcpap), 0, 'f', decimals) .arg(p_profile->calcMax(CPAP_IPAP, MT_CPAP, cpapyear, lastcpap), 0, 'f', decimals); html += QString("<tr><td>%1</td><td>%2</td><td>%3</td><td>%4</td><td>%5</td><td>%6</td></tr>") .arg(QString("%1% " + STR_TR_IPAP).arg(percentile * 100.0, 0, 'f', 0)) .arg(p_profile->calcPercentile(CPAP_IPAP, percentile, MT_CPAP), 0, 'f', decimals) .arg(p_profile->calcPercentile(CPAP_IPAP, percentile, MT_CPAP, cpapweek, lastcpap), 0, 'f', decimals) .arg(p_profile->calcPercentile(CPAP_IPAP, percentile, MT_CPAP, cpapmonth, lastcpap), 0, 'f', decimals) .arg(p_profile->calcPercentile(CPAP_IPAP, percentile, MT_CPAP, cpap6month, lastcpap), 0, 'f', decimals) .arg(p_profile->calcPercentile(CPAP_IPAP, percentile, MT_CPAP, cpapyear, lastcpap), 0, 'f', decimals); } else if (cpapmode >= MODE_APAP) { html += QString("<tr><td>%1</td><td>%2</td><td>%3</td><td>%4</td><td>%5</td><td>%6</td></tr>") .arg(tr("Average Pressure")) .arg(p_profile->calcWavg(CPAP_Pressure, MT_CPAP), 0, 'f', decimals) .arg(p_profile->calcWavg(CPAP_Pressure, MT_CPAP, cpapweek, lastcpap), 0, 'f', decimals) .arg(p_profile->calcWavg(CPAP_Pressure, MT_CPAP, cpapmonth, lastcpap), 0, 'f', decimals) .arg(p_profile->calcWavg(CPAP_Pressure, MT_CPAP, cpap6month, lastcpap), 0, 'f', decimals) .arg(p_profile->calcWavg(CPAP_Pressure, MT_CPAP, cpapyear, lastcpap), 0, 'f', decimals); html += QString("<tr><td>%1</td><td>%2</td><td>%3</td><td>%4</td><td>%5</td><td>%6</td></tr>") .arg(tr("%1% Pressure").arg(percentile * 100.0, 0, 'f', 0)) .arg(p_profile->calcPercentile(CPAP_Pressure, percentile, MT_CPAP), 0, 'f', decimals) .arg(p_profile->calcPercentile(CPAP_Pressure, percentile, MT_CPAP, cpapweek, lastcpap), 0, 'f', decimals) .arg(p_profile->calcPercentile(CPAP_Pressure, percentile, MT_CPAP, cpapmonth, lastcpap), 0, 'f', decimals) .arg(p_profile->calcPercentile(CPAP_Pressure, percentile, MT_CPAP, cpap6month, lastcpap), 0, 'f', decimals) .arg(p_profile->calcPercentile(CPAP_Pressure, percentile, MT_CPAP, cpapyear, lastcpap), 0, 'f', decimals); } else { html += QString("<tr><td>%1</td><td>%2</td><td>%3</td><td>%4</td><td>%5</td><td>%6</td></tr>") .arg(tr("Pressure")) .arg(p_profile->calcSettingsMin(CPAP_Pressure, MT_CPAP), 0, 'f', decimals) .arg(p_profile->calcSettingsMin(CPAP_Pressure, MT_CPAP, cpapweek, lastcpap), 0, 'f', decimals) .arg(p_profile->calcSettingsMin(CPAP_Pressure, MT_CPAP, cpapmonth, lastcpap), 0, 'f', decimals) .arg(p_profile->calcSettingsMin(CPAP_Pressure, MT_CPAP, cpap6month, lastcpap), 0, 'f', decimals) .arg(p_profile->calcSettingsMin(CPAP_Pressure, MT_CPAP, cpapyear, lastcpap), 0, 'f', decimals); } //html+="<tr><td colspan=6>TODO: 90% pressure.. Any point showing if this is all CPAP?</td></tr>"; ChannelID leak; if (p_profile->calcCount(CPAP_LeakTotal, MT_CPAP, cpapyear, lastcpap) > 0) { leak = CPAP_LeakTotal; } else { leak = CPAP_Leak; } html += QString("<tr><td>%1</td><td>%2</td><td>%3</td><td>%4</td><td>%5</td><td>%6</td></tr>") .arg(tr("Average %1").arg(schema::channel[leak].label())) .arg(p_profile->calcWavg(leak, MT_CPAP), 0, 'f', decimals) .arg(p_profile->calcWavg(leak, MT_CPAP, cpapweek, lastcpap), 0, 'f', decimals) .arg(p_profile->calcWavg(leak, MT_CPAP, cpapmonth, lastcpap), 0, 'f', decimals) .arg(p_profile->calcWavg(leak, MT_CPAP, cpap6month, lastcpap), 0, 'f', decimals) .arg(p_profile->calcWavg(leak, MT_CPAP, cpapyear, lastcpap), 0, 'f', decimals); html += QString("<tr><td>%1</td><td>%2</td><td>%3</td><td>%4</td><td>%5</td><td>%6</td></tr>") .arg(tr("%1% %2").arg(percentile * 100.0f, 0, 'f', 0).arg(schema::channel[leak].label())) .arg(p_profile->calcPercentile(leak, percentile, MT_CPAP), 0, 'f', decimals) .arg(p_profile->calcPercentile(leak, percentile, MT_CPAP, cpapweek, lastcpap), 0, 'f', decimals) .arg(p_profile->calcPercentile(leak, percentile, MT_CPAP, cpapmonth, lastcpap), 0, 'f', decimals) .arg(p_profile->calcPercentile(leak, percentile, MT_CPAP, cpap6month, lastcpap), 0, 'f', decimals) .arg(p_profile->calcPercentile(leak, percentile, MT_CPAP, cpapyear, lastcpap), 0, 'f', decimals); } } int oxisize = oximeters.size(); if (oxisize > 0) { QDate lastoxi = p_profile->LastGoodDay(MT_OXIMETER); QDate firstoxi = p_profile->FirstGoodDay(MT_OXIMETER); int days = PROFILE.countDays(MT_OXIMETER, firstoxi, lastoxi); if (days > 0) { html += QString("<tr><td colspan=6 align=center><b>%1</b></td></tr>").arg(tr("Oximetry Statistics")); if (days == 1) { html += QString("<tr><td colspan=6 align=center>%1</td></tr>").arg(QString( tr("%1 day of Oximetry Data, on %2.")).arg(days).arg(firstoxi.toString( Qt::SystemLocaleShortDate))); } else { html += QString("<tr><td colspan=6 align=center>%1</td></tr>").arg(QString( tr("%1 days of Oximetry Data, between %2 and %3")).arg(days).arg(firstoxi.toString( Qt::SystemLocaleShortDate)).arg(lastoxi.toString(Qt::SystemLocaleShortDate))); } html += QString("<tr><td><b>%1</b></td><td><b>%2</b></td><td><b>%3</b></td><td><b>%4</b></td><td><b>%5</b></td><td><b>%6</td></tr>") .arg(tr("Details")).arg(tr("Most Recent")).arg(tr("Last 7 Days")).arg(tr("Last 30 Days")).arg( tr("Last 6 months")).arg(tr("Last Year")); QDate oxiweek = lastoxi.addDays(-7); QDate oximonth = lastoxi.addDays(-30); QDate oxi6month = lastoxi.addMonths(-6); QDate oxiyear = lastoxi.addYears(-12); if (oxiweek < firstoxi) { oxiweek = firstoxi; } if (oximonth < firstoxi) { oximonth = firstoxi; } if (oxi6month < firstoxi) { oxi6month = firstoxi; } if (oxiyear < firstoxi) { oxiyear = firstoxi; } html += QString("<tr><td>%1</td><td>%2</td><td>%3</td><td>%4</td><td>%5</td><td>%6</td></tr>") .arg(tr("Average SpO2")) .arg(p_profile->calcWavg(OXI_SPO2, MT_OXIMETER), 0, 'f', decimals) .arg(p_profile->calcWavg(OXI_SPO2, MT_OXIMETER, oxiweek, lastoxi), 0, 'f', decimals) .arg(p_profile->calcWavg(OXI_SPO2, MT_OXIMETER, oximonth, lastoxi), 0, 'f', decimals) .arg(p_profile->calcWavg(OXI_SPO2, MT_OXIMETER, oxi6month, lastoxi), 0, 'f', decimals) .arg(p_profile->calcWavg(OXI_SPO2, MT_OXIMETER, oxiyear, lastoxi), 0, 'f', decimals); html += QString("<tr><td>%1</td><td>%2</td><td>%3</td><td>%4</td><td>%5</td><td>%6</td></tr>") .arg(tr("Minimum SpO2")) .arg(p_profile->calcMin(OXI_SPO2, MT_OXIMETER), 0, 'f', decimals) .arg(p_profile->calcMin(OXI_SPO2, MT_OXIMETER, oxiweek, lastoxi), 0, 'f', decimals) .arg(p_profile->calcMin(OXI_SPO2, MT_OXIMETER, oximonth, lastoxi), 0, 'f', decimals) .arg(p_profile->calcMin(OXI_SPO2, MT_OXIMETER, oxi6month, lastoxi), 0, 'f', decimals) .arg(p_profile->calcMin(OXI_SPO2, MT_OXIMETER, oxiyear, lastoxi), 0, 'f', decimals); html += QString("<tr><td>%1</td><td>%2</td><td>%3</td><td>%4</td><td>%5</td><td>%6</td></tr>") .arg(tr("SpO2 Events / Hour")) .arg(p_profile->calcCount(OXI_SPO2Drop, MT_OXIMETER) / p_profile->calcHours(MT_OXIMETER), 0, 'f', decimals) .arg(p_profile->calcCount(OXI_SPO2Drop, MT_OXIMETER, oxiweek, lastoxi) / p_profile->calcHours(MT_OXIMETER, oxiweek, lastoxi), 0, 'f', decimals) .arg(p_profile->calcCount(OXI_SPO2Drop, MT_OXIMETER, oximonth, lastoxi) / p_profile->calcHours(MT_OXIMETER, oximonth, lastoxi), 0, 'f', decimals) .arg(p_profile->calcCount(OXI_SPO2Drop, MT_OXIMETER, oxi6month, lastoxi) / p_profile->calcHours(MT_OXIMETER, oxi6month, lastoxi), 0, 'f', decimals) .arg(p_profile->calcCount(OXI_SPO2Drop, MT_OXIMETER, oxiyear, lastoxi) / p_profile->calcHours(MT_OXIMETER, oxiyear, lastoxi), 0, 'f', decimals); html += QString("<tr><td>%1</td><td>%2\%</td><td>%3\%</td><td>%4\%</td><td>%5\%</td><td>%6\%</td></tr>") .arg(tr("% of time in SpO2 Events")) .arg(100.0 / p_profile->calcHours(MT_OXIMETER) * p_profile->calcSum(OXI_SPO2Drop, MT_OXIMETER) / 3600.0, 0, 'f', decimals) .arg(100.0 / p_profile->calcHours(MT_OXIMETER, oxiweek, lastoxi) * p_profile->calcSum(OXI_SPO2Drop, MT_OXIMETER, oxiweek, lastoxi) / 3600.0, 0, 'f', decimals) .arg(100.0 / p_profile->calcHours(MT_OXIMETER, oximonth, lastoxi) * p_profile->calcSum(OXI_SPO2Drop, MT_OXIMETER, oximonth, lastoxi) / 3600.0, 0, 'f', decimals) .arg(100.0 / p_profile->calcHours(MT_OXIMETER, oxi6month, lastoxi) * p_profile->calcSum(OXI_SPO2Drop, MT_OXIMETER, oxi6month, lastoxi) / 3600.0, 0, 'f',decimals) .arg(100.0 / p_profile->calcHours(MT_OXIMETER, oxiyear, lastoxi) * p_profile->calcSum(OXI_SPO2Drop, MT_OXIMETER, oxiyear, lastoxi) / 3600.0, 0, 'f', decimals); html += QString("<tr><td>%1</td><td>%2</td><td>%3</td><td>%4</td><td>%5</td><td>%6</td></tr>") .arg(tr("Average Pulse Rate")) .arg(p_profile->calcWavg(OXI_Pulse, MT_OXIMETER), 0, 'f', decimals) .arg(p_profile->calcWavg(OXI_Pulse, MT_OXIMETER, oxiweek, lastoxi), 0, 'f', decimals) .arg(p_profile->calcWavg(OXI_Pulse, MT_OXIMETER, oximonth, lastoxi), 0, 'f', decimals) .arg(p_profile->calcWavg(OXI_Pulse, MT_OXIMETER, oxi6month, lastoxi), 0, 'f', decimals) .arg(p_profile->calcWavg(OXI_Pulse, MT_OXIMETER, oxiyear, lastoxi), 0, 'f', decimals); html += QString("<tr><td>%1</td><td>%2</td><td>%3</td><td>%4</td><td>%5</td><td>%6</td></tr>") .arg(tr("Minimum Pulse Rate")) .arg(p_profile->calcMin(OXI_Pulse, MT_OXIMETER), 0, 'f', decimals) .arg(p_profile->calcMin(OXI_Pulse, MT_OXIMETER, oxiweek, lastoxi), 0, 'f', decimals) .arg(p_profile->calcMin(OXI_Pulse, MT_OXIMETER, oximonth, lastoxi), 0, 'f', decimals) .arg(p_profile->calcMin(OXI_Pulse, MT_OXIMETER, oxi6month, lastoxi), 0, 'f', decimals) .arg(p_profile->calcMin(OXI_Pulse, MT_OXIMETER, oxiyear, lastoxi), 0, 'f', decimals); html += QString("<tr><td>%1</td><td>%2</td><td>%3</td><td>%4</td><td>%5</td><td>%6</td></tr>") .arg(tr("Maximum Pulse Rate")) .arg(p_profile->calcMax(OXI_Pulse, MT_OXIMETER), 0, 'f', decimals) .arg(p_profile->calcMax(OXI_Pulse, MT_OXIMETER, oxiweek, lastoxi), 0, 'f', decimals) .arg(p_profile->calcMax(OXI_Pulse, MT_OXIMETER, oximonth, lastoxi), 0, 'f', decimals) .arg(p_profile->calcMax(OXI_Pulse, MT_OXIMETER, oxi6month, lastoxi), 0, 'f', decimals) .arg(p_profile->calcMax(OXI_Pulse, MT_OXIMETER, oxiyear, lastoxi), 0, 'f', decimals); html += QString("<tr><td>%1</td><td>%2</td><td>%3</td><td>%4</td><td>%5</td><td>%6</td></tr>") .arg(tr("Pulse Change Events / Hour")) .arg(p_profile->calcCount(OXI_PulseChange, MT_OXIMETER) / p_profile->calcHours(MT_OXIMETER), 0, 'f', decimals) .arg(p_profile->calcCount(OXI_PulseChange, MT_OXIMETER, oxiweek, lastoxi) / p_profile->calcHours(MT_OXIMETER, oxiweek, lastoxi), 0, 'f', decimals) .arg(p_profile->calcCount(OXI_PulseChange, MT_OXIMETER, oximonth, lastoxi) / p_profile->calcHours(MT_OXIMETER, oximonth, lastoxi), 0, 'f', decimals) .arg(p_profile->calcCount(OXI_PulseChange, MT_OXIMETER, oxi6month, lastoxi) / p_profile->calcHours(MT_OXIMETER, oxi6month, lastoxi), 0, 'f', decimals) .arg(p_profile->calcCount(OXI_PulseChange, MT_OXIMETER, oxiyear, lastoxi) / p_profile->calcHours(MT_OXIMETER, oxiyear, lastoxi), 0, 'f', decimals); } } html += "</table>"; html += "</div>"; QList<UsageData> AHI; //QDate bestAHIdate, worstAHIdate; //EventDataType bestAHI=999.0, worstAHI=0; if (cpapdays > 0) { QDate first, last = lastcpap; CPAPMode mode = MODE_UNKNOWN, cmode = MODE_UNKNOWN; EventDataType cmin = 0, cmax = 0, cps = 0, cpshi = 0, cmaxipap = 0, min = 0, max = 0, maxipap = 0, ps = 0, pshi = 0; Machine *mach = nullptr, *lastmach = nullptr; PRTypes lastpr = PR_UNKNOWN, prelief = PR_UNKNOWN; short prelset = 0, lastprelset = -1; QDate date = lastcpap; Day *day; bool lastchanged = false; QVector<RXChange> rxchange; EventDataType hours; int compliant = 0; do { day = PROFILE.GetGoodDay(date, MT_CPAP); if (day) { lastchanged = false; hours = day->hours(); if (hours > PROFILE.cpap->complianceHours()) { compliant++; } EventDataType ahi = day->count(CPAP_Obstructive) + day->count(CPAP_Hypopnea) + day->count( CPAP_Apnea) + day->count(CPAP_ClearAirway); if (PROFILE.general->calculateRDI()) { ahi += day->count(CPAP_RERA); } ahi /= hours; AHI.push_back(UsageData(date, ahi, hours)); prelief = (PRTypes)(int)round(day->settings_wavg(CPAP_PresReliefType)); prelset = round(day->settings_wavg(CPAP_PresReliefSet)); mode = (CPAPMode)(int)round(day->settings_wavg(CPAP_Mode)); mach = day->machine; min = max = ps = pshi = maxipap = 0; if (mode == MODE_CPAP) { min = day->settings_min(CPAP_Pressure); } else if (mode < MODE_BIPAP) { min = day->settings_min(CPAP_PressureMin); max = day->settings_max(CPAP_PressureMax); } else { // BIPAP or ASV machines // min & max hold EPAP if (day->settingExists(CPAP_EPAPLo)) { min = day->settings_min(CPAP_EPAPLo); } else if (day->settingExists(CPAP_EPAP)) { max = min = day->settings_min(CPAP_EPAP); } if (day->settingExists(CPAP_EPAPHi)) { max = day->settings_min(CPAP_EPAPHi); } if (day->settingExists(CPAP_PSMin)) { ps = day->settings_min(CPAP_PSMin); } else if (day->settingExists(CPAP_PS)) { pshi = ps = day->settings_min(CPAP_PS); } if (day->settingExists(CPAP_PSMax)) { pshi = day->settings_max(CPAP_PSMax); } if (day->settingExists(CPAP_IPAPHi)) { maxipap = day->settings_max(CPAP_IPAPHi); } } if ((mode != cmode) || (min != cmin) || (max != cmax) || (ps != cps) || (pshi != cpshi) || (maxipap != cmaxipap) || (mach != lastmach) || (prelset != lastprelset)) { if ((cmode != MODE_UNKNOWN) && (lastmach != nullptr)) { first = date.addDays(1); int days = PROFILE.countDays(MT_CPAP, first, last); RXChange rx; rx.first = first; rx.last = last; rx.days = days; rx.ahi = calcAHI(first, last); rx.fl = calcFL(first, last); rx.mode = cmode; rx.min = cmin; rx.max = cmax; rx.ps = cps; rx.pshi = cpshi; rx.maxipap = cmaxipap; rx.prelief = lastpr; rx.prelset = lastprelset; rx.machine = lastmach; if (mode < MODE_BIPAP) { rx.per1 = p_profile->calcPercentile(CPAP_Pressure, percentile, MT_CPAP, first, last); rx.per2 = 0; } else if (mode < MODE_ASV) { rx.per1 = p_profile->calcPercentile(CPAP_EPAP, percentile, MT_CPAP, first, last); rx.per2 = p_profile->calcPercentile(CPAP_IPAP, percentile, MT_CPAP, first, last); } else { rx.per1 = p_profile->calcPercentile(CPAP_EPAP, percentile, MT_CPAP, first, last); rx.per2 = p_profile->calcPercentile(CPAP_IPAP, percentile, MT_CPAP, first, last); } rx.weighted = float(rx.days) / float(cpapdays) * rx.ahi; rxchange.push_back(rx); } cmode = mode; cmin = min; cmax = max; cps = ps; cpshi = pshi; cmaxipap = maxipap; lastpr = prelief; lastprelset = prelset; last = date; lastmach = mach; lastchanged = true; } } date = date.addDays(-1); } while (date >= firstcpap); // Sort list by AHI qSort(AHI); lastchanged = false; if (!lastchanged && (mach != nullptr)) { // last=date.addDays(1); first = firstcpap; int days = PROFILE.countDays(MT_CPAP, first, last); RXChange rx; rx.first = first; rx.last = last; rx.days = days; rx.ahi = calcAHI(first, last); rx.fl = calcFL(first, last); rx.mode = mode; rx.min = min; rx.max = max; rx.ps = ps; rx.pshi = pshi; rx.maxipap = maxipap; rx.prelief = prelief; rx.prelset = prelset; rx.machine = mach; if (mode < MODE_BIPAP) { rx.per1 = p_profile->calcPercentile(CPAP_Pressure, percentile, MT_CPAP, first, last); rx.per2 = 0; } else if (mode < MODE_ASV) { rx.per1 = p_profile->calcPercentile(CPAP_EPAP, percentile, MT_CPAP, first, last); rx.per2 = p_profile->calcPercentile(CPAP_IPAP, percentile, MT_CPAP, first, last); } else { rx.per1 = p_profile->calcPercentile(CPAP_EPAP, percentile, MT_CPAP, first, last); rx.per2 = p_profile->calcPercentile(CPAP_IPAPHi, percentile, MT_CPAP, first, last); } rx.weighted = float(rx.days) / float(cpapdays); //rx.weighted=float(days)*rx.ahi; rxchange.push_back(rx); } int rxthresh = 5; QVector<RXChange *> tmpRX; for (int i = 0; i < rxchange.size(); i++) { RXChange &rx = rxchange[i]; if (rx.days > rxthresh) { tmpRX.push_back(&rx); } } QString recbox = "<html><head><style type='text/css'>" "p,a,td,body { font-family: '" + QApplication::font().family() + "'; }" "p,a,td,body { font-size: " + QString::number(QApplication::font().pointSize() + 2) + "px; }" "a:link,a:visited { color: inherit; text-decoration: none; }" //font-weight: normal; "a:hover { background-color: inherit; color: white; text-decoration:none; font-weight: bold; }" "</style></head><body>"; recbox += "<table width=100% cellpadding=1 cellspacing=0>"; int numdays = AHI.size(); if (numdays > 1) { int z = numdays / 2; if (z > 4) { z = 4; } recbox += QString("<tr><td colspan=2 align=center><b>%1</b></td></tr>").arg( tr("Usage Information")); recbox += QString("<tr><td>%1</td><td align=right>%2</td></tr>").arg(tr("Total Days")).arg( numdays); if (PROFILE.cpap->showComplianceInfo()) { recbox += QString("<tr><td>%1</td><td align=right>%2</td></tr>").arg(tr("Compliant Days")).arg( compliant); } int highahi = 0; for (int i = 0; i < numdays; i++) { if (AHI.at(i).ahi > 5.0) { highahi++; } } recbox += QString("<tr><td>%1</td><td align=right>%2</td></tr>").arg(tr("Days AHI >5.0")).arg( highahi); recbox += QString("<tr><td colspan=2> </td></tr>"); recbox += QString("<tr><td colspan=2 align=center><b>%1</b></td></tr>").arg(tr("Best %1").arg( ahitxt)); for (int i = 0; i < z; i++) { const UsageData &a = AHI.at(i); recbox += QString("<tr><td><a href='daily=%1'>%2</a></td><td align=right>%3</td></tr>") .arg(a.date.toString(Qt::ISODate)) .arg(a.date.toString(Qt::SystemLocaleShortDate)) .arg(a.ahi, 0, 'f', decimals); } recbox += QString("<tr><td colspan=2> </td></tr>"); recbox += QString("<tr><td colspan=2 align=center><b>%1</b></td></tr>").arg( tr("Worst %1").arg(ahitxt)); for (int i = 0; i < z; i++) { const UsageData &a = AHI.at((numdays - 1) - i); recbox += QString("<tr><td><a href='daily=%1'>%2</a></td><td align=right>%3</td></tr>") .arg(a.date.toString(Qt::ISODate)) .arg(a.date.toString(Qt::SystemLocaleShortDate)) .arg(a.ahi, 0, 'f', decimals); } recbox += QString("<tr><td colspan=2> </td></tr>"); } if (tmpRX.size() > 0) { RXsort = RX_ahi; QString minstr, maxstr, modestr, maxhistr; qSort(tmpRX.begin(), tmpRX.end(), RXSort); tmpRX[0]->highlight = 4; // worst int ls = tmpRX.size() - 1; tmpRX[ls]->highlight = 1; //best CPAPMode mode = (CPAPMode)(int)PROFILE.calcSettingsMax(CPAP_Mode, MT_CPAP, tmpRX[ls]->first, tmpRX[ls]->last); if (mode < MODE_APAP) { // is CPAP? minstr = STR_TR_Pressure; maxstr = ""; modestr = STR_TR_CPAP; } else if (mode < MODE_BIPAP) { // is AUTO? minstr = STR_TR_Min; maxstr = STR_TR_Max; modestr = STR_TR_APAP; } else { if (mode < MODE_ASV) { // BIPAP minstr = STR_TR_EPAP; maxstr = STR_TR_IPAP; modestr = STR_TR_BiLevel; } else { minstr = STR_TR_EPAP; maxstr = STR_TR_IPAPLo; maxhistr = STR_TR_IPAPHi; modestr = STR_TR_STASV; } } recbox += QString("<tr><td colspan=2><table width=100% border=0 cellpadding=1 cellspacing=0><tr><td colspan=2 align=center><b>%3</b></td></tr>") .arg(tr("Best RX Setting")); recbox += QString("<tr><td valign=top>") + STR_TR_Start + "<br/>" + STR_TR_End + QString("</td><td align=right><a href='overview=%1,%2'>%3<br/>%4</a></td></tr>") .arg(tmpRX[ls]->first.toString(Qt::ISODate)) .arg(tmpRX[ls]->last.toString(Qt::ISODate)) .arg(tmpRX[ls]->first.toString(Qt::SystemLocaleShortDate)) .arg(tmpRX[ls]->last.toString(Qt::SystemLocaleShortDate)); recbox += QString("<tr><td><b>%1</b></td><td align=right><b>%2</b></td></tr>").arg(ahitxt).arg( tmpRX[ls]->ahi, 0, 'f', decimals); recbox += QString("<tr><td>%1</td><td align=right>%2</td></tr>").arg(STR_TR_Mode).arg(modestr); recbox += QString("<tr><td>%1</td><td align=right>%2%3</td></tr>").arg(minstr).arg(tmpRX[ls]->min, 0, 'f', 1).arg(STR_UNIT_CMH2O); if (!maxstr.isEmpty()) { recbox += QString("<tr><td>%1</td><td align=right>%2%3</td></tr>").arg(maxstr).arg(tmpRX[ls]->max, 0, 'f', 1).arg(STR_UNIT_CMH2O); } if (!maxhistr.isEmpty()) { recbox += QString("<tr><td>%1</td><td align=right>%2%3</td></tr>").arg(maxhistr).arg(tmpRX[ls]->maxipap, 0, 'f', 1).arg(STR_UNIT_CMH2O); } recbox += "</table></td></tr>"; recbox += QString("<tr><td colspan=2> </td></tr>"); mode = (CPAPMode)(int)PROFILE.calcSettingsMax(CPAP_Mode, MT_CPAP, tmpRX[0]->first, tmpRX[0]->last); if (mode < MODE_APAP) { // is CPAP? minstr = STR_TR_Pressure; maxstr = ""; modestr = STR_TR_CPAP; } else if (mode < MODE_BIPAP) { // is AUTO? minstr = STR_TR_Min; maxstr = STR_TR_Max; modestr = STR_TR_APAP; } else if (mode < MODE_ASV) { // BIPAP or greater minstr = STR_TR_EPAP; maxstr = STR_TR_IPAP; modestr = STR_TR_BiLevel; } else { minstr = STR_TR_EPAP; maxstr = STR_TR_IPAPLo; maxhistr = STR_TR_IPAPHi; modestr = STR_TR_STASV; } recbox += QString("<tr><td colspan=2><table width=100% border=0 cellpadding=1 cellspacing=0><tr><td colspan=2 align=center><b>%3</b></td></tr>") .arg(tr("Worst RX Setting")); recbox += QString("<tr><td valign=top>") + STR_TR_Start + "<br/>" + STR_TR_End + QString("</td><td align=right><a href='overview=%1,%2'>%3<br/>%4</a></td></tr>") .arg(tmpRX[0]->first.toString(Qt::ISODate)) .arg(tmpRX[0]->last.toString(Qt::ISODate)) .arg(tmpRX[0]->first.toString(Qt::SystemLocaleShortDate)) .arg(tmpRX[0]->last.toString(Qt::SystemLocaleShortDate)); recbox += QString("<tr><td><b>%1</b></td><td align=right><b>%2</b></td></tr>").arg(ahitxt).arg( tmpRX[0]->ahi, 0, 'f', decimals); recbox += QString("<tr><td>%1</td><td align=right>%2</td></tr>").arg(STR_TR_Mode).arg(modestr); recbox += QString("<tr><td>%1</td><td align=right>%2%3</td></tr>").arg(minstr).arg(tmpRX[0]->min, 0, 'f', 1).arg(STR_UNIT_CMH2O); if (!maxstr.isEmpty()) { recbox += QString("<tr><td>%1</td><td align=right>%2%3</td></tr>").arg(maxstr).arg(tmpRX[0]->max, 0, 'f', 1).arg(STR_UNIT_CMH2O); } if (!maxhistr.isEmpty()) { recbox += QString("<tr><td>%1</td><td align=right>%2%3</td></tr>").arg(maxhistr).arg(tmpRX[0]->maxipap, 0, 'f', 1).arg(STR_UNIT_CMH2O); } recbox += "</table></td></tr>"; } recbox += "</table>"; recbox += "</body></html>"; mainwin->setRecBoxHTML(recbox); /*RXsort=RX_min; RXorder=true; qSort(rxchange.begin(),rxchange.end());*/ html += "<div align=center>"; html += QString("<br/><b>") + tr("Changes to Prescription Settings") + "</b>"; html += QString("<table cellpadding=2 cellspacing=0 border=1 width=90%>"); QString extratxt; QString tooltip; html += QString("<tr><td><b>%1</b></td><td><b>%2</b></td><td><b>%3</b></td><td><b>%4</b></td><td><b>%5</b></td><td><b>%6</b></td><td><b>%7</b></td><td><b>%8</b></td><td><b>%9</b></td></tr>") .arg(STR_TR_First) .arg(STR_TR_Last) .arg(tr("Days")) .arg(ahitxt) .arg(tr("FL")) .arg(STR_TR_Machine) .arg(tr("Pr. Rel.")) .arg(STR_TR_Mode) .arg(tr("Pressure Settings")); for (int i = 0; i < rxchange.size(); i++) { RXChange rx = rxchange.at(i); QString color; if (rx.highlight == 1) { color = "#c0ffc0"; } else if (rx.highlight == 2) { color = "#e0ffe0"; } else if (rx.highlight == 3) { color = "#ffe0e0"; } else if (rx.highlight == 4) { color = "#ffc0c0"; } else { color = ""; } QString machstr; if (rx.machine->properties.contains(STR_PROP_Brand)) { machstr += rx.machine->properties[STR_PROP_Brand]; } if (rx.machine->properties.contains(STR_PROP_Model)) { machstr += " " + rx.machine->properties[STR_PROP_Model]; } if (rx.machine->properties.contains(STR_PROP_Serial)) { machstr += " (" + rx.machine->properties[STR_PROP_Serial] + ")<br/>"; } mode = rx.mode; extratxt = "<table cellpadding=0 cellspacing=0 border=0 width=100%><tr>"; // tooltip=QString("%1 %2% ").arg(machstr).arg(percentile*100.0)+STR_TR_EPAP+ // QString("=%1<br/>%2% ").arg(rx.per1,0,'f',decimals).arg(percentile*100.0)+ // STR_TR_IPAP+QString("=%1").arg(rx.per2,0,'f',decimals); if (mode >= MODE_BIPAP) { if (rx.min > 0) { extratxt += "<td>"+QString(tr("EPAP %1")) .arg(rx.min, 4, 'f', 1); } if ((rx.max > 0) && (rx.min != rx.max)) { extratxt += QString(" - %2") .arg(rx.max, 4, 'f', 1); } extratxt += "</td><td>"; if (rx.ps > 0) { extratxt += "<td>"+QString(tr("PS %1")).arg(rx.ps, 4, 'f', 1); } if ((rx.pshi > 0) && (rx.ps != rx.pshi)) { extratxt += QString(" - %2").arg(rx.pshi, 4, 'f', 1); } extratxt += "</td>"; if (rx.maxipap > 0) { extratxt += "<td>"+QString(tr("IPAP %1")+"</td>") .arg(rx.maxipap, 4, 'f', 1); } tooltip = QString("%1 %2% ").arg(machstr).arg(percentile * 100.0) + STR_TR_EPAP + QString("=%1<br/>%2% ").arg(rx.per1, 0, 'f', decimals) .arg(percentile * 100.0) + STR_TR_IPAP + QString("=%1").arg(rx.per2, 0, 'f', decimals); } else if (mode > MODE_CPAP) { extratxt += "<td align=left>"+QString(tr("APAP %1 - %2")+"</td><td align=left></td>") .arg(rx.min, 4, 'f', 1) .arg(rx.max, 4, 'f', 1); tooltip = QString("%1 %2% ").arg(machstr).arg(percentile * 100.0) + STR_TR_Pressure + QString("=%2").arg(rx.per1, 0, 'f', decimals); } else { if (cpapmode > MODE_CPAP) { extratxt += "<td colspan=2>"+QString(tr("CPAP %1")+"</td>").arg(rx.min, 4, 'f', 1); tooltip = QString("%1").arg(machstr); } else { extratxt += ""; tooltip = ""; } } extratxt += "</tr></table>"; QString presrel; if (rx.prelset > 0) { presrel = schema::channel[CPAP_PresReliefType].option(int(rx.prelief)); presrel += QString(" x%1").arg(rx.prelset); } else { presrel = STR_TR_None; } QString tooltipshow, tooltiphide; if (!tooltip.isEmpty()) { tooltipshow = QString("tooltip.show(\"%1\");").arg(tooltip); tooltiphide = "tooltip.hide();"; } html += QString("<tr bgcolor='" + color + "' onmouseover='ChangeColor(this, \"#eeeeee\"); %12' onmouseout='ChangeColor(this, \"" + color + "\"); %13' onclick='alert(\"overview=%1,%2\");'><td>%3</td><td>%4</td><td>%5</td><td>%6</td><td>%7</td><td>%8</td><td>%9</td><td>%10</td><td>%11</td></tr>") .arg(rx.first.toString(Qt::ISODate)) .arg(rx.last.toString(Qt::ISODate)) .arg(rx.first.toString(Qt::SystemLocaleShortDate)) .arg(rx.last.toString(Qt::SystemLocaleShortDate)) .arg(rx.days) .arg(rx.ahi, 0, 'f', decimals) .arg(rx.fl, 0, 'f', decimals) // Not the best way to do this.. Todo: Add an extra field for data.. .arg(rx.machine->GetClass()) .arg(presrel) .arg(schema::channel[CPAP_Mode].option(int(rx.mode) - 1)) .arg(extratxt) .arg(tooltipshow) .arg(tooltiphide); } html += "</table>"; html += QString("<i>") + tr("Efficacy highlighting ignores prescription settings with less than %1 days of recorded data.").arg( rxthresh) + QString("</i><br/>"); html += "</div>"; } if (mach.size() > 0) { html += "<div align=center>"; html += QString("<br/><b>") + tr("Machine Information") + "</b>"; html += QString("<table cellpadding=2 cellspacing=0 border=1 width=90%>"); html += QString("<tr><td><b>%1</b></td><td><b>%2</b></td><td><b>%3</b></td><td><b>%4</b></td><td><b>%5</b></td></tr>") .arg(STR_TR_Brand) .arg(STR_TR_Model) .arg(STR_TR_Serial) .arg(tr("First Use")) .arg(tr("Last Use")); Machine *m; for (int i = 0; i < mach.size(); i++) { m = mach.at(i); if (m->GetType() == MT_JOURNAL) { continue; } QString mn = m->properties[STR_PROP_ModelNumber]; //if (mn.isEmpty()) html += QString("<tr><td>%1</td><td>%2</td><td>%3</td><td>%4</td><td>%5</td></tr>") .arg(m->properties[STR_PROP_Brand]) .arg(m->properties[STR_PROP_Model] + " " + m->properties[STR_PROP_SubModel] + (mn.isEmpty() ? "" : QString(" (") + mn + QString(")"))) .arg(m->properties[STR_PROP_Serial]) .arg(m->FirstDay().toString(Qt::SystemLocaleShortDate)) .arg(m->LastDay().toString(Qt::SystemLocaleShortDate)); } html += "</table>"; html += "</div>"; } html += "<script type='text/javascript' language='javascript' src='qrc:/docs/script.js'></script>"; //updateFavourites(); html += htmlFooter(); return html; }