From ee787b29389257355bbedb28c275893715735e86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20B=20M=C3=A5rtensson?= <53905247+Jon-b-m@users.noreply.github.com> Date: Mon, 9 Oct 2023 01:17:05 +0200 Subject: [PATCH] Import Profile Settings from Nightcsout (#238) Import Basal settings, carb ratios, sensitivities and glucose targets from Nightscout manually when tapping "Import Settings from Nightcsout"in the iAPS Nightscout settings. co-author @dnzxy --- .../Core_Data.xcdatamodel/contents | 4 + FreeAPS.xcodeproj/project.pbxproj | 4 + .../javascript/bundle/determine-basal.js | 2 +- FreeAPS/Sources/APS/OpenAPS/Constants.swift | 3 +- .../Main/en.lproj/Localizable.strings | 30 ++++ .../Main/sv.lproj/Localizable.strings | 30 ++++ FreeAPS/Sources/Models/FetchedProfile.swift | 24 +++ FreeAPS/Sources/Models/NightscoutStatus.swift | 2 +- .../Sources/Models/RawFetchedProfile.swift | 24 +++ .../NightscoutConfigStateModel.swift | 161 ++++++++++++++++++ .../View/NightscoutConfigRootView.swift | 64 ++++++- .../Services/Network/NightscoutAPI.swift | 4 + .../Services/Network/NightscoutManager.swift | 4 +- FreeAPS/Sources/Views/TagCloudView.swift | 2 +- 14 files changed, 350 insertions(+), 8 deletions(-) create mode 100644 FreeAPS/Sources/Models/FetchedProfile.swift create mode 100644 FreeAPS/Sources/Models/RawFetchedProfile.swift diff --git a/Core_Data.xcdatamodeld/Core_Data.xcdatamodel/contents b/Core_Data.xcdatamodeld/Core_Data.xcdatamodel/contents index babd4f4dad..41aaab7962 100644 --- a/Core_Data.xcdatamodeld/Core_Data.xcdatamodel/contents +++ b/Core_Data.xcdatamodeld/Core_Data.xcdatamodel/contents @@ -31,6 +31,10 @@ + + + + diff --git a/FreeAPS.xcodeproj/project.pbxproj b/FreeAPS.xcodeproj/project.pbxproj index 681cc4eb3c..f6ccc5a7c7 100644 --- a/FreeAPS.xcodeproj/project.pbxproj +++ b/FreeAPS.xcodeproj/project.pbxproj @@ -302,6 +302,7 @@ BF1667ADE69E4B5B111CECAE /* ManualTempBasalProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 680C4420C9A345D46D90D06C /* ManualTempBasalProvider.swift */; }; C967DACD3B1E638F8B43BE06 /* ManualTempBasalStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFCFE0781F9074C2917890E8 /* ManualTempBasalStateModel.swift */; }; CA370FC152BC98B3D1832968 /* BasalProfileEditorRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8BCB0C37DEB5EC377B9612 /* BasalProfileEditorRootView.swift */; }; + CC6C406E2ACDD69E009B8058 /* RawFetchedProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC6C406D2ACDD69E009B8058 /* RawFetchedProfile.swift */; }; CD78BB94E43B249D60CC1A1B /* NotificationsConfigRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22963BD06A9C83959D4914E4 /* NotificationsConfigRootView.swift */; }; CE2FAD38297D69E1001A872C /* ShareClient.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CE398D1A297D69A900DF218F /* ShareClient.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; CE2FAD3A297D93F0001A872C /* BloodGlucoseExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE2FAD39297D93F0001A872C /* BloodGlucoseExtensions.swift */; }; @@ -816,6 +817,7 @@ C19984D62EFC0035A9E9644D /* BolusProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BolusProvider.swift; sourceTree = ""; }; C377490C77661D75E8C50649 /* ManualTempBasalRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ManualTempBasalRootView.swift; sourceTree = ""; }; C8D1A7CA8C10C4403D4BBFA7 /* BolusDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BolusDataFlow.swift; sourceTree = ""; }; + CC6C406D2ACDD69E009B8058 /* RawFetchedProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RawFetchedProfile.swift; sourceTree = ""; }; CE2FAD39297D93F0001A872C /* BloodGlucoseExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BloodGlucoseExtensions.swift; sourceTree = ""; }; CE398D012977349800DF218F /* CryptoKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CryptoKit.framework; path = System/Library/Frameworks/CryptoKit.framework; sourceTree = SDKROOT; }; CE398D15297C9D1D00DF218F /* dexcomSourceG7.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dexcomSourceG7.swift; sourceTree = ""; }; @@ -1603,6 +1605,7 @@ 19D4E4EA29FC6A9F00351451 /* TIRforChart.swift */, 19A910352A24D6D700C8951B /* DateFilter.swift */, 193F6CDC2A512C8F001240FD /* Loops.swift */, + CC6C406D2ACDD69E009B8058 /* RawFetchedProfile.swift */, ); path = Models; sourceTree = ""; @@ -2689,6 +2692,7 @@ 63E890B4D951EAA91C071D5C /* BasalProfileEditorStateModel.swift in Sources */, CE398D16297C9D1D00DF218F /* dexcomSourceG7.swift in Sources */, 38FEF3FA2737E42000574A46 /* BaseStateModel.swift in Sources */, + CC6C406E2ACDD69E009B8058 /* RawFetchedProfile.swift in Sources */, 385CEA8225F23DFD002D6D5B /* NightscoutStatus.swift in Sources */, F90692AA274B7AAE0037068D /* HealthKitManager.swift in Sources */, 38887CCE25F5725200944304 /* IOBEntry.swift in Sources */, diff --git a/FreeAPS/Resources/javascript/bundle/determine-basal.js b/FreeAPS/Resources/javascript/bundle/determine-basal.js index 9e9544edff..e3a315bbdb 100644 --- a/FreeAPS/Resources/javascript/bundle/determine-basal.js +++ b/FreeAPS/Resources/javascript/bundle/determine-basal.js @@ -1 +1 @@ -var freeaps_determineBasal;(()=>{var e={5546:(e,t,a)=>{var r=a(6880);function o(e,t){t||(t=0);var a=Math.pow(10,t);return Math.round(e*a)/a}function n(e,t){return"mmol/L"===t.out_units?o(.0555*e,1):Math.round(e)}e.exports=function(e,t,a,i,s,l,u,d,m,c,g,h,p,v){var B=i.min_bg,f=v.overrideTarget;0!=f&&v.useOverride&&!i.temptargetSet&&(B=f);const b=v.smbIsOff,M=v.advancedSettings,_=v.isfAndCr,y=v.isf,x=v.cr,S=v.smbIsAlwaysOff,D=v.start,w=v.end,G=v.smbMinutes,T=v.uamMinutes;var C=0,U=B,O=0,R="",A="",I="",F="",j="",P=0,E=0,q=0,W=0,k=0,L=0;const z=v.weightedAverage;var N=1,H=i.sens,Z=i.carb_ratio;v.useOverride&&(N=v.overridePercentage/100,_?(H/=N,Z/=N):(x&&(Z/=N),y&&(H/=N)));const $=i.weightPercentage,J=v.average_total_data;function K(e,t){var a=e.getTime();return new Date(a+36e5*t)}function Q(e){var t=i.bolus_increment;.1!=t&&(t=.05);var a=e/t;return a>=1?o(Math.floor(a)*t,5):0}function V(e){function t(e){return e<10&&(e="0"+e),e}return t(e.getHours())+":"+t(e.getMinutes())+":00"}function X(e,t){var a=new Date("1/1/1999 "+e),r=new Date("1/1/1999 "+t);return(a.getTime()-r.getTime())/36e5}function Y(e,t){var a=0,r=t,o=(e-t)/36e5,n=0,i=o,s=0;do{if(o>0){var l=V(r),u=p[0].rate;for(let e=0;e=(s=X(p[e+1].start,p[e].start))?n=s:o=s?n=s:od)if(e+1=(s=X(m,l))?n=s:o=(s=X("23:59:59",l))?n=s:o0&&o21)k=Y(ee,(ae=24-O,re=ee.getTime(),new Date(re-36e5*ae))),F="24 hours of data is required for an accurate tdd calculation. Currently only "+O.toPrecision(3)+" hours of pump history data are available. Using your pump scheduled basals to fill in the missing hours. Scheduled basals added: "+k.toPrecision(5)+" U. ";else O<21?(pe=!1,enableDynamicCR=!1):F=""}else console.log("Pumphistory is empty!"),pe=!1,enableDynamicCR=!1;var ae,re;for(let e=0;e0){P=e,L=g[e].rate;var oe=g[e-1]["duration (min)"]/60,ne=oe,ie=new Date(g[e-1].timestamp),se=ie,le=0;do{if(e--,0==e){se=new Date;break}if("TempBasal"==g[e]._type||"PumpSuspend"==g[e]._type){se=new Date(g[e].timestamp);break}var ue=e-2;if(ue>=0&&"Rewind"==g[ue]._type){let e=g[ue].timestamp;for(;ue-1>=0&&"Prime"==g[ue-=1]._type;)le=(g[ue].timestamp-e)/36e5;le>=oe&&(se=e,le=0)}}while(e>0);var de=(se-ie)/36e5;de0&&(--r,"TempBasal"==g[r]._type)){a=new Date(g[r].timestamp);break}}while(r>0);(a-t)/36e5>0&&(k+=Y(a,t))}for(let e=g.length-1;e>0;e--)if("TempBasalDuration"==g[e]._type){let t=g[e]["duration (min)"]/60,a=new Date(g[e].timestamp);var me=a;let r=e;do{if(--r,r>=0&&("TempBasal"==g[r]._type||"PumpSuspend"==g[r]._type)){me=new Date(g[r].timestamp);break}}while(r>0);if(0==e&&"TempBasalDuration"==g[0]._type&&(me=new Date,t=g[e]["duration (min)"]/60),(me-a)/36e5-t>0){k+=Y(me,K(a,t))}}var ce,ge={TDD:o(E=W+q+k,5),bolus:o(W,5),temp_basal:o(q,5),scheduled_basal:o(k,5)};O>21?(A=". Bolus insulin: "+W.toPrecision(5)+" U",I=". Temporary basal insulin: "+q.toPrecision(5)+" U",R=". Insulin with scheduled basal rate: "+k.toPrecision(5)+" U",j=F+(" TDD past 24h is: "+E.toPrecision(5)+" U")+A+I+R,tddReason=", TDD: "+o(E,2)+" U, "+o(W/E*100,0)+"% Bolus "+o((q+k)/E*100,0)+"% Basal"):tddReason=", TDD: Not enough pumpData (< 21h)";const he=e.glucose;var pe=h.useNewFormula;const ve=h.enableDynamicCR,Be=Math.min(i.autosens_min,i.autosens_max),fe=Math.max(i.autosens_min,i.autosens_max);(fe==Be||fe<1||Be>1)&&(pe=!1,console.log("Dynamic ISF disabled due to current autosens settings"));const be=h.adjustmentFactor,Me=B;var _e=!1,ye="",xe=1,Se="";J>0&&(xe=z/J),Se=xe>1?"Basal adjustment with a 24 hour to total average (up to 14 days of data) TDD ratio (limited by Autosens max setting). Basal Ratio: "+(xe=o(xe=Math.min(xe,i.autosens_max),2))+". Upper limit = Autosens max ("+i.autosens_max+")":xe<1?"Basal adjustment with a 24 hour to to total average (up to 14 days of data) TDD ratio (limited by Autosens min setting). Basal Ratio: "+(xe=o(xe=Math.max(xe,i.autosens_min),2))+". Lower limit = Autosens min ("+i.autosens_min+")":"Basal adjusted with a 24 hour to total average (up to 14 days of data) TDD ratio: "+xe,Se=", Basal ratio: "+xe,(i.high_temptarget_raises_sensitivity||i.exercise_mode||v.isEnabled)&&(_e=!0),Me>=118&&_e&&(pe=!1,ye="Dynamic ISF temporarily off due to a high temp target/exercising. Current min target: "+Me);var De=", Dynamic ratios log: ",we=", AF: "+be,Ge="BG: "+he+" mg/dl ("+(.0555*he).toPrecision(2)+" mmol/l)",Te="",Ce="";const Ue=h.curve,Oe=h.insulinPeakTime,Re=h.useCustomPeakTime;var Ae=55,Ie=65;switch(Ue){case"rapid-acting":Ie=65;break;case"ultra-rapid":Ie=50}Re?(Ae=120-Oe,console.log("Custom insulinpeakTime set to :"+Oe+", insulinFactor: "+Ae)):(Ae=120-Ie,console.log("insulinFactor set to : "+Ae)),ce=E,$<1&&z>0&&(E=z,console.log("Using weighted TDD average: "+o(E,2)+" U, instead of past 24 h ("+o(ce,2)+" U), weight: "+$),Ce=", Weighted TDD: "+o(E,2)+" U");const Fe=h.sigmoid;var je="";if(pe){var Pe=H*be*E*Math.log(he/Ae+1)/1800;Te=", Logarithmic formula"}if(pe&&Fe){const e=Be,t=fe-e,a=.0555*(he-B);var Ee=xe,qe=fe-1;1==fe&&(qe=fe+.01-1);const r=Math.log10(1/qe-e/qe)/Math.log10(Math.E),o=a*be*Ee+r;Pe=t/(1+Math.exp(-o))+e,Te=", Sigmoid function"}var We=Z;const ke=o(Z,1);var Le="",ze="";if(pe&&E>0){if(Le=", Dynamic ISF/CR: On/",Pe>fe?(ye=", Dynamic ISF limited by autosens_max setting: "+fe+" ("+o(Pe,2)+"), ",ze=", Autosens/Dynamic Limit: "+fe+" ("+o(Pe,2)+")",Pe=fe):Pe-.5?"+"+o(e.delta,0):o(e.delta,0);var tt=Math.min(e.delta,e.short_avgdelta),at=Math.min(e.short_avgdelta,e.long_avgdelta),rt=Math.max(e.delta,e.short_avgdelta,e.long_avgdelta);(Ye<=10||38===Ye||et>=3)&&(He.reason="CGM is calibrating, in ??? state, or noise is high");if(Ye>60&&0==e.delta&&e.short_avgdelta>-1&&e.short_avgdelta<1&&e.long_avgdelta>-1&&e.long_avgdelta<1&&("fakecgm"==e.device?(console.error("CGM data is unchanged ("+n(Ye,i)+"+"+n(e.delta,i)+") for 5m w/ "+n(e.short_avgdelta,i)+" mg/dL ~15m change & "+n(e.long_avgdelta,2)+" mg/dL ~45m change"),console.error("Simulator mode detected ("+e.device+"): continuing anyway")):!0),Xe>12||Xe<-5?He.reason="If current system time "+Ke+" is correct, then BG data is too old. The last BG data was read "+Xe+"m ago at "+Ve:0===e.short_avgdelta&&0===e.long_avgdelta&&(e.last_cal&&e.last_cal<3?He.reason="CGM was just calibrated":He.reason="CGM data is unchanged ("+n(Ye,i)+"+"+n(e.delta,i)+") for 5m w/ "+n(e.short_avgdelta,i)+" mg/dL ~15m change & "+n(e.long_avgdelta,i)+" mg/dL ~45m change"),Ye<=10||38===Ye||et>=3||Xe>12||Xe<-5||0===e.short_avgdelta&&0===e.long_avgdelta)return t.rate>=Je?(He.reason+=". Canceling high temp basal of "+t.rate,He.deliverAt=Ze,He.temp="absolute",He.duration=0,He.rate=0,He):0===t.rate&&t.duration>30?(He.reason+=". Shortening "+t.duration+"m long zero temp to 30m. ",He.deliverAt=Ze,He.temp="absolute",He.duration=30,He.rate=0,He):(He.reason+=". Temp "+t.rate+" <= current basal "+Je+"U/hr; doing nothing. ",He);var ot,nt,it,st,lt=i.max_iob;if(void 0!==B&&(nt=B),void 0!==i.max_bg&&(it=B),void 0!==i.enableSMB_high_bg_target&&(st=i.enableSMB_high_bg_target),void 0===B)return He.error="Error: could not determine target_bg. ",He;ot=B;var ut=i.exercise_mode||i.high_temptarget_raises_sensitivity||v.isEnabled,dt=100,mt=160;if(mt=i.half_basal_exercise_target,v.isEnabled){const e=v.hbt;console.log("Half Basal Target used: "+n(e,i)+" "+i.out_units),mt=e}else console.log("Default Half Basal Target used: "+n(mt,i)+" "+i.out_units);if(ut&&i.temptargetSet&&ot>dt||i.low_temptarget_lowers_sensitivity&&i.temptargetSet&&ot=ot&&sensitivityRatio0&&(process.stderr.write("TDD-adjustment of basals activated, using tdd24h_14d_Ratio "+o(xe,2)+", TDD 24h = "+o(ce,2)+"U, Weighted average TDD = "+o(z,2)+"U, (Weight percentage = "+$+"), Total data of TDDs (up to 14 days) average = "+o(J,2)+"U. "),Je!==$e*N?process.stderr.write("Adjusting basal from "+$e*N+" U/h to "+Je+" U/h; "):process.stderr.write("Basal unchanged: "+Je+" U/h; "))),i.temptargetSet);else if(void 0!==s&&s&&(i.sensitivity_raises_target&&s.ratio<1||i.resistance_lowers_target&&s.ratio>1)){nt=o((nt-60)/s.ratio)+60,it=o((it-60)/s.ratio)+60;var gt=o((ot-60)/s.ratio)+60;ot===(gt=Math.max(80,gt))?process.stderr.write("target_bg unchanged: "+n(gt,i)+"; "):process.stderr.write("target_bg from "+n(gt,i)+" to "+n(gt,i)+"; "),ot=gt}var ht=n(ot,i);ot!=B&&(ht=0!==f&&f!==ot?n(B,i)+"→"+n(f,i)+"→"+n(ot,i):n(B,i)+"→"+n(ot,i));var pt=200,vt=200,Bt=200;if(e.noise>=2){var ft=Math.max(1.1,i.noisyCGMTargetMultiplier);Math.min(250,i.maxRaw);pt=o(Math.min(200,nt*ft)),vt=o(Math.min(200,ot*ft)),Bt=o(Math.min(200,it*ft)),process.stderr.write("Raising target_bg for noisy / raw CGM data, from "+n(gt,i)+" to "+n(vt,i)+"; "),nt=pt,ot=vt,it=Bt}U=nt-.5*(nt-40);var bt=i.threshold_setting;bt>U&&bt<=120&&bt>=65?(console.error("Threshold changed in settings from "+n(U,i)+" to "+n(bt,i)+". "),U=bt):console.error("Current threshold: "+n(U,i));var Mt="",_t=(o(H,1),H);if(void 0!==s&&s&&((_t=o(_t=H/sensitivityRatio,1))!==H?process.stderr.write("ISF from "+n(H,i)+" to "+n(_t,i)):process.stderr.write("ISF unchanged: "+n(_t,i)),Mt+="Autosens ratio: "+o(sensitivityRatio,2)+", ISF: "+n(H,i)+"→"+n(_t,i)),console.error("CR:"+Z),void 0===a)return He.error="Error: iob_data undefined. ",He;var yt,xt=a;if(a.length,a.length>1&&(a=xt[0]),void 0===a.activity||void 0===a.iob)return He.error="Error: iob_data missing some property. ",He;var St=((yt=void 0!==a.lastTemp?o((new Date(Ke).getTime()-a.lastTemp.date)/6e4):0)+t.duration)%30;if(console.error("currenttemp:"+t.rate+" lastTempAge:"+yt+"m, tempModulus:"+St+"m"),He.temp="absolute",He.deliverAt=Ze,d&&t&&a.lastTemp&&t.rate!==a.lastTemp.rate&&yt>10&&t.duration)return He.reason="Warning: currenttemp rate "+t.rate+" != lastTemp rate "+a.lastTemp.rate+" from pumphistory; canceling temp",u.setTempBasal(0,0,i,He,t);if(t&&a.lastTemp&&t.duration>0){var Dt=yt-a.lastTemp.duration;if(Dt>5&&yt>10)return He.reason="Warning: currenttemp running but lastTemp from pumphistory ended "+Dt+"m ago; canceling temp",u.setTempBasal(0,0,i,He,t)}var wt=o(-a.activity*_t*5,2),Gt=o(6*(tt-wt));Gt<0&&(Gt=o(6*(at-wt)))<0&&(Gt=o(6*(e.long_avgdelta-wt)));var Tt=Ye,Ct=(Tt=a.iob>0?o(Ye-a.iob*_t):o(Ye-a.iob*Math.min(_t,H)))+Gt;if(void 0===Ct||isNaN(Ct))return He.error="Error: could not calculate eventualBG. Sensitivity: "+_t+" Deviation: "+Gt,He;var Ut,Ot,Rt=function(e,t,a){return o(a+(e-t)/24,1)}(ot,Ct,wt);He={temp:"absolute",bg:Ye,tick:Qe,eventualBG:Ct,insulinReq:0,reservoir:m,deliverAt:Ze,sensitivityRatio,CR:o(Z,1),TDD:ce,insulin:ge,current_target:ot,insulinForManualBolus:C,manualBolusErrorString:0,minDelta:tt,expectedDelta:Rt,minGuardBG:Ot,minPredBG:Ut,threshold:n(U,i)};var At=[],It=[],Ft=[],jt=[];At.push(Ye),It.push(Ye),jt.push(Ye),Ft.push(Ye);var Pt=function(e,t,a,r,o,i){return t?!e.allowSMB_with_high_temptarget&&e.temptargetSet&&o>100?(console.error("SMB disabled due to high temptarget of "+o),!1):!0===a.bwFound&&!1===e.A52_risk_enable?(console.error("SMB disabled due to Bolus Wizard activity in the last 6 hours."),!1):!0===e.enableSMB_always?(a.bwFound?console.error("Warning: SMB enabled within 6h of using Bolus Wizard: be sure to easy bolus 30s before using Bolus Wizard"):console.error("SMB enabled due to enableSMB_always"),!0):!0===e.enableSMB_with_COB&&a.mealCOB?(a.bwCarbs?console.error("Warning: SMB enabled with Bolus Wizard carbs: be sure to easy bolus 30s before using Bolus Wizard"):console.error("SMB enabled for COB of "+a.mealCOB),!0):!0===e.enableSMB_after_carbs&&a.carbs?(a.bwCarbs?console.error("Warning: SMB enabled with Bolus Wizard carbs: be sure to easy bolus 30s before using Bolus Wizard"):console.error("SMB enabled for 6h after carb entry"),!0):!0===e.enableSMB_with_temptarget&&e.temptargetSet&&o<100?(a.bwFound?console.error("Warning: SMB enabled within 6h of using Bolus Wizard: be sure to easy bolus 30s before using Bolus Wizard"):console.error("SMB enabled for temptarget of "+n(o,e)),!0):!0===e.enableSMB_high_bg&&null!==i&&r>=i?(console.error("Checking BG to see if High for SMB enablement."),console.error("Current BG",r," | High BG ",i),a.bwFound?console.error("Warning: High BG SMB enabled within 6h of using Bolus Wizard: be sure to easy bolus 30s before using Bolus Wizard"):console.error("High BG detected. Enabling SMB."),!0):(console.error("SMB disabled (no enableSMB preferences active or no condition satisfied)"),!1):(console.error("SMB disabled (!microBolusAllowed)"),!1)}(i,d,l,Ye,ot,st);if(b)if(S){let e=c.getHours();e>=D&&e<=w&&(console.error("SMB disabled by schedule (a Profile is active with SMBs disabled)"),Pt=!1)}else console.error("SMBs are disabled (a Profile is active with SMBs disabled)"),Pt=!1;var Et=i.enableUAM,qt=0,Wt=0;qt=o(tt-wt,1);var kt=o(tt-wt,1);csf=_t/Z,console.error("profile.sens:"+n(H,i)+", sens:"+n(_t,i)+", CSF:"+o(csf,1));var Lt=o(30*csf*5/60,1);qt>Lt&&(console.error("Limiting carb impact from "+qt+" to "+Lt+"mg/dL/5m (30g/h)"),qt=Lt);var zt=3;sensitivityRatio&&(zt/=sensitivityRatio);var Nt=zt;if(l.carbs){zt=Math.max(zt,l.mealCOB/20);var Ht=o((new Date(Ke).getTime()-l.lastCarbTime)/6e4),Zt=(l.carbs-l.mealCOB)/l.carbs;Nt=o(Nt=zt+1.5*Ht/60,1),console.error("Last carbs "+Ht+" minutes ago; remainingCATime:"+Nt+"hours; "+o(100*Zt,1)+"% carbs absorbed")}var $t=Math.max(0,qt/5*60*Nt/2)/csf,Jt=90,Kt=1;i.remainingCarbsCap&&(Jt=Math.min(90,i.remainingCarbsCap)),i.remainingCarbsFraction&&(Kt=Math.min(1,i.remainingCarbsFraction));var Qt=1-Kt,Vt=Math.max(0,l.mealCOB-$t-l.carbs*Qt),Xt=(Vt=Math.min(Jt,Vt))*csf*5/60/(Nt/2),Yt=o(l.slopeFromMaxDeviation,2),ea=o(l.slopeFromMinDeviation,2),ta=Math.min(Yt,-ea/3);Wt=0===qt?0:Math.min(60*Nt/5/2,Math.max(0,l.mealCOB*csf/qt)),console.error("Carb Impact:"+qt+"mg/dL per 5m; CI Duration:"+o(5*Wt/60*2,1)+"hours; remaining CI ("+Nt/2+"h peak):"+o(Xt,1)+"mg/dL per 5m");var aa,ra,oa,na,ia=999,sa=999,la=999,ua=999,da=999,ma=999,ca=999,ga=Ct,ha=Ye,pa=Ye,va=0,Ba=[],fa=[];try{xt.forEach((function(e){var t=o(-e.activity*_t*5,2),a=o(-e.iobWithZeroTemp.activity*_t*5,2),r=Tt,n=qt*(1-Math.min(1,It.length/12));if(!0===(pe&&!Fe))ga=It[It.length-1]+o(-e.activity*(1800/(E*be*Math.log(Math.max(It[It.length-1],39)/Ae+1)))*5,2)+n,r=jt[jt.length-1]+o(-e.iobWithZeroTemp.activity*(1800/(E*be*Math.log(Math.max(jt[jt.length-1],39)/Ae+1)))*5,2),console.log("Dynamic ISF (Logarithmic Formula) )adjusted predictions for IOB and ZT: IOBpredBG: "+o(ga,2)+" , ZTpredBG: "+o(r,2));else ga=It[It.length-1]+t+n,r=jt[jt.length-1]+a;var i=Math.max(0,Math.max(0,qt)*(1-At.length/Math.max(2*Wt,1))),s=Math.min(At.length,12*Nt-At.length),l=Math.max(0,s/(Nt/2*12)*Xt);i+l,Ba.push(o(l,0)),fa.push(o(i,0)),COBpredBG=At[At.length-1]+t+Math.min(0,n)+i+l;var u=Math.max(0,kt+Ft.length*ta),d=Math.max(0,kt*(1-Ft.length/Math.max(36,1))),m=Math.min(u,d);if(m>0&&(va=o(5*(Ft.length+1)/60,1)),!0===(pe&&!Fe))UAMpredBG=Ft[Ft.length-1]+o(-e.activity*(1800/(E*be*Math.log(Math.max(Ft[Ft.length-1],39)/Ae+1)))*5,2)+Math.min(0,n)+m,console.log("Dynamic ISF (Logarithmic Formula) adjusted prediction for UAM: UAMpredBG: "+o(UAMpredBG,2));else UAMpredBG=Ft[Ft.length-1]+t+Math.min(0,n)+m;It.length<48&&It.push(ga),At.length<48&&At.push(COBpredBG),Ft.length<48&&Ft.push(UAMpredBG),jt.length<48&&jt.push(r),COBpredBG18&&gaha&&(ha=ga),(Wt||Xt>0)&&At.length>18&&COBpredBG0)&&COBpredBG>ha&&(pa=COBpredBG),Et&&Ft.length>12&&UAMpredBGha&&UAMpredBG}))}catch(e){console.error("Problem with iobArray. Optional feature Advanced Meal Assist disabled")}l.mealCOB&&(console.error("predCIs (mg/dL/5m):"+fa.join(" ")),console.error("remainingCIs: "+Ba.join(" "))),He.predBGs={},It.forEach((function(e,t,a){a[t]=o(Math.min(401,Math.max(39,e)))}));for(var ba=It.length-1;ba>12&&It[ba-1]===It[ba];ba--)It.pop();for(He.predBGs.IOB=It,ra=o(It[It.length-1]),jt.forEach((function(e,t,a){a[t]=o(Math.min(401,Math.max(39,e)))})),ba=jt.length-1;ba>6&&!(jt[ba-1]>=jt[ba]||jt[ba]<=ot);ba--)jt.pop();if(He.predBGs.ZT=jt,o(jt[jt.length-1]),l.mealCOB>0&&(qt>0||Xt>0)){for(At.forEach((function(e,t,a){a[t]=o(Math.min(1500,Math.max(39,e)))})),ba=At.length-1;ba>12&&At[ba-1]===At[ba];ba--)At.pop();He.predBGs.COB=At,oa=o(At[At.length-1]),Ct=Math.max(Ct,o(At[At.length-1])),console.error("COBpredBG: "+o(At[At.length-1]))}if(qt>0||Xt>0){if(Et){for(Ft.forEach((function(e,t,a){a[t]=o(Math.min(401,Math.max(39,e)))})),ba=Ft.length-1;ba>12&&Ft[ba-1]===Ft[ba];ba--)Ft.pop();He.predBGs.UAM=Ft,na=o(Ft[Ft.length-1]),Ft[Ft.length-1]&&(Ct=Math.max(Ct,o(Ft[Ft.length-1])))}He.eventualBG=Ct}console.error("UAM Impact:"+kt+"mg/dL per 5m; UAM Duration:"+va+"hours"),ia=Math.max(39,ia),sa=Math.max(39,sa),la=Math.max(39,la),Ut=o(ia);var Ma=l.mealCOB/l.carbs;aa=o(la<999&&sa<999?(1-Ma)*UAMpredBG+Ma*COBpredBG:sa<999?(ga+COBpredBG)/2:la<999?(ga+UAMpredBG)/2:ga),ca>aa&&(aa=ca),Ot=o(Ot=Wt||Xt>0?Et?Ma*ua+(1-Ma)*da:ua:Et?da:ma);var _a=la;if(cala&&(_a=(la+ca)/2);if(_a=o(_a),l.carbs)if(!Et&&sa<999)Ut=o(Math.max(ia,sa));else if(sa<999){var xa=Ma*sa+(1-Ma)*_a;Ut=o(Math.max(ia,sa,xa))}else Ut=Et?_a:Ot;else Et&&(Ut=o(Math.max(ia,_a)));Ut=Math.min(Ut,aa),process.stderr.write("minPredBG: "+Ut+" minIOBPredBG: "+ia+" minZTGuardBG: "+ca),sa<999&&process.stderr.write(" minCOBPredBG: "+sa),la<999&&process.stderr.write(" minUAMPredBG: "+la),console.error(" avgPredBG:"+aa+" COB/Carbs:"+l.mealCOB+"/"+l.carbs),pa>Ye&&(Ut=Math.min(Ut,pa)),He.COB=l.mealCOB,He.IOB=a.iob,He.BGI=n(wt,i),He.deviation=n(Gt,i),He.ISF=n(_t,i),He.CR=o(Z,1),He.target_bg=n(ot,i),He.TDD=o(ce,2),He.current_target=o(ot,0);var Sa=He.CR;ke!=He.CR&&(Sa=ke+"→"+He.CR),He.reason=Mt+", COB: "+He.COB+", Dev: "+He.deviation+", BGI: "+He.BGI+", CR: "+Sa+", Target: "+ht+", minPredBG "+n(Ut,i)+", minGuardBG "+n(Ot,i)+", IOBpredBG "+n(ra,i),oa>0&&(He.reason+=", COBpredBG "+n(oa,i)),na>0&&(He.reason+=", UAMpredBG "+n(na,i)),He.reason+=tddReason,.5!=i.smb_delivery_ratio&&(He.reason+=", SMB Ratio: "+i.smb_delivery_ratio),He.reason+="; ";var Da=Tt;Da<40&&(Da=Math.min(Ot,Da));var wa,Ga=U-Da,Ta=240,Ca=240;if(l.mealCOB>0&&(qt>0||Xt>0)){for(ba=0;bawa*Ye&&(console.error("maxDelta "+n(rt,i)+" > "+100*wa+"% of BG "+n(Ye,i)+" - disabling SMB"),He.reason+="maxDelta "+n(rt,i)+" > "+100*wa+"% of BG "+n(Ye,i)+" - SMB disabled!, ",Pt=!1),console.error("BG projected to remain above "+n(nt,i)+" for "+Ta+"minutes"),(Ca<240||Ta<60)&&console.error("BG projected to remain above "+n(U,i)+" for "+Ca+"minutes");var Ua=Ca,Oa=i.current_basal*N*_t*Ua/60,Ra=Math.max(0,l.mealCOB-.25*l.carbs),Aa=(Ga-Oa)/csf-Ra;Oa=o(Oa),Aa=o(Aa),console.error("naive_eventualBG:",Tt,"bgUndershoot:",Ga,"zeroTempDuration:",Ua,"zeroTempEffect:",Oa,"carbsReq:",Aa),"Could not parse clock data"==l.reason?console.error("carbsReq unknown: Could not parse clock data"):Aa>=i.carbsReqThreshold&&Ca<=45&&(He.carbsReq=Aa,He.reason+=Aa+" add'l carbs req w/in "+Ca+"m; ");var Ia=0;if(Ye0&&tt>Rt)He.reason+="IOB "+a.iob+" < "+o(-i.current_basal*N*20/60,2),He.reason+=" and minDelta "+n(tt,i)+" > expectedDelta "+n(Rt,i)+"; ";else if(Ye=55)return He.reason+="; Canceling temp at "+He.deliverAt.getMinutes()+"m past the hour. ",u.setTempBasal(0,0,i,He,t);var Fa=0,ja=Je,Pa=0;if(CtRt&&tt>0&&!Aa)return Tt<40?(He.reason+=", naive_eventualBG < 40. ",u.setTempBasal(0,30,i,He,t)):(e.delta>tt?He.reason+=", but Delta "+n(Qe,i)+" > expectedDelta "+n(Rt,i):He.reason+=", but Min. Delta "+tt.toFixed(2)+" > Exp. Delta "+n(Rt,i),t.duration>15&&r(Je,i)===r(t.rate,i)?(He.reason+=", temp "+t.rate+" ~ req "+Je+"U/hr. ",He):(He.reason+="; setting current basal of "+Je+" as temp. ",u.setTempBasal(Je,30,i,He,t)));Fa=o(Fa=2*Math.min(0,(Ct-ot)/_t),2);var Ea=Math.min(0,(Tt-ot)/_t);if(Ea=o(Ea,2),tt<0&&tt>Rt)Fa=o(Fa*(tt/Rt),2);ja=r(ja=Je+2*Fa,i),Pa=t.duration*(t.rate-Je)/60;var qa=Math.min(Fa,Ea);if(console.log("naiveInsulinReq:"+Ea),Pa5&&ja>=.8*t.rate)return He.reason+=", temp "+t.rate+" ~< req "+ja+"U/hr. ",He;if(ja<=0){if((Ia=o(60*((Ga=ot-Tt)/_t)/i.current_basal*N))<0?Ia=0:(Ia=30*o(Ia/30),Ia=Math.min(120,Math.max(0,Ia))),Ia>0)return He.reason+=", setting "+Ia+"m zero temp. ",u.setTempBasal(ja,Ia,i,He,t)}else He.reason+=", setting "+ja+"U/hr. ";return u.setTempBasal(ja,30,i,He,t)}if(tt=2||Rt+-1*tt>=2)&&(He.manualBolusErrorString=tt>=0&&Rt>0?3:tt<0&&Rt<=0||tt<0&&Rt>=0?4:5),He.insulinForManualBolus=o((He.eventualBG-He.target_bg)/_t,2),!d||!Pt))return e.delta "+n(nt,i)+" but Delta "+n(Qe,i)+" < Exp. Delta "+n(Rt,i):He.reason+="Eventual BG "+n(Ct,i)+" > "+n(nt,i)+" but Min. Delta "+tt.toFixed(2)+" < Exp. Delta "+n(Rt,i),t.duration>15&&r(Je,i)===r(t.rate,i)?(He.reason+=", temp "+t.rate+" ~ req "+Je+"U/hr. ",He):(He.reason+="; setting current basal of "+Je+" as temp. ",u.setTempBasal(Je,30,i,He,t));if(Math.min(Ct,Ut)nt&&(He.manualBolusErrorString=6,He.insulinForManualBolus=o((He.eventualBG-He.target_bg)/_t,2),He.minPredBG=Ut),!d||!Pt))return He.reason+=n(Ct,i)+"-"+n(Ut,i)+" in range: no temp required",t.duration>15&&r(Je,i)===r(t.rate,i)?(He.reason+=", temp "+t.rate+" ~ req "+Je+"U/hr. ",He):(He.reason+="; setting current basal of "+Je+" as temp. ",u.setTempBasal(Je,30,i,He,t));if(Ct>=it&&(He.reason+="Eventual BG "+n(Ct,i)+" >= "+n(it,i)+", ",Ct>it&&(He.insulinForManualBolus=o((Ct-ot)/_t,2))),a.iob>lt)return He.reason+="IOB "+o(a.iob,2)+" > max_iob "+lt,t.duration>15&&r(Je,i)===r(t.rate,i)?(He.reason+=", temp "+t.rate+" ~ req "+Je+"U/hr. ",He):(He.reason+="; setting current basal of "+Je+" as temp. ",u.setTempBasal(Je,30,i,He,t));Fa=o((Math.min(Ut,Ct)-ot)/_t,2),C=o((Ct-ot)/_t,2),Fa>lt-a.iob?(console.error("SMB limited by maxIOB: "+lt-a.iob+" (. insulinReq: "+Fa+" U)"),He.reason+="max_iob "+lt+", ",Fa=lt-a.iob):console.error("SMB not limited by maxIOB ( insulinReq: "+Fa+" U)."),C>lt-a.iob?(console.error("Ev. Bolus limited by maxIOB: "+lt-a.iob+" (. insulinForManualBolus: "+C+" U)"),He.reason+="max_iob "+lt+", "):console.error("Ev. Bolus would not be limited by maxIOB ( insulinForManualBolus: "+C+" U)."),ja=r(ja=Je+2*Fa,i),Fa=o(Fa,3),He.insulinReq=Fa;var Wa=o((new Date(Ke).getTime()-a.lastBolusTime)/6e4,1);if(d&&Pt&&Ye>U){var ka=30;void 0!==i.maxSMBBasalMinutes&&(ka=i.maxSMBBasalMinutes);var La=30;void 0!==i.maxUAMSMBBasalMinutes&&(La=i.maxUAMSMBBasalMinutes),v.useOverride&&M&&G!==ka&&(console.error("SMB Max Minutes - setting overriden from "+ka+" to "+G),ka=G),v.useOverride&&M&&T!==La&&(console.error("UAM Max Minutes - setting overriden from "+La+" to "+T),La=T);var za=o(l.mealCOB/Z,3),Na=0;void 0===ka?(Na=o(i.current_basal*N*30/60,1),console.error("smbMinutesSetting undefined: defaulting to 30m"),Fa>Na&&console.error("SMB limited by maxBolus: "+Na+" ( "+Fa+" U)")):a.iob>za&&a.iob>0?(console.error("IOB"+a.iob+"> COB"+l.mealCOB+"; mealInsulinReq ="+za),La?(console.error("maxUAMSMBBasalMinutes: "+La+", profile.current_basal: "+i.current_basal*N),Na=o(i.current_basal*N*La/60,1)):(console.error("maxUAMSMBBasalMinutes undefined: defaulting to 30m"),Na=o(i.current_basal*N*30/60,1)),Fa>Na?console.error("SMB limited by maxUAMSMBBasalMinutes [ "+La+"m ]: "+Na+"U ( "+Fa+"U )"):console.error("SMB is not limited by maxUAMSMBBasalMinutes. ( insulinReq: "+Fa+"U )")):(console.error(".maxSMBBasalMinutes: "+ka+", profile.current_basal: "+i.current_basal*N),Fa>(Na=o(i.current_basal*ka/60,1))?console.error("SMB limited by maxSMBBasalMinutes: "+ka+"m ]: "+Na+"U ( insulinReq: "+Fa+"U )"):console.error("SMB is not limited by maxSMBBasalMinutes. ( insulinReq: "+Fa+"U )"));var Ha=i.bolus_increment,Za=1/Ha,$a=i.smb_delivery_ratio;$a>.5&&console.error("SMB Delivery Ratio increased from default 0.5 to "+o($a,2));var Ja=Math.min(Fa*$a,Na);Ja=Math.floor(Ja*Za)/Za,Ia=o(60*((ot-(Tt+ia)/2)/_t)/i.current_basal*N),Fa>0&&Ja=30?(Ia=30*o(Ia/30),Ia=Math.min(60,Math.max(0,Ia))):(Ka=o(Je*Ia/30,2),Ia=30),He.reason+=" insulinReq "+Fa,Ja>=Na&&(He.reason+="; maxBolus "+Na),Ia>0&&(He.reason+="; setting "+Ia+"m low temp of "+Ka+"U/h"),He.reason+=". ";var Qa=3;i.SMBInterval&&(Qa=Math.min(10,Math.max(1,i.SMBInterval)));var Va=o(Qa-Wa,0),Xa=o(60*(Qa-Wa),0)%60;if(console.error("naive_eventualBG "+Tt+","+Ia+"m "+Ka+"U/h temp needed; last bolus "+Wa+"m ago; maxBolus: "+Na),Wa>Qa?Ja>0&&(He.units=Ja,He.reason+="Microbolusing "+Ja+"U. "):He.reason+="Waiting "+Va+"m "+Xa+"s to microbolus again. ",Ia>0)return He.rate=Ka,He.duration=Ia,He}var Ya=u.getMaxSafeBasal(i);return ja>Ya&&(He.reason+="adj. req. rate: "+ja+" to maxSafeBasal: "+o(Ya,2)+", ",ja=r(Ya,i)),(Pa=t.duration*(t.rate-Je)/60)>=2*Fa?(He.reason+=t.duration+"m@"+t.rate.toFixed(2)+" > 2 * insulinReq. Setting temp basal of "+ja+"U/hr. ",u.setTempBasal(ja,30,i,He,t)):void 0===t.duration||0===t.duration?(He.reason+="no temp, setting "+ja+"U/hr. ",u.setTempBasal(ja,30,i,He,t)):t.duration>5&&r(ja,i)<=r(t.rate,i)?(He.reason+="temp "+t.rate+" >~ req "+ja+"U/hr. ",He):(He.reason+="temp "+t.rate+"<"+ja+"U/hr. ",u.setTempBasal(ja,30,i,He,t))}},6880:(e,t,a)=>{var r=a(6654);e.exports=function(e,t){var a=20;void 0!==t&&"string"==typeof t.model&&(r(t.model,"54")||r(t.model,"23"))&&(a=40);return e<1?Math.round(e*a)/a:e<10?Math.round(20*e)/20:Math.round(10*e)/10}},2705:(e,t,a)=>{var r=a(5639).Symbol;e.exports=r},9932:e=>{e.exports=function(e,t){for(var a=-1,r=null==e?0:e.length,o=Array(r);++a{e.exports=function(e,t,a){return e==e&&(void 0!==a&&(e=e<=a?e:a),void 0!==t&&(e=e>=t?e:t)),e}},4239:(e,t,a)=>{var r=a(2705),o=a(9607),n=a(2333),i=r?r.toStringTag:void 0;e.exports=function(e){return null==e?void 0===e?"[object Undefined]":"[object Null]":i&&i in Object(e)?o(e):n(e)}},531:(e,t,a)=>{var r=a(2705),o=a(9932),n=a(1469),i=a(3448),s=r?r.prototype:void 0,l=s?s.toString:void 0;e.exports=function e(t){if("string"==typeof t)return t;if(n(t))return o(t,e)+"";if(i(t))return l?l.call(t):"";var a=t+"";return"0"==a&&1/t==-Infinity?"-0":a}},7561:(e,t,a)=>{var r=a(7990),o=/^\s+/;e.exports=function(e){return e?e.slice(0,r(e)+1).replace(o,""):e}},1957:(e,t,a)=>{var r="object"==typeof a.g&&a.g&&a.g.Object===Object&&a.g;e.exports=r},9607:(e,t,a)=>{var r=a(2705),o=Object.prototype,n=o.hasOwnProperty,i=o.toString,s=r?r.toStringTag:void 0;e.exports=function(e){var t=n.call(e,s),a=e[s];try{e[s]=void 0;var r=!0}catch(e){}var o=i.call(e);return r&&(t?e[s]=a:delete e[s]),o}},2333:e=>{var t=Object.prototype.toString;e.exports=function(e){return t.call(e)}},5639:(e,t,a)=>{var r=a(1957),o="object"==typeof self&&self&&self.Object===Object&&self,n=r||o||Function("return this")();e.exports=n},7990:e=>{var t=/\s/;e.exports=function(e){for(var a=e.length;a--&&t.test(e.charAt(a)););return a}},6654:(e,t,a)=>{var r=a(9750),o=a(531),n=a(554),i=a(9833);e.exports=function(e,t,a){e=i(e),t=o(t);var s=e.length,l=a=void 0===a?s:r(n(a),0,s);return(a-=t.length)>=0&&e.slice(a,l)==t}},1469:e=>{var t=Array.isArray;e.exports=t},3218:e=>{e.exports=function(e){var t=typeof e;return null!=e&&("object"==t||"function"==t)}},7005:e=>{e.exports=function(e){return null!=e&&"object"==typeof e}},3448:(e,t,a)=>{var r=a(4239),o=a(7005);e.exports=function(e){return"symbol"==typeof e||o(e)&&"[object Symbol]"==r(e)}},8601:(e,t,a)=>{var r=a(4841),o=1/0;e.exports=function(e){return e?(e=r(e))===o||e===-1/0?17976931348623157e292*(e<0?-1:1):e==e?e:0:0===e?e:0}},554:(e,t,a)=>{var r=a(8601);e.exports=function(e){var t=r(e),a=t%1;return t==t?a?t-a:t:0}},4841:(e,t,a)=>{var r=a(7561),o=a(3218),n=a(3448),i=/^[-+]0x[0-9a-f]+$/i,s=/^0b[01]+$/i,l=/^0o[0-7]+$/i,u=parseInt;e.exports=function(e){if("number"==typeof e)return e;if(n(e))return NaN;if(o(e)){var t="function"==typeof e.valueOf?e.valueOf():e;e=o(t)?t+"":t}if("string"!=typeof e)return 0===e?e:+e;e=r(e);var a=s.test(e);return a||l.test(e)?u(e.slice(2),a?2:8):i.test(e)?NaN:+e}},9833:(e,t,a)=>{var r=a(531);e.exports=function(e){return null==e?"":r(e)}}},t={};function a(r){var o=t[r];if(void 0!==o)return o.exports;var n=t[r]={exports:{}};return e[r](n,n.exports,a),n.exports}a.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}();var r=a(5546);freeaps_determineBasal=r})(); +var freeaps_determineBasal;(()=>{var e={5546:(e,t,a)=>{var r=a(6880);function o(e,t){t||(t=0);var a=Math.pow(10,t);return Math.round(e*a)/a}function n(e,t){return"mmol/L"===t.out_units?o(.0555*e,1):Math.round(e)}e.exports=function(e,t,a,i,s,l,u,d,m,c,g,h,p,v){var B=i.min_bg,f=v.overrideTarget;0!=f&&v.useOverride&&!i.temptargetSet&&(B=f);const b=v.smbIsOff,M=v.advancedSettings,_=v.isfAndCr,y=v.isf,x=v.cr,S=v.smbIsAlwaysOff,D=v.start,w=v.end,G=v.smbMinutes,T=v.uamMinutes;var C=0,U=B,O=0,R="",A="",I="",F="",j="",P=0,E=0,q=0,W=0,k=0,L=0;const z=v.weightedAverage;var N=1,H=i.sens,Z=i.carb_ratio;v.useOverride&&(N=v.overridePercentage/100,_?(H/=N,Z/=N):(x&&(Z/=N),y&&(H/=N)));const $=i.weightPercentage,J=v.average_total_data;function K(e,t){var a=e.getTime();return new Date(a+36e5*t)}function Q(e){var t=i.bolus_increment;.1!=t&&(t=.05);var a=e/t;return a>=1?o(Math.floor(a)*t,5):0}function V(e){function t(e){return e<10&&(e="0"+e),e}return t(e.getHours())+":"+t(e.getMinutes())+":00"}function X(e,t){var a=new Date("1/1/1999 "+e),r=new Date("1/1/1999 "+t);return(a.getTime()-r.getTime())/36e5}function Y(e,t){var a=0,r=t,o=(e-t)/36e5,n=0,i=o,s=0;do{if(o>0){var l=V(r),u=p[0].rate;for(let e=0;e=(s=X(p[e+1].start,p[e].start))?n=s:o=s?n=s:od)if(e+1=(s=X(m,l))?n=s:o=(s=X("23:59:59",l))?n=s:o0&&o21)k=Y(ee,(ae=24-O,re=ee.getTime(),new Date(re-36e5*ae))),F="24 hours of data is required for an accurate tdd calculation. Currently only "+O.toPrecision(3)+" hours of pump history data are available. Using your pump scheduled basals to fill in the missing hours. Scheduled basals added: "+k.toPrecision(5)+" U. ";else O<21?(pe=!1,enableDynamicCR=!1):F=""}else console.log("Pumphistory is empty!"),pe=!1,enableDynamicCR=!1;var ae,re;for(let e=0;e0){P=e,L=g[e].rate;var oe=g[e-1]["duration (min)"]/60,ne=oe,ie=new Date(g[e-1].timestamp),se=ie,le=0;do{if(e--,0==e){se=new Date;break}if("TempBasal"==g[e]._type||"PumpSuspend"==g[e]._type){se=new Date(g[e].timestamp);break}var ue=e-2;if(ue>=0&&"Rewind"==g[ue]._type){let e=g[ue].timestamp;for(;ue-1>=0&&"Prime"==g[ue-=1]._type;)le=(g[ue].timestamp-e)/36e5;le>=oe&&(se=e,le=0)}}while(e>0);var de=(se-ie)/36e5;de0&&(--r,"TempBasal"==g[r]._type)){a=new Date(g[r].timestamp);break}}while(r>0);(a-t)/36e5>0&&(k+=Y(a,t))}for(let e=g.length-1;e>0;e--)if("TempBasalDuration"==g[e]._type){let t=g[e]["duration (min)"]/60,a=new Date(g[e].timestamp);var me=a;let r=e;do{if(--r,r>=0&&("TempBasal"==g[r]._type||"PumpSuspend"==g[r]._type)){me=new Date(g[r].timestamp);break}}while(r>0);if(0==e&&"TempBasalDuration"==g[0]._type&&(me=new Date,t=g[e]["duration (min)"]/60),(me-a)/36e5-t>0){k+=Y(me,K(a,t))}}var ce,ge={TDD:o(E=W+q+k,5),bolus:o(W,5),temp_basal:o(q,5),scheduled_basal:o(k,5)};O>21?(A=". Bolus insulin: "+W.toPrecision(5)+" U",I=". Temporary basal insulin: "+q.toPrecision(5)+" U",R=". Insulin with scheduled basal rate: "+k.toPrecision(5)+" U",j=F+(" TDD past 24h is: "+E.toPrecision(5)+" U")+A+I+R,tddReason=", Total insulin: "+o(E,2)+" U, "+o(W/E*100,0)+"% Bolus "+o((q+k)/E*100,0)+"% Basal"):tddReason=", TDD: Not enough pumpData (< 21h)";const he=e.glucose;var pe=h.useNewFormula;const ve=h.enableDynamicCR,Be=Math.min(i.autosens_min,i.autosens_max),fe=Math.max(i.autosens_min,i.autosens_max);(fe==Be||fe<1||Be>1)&&(pe=!1,console.log("Dynamic ISF disabled due to current autosens settings"));const be=h.adjustmentFactor,Me=B;var _e=!1,ye="",xe=1,Se="";J>0&&(xe=z/J),Se=xe>1?"Basal adjustment with a 24 hour to total average (up to 14 days of data) TDD ratio (limited by Autosens max setting). Basal Ratio: "+(xe=o(xe=Math.min(xe,i.autosens_max),2))+". Upper limit = Autosens max ("+i.autosens_max+")":xe<1?"Basal adjustment with a 24 hour to to total average (up to 14 days of data) TDD ratio (limited by Autosens min setting). Basal Ratio: "+(xe=o(xe=Math.max(xe,i.autosens_min),2))+". Lower limit = Autosens min ("+i.autosens_min+")":"Basal adjusted with a 24 hour to total average (up to 14 days of data) TDD ratio: "+xe,Se=", Basal ratio: "+xe,(i.high_temptarget_raises_sensitivity||i.exercise_mode||v.isEnabled)&&(_e=!0),Me>=118&&_e&&(pe=!1,ye="Dynamic ISF temporarily off due to a high temp target/exercising. Current min target: "+Me);var De=", Dynamic ratios log: ",we=", AF: "+be,Ge="BG: "+he+" mg/dl ("+(.0555*he).toPrecision(2)+" mmol/l)",Te="",Ce="";const Ue=h.curve,Oe=h.insulinPeakTime,Re=h.useCustomPeakTime;var Ae=55,Ie=65;switch(Ue){case"rapid-acting":Ie=65;break;case"ultra-rapid":Ie=50}Re?(Ae=120-Oe,console.log("Custom insulinpeakTime set to :"+Oe+", insulinFactor: "+Ae)):(Ae=120-Ie,console.log("insulinFactor set to : "+Ae)),ce=E,$<1&&z>0&&(E=z,console.log("Using weighted TDD average: "+o(E,2)+" U, instead of past 24 h ("+o(ce,2)+" U), weight: "+$),Ce=", Weighted TDD: "+o(E,2)+" U");const Fe=h.sigmoid;var je="";if(pe){var Pe=H*be*E*Math.log(he/Ae+1)/1800;Te=", Logarithmic formula"}if(pe&&Fe){const e=Be,t=fe-e,a=.0555*(he-B);var Ee=xe,qe=fe-1;1==fe&&(qe=fe+.01-1);const r=Math.log10(1/qe-e/qe)/Math.log10(Math.E),o=a*be*Ee+r;Pe=t/(1+Math.exp(-o))+e,Te=", Sigmoid function"}var We=Z;const ke=o(Z,1);var Le="",ze="";if(pe&&E>0){if(Le=", Dynamic ISF/CR: On/",Pe>fe?(ye=", Dynamic ISF limited by autosens_max setting: "+fe+" ("+o(Pe,2)+"), ",ze=", Autosens/Dynamic Limit: "+fe+" ("+o(Pe,2)+")",Pe=fe):Pe-.5?"+"+o(e.delta,0):o(e.delta,0);var tt=Math.min(e.delta,e.short_avgdelta),at=Math.min(e.short_avgdelta,e.long_avgdelta),rt=Math.max(e.delta,e.short_avgdelta,e.long_avgdelta);(Ye<=10||38===Ye||et>=3)&&(He.reason="CGM is calibrating, in ??? state, or noise is high");if(Ye>60&&0==e.delta&&e.short_avgdelta>-1&&e.short_avgdelta<1&&e.long_avgdelta>-1&&e.long_avgdelta<1&&("fakecgm"==e.device?(console.error("CGM data is unchanged ("+n(Ye,i)+"+"+n(e.delta,i)+") for 5m w/ "+n(e.short_avgdelta,i)+" mg/dL ~15m change & "+n(e.long_avgdelta,2)+" mg/dL ~45m change"),console.error("Simulator mode detected ("+e.device+"): continuing anyway")):!0),Xe>12||Xe<-5?He.reason="If current system time "+Ke+" is correct, then BG data is too old. The last BG data was read "+Xe+"m ago at "+Ve:0===e.short_avgdelta&&0===e.long_avgdelta&&(e.last_cal&&e.last_cal<3?He.reason="CGM was just calibrated":He.reason="CGM data is unchanged ("+n(Ye,i)+"+"+n(e.delta,i)+") for 5m w/ "+n(e.short_avgdelta,i)+" mg/dL ~15m change & "+n(e.long_avgdelta,i)+" mg/dL ~45m change"),Ye<=10||38===Ye||et>=3||Xe>12||Xe<-5||0===e.short_avgdelta&&0===e.long_avgdelta)return t.rate>=Je?(He.reason+=". Canceling high temp basal of "+t.rate,He.deliverAt=Ze,He.temp="absolute",He.duration=0,He.rate=0,He):0===t.rate&&t.duration>30?(He.reason+=". Shortening "+t.duration+"m long zero temp to 30m. ",He.deliverAt=Ze,He.temp="absolute",He.duration=30,He.rate=0,He):(He.reason+=". Temp "+t.rate+" <= current basal "+Je+"U/hr; doing nothing. ",He);var ot,nt,it,st,lt=i.max_iob;if(void 0!==B&&(nt=B),void 0!==i.max_bg&&(it=B),void 0!==i.enableSMB_high_bg_target&&(st=i.enableSMB_high_bg_target),void 0===B)return He.error="Error: could not determine target_bg. ",He;ot=B;var ut=i.exercise_mode||i.high_temptarget_raises_sensitivity||v.isEnabled,dt=100,mt=160;if(mt=i.half_basal_exercise_target,v.isEnabled){const e=v.hbt;console.log("Half Basal Target used: "+n(e,i)+" "+i.out_units),mt=e}else console.log("Default Half Basal Target used: "+n(mt,i)+" "+i.out_units);if(ut&&i.temptargetSet&&ot>dt||i.low_temptarget_lowers_sensitivity&&i.temptargetSet&&ot=ot&&sensitivityRatio0&&(process.stderr.write("TDD-adjustment of basals activated, using tdd24h_14d_Ratio "+o(xe,2)+", TDD 24h = "+o(ce,2)+"U, Weighted average TDD = "+o(z,2)+"U, (Weight percentage = "+$+"), Total data of TDDs (up to 14 days) average = "+o(J,2)+"U. "),Je!==$e*N?process.stderr.write("Adjusting basal from "+$e*N+" U/h to "+Je+" U/h; "):process.stderr.write("Basal unchanged: "+Je+" U/h; "))),i.temptargetSet);else if(void 0!==s&&s&&(i.sensitivity_raises_target&&s.ratio<1||i.resistance_lowers_target&&s.ratio>1)){nt=o((nt-60)/s.ratio)+60,it=o((it-60)/s.ratio)+60;var gt=o((ot-60)/s.ratio)+60;ot===(gt=Math.max(80,gt))?process.stderr.write("target_bg unchanged: "+n(gt,i)+"; "):process.stderr.write("target_bg from "+n(gt,i)+" to "+n(gt,i)+"; "),ot=gt}var ht=n(ot,i);ot!=B&&(ht=0!==f&&f!==ot?n(B,i)+"→"+n(f,i)+"→"+n(ot,i):n(B,i)+"→"+n(ot,i));var pt=200,vt=200,Bt=200;if(e.noise>=2){var ft=Math.max(1.1,i.noisyCGMTargetMultiplier);Math.min(250,i.maxRaw);pt=o(Math.min(200,nt*ft)),vt=o(Math.min(200,ot*ft)),Bt=o(Math.min(200,it*ft)),process.stderr.write("Raising target_bg for noisy / raw CGM data, from "+n(gt,i)+" to "+n(vt,i)+"; "),nt=pt,ot=vt,it=Bt}U=nt-.5*(nt-40);var bt=i.threshold_setting;bt>U&&bt<=120&&bt>=65?(console.error("Threshold changed in settings from "+n(U,i)+" to "+n(bt,i)+". "),U=bt):console.error("Current threshold: "+n(U,i));var Mt="",_t=(o(H,1),H);if(void 0!==s&&s&&((_t=o(_t=H/sensitivityRatio,1))!==H?process.stderr.write("ISF from "+n(H,i)+" to "+n(_t,i)):process.stderr.write("ISF unchanged: "+n(_t,i)),Mt+="Autosens ratio: "+o(sensitivityRatio,2)+", ISF: "+n(H,i)+"→"+n(_t,i)),console.error("CR:"+Z),void 0===a)return He.error="Error: iob_data undefined. ",He;var yt,xt=a;if(a.length,a.length>1&&(a=xt[0]),void 0===a.activity||void 0===a.iob)return He.error="Error: iob_data missing some property. ",He;var St=((yt=void 0!==a.lastTemp?o((new Date(Ke).getTime()-a.lastTemp.date)/6e4):0)+t.duration)%30;if(console.error("currenttemp:"+t.rate+" lastTempAge:"+yt+"m, tempModulus:"+St+"m"),He.temp="absolute",He.deliverAt=Ze,d&&t&&a.lastTemp&&t.rate!==a.lastTemp.rate&&yt>10&&t.duration)return He.reason="Warning: currenttemp rate "+t.rate+" != lastTemp rate "+a.lastTemp.rate+" from pumphistory; canceling temp",u.setTempBasal(0,0,i,He,t);if(t&&a.lastTemp&&t.duration>0){var Dt=yt-a.lastTemp.duration;if(Dt>5&&yt>10)return He.reason="Warning: currenttemp running but lastTemp from pumphistory ended "+Dt+"m ago; canceling temp",u.setTempBasal(0,0,i,He,t)}var wt=o(-a.activity*_t*5,2),Gt=o(6*(tt-wt));Gt<0&&(Gt=o(6*(at-wt)))<0&&(Gt=o(6*(e.long_avgdelta-wt)));var Tt=Ye,Ct=(Tt=a.iob>0?o(Ye-a.iob*_t):o(Ye-a.iob*Math.min(_t,H)))+Gt;if(void 0===Ct||isNaN(Ct))return He.error="Error: could not calculate eventualBG. Sensitivity: "+_t+" Deviation: "+Gt,He;var Ut,Ot,Rt=function(e,t,a){return o(a+(e-t)/24,1)}(ot,Ct,wt);He={temp:"absolute",bg:Ye,tick:Qe,eventualBG:Ct,insulinReq:0,reservoir:m,deliverAt:Ze,sensitivityRatio,CR:o(Z,1),TDD:ce,insulin:ge,current_target:ot,insulinForManualBolus:C,manualBolusErrorString:0,minDelta:tt,expectedDelta:Rt,minGuardBG:Ot,minPredBG:Ut,threshold:n(U,i)};var At=[],It=[],Ft=[],jt=[];At.push(Ye),It.push(Ye),jt.push(Ye),Ft.push(Ye);var Pt=function(e,t,a,r,o,i){return t?!e.allowSMB_with_high_temptarget&&e.temptargetSet&&o>100?(console.error("SMB disabled due to high temptarget of "+o),!1):!0===a.bwFound&&!1===e.A52_risk_enable?(console.error("SMB disabled due to Bolus Wizard activity in the last 6 hours."),!1):!0===e.enableSMB_always?(a.bwFound?console.error("Warning: SMB enabled within 6h of using Bolus Wizard: be sure to easy bolus 30s before using Bolus Wizard"):console.error("SMB enabled due to enableSMB_always"),!0):!0===e.enableSMB_with_COB&&a.mealCOB?(a.bwCarbs?console.error("Warning: SMB enabled with Bolus Wizard carbs: be sure to easy bolus 30s before using Bolus Wizard"):console.error("SMB enabled for COB of "+a.mealCOB),!0):!0===e.enableSMB_after_carbs&&a.carbs?(a.bwCarbs?console.error("Warning: SMB enabled with Bolus Wizard carbs: be sure to easy bolus 30s before using Bolus Wizard"):console.error("SMB enabled for 6h after carb entry"),!0):!0===e.enableSMB_with_temptarget&&e.temptargetSet&&o<100?(a.bwFound?console.error("Warning: SMB enabled within 6h of using Bolus Wizard: be sure to easy bolus 30s before using Bolus Wizard"):console.error("SMB enabled for temptarget of "+n(o,e)),!0):!0===e.enableSMB_high_bg&&null!==i&&r>=i?(console.error("Checking BG to see if High for SMB enablement."),console.error("Current BG",r," | High BG ",i),a.bwFound?console.error("Warning: High BG SMB enabled within 6h of using Bolus Wizard: be sure to easy bolus 30s before using Bolus Wizard"):console.error("High BG detected. Enabling SMB."),!0):(console.error("SMB disabled (no enableSMB preferences active or no condition satisfied)"),!1):(console.error("SMB disabled (!microBolusAllowed)"),!1)}(i,d,l,Ye,ot,st);if(b)if(S){let e=c.getHours();e>=D&&e<=w&&(console.error("SMB disabled by schedule (a Profile is active with SMBs disabled)"),Pt=!1)}else console.error("SMBs are disabled (a Profile is active with SMBs disabled)"),Pt=!1;var Et=i.enableUAM,qt=0,Wt=0;qt=o(tt-wt,1);var kt=o(tt-wt,1);csf=_t/Z,console.error("profile.sens:"+n(H,i)+", sens:"+n(_t,i)+", CSF:"+o(csf,1));var Lt=o(30*csf*5/60,1);qt>Lt&&(console.error("Limiting carb impact from "+qt+" to "+Lt+"mg/dL/5m (30g/h)"),qt=Lt);var zt=3;sensitivityRatio&&(zt/=sensitivityRatio);var Nt=zt;if(l.carbs){zt=Math.max(zt,l.mealCOB/20);var Ht=o((new Date(Ke).getTime()-l.lastCarbTime)/6e4),Zt=(l.carbs-l.mealCOB)/l.carbs;Nt=o(Nt=zt+1.5*Ht/60,1),console.error("Last carbs "+Ht+" minutes ago; remainingCATime:"+Nt+"hours; "+o(100*Zt,1)+"% carbs absorbed")}var $t=Math.max(0,qt/5*60*Nt/2)/csf,Jt=90,Kt=1;i.remainingCarbsCap&&(Jt=Math.min(90,i.remainingCarbsCap)),i.remainingCarbsFraction&&(Kt=Math.min(1,i.remainingCarbsFraction));var Qt=1-Kt,Vt=Math.max(0,l.mealCOB-$t-l.carbs*Qt),Xt=(Vt=Math.min(Jt,Vt))*csf*5/60/(Nt/2),Yt=o(l.slopeFromMaxDeviation,2),ea=o(l.slopeFromMinDeviation,2),ta=Math.min(Yt,-ea/3);Wt=0===qt?0:Math.min(60*Nt/5/2,Math.max(0,l.mealCOB*csf/qt)),console.error("Carb Impact:"+qt+"mg/dL per 5m; CI Duration:"+o(5*Wt/60*2,1)+"hours; remaining CI ("+Nt/2+"h peak):"+o(Xt,1)+"mg/dL per 5m");var aa,ra,oa,na,ia=999,sa=999,la=999,ua=999,da=999,ma=999,ca=999,ga=Ct,ha=Ye,pa=Ye,va=0,Ba=[],fa=[];try{xt.forEach((function(e){var t=o(-e.activity*_t*5,2),a=o(-e.iobWithZeroTemp.activity*_t*5,2),r=Tt,n=qt*(1-Math.min(1,It.length/12));if(!0===(pe&&!Fe))ga=It[It.length-1]+o(-e.activity*(1800/(E*be*Math.log(Math.max(It[It.length-1],39)/Ae+1)))*5,2)+n,r=jt[jt.length-1]+o(-e.iobWithZeroTemp.activity*(1800/(E*be*Math.log(Math.max(jt[jt.length-1],39)/Ae+1)))*5,2),console.log("Dynamic ISF (Logarithmic Formula) )adjusted predictions for IOB and ZT: IOBpredBG: "+o(ga,2)+" , ZTpredBG: "+o(r,2));else ga=It[It.length-1]+t+n,r=jt[jt.length-1]+a;var i=Math.max(0,Math.max(0,qt)*(1-At.length/Math.max(2*Wt,1))),s=Math.min(At.length,12*Nt-At.length),l=Math.max(0,s/(Nt/2*12)*Xt);i+l,Ba.push(o(l,0)),fa.push(o(i,0)),COBpredBG=At[At.length-1]+t+Math.min(0,n)+i+l;var u=Math.max(0,kt+Ft.length*ta),d=Math.max(0,kt*(1-Ft.length/Math.max(36,1))),m=Math.min(u,d);if(m>0&&(va=o(5*(Ft.length+1)/60,1)),!0===(pe&&!Fe))UAMpredBG=Ft[Ft.length-1]+o(-e.activity*(1800/(E*be*Math.log(Math.max(Ft[Ft.length-1],39)/Ae+1)))*5,2)+Math.min(0,n)+m,console.log("Dynamic ISF (Logarithmic Formula) adjusted prediction for UAM: UAMpredBG: "+o(UAMpredBG,2));else UAMpredBG=Ft[Ft.length-1]+t+Math.min(0,n)+m;It.length<48&&It.push(ga),At.length<48&&At.push(COBpredBG),Ft.length<48&&Ft.push(UAMpredBG),jt.length<48&&jt.push(r),COBpredBG18&&gaha&&(ha=ga),(Wt||Xt>0)&&At.length>18&&COBpredBG0)&&COBpredBG>ha&&(pa=COBpredBG),Et&&Ft.length>12&&UAMpredBGha&&UAMpredBG}))}catch(e){console.error("Problem with iobArray. Optional feature Advanced Meal Assist disabled")}l.mealCOB&&(console.error("predCIs (mg/dL/5m):"+fa.join(" ")),console.error("remainingCIs: "+Ba.join(" "))),He.predBGs={},It.forEach((function(e,t,a){a[t]=o(Math.min(401,Math.max(39,e)))}));for(var ba=It.length-1;ba>12&&It[ba-1]===It[ba];ba--)It.pop();for(He.predBGs.IOB=It,ra=o(It[It.length-1]),jt.forEach((function(e,t,a){a[t]=o(Math.min(401,Math.max(39,e)))})),ba=jt.length-1;ba>6&&!(jt[ba-1]>=jt[ba]||jt[ba]<=ot);ba--)jt.pop();if(He.predBGs.ZT=jt,o(jt[jt.length-1]),l.mealCOB>0&&(qt>0||Xt>0)){for(At.forEach((function(e,t,a){a[t]=o(Math.min(1500,Math.max(39,e)))})),ba=At.length-1;ba>12&&At[ba-1]===At[ba];ba--)At.pop();He.predBGs.COB=At,oa=o(At[At.length-1]),Ct=Math.max(Ct,o(At[At.length-1])),console.error("COBpredBG: "+o(At[At.length-1]))}if(qt>0||Xt>0){if(Et){for(Ft.forEach((function(e,t,a){a[t]=o(Math.min(401,Math.max(39,e)))})),ba=Ft.length-1;ba>12&&Ft[ba-1]===Ft[ba];ba--)Ft.pop();He.predBGs.UAM=Ft,na=o(Ft[Ft.length-1]),Ft[Ft.length-1]&&(Ct=Math.max(Ct,o(Ft[Ft.length-1])))}He.eventualBG=Ct}console.error("UAM Impact:"+kt+"mg/dL per 5m; UAM Duration:"+va+"hours"),ia=Math.max(39,ia),sa=Math.max(39,sa),la=Math.max(39,la),Ut=o(ia);var Ma=l.mealCOB/l.carbs;aa=o(la<999&&sa<999?(1-Ma)*UAMpredBG+Ma*COBpredBG:sa<999?(ga+COBpredBG)/2:la<999?(ga+UAMpredBG)/2:ga),ca>aa&&(aa=ca),Ot=o(Ot=Wt||Xt>0?Et?Ma*ua+(1-Ma)*da:ua:Et?da:ma);var _a=la;if(cala&&(_a=(la+ca)/2);if(_a=o(_a),l.carbs)if(!Et&&sa<999)Ut=o(Math.max(ia,sa));else if(sa<999){var xa=Ma*sa+(1-Ma)*_a;Ut=o(Math.max(ia,sa,xa))}else Ut=Et?_a:Ot;else Et&&(Ut=o(Math.max(ia,_a)));Ut=Math.min(Ut,aa),process.stderr.write("minPredBG: "+Ut+" minIOBPredBG: "+ia+" minZTGuardBG: "+ca),sa<999&&process.stderr.write(" minCOBPredBG: "+sa),la<999&&process.stderr.write(" minUAMPredBG: "+la),console.error(" avgPredBG:"+aa+" COB/Carbs:"+l.mealCOB+"/"+l.carbs),pa>Ye&&(Ut=Math.min(Ut,pa)),He.COB=l.mealCOB,He.IOB=a.iob,He.BGI=n(wt,i),He.deviation=n(Gt,i),He.ISF=n(_t,i),He.CR=o(Z,1),He.target_bg=n(ot,i),He.TDD=o(ce,2),He.current_target=o(ot,0);var Sa=He.CR;ke!=He.CR&&(Sa=ke+"→"+He.CR);var Da="";.5!=i.smb_delivery_ratio&&(Da=", SMB Ratio "+i.smb_delivery_ratio),He.reason=Mt+", COB: "+He.COB+", Dev: "+He.deviation+", BGI: "+He.BGI+", CR: "+Sa+", Target: "+ht+", minPredBG "+n(Ut,i)+", minGuardBG "+n(Ot,i)+", IOBpredBG "+n(ra,i)+Da,oa>0&&(He.reason+=", COBpredBG "+n(oa,i)),na>0&&(He.reason+=", UAMpredBG "+n(na,i)),He.reason+=tddReason,He.reason+="; ";var wa=Tt;wa<40&&(wa=Math.min(Ot,wa));var Ga,Ta=U-wa,Ca=240,Ua=240;if(l.mealCOB>0&&(qt>0||Xt>0)){for(ba=0;baGa*Ye&&(console.error("maxDelta "+n(rt,i)+" > "+100*Ga+"% of BG "+n(Ye,i)+" - disabling SMB"),He.reason+="maxDelta "+n(rt,i)+" > "+100*Ga+"% of BG "+n(Ye,i)+" - SMB disabled!, ",Pt=!1),console.error("BG projected to remain above "+n(nt,i)+" for "+Ca+"minutes"),(Ua<240||Ca<60)&&console.error("BG projected to remain above "+n(U,i)+" for "+Ua+"minutes");var Oa=Ua,Ra=i.current_basal*N*_t*Oa/60,Aa=Math.max(0,l.mealCOB-.25*l.carbs),Ia=(Ta-Ra)/csf-Aa;Ra=o(Ra),Ia=o(Ia),console.error("naive_eventualBG:",Tt,"bgUndershoot:",Ta,"zeroTempDuration:",Oa,"zeroTempEffect:",Ra,"carbsReq:",Ia),"Could not parse clock data"==l.reason?console.error("carbsReq unknown: Could not parse clock data"):Ia>=i.carbsReqThreshold&&Ua<=45&&(He.carbsReq=Ia,He.reason+=Ia+" add'l carbs req w/in "+Ua+"m; ");var Fa=0;if(Ye0&&tt>Rt)He.reason+="IOB "+a.iob+" < "+o(-i.current_basal*N*20/60,2),He.reason+=" and minDelta "+n(tt,i)+" > expectedDelta "+n(Rt,i)+"; ";else if(Ye=55)return He.reason+="; Canceling temp at "+He.deliverAt.getMinutes()+"m past the hour. ",u.setTempBasal(0,0,i,He,t);var ja=0,Pa=Je,Ea=0;if(CtRt&&tt>0&&!Ia)return Tt<40?(He.reason+=", naive_eventualBG < 40. ",u.setTempBasal(0,30,i,He,t)):(e.delta>tt?He.reason+=", but Delta "+n(Qe,i)+" > expectedDelta "+n(Rt,i):He.reason+=", but Min. Delta "+tt.toFixed(2)+" > Exp. Delta "+n(Rt,i),t.duration>15&&r(Je,i)===r(t.rate,i)?(He.reason+=", temp "+t.rate+" ~ req "+Je+"U/hr. ",He):(He.reason+="; setting current basal of "+Je+" as temp. ",u.setTempBasal(Je,30,i,He,t)));ja=o(ja=2*Math.min(0,(Ct-ot)/_t),2);var qa=Math.min(0,(Tt-ot)/_t);if(qa=o(qa,2),tt<0&&tt>Rt)ja=o(ja*(tt/Rt),2);Pa=r(Pa=Je+2*ja,i),Ea=t.duration*(t.rate-Je)/60;var Wa=Math.min(ja,qa);if(console.log("naiveInsulinReq:"+qa),Ea5&&Pa>=.8*t.rate)return He.reason+=", temp "+t.rate+" ~< req "+Pa+"U/hr. ",He;if(Pa<=0){if((Fa=o(60*((Ta=ot-Tt)/_t)/i.current_basal*N))<0?Fa=0:(Fa=30*o(Fa/30),Fa=Math.min(120,Math.max(0,Fa))),Fa>0)return He.reason+=", setting "+Fa+"m zero temp. ",u.setTempBasal(Pa,Fa,i,He,t)}else He.reason+=", setting "+Pa+"U/hr. ";return u.setTempBasal(Pa,30,i,He,t)}if(tt=2||Rt+-1*tt>=2)&&(He.manualBolusErrorString=tt>=0&&Rt>0?3:tt<0&&Rt<=0||tt<0&&Rt>=0?4:5),He.insulinForManualBolus=o((He.eventualBG-He.target_bg)/_t,2),!d||!Pt))return e.delta "+n(nt,i)+" but Delta "+n(Qe,i)+" < Exp. Delta "+n(Rt,i):He.reason+="Eventual BG "+n(Ct,i)+" > "+n(nt,i)+" but Min. Delta "+tt.toFixed(2)+" < Exp. Delta "+n(Rt,i),t.duration>15&&r(Je,i)===r(t.rate,i)?(He.reason+=", temp "+t.rate+" ~ req "+Je+"U/hr. ",He):(He.reason+="; setting current basal of "+Je+" as temp. ",u.setTempBasal(Je,30,i,He,t));if(Math.min(Ct,Ut)nt&&(He.manualBolusErrorString=6,He.insulinForManualBolus=o((He.eventualBG-He.target_bg)/_t,2),He.minPredBG=Ut),!d||!Pt))return He.reason+=n(Ct,i)+"-"+n(Ut,i)+" in range: no temp required",t.duration>15&&r(Je,i)===r(t.rate,i)?(He.reason+=", temp "+t.rate+" ~ req "+Je+"U/hr. ",He):(He.reason+="; setting current basal of "+Je+" as temp. ",u.setTempBasal(Je,30,i,He,t));if(Ct>=it&&(He.reason+="Eventual BG "+n(Ct,i)+" >= "+n(it,i)+", ",Ct>it&&(He.insulinForManualBolus=o((Ct-ot)/_t,2))),a.iob>lt)return He.reason+="IOB "+o(a.iob,2)+" > max_iob "+lt,t.duration>15&&r(Je,i)===r(t.rate,i)?(He.reason+=", temp "+t.rate+" ~ req "+Je+"U/hr. ",He):(He.reason+="; setting current basal of "+Je+" as temp. ",u.setTempBasal(Je,30,i,He,t));ja=o((Math.min(Ut,Ct)-ot)/_t,2),C=o((Ct-ot)/_t,2),ja>lt-a.iob?(console.error("SMB limited by maxIOB: "+lt-a.iob+" (. insulinReq: "+ja+" U)"),He.reason+="max_iob "+lt+", ",ja=lt-a.iob):console.error("SMB not limited by maxIOB ( insulinReq: "+ja+" U)."),C>lt-a.iob?(console.error("Ev. Bolus limited by maxIOB: "+lt-a.iob+" (. insulinForManualBolus: "+C+" U)"),He.reason+="max_iob "+lt+", "):console.error("Ev. Bolus would not be limited by maxIOB ( insulinForManualBolus: "+C+" U)."),Pa=r(Pa=Je+2*ja,i),ja=o(ja,3),He.insulinReq=ja;var ka=o((new Date(Ke).getTime()-a.lastBolusTime)/6e4,1);if(d&&Pt&&Ye>U){var La=30;void 0!==i.maxSMBBasalMinutes&&(La=i.maxSMBBasalMinutes);var za=30;void 0!==i.maxUAMSMBBasalMinutes&&(za=i.maxUAMSMBBasalMinutes),v.useOverride&&M&&G!==La&&(console.error("SMB Max Minutes - setting overriden from "+La+" to "+G),La=G),v.useOverride&&M&&T!==za&&(console.error("UAM Max Minutes - setting overriden from "+za+" to "+T),za=T);var Na=o(l.mealCOB/Z,3),Ha=0;void 0===La?(Ha=o(i.current_basal*N*30/60,1),console.error("smbMinutesSetting undefined: defaulting to 30m"),ja>Ha&&console.error("SMB limited by maxBolus: "+Ha+" ( "+ja+" U)")):a.iob>Na&&a.iob>0?(console.error("IOB"+a.iob+"> COB"+l.mealCOB+"; mealInsulinReq ="+Na),za?(console.error("maxUAMSMBBasalMinutes: "+za+", profile.current_basal: "+i.current_basal*N),Ha=o(i.current_basal*N*za/60,1)):(console.error("maxUAMSMBBasalMinutes undefined: defaulting to 30m"),Ha=o(i.current_basal*N*30/60,1)),ja>Ha?console.error("SMB limited by maxUAMSMBBasalMinutes [ "+za+"m ]: "+Ha+"U ( "+ja+"U )"):console.error("SMB is not limited by maxUAMSMBBasalMinutes. ( insulinReq: "+ja+"U )")):(console.error(".maxSMBBasalMinutes: "+La+", profile.current_basal: "+i.current_basal*N),ja>(Ha=o(i.current_basal*La/60,1))?console.error("SMB limited by maxSMBBasalMinutes: "+La+"m ]: "+Ha+"U ( insulinReq: "+ja+"U )"):console.error("SMB is not limited by maxSMBBasalMinutes. ( insulinReq: "+ja+"U )"));var Za=i.bolus_increment,$a=1/Za,Ja=i.smb_delivery_ratio;Ja>.5&&console.error("SMB Delivery Ratio increased from default 0.5 to "+o(Ja,2));var Ka=Math.min(ja*Ja,Ha);Ka=Math.floor(Ka*$a)/$a,Fa=o(60*((ot-(Tt+ia)/2)/_t)/i.current_basal*N),ja>0&&Ka=30?(Fa=30*o(Fa/30),Fa=Math.min(60,Math.max(0,Fa))):(Qa=o(Je*Fa/30,2),Fa=30),He.reason+=" insulinReq "+ja,Ka>=Ha&&(He.reason+="; maxBolus "+Ha),Fa>0&&(He.reason+="; setting "+Fa+"m low temp of "+Qa+"U/h"),He.reason+=". ";var Va=3;i.SMBInterval&&(Va=Math.min(10,Math.max(1,i.SMBInterval)));var Xa=o(Va-ka,0),Ya=o(60*(Va-ka),0)%60;if(console.error("naive_eventualBG "+Tt+","+Fa+"m "+Qa+"U/h temp needed; last bolus "+ka+"m ago; maxBolus: "+Ha),ka>Va?Ka>0&&(He.units=Ka,He.reason+="Microbolusing "+Ka+"U. "):He.reason+="Waiting "+Xa+"m "+Ya+"s to microbolus again. ",Fa>0)return He.rate=Qa,He.duration=Fa,He}var er=u.getMaxSafeBasal(i);return Pa>er&&(He.reason+="adj. req. rate: "+Pa+" to maxSafeBasal: "+o(er,2)+", ",Pa=r(er,i)),(Ea=t.duration*(t.rate-Je)/60)>=2*ja?(He.reason+=t.duration+"m@"+t.rate.toFixed(2)+" > 2 * insulinReq. Setting temp basal of "+Pa+"U/hr. ",u.setTempBasal(Pa,30,i,He,t)):void 0===t.duration||0===t.duration?(He.reason+="no temp, setting "+Pa+"U/hr. ",u.setTempBasal(Pa,30,i,He,t)):t.duration>5&&r(Pa,i)<=r(t.rate,i)?(He.reason+="temp "+t.rate+" >~ req "+Pa+"U/hr. ",He):(He.reason+="temp "+t.rate+"<"+Pa+"U/hr. ",u.setTempBasal(Pa,30,i,He,t))}},6880:(e,t,a)=>{var r=a(6654);e.exports=function(e,t){var a=20;void 0!==t&&"string"==typeof t.model&&(r(t.model,"54")||r(t.model,"23"))&&(a=40);return e<1?Math.round(e*a)/a:e<10?Math.round(20*e)/20:Math.round(10*e)/10}},2705:(e,t,a)=>{var r=a(5639).Symbol;e.exports=r},9932:e=>{e.exports=function(e,t){for(var a=-1,r=null==e?0:e.length,o=Array(r);++a{e.exports=function(e,t,a){return e==e&&(void 0!==a&&(e=e<=a?e:a),void 0!==t&&(e=e>=t?e:t)),e}},4239:(e,t,a)=>{var r=a(2705),o=a(9607),n=a(2333),i=r?r.toStringTag:void 0;e.exports=function(e){return null==e?void 0===e?"[object Undefined]":"[object Null]":i&&i in Object(e)?o(e):n(e)}},531:(e,t,a)=>{var r=a(2705),o=a(9932),n=a(1469),i=a(3448),s=r?r.prototype:void 0,l=s?s.toString:void 0;e.exports=function e(t){if("string"==typeof t)return t;if(n(t))return o(t,e)+"";if(i(t))return l?l.call(t):"";var a=t+"";return"0"==a&&1/t==-Infinity?"-0":a}},7561:(e,t,a)=>{var r=a(7990),o=/^\s+/;e.exports=function(e){return e?e.slice(0,r(e)+1).replace(o,""):e}},1957:(e,t,a)=>{var r="object"==typeof a.g&&a.g&&a.g.Object===Object&&a.g;e.exports=r},9607:(e,t,a)=>{var r=a(2705),o=Object.prototype,n=o.hasOwnProperty,i=o.toString,s=r?r.toStringTag:void 0;e.exports=function(e){var t=n.call(e,s),a=e[s];try{e[s]=void 0;var r=!0}catch(e){}var o=i.call(e);return r&&(t?e[s]=a:delete e[s]),o}},2333:e=>{var t=Object.prototype.toString;e.exports=function(e){return t.call(e)}},5639:(e,t,a)=>{var r=a(1957),o="object"==typeof self&&self&&self.Object===Object&&self,n=r||o||Function("return this")();e.exports=n},7990:e=>{var t=/\s/;e.exports=function(e){for(var a=e.length;a--&&t.test(e.charAt(a)););return a}},6654:(e,t,a)=>{var r=a(9750),o=a(531),n=a(554),i=a(9833);e.exports=function(e,t,a){e=i(e),t=o(t);var s=e.length,l=a=void 0===a?s:r(n(a),0,s);return(a-=t.length)>=0&&e.slice(a,l)==t}},1469:e=>{var t=Array.isArray;e.exports=t},3218:e=>{e.exports=function(e){var t=typeof e;return null!=e&&("object"==t||"function"==t)}},7005:e=>{e.exports=function(e){return null!=e&&"object"==typeof e}},3448:(e,t,a)=>{var r=a(4239),o=a(7005);e.exports=function(e){return"symbol"==typeof e||o(e)&&"[object Symbol]"==r(e)}},8601:(e,t,a)=>{var r=a(4841),o=1/0;e.exports=function(e){return e?(e=r(e))===o||e===-1/0?17976931348623157e292*(e<0?-1:1):e==e?e:0:0===e?e:0}},554:(e,t,a)=>{var r=a(8601);e.exports=function(e){var t=r(e),a=t%1;return t==t?a?t-a:t:0}},4841:(e,t,a)=>{var r=a(7561),o=a(3218),n=a(3448),i=/^[-+]0x[0-9a-f]+$/i,s=/^0b[01]+$/i,l=/^0o[0-7]+$/i,u=parseInt;e.exports=function(e){if("number"==typeof e)return e;if(n(e))return NaN;if(o(e)){var t="function"==typeof e.valueOf?e.valueOf():e;e=o(t)?t+"":t}if("string"!=typeof e)return 0===e?e:+e;e=r(e);var a=s.test(e);return a||l.test(e)?u(e.slice(2),a?2:8):i.test(e)?NaN:+e}},9833:(e,t,a)=>{var r=a(531);e.exports=function(e){return null==e?"":r(e)}}},t={};function a(r){var o=t[r];if(void 0!==o)return o.exports;var n=t[r]={exports:{}};return e[r](n,n.exports,a),n.exports}a.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}();var r=a(5546);freeaps_determineBasal=r})(); diff --git a/FreeAPS/Sources/APS/OpenAPS/Constants.swift b/FreeAPS/Sources/APS/OpenAPS/Constants.swift index 9f3873dabc..f096f66a9d 100644 --- a/FreeAPS/Sources/APS/OpenAPS/Constants.swift +++ b/FreeAPS/Sources/APS/OpenAPS/Constants.swift @@ -54,7 +54,6 @@ extension OpenAPS { static let iob = "monitor/iob.json" static let cgmState = "monitor/cgm-state.json" static let podAge = "monitor/pod-age.json" - // static let tdd = "monitor/tdd.json" static let oref2_variables = "monitor/oref2_variables.json" static let alertHistory = "monitor/alerthistory.json" static let statistics = "monitor/statistics.json" @@ -86,6 +85,8 @@ extension OpenAPS { static let uploadedCGMState = "upload/uploaded-cgm-state.json" static let uploadedPodAge = "upload/uploaded-pod-age.json" static let uploadedProfile = "upload/uploaded-profile.json" + static let profile = "fetched/profile.json" + static let test = "upload/test.json" } enum FreeAPS { diff --git a/FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings index b20193493d..3bb172ada6 100644 --- a/FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings @@ -314,6 +314,36 @@ Enact a temp Basal or a temp target */ /* Allow remote control from NS */ "Remote control" = "Remote control"; +/* Imported Profiles Alert */ +"\nNow please verify all of your new settings thoroughly:\n\n* Basal Settings\n * Carb Ratios\n * Glucose Targets\n * Insulin Sensitivities\n\n in iAPS Settings > Configuration.\n\nBad or invalid profile settings could have disatrous effects." = "\nNow please verify all of your new settings thoroughly:\n\n* Basal Settings\n * Carb Ratios\n * Glucose Targets\n * Insulin Sensitivities\n\n in iAPS Settings > Configuration.\n\nBad or invalid profile settings could have disatrous effects."; + +/* Failed Profile Import Alert */ +"\nImport failed:\n\n*" = "\nImport failed:\n\n*"; + +/* Profile Import Alert */ +"This will replace some or all of your current pump settings. Are you sure you want to import profile settings from Nightscout?" = "This will replace some or all of your current pump settings. Are you sure you want to import profile settings from Nightscout?"; + +/* */ +"Yes, Import" = "Yes, Import"; + +/* */ +"Import settings from Nightscout" = "Import settings from Nightscout"; + +/* */ +"Import settings?" = "Import settings?"; + +/* */ +"Import from Nightscout" = "Import from Nightscout"; + +/* */ +"Settings imported" = "Settings imported"; + +/* Import Error */ +"Mismatching glucose units in Nightscout and Pump Settings. Import settings aborted." = "Mismatching glucose units in Nightscout and Pump Settings. Import settings aborted."; + +/* Import Error */ +"Can't find the default Nightscout Profile." = "Can't find the default Nightscout Profile."; + /* Add Medtronic pump */ "Add Medtronic" = "Add Medtronic"; diff --git a/FreeAPS/Sources/Localizations/Main/sv.lproj/Localizable.strings b/FreeAPS/Sources/Localizations/Main/sv.lproj/Localizable.strings index fd3cef0d0a..76f39fcca4 100644 --- a/FreeAPS/Sources/Localizations/Main/sv.lproj/Localizable.strings +++ b/FreeAPS/Sources/Localizations/Main/sv.lproj/Localizable.strings @@ -314,6 +314,36 @@ Enact a temp Basal or a temp target */ /* Allow remote control from NS */ "Remote control" = "Fjärrstyrning"; +/* Imported Profiles Alert */ +"\nNow please verify all of your new settings thoroughly:\n\n* Basal Settings\n * Carb Ratios\n * Glucose Targets\n * Insulin Sensitivities\n\n in iAPS Settings > Configuration.\n\nBad or invalid profile settings could have disatrous effects." = "\nKontrollera nu alla dina nya pumpinställningar noga:\n\n* Basalinställningar\n * Insulinkvoter\n * Målvärden\n * Insulinkänslighet\n\n i Inställningar > Konfiguration.\n\nDåliga eller ogiltiga pumpinställningar kan gå katastrofala följder."; + +/* Failed Profile Import Alert */ +"\nImport failed:\n\n*" = "\nImport misslyckades:\n\n*"; + +/* Profile Import Alert */ +"This will replace some or all of your current pump settings. Are you sure you want to import profile settings from Nightscout?" = "Detta kommer att ersätta alla eller vissa av dina pumpinställningar. Är du säker att du vill fortsätta med import av inställningar?"; + +/* */ +"Yes, Import" = "Ja, importera"; + +/* */ +"Import settings from Nightscout" = "Importera inställningar från Nightscout"; + +/* */ +"Import settings?" = "Importera inställningar?"; + +/* */ +"Import from Nightscout" = "Import från Nightscout"; + +/* */ +"Settings imported" = "Inställningar importerade"; + +/* Import Error */ +"Mismatching glucose units in Nightscout and Pump Settings. Import settings aborted." = "Du har olika inställningar för blodsockernehet i Nightscout och iAPS. Import därför avbruten."; + +/* Import Error */ +"Can't find the default Nightscout Profile." = "Kan inte hitta en profil i Nightcsout"; + /* Add Medtronic pump */ "Add Medtronic" = "Lägg till Medtronic"; diff --git a/FreeAPS/Sources/Models/FetchedProfile.swift b/FreeAPS/Sources/Models/FetchedProfile.swift new file mode 100644 index 0000000000..8411ea979c --- /dev/null +++ b/FreeAPS/Sources/Models/FetchedProfile.swift @@ -0,0 +1,24 @@ +import Foundation + +struct FetchedNightscoutProfileStore: JSON { + let _id: String + let defaultProfile: String + let startDate: String + let mills: Decimal + let enteredBy: String + let store: [String: ScheduledNightscoutProfile] + let created_at: String +} + +struct FetchedNightscoutProfile: JSON { + let dia: Decimal + let carbs_hr: Int + let delay: Decimal + let timezone: String + let target_low: [NightscoutTimevalue] + let target_high: [NightscoutTimevalue] + let sens: [NightscoutTimevalue] + let basal: [NightscoutTimevalue] + let carbratio: [NightscoutTimevalue] + let units: String +} diff --git a/FreeAPS/Sources/Models/NightscoutStatus.swift b/FreeAPS/Sources/Models/NightscoutStatus.swift index ca58c90964..057e876a32 100644 --- a/FreeAPS/Sources/Models/NightscoutStatus.swift +++ b/FreeAPS/Sources/Models/NightscoutStatus.swift @@ -29,7 +29,7 @@ struct Uploader: JSON { struct NightscoutTimevalue: JSON { let time: String let value: Decimal - let timeAsSeconds: Int + let timeAsSeconds: Int? } struct ScheduledNightscoutProfile: JSON { diff --git a/FreeAPS/Sources/Models/RawFetchedProfile.swift b/FreeAPS/Sources/Models/RawFetchedProfile.swift new file mode 100644 index 0000000000..8411ea979c --- /dev/null +++ b/FreeAPS/Sources/Models/RawFetchedProfile.swift @@ -0,0 +1,24 @@ +import Foundation + +struct FetchedNightscoutProfileStore: JSON { + let _id: String + let defaultProfile: String + let startDate: String + let mills: Decimal + let enteredBy: String + let store: [String: ScheduledNightscoutProfile] + let created_at: String +} + +struct FetchedNightscoutProfile: JSON { + let dia: Decimal + let carbs_hr: Int + let delay: Decimal + let timezone: String + let target_low: [NightscoutTimevalue] + let target_high: [NightscoutTimevalue] + let sens: [NightscoutTimevalue] + let basal: [NightscoutTimevalue] + let carbratio: [NightscoutTimevalue] + let units: String +} diff --git a/FreeAPS/Sources/Modules/NightscoutConfig/NightscoutConfigStateModel.swift b/FreeAPS/Sources/Modules/NightscoutConfig/NightscoutConfigStateModel.swift index f3c1997c09..29a7315c57 100644 --- a/FreeAPS/Sources/Modules/NightscoutConfig/NightscoutConfigStateModel.swift +++ b/FreeAPS/Sources/Modules/NightscoutConfig/NightscoutConfigStateModel.swift @@ -1,5 +1,6 @@ import CGMBLEKit import Combine +import CoreData import G7SensorKit import SwiftDate import SwiftUI @@ -11,6 +12,9 @@ extension NightscoutConfig { @Injected() private var glucoseStorage: GlucoseStorage! @Injected() private var healthKitManager: HealthKitManager! @Injected() private var cgmManager: FetchGlucoseManager! + @Injected() private var storage: FileStorage! + + let coredataContext = CoreDataStack.shared.persistentContainer.viewContext @Published var url = "" @Published var secret = "" @@ -22,10 +26,12 @@ extension NightscoutConfig { @Published var uploadGlucose = true // Upload Glucose @Published var useLocalSource = false @Published var localPort: Decimal = 0 + @Published var units: GlucoseUnits = .mmolL override func subscribe() { url = keychain.getValue(String.self, forKey: Config.urlKey) ?? "" secret = keychain.getValue(String.self, forKey: Config.secretKey) ?? "" + units = settingsManager.settings.units subscribeSetting(\.isUploadEnabled, on: $isUploadEnabled) { isUploadEnabled = $0 } subscribeSetting(\.useLocalGlucoseSource, on: $useLocalSource) { useLocalSource = $0 } @@ -68,6 +74,161 @@ extension NightscoutConfig { .store(in: &lifetime) } + private var nightscoutAPI: NightscoutAPI? { + guard let urlString = keychain.getValue(String.self, forKey: NightscoutConfig.Config.urlKey), + let url = URL(string: urlString), + let secret = keychain.getValue(String.self, forKey: NightscoutConfig.Config.secretKey) + else { + return nil + } + return NightscoutAPI(url: url, secret: secret) + } + + func importSettings() { + guard let nightscout = nightscoutAPI else { + saveError("Can't access nightscoutAPI") + return + } + let group = DispatchGroup() + group.enter() + var error = "" + let path = "/api/v1/profile.json" + let timeout: TimeInterval = 60 + + var components = URLComponents() + components.scheme = nightscout.url.scheme + components.host = nightscout.url.host + components.port = nightscout.url.port + components.path = path + components.queryItems = [ + URLQueryItem(name: "count", value: "1") + ] + var url = URLRequest(url: components.url!) + url.allowsConstrainedNetworkAccess = false + url.timeoutInterval = timeout + + if let secret = nightscout.secret { + url.addValue(secret.sha1(), forHTTPHeaderField: "api-secret") + } + let task = URLSession.shared.dataTask(with: url) { data, response, error_ in + if let error_ = error_ { + print("Error occured: " + error_.localizedDescription) + // handle error + self.saveError("Error occured: " + error_.localizedDescription) + error = error_.localizedDescription + return + } + guard let httpResponse = response as? HTTPURLResponse, + (200 ... 299).contains(httpResponse.statusCode) + else { + print("Error occured! " + error_.debugDescription) + // handle error + self.saveError(error_.debugDescription) + return + } + let jsonDecoder = JSONCoding.decoder + + if let mimeType = httpResponse.mimeType, mimeType == "application/json", + let data = data + { + do { + let fetchedProfileStore = try jsonDecoder.decode([FetchedNightscoutProfileStore].self, from: data) + guard let fetchedProfile: ScheduledNightscoutProfile = fetchedProfileStore.first?.store["default"] + else { + error = "Can't find the default Nightscout Profile." + group.leave() + return + } + + guard fetchedProfile.units.contains(self.units.rawValue.prefix(4)) else { + debug( + .nightscout, + "Mismatching glucose units in Nightscout and Pump Settings. Import settings aborted." + ) + error = "Mismatching glucose units in Nightscout and Pump Settings. Import settings aborted." + group.leave() + return + } + + let carbratios = fetchedProfile.carbratio + .map { carbratio -> CarbRatioEntry in + CarbRatioEntry( + start: carbratio.time, + offset: (carbratio.timeAsSeconds ?? self.offset(carbratio.time)) / 60, + ratio: carbratio.value + ) } + let carbratiosProfile = CarbRatios(units: CarbUnit.grams, schedule: carbratios) + + let basals = fetchedProfile.basal + .map { basal -> BasalProfileEntry in + BasalProfileEntry( + start: basal.time, + minutes: (basal.timeAsSeconds ?? self.offset(basal.time)) / 60, + rate: basal.value + ) } + + let sensitivities = fetchedProfile.sens.map { sensitivity -> InsulinSensitivityEntry in + InsulinSensitivityEntry( + sensitivity: self.units == .mmolL ? sensitivity.value : sensitivity.value.asMgdL, + offset: (sensitivity.timeAsSeconds ?? self.offset(sensitivity.time)) / 60, + start: sensitivity.time + ) } + let sensitivitiesProfile = InsulinSensitivities( + units: self.units, + userPrefferedUnits: self.units, + sensitivities: sensitivities + ) + + let targets = fetchedProfile.target_low + .map { target -> BGTargetEntry in + BGTargetEntry( + low: self.units == .mmolL ? target.value : target.value.asMgdL, + high: self.units == .mmolL ? target.value : target.value.asMgdL, + start: target.time, + offset: (target.timeAsSeconds ?? self.offset(target.time)) / 60 + ) } + let targetsProfile = BGTargets( + units: self.units, + userPrefferedUnits: self.units, + targets: targets + ) + + self.storage.save(carbratiosProfile, as: OpenAPS.Settings.carbRatios) + self.storage.save(basals, as: OpenAPS.Settings.basalProfile) + self.storage.save(sensitivitiesProfile, as: OpenAPS.Settings.insulinSensitivities) + self.storage.save(targetsProfile, as: OpenAPS.Settings.bgTargets) + + group.leave() + + } catch let parsingError { + print(parsingError) + } + } + } + task.resume() + group.wait(wallTimeout: .now() + 5) + group.notify(queue: .global(qos: .background)) { + self.saveError(error) + } + } + + func offset(_ string: String) -> Int { + let hours = Int(string.prefix(2)) ?? 0 + let minutes = Int(string.suffix(2)) ?? 0 + return hours * 60 + minutes * 60 + } + + func saveError(_ string: String) { + coredataContext.performAndWait { + let saveToCoreData = ImportError(context: self.coredataContext) + saveToCoreData.date = Date() + saveToCoreData.error = string + if coredataContext.hasChanges { + try? coredataContext.save() + } + } + } + func backfillGlucose() { backfilling = true nightscoutManager.fetchGlucose(since: Date().addingTimeInterval(-1.days.timeInterval)) diff --git a/FreeAPS/Sources/Modules/NightscoutConfig/View/NightscoutConfigRootView.swift b/FreeAPS/Sources/Modules/NightscoutConfig/View/NightscoutConfigRootView.swift index 80eda96111..2e54515a11 100644 --- a/FreeAPS/Sources/Modules/NightscoutConfig/View/NightscoutConfigRootView.swift +++ b/FreeAPS/Sources/Modules/NightscoutConfig/View/NightscoutConfigRootView.swift @@ -1,3 +1,4 @@ +import CoreData import SwiftUI import Swinject @@ -5,6 +6,16 @@ extension NightscoutConfig { struct RootView: BaseView { let resolver: Resolver @StateObject var state = StateModel() + @State var importAlert: Alert? + @State var isImportAlertPresented = false + @State var importedHasRun = false + + @FetchRequest( + entity: ImportError.entity(), + sortDescriptors: [NSSortDescriptor(key: "date", ascending: false)], predicate: NSPredicate( + format: "date > %@", Date().addingTimeInterval(-1.minutes.timeInterval) as NSDate + ) + ) var fetchedErrors: FetchedResults private var portFormater: NumberFormatter { let formatter = NumberFormatter() @@ -53,14 +64,58 @@ extension NightscoutConfig { Text("Allow Uploads") } - Section(header: Text("Local glucose source")) { + Section { + Button("Import settings from Nightscout") { + importAlert = Alert( + title: Text("Import settings?"), + message: Text( + "\n" + + NSLocalizedString( + "This will replace some or all of your current pump settings. Are you sure you want to import profile settings from Nightscout?", + comment: "Profile Import Alert" + ) + + "\n" + ), + primaryButton: .destructive( + Text("Yes, Import"), + action: { + state.importSettings() + importedHasRun = true + } + ), + secondaryButton: .cancel() + ) + isImportAlertPresented.toggle() + }.disabled(state.url.isEmpty || state.connecting) + + } header: { Text("Import from Nightscout") } + + .alert(isPresented: $importedHasRun) { + Alert( + title: Text("Settings imported"), + message: Text( + (fetchedErrors.first?.error ?? "").count < 4 ? + NSLocalizedString( + "\nNow please verify all of your new settings thoroughly:\n\n* Basal Settings\n * Carb Ratios\n * Glucose Targets\n * Insulin Sensitivities\n\n in iAPS Settings > Configuration.\n\nBad or invalid profile settings could have disatrous effects.", + comment: "Imported Profiles Alert" + ) : + NSLocalizedString("\nImport failed:\n\n*", comment: "Failed Profile Import Alert") + + NSLocalizedString(fetchedErrors.first?.error ?? "", comment: "Import Error") + ), + primaryButton: .destructive( + Text("OK") + ), + secondaryButton: .cancel() + ) + } + + Section { Toggle("Use local glucose server", isOn: $state.useLocalSource) HStack { Text("Port") DecimalTextField("", value: $state.localPort, formatter: portFormater) } - } - + } header: { Text("Local glucose source") } Section { Button("Backfill glucose") { state.backfillGlucose() } .disabled(state.url.isEmpty || state.connecting || state.backfilling) @@ -69,6 +124,9 @@ extension NightscoutConfig { .onAppear(perform: configureView) .navigationBarTitle("Nightscout Config") .navigationBarTitleDisplayMode(.automatic) + .alert(isPresented: $isImportAlertPresented) { + importAlert! + } } } } diff --git a/FreeAPS/Sources/Services/Network/NightscoutAPI.swift b/FreeAPS/Sources/Services/Network/NightscoutAPI.swift index ec47340bf8..f34f9fa8ae 100644 --- a/FreeAPS/Sources/Services/Network/NightscoutAPI.swift +++ b/FreeAPS/Sources/Services/Network/NightscoutAPI.swift @@ -1,6 +1,8 @@ import Combine import CommonCrypto import Foundation +import JavaScriptCore +import Swinject class NightscoutAPI { init(url: URL, secret: String? = nil) { @@ -27,6 +29,8 @@ class NightscoutAPI { let secret: String? private let service = NetworkService() + + @Injected() private var settingsManager: SettingsManager! } extension NightscoutAPI { diff --git a/FreeAPS/Sources/Services/Network/NightscoutManager.swift b/FreeAPS/Sources/Services/Network/NightscoutManager.swift index 436267dc5c..069f31b8eb 100644 --- a/FreeAPS/Sources/Services/Network/NightscoutManager.swift +++ b/FreeAPS/Sources/Services/Network/NightscoutManager.swift @@ -12,9 +12,9 @@ protocol NightscoutManager: GlucoseSource { func deleteCarbs(at date: Date, isFPU: Bool?, fpuID: String?, syncID: String) func deleteInsulin(at date: Date) func uploadStatus() + func uploadGlucose() func uploadStatistics(dailystat: Statistics) func uploadPreferences() - func uploadGlucose() func uploadProfile() var cgmURL: URL? { get } } @@ -557,6 +557,7 @@ final class BaseNightscoutManager: NightscoutManager, Injectable { switch completion { case .finished: self.storage.save(glucose, as: fileToSave) + debug(.nightscout, "Glucose uploaded") case let .failure(error): debug(.nightscout, error.localizedDescription) } @@ -586,6 +587,7 @@ final class BaseNightscoutManager: NightscoutManager, Injectable { switch completion { case .finished: self.storage.save(treatments, as: fileToSave) + debug(.nightscout, "Treatments uploaded") case let .failure(error): debug(.nightscout, error.localizedDescription) } diff --git a/FreeAPS/Sources/Views/TagCloudView.swift b/FreeAPS/Sources/Views/TagCloudView.swift index f32f70af1b..df49349aaa 100644 --- a/FreeAPS/Sources/Views/TagCloudView.swift +++ b/FreeAPS/Sources/Views/TagCloudView.swift @@ -67,7 +67,7 @@ struct TagCloudView: View { textTag where textTag.contains("Autosens/Dynamic Limit:"), textTag where textTag.contains("Dynamic ISF/CR"), textTag where textTag.contains("Basal ratio"), - textTag where textTag.contains("SMB Ratio:"): + textTag where textTag.contains("SMB Ratio"): return .zt default: return .insulin