Просмотр исходного кода

Merge pull request #542 from nightscout/sync-trio-public

Sync Trio:dev public with Trio-dev:dev
Deniz Cengiz 11 месяцев назад
Родитель
Сommit
44be5226e5
82 измененных файлов с 6223 добавлено и 7975 удалено
  1. 1 2
      .github/CODEOWNERS
  2. 29 14
      .github/workflows/build_trio.yml
  3. 132 0
      .github/workflows/unit_tests.yml
  4. 22 7
      Config.xcconfig
  5. 1 1
      DanaKit
  6. 15 15
      Gemfile.lock
  7. 1 1
      Model/Helper/CarbEntryStored+helper.swift
  8. 6 3
      Model/Helper/GlucoseStored+helper.swift
  9. 6 4
      Model/Helper/OverrideStored+helper.swift
  10. 27 11
      Trio.xcodeproj/project.pbxproj
  11. 19 0
      Trio.xcodeproj/xcshareddata/xcschemes/Trio Tests.xcscheme
  12. 20 0
      Trio/Resources/Assets.xcassets/app_icons/trioCircledNoBackground.appiconset/Contents.json
  13. BIN
      Trio/Resources/Assets.xcassets/app_icons/trioCircledNoBackground.appiconset/trioCircledNoBackground watch.png
  14. BIN
      Trio/Resources/Assets.xcassets/app_icons/trioCircledNoBackground.appiconset/trioCircledNoBackground.png
  15. BIN
      Trio/Resources/Assets.xcassets/app_icons/trioCircledNoBackground.imageset/ComplicationIcon.png
  16. 0 12
      Trio/Resources/Assets.xcassets/app_icons/trioCircledNoBackground.imageset/Contents.json
  17. 12 0
      Trio/Resources/InfoPlist.xcstrings
  18. 1 1
      Trio/Resources/javascript/bundle/autosens.js
  19. 1 1
      Trio/Resources/javascript/bundle/autotune-core.js
  20. 1 1
      Trio/Resources/javascript/bundle/autotune-prep.js
  21. 1 1
      Trio/Resources/javascript/bundle/basal-set-temp.js
  22. 1 1
      Trio/Resources/javascript/bundle/determine-basal.js
  23. 1 1
      Trio/Resources/javascript/bundle/glucose-get-last.js
  24. 1 1
      Trio/Resources/javascript/bundle/iob.js
  25. 1 1
      Trio/Resources/javascript/bundle/meal.js
  26. 1 1
      Trio/Resources/javascript/bundle/profile.js
  27. 2 2
      Trio/Resources/javascript/prepare/autosens.js
  28. 1 1
      Trio/Resources/javascript/prepare/autotune-core.js
  29. 1 1
      Trio/Resources/javascript/prepare/autotune-prep.js
  30. 7 7
      Trio/Resources/javascript/prepare/determine-basal.js
  31. 1 1
      Trio/Resources/javascript/prepare/iob.js
  32. 1 1
      Trio/Resources/javascript/prepare/meal.js
  33. 6 6
      Trio/Resources/javascript/prepare/profile.js
  34. 22 15
      Trio/Sources/APS/APSManager.swift
  35. 8 8
      Trio/Sources/APS/FetchTreatmentsManager.swift
  36. 1 1
      Trio/Sources/APS/OpenAPS/Constants.swift
  37. 53 23
      Trio/Sources/APS/OpenAPS/OpenAPS.swift
  38. 13 15
      Trio/Sources/APS/Storage/DeterminationStorage.swift
  39. 10 5
      Trio/Sources/APS/Storage/TDDStorage.swift
  40. 1 1
      Trio/Sources/Application/AppState.swift
  41. 273 0
      Trio/Sources/Application/LockedResolver.swift
  42. 6 5
      Trio/Sources/Application/TrioApp.swift
  43. 1 1
      Trio/Sources/Helpers/MainChartHelper.swift
  44. 4844 7311
      Trio/Sources/Localizations/Main/Localizable.xcstrings
  45. 7 3
      Trio/Sources/Models/Oref2_variables.swift
  46. 1 1
      Trio/Sources/Modules/Adjustments/AdjustmentsStateModel+Extensions/AdjustmentsStateModel+Overrides.swift
  47. 30 13
      Trio/Sources/Modules/Adjustments/View/AdjustmentsRootView.swift
  48. 1 5
      Trio/Sources/Modules/Adjustments/View/Overrides/AdjustmentsRootView+Overrides.swift
  49. 1 7
      Trio/Sources/Modules/Adjustments/View/TempTargets/AdjustmentsRootView+TempTargets.swift
  50. 9 7
      Trio/Sources/Modules/BolusCalculatorConfig/View/BolusCalculatorConfigRootView.swift
  51. 1 1
      Trio/Sources/Modules/DynamicSettings/View/DynamicSettingsRootView.swift
  52. 25 7
      Trio/Sources/Modules/GeneralSettings/View/UnitsLimitsSettingsRootView.swift
  53. 1 1
      Trio/Sources/Modules/Home/View/Chart/ChartElements/SelectionPopoverView.swift
  54. 2 1
      Trio/Sources/Modules/Home/View/Header/PumpView.swift
  55. 8 10
      Trio/Sources/Modules/Home/View/HomeRootView.swift
  56. 1 1
      Trio/Sources/Modules/Onboarding/View/OnboardingView+AlgorithmUtil.swift
  57. 25 8
      Trio/Sources/Modules/Onboarding/View/OnboardingView+Util.swift
  58. 1 1
      Trio/Sources/Modules/SMBSettings/View/SMBSettingsRootView.swift
  59. 6 8
      Trio/Sources/Modules/Settings/SettingItems.swift
  60. 21 2
      Trio/Sources/Modules/Treatments/TreatmentsStateModel.swift
  61. 14 3
      Trio/Sources/Modules/Treatments/View/ForecastChart.swift
  62. 20 6
      Trio/Sources/Modules/Treatments/View/PopupView.swift
  63. 9 1
      Trio/Sources/Modules/Treatments/View/TreatmentsRootView.swift
  64. 28 17
      Trio/Sources/Services/BolusCalculator/BolusCalculationManager.swift
  65. 14 1
      Trio/Sources/Services/LiveActivity/LiveActivityManager.swift
  66. 30 20
      Trio/Sources/Services/Network/Nightscout/NightscoutAPI.swift
  67. 3 1
      Trio/Sources/Services/WatchManager/AppleWatchManager.swift
  68. 6 4
      Trio/Sources/Shortcuts/Bolus/BolusIntent.swift
  69. 7 4
      Trio/Sources/Shortcuts/Bolus/BolusIntentRequest.swift
  70. 26 14
      Trio/Sources/Shortcuts/Carbs/AddCarbPresetIntent.swift
  71. 43 6
      Trio/Sources/Shortcuts/Carbs/CarbPresetIntentRequest.swift
  72. 9 5
      Trio/Sources/Shortcuts/Override/ApplyOverridePresetIntent.swift
  73. 1 1
      Trio/Sources/Shortcuts/Override/CancelOverrideIntent.swift
  74. 14 4
      Trio/Sources/Shortcuts/TempPresets/ApplyTempPresetIntent.swift
  75. 1 1
      Trio/Sources/Shortcuts/TempPresets/CancelTempPresetIntent.swift
  76. 183 18
      TrioTests/BolusCalculatorTests/BolusCalculatorTests.swift
  77. 48 0
      TrioTests/DynamicISFEnableTests.swift
  78. 2 2
      fastlane/testflight.md
  79. 10 1
      oref0_source_version.txt
  80. 54 59
      trio-oref/lib/determine-basal/determine-basal.js
  81. 9 247
      trio-oref/lib/glucose-get-last.js
  82. 1 1
      trio-oref/oref_source_file_info.txt

+ 1 - 2
.github/CODEOWNERS

@@ -1,2 +1 @@
-*    @dnzxy @bjornoleh @MikePlante1 @aug0211 @AndreasStokholm @Sjoerd-Bo3 @t1dude
-*.js @dnzxy @bjornoleh @MikePlante1 @aug0211 @AndreasStokholm @Sjoerd-Bo3 @t1dude @jeremystorring
+*    @dnzxy @bjornoleh @MikePlante1 @AndreasStokholm @Sjoerd-Bo3 @t1dude @marv-out

+ 29 - 14
.github/workflows/build_trio.yml

@@ -55,29 +55,36 @@ jobs:
 
       - name: Check for alive branches
         if: steps.workflow-permission.outputs.has_permission == 'true'
+        id: check-alive
         env:
           GITHUB_TOKEN: ${{ secrets.GH_PAT }}
         run: |
-          if [[ $(gh api -H "Accept: application/vnd.github+json" /repos/${{ github.repository_owner }}/Trio/branches | jq --raw-output '[.[] | select(.name == "alive-main" or .name == "alive-dev")] | length > 0') == "true" ]]; then
-            echo "Branches 'alive-main' or 'alive-dev' exist."
-            echo "ALIVE_BRANCH_EXISTS=true" >> $GITHUB_ENV
+          branch_list=$(gh api -H "Accept: application/vnd.github+json" /repos/${{ github.repository_owner }}/Trio/branches | jq -r '.[].name')
+      
+          if echo "$branch_list" | grep -q '^alive-main$'; then
+            echo "alive-main exists"
+            echo "ALIVE_MAIN_EXISTS=true" >> $GITHUB_ENV
+          else
+            echo "alive-main missing"
+            echo "ALIVE_MAIN_EXISTS=false" >> $GITHUB_ENV
+          fi
+      
+          if echo "$branch_list" | grep -q '^alive-dev$'; then
+            echo "alive-dev exists"
+            echo "ALIVE_DEV_EXISTS=true" >> $GITHUB_ENV
           else
-            echo "Branches 'alive-main' and 'alive-dev' do not exist."
-            echo "ALIVE_BRANCH_EXISTS=false" >> $GITHUB_ENV
+            echo "alive-dev missing"
+            echo "ALIVE_DEV_EXISTS=false" >> $GITHUB_ENV
           fi
 
-      - name: Create alive branches
-        if: env.ALIVE_BRANCH_EXISTS == 'false'
+      - name: Create alive-main branch if missing
+        if: env.ALIVE_MAIN_EXISTS == 'false'
         env:
           GITHUB_TOKEN: ${{ secrets.GH_PAT }}
         run: |
-          # Get ref for UPSTREAM_REPO:main
           SHA_MAIN=$(curl -sS -H "Authorization: token $GITHUB_TOKEN" https://api.github.com/repos/${{ env.UPSTREAM_REPO }}/git/refs/heads/main | jq -r '.object.sha')
-
-          # Get ref for UPSTREAM_REPO:dev
-          SHA_DEV=$(curl -sS -H "Authorization: token $GITHUB_TOKEN" https://api.github.com/repos/${{ env.UPSTREAM_REPO }}/git/refs/heads/dev | jq -r '.object.sha')
-
-          # Create alive-main branch in Trio fork based on UPSTREAM_REPO:main
+      
+          echo "Creating alive-main from upstream main"
           gh api \
             --method POST \
             -H "Authorization: token $GITHUB_TOKEN" \
@@ -86,7 +93,14 @@ jobs:
             -f ref='refs/heads/alive-main' \
             -f sha=$SHA_MAIN
 
-          # Create alive-dev branch in Trio fork based on UPSTREAM_REPO:dev
+      - name: Create alive-dev branch if missing
+        if: env.ALIVE_DEV_EXISTS == 'false'
+        env:
+          GITHUB_TOKEN: ${{ secrets.GH_PAT }}
+        run: |
+          SHA_DEV=$(curl -sS -H "Authorization: token $GITHUB_TOKEN" https://api.github.com/repos/${{ env.UPSTREAM_REPO }}/git/refs/heads/dev | jq -r '.object.sha')
+      
+          echo "Creating alive-dev from upstream dev"
           gh api \
             --method POST \
             -H "Authorization: token $GITHUB_TOKEN" \
@@ -95,6 +109,7 @@ jobs:
             -f ref='refs/heads/alive-dev' \
             -f sha=$SHA_DEV
 
+                  
   # Checks for changes in upstream repository; if changes exist prompts sync for build
   # Performs keepalive to avoid stale fork
   check_latest_from_upstream:

+ 132 - 0
.github/workflows/unit_tests.yml

@@ -0,0 +1,132 @@
+name: zzz [DO NOT RUN] Automated unit tests
+
+on:
+  pull_request:
+    branches:
+      - dev
+    types: [opened, synchronize]
+    paths-ignore:
+      - '**.md'
+      - '**/README'
+      - '**.yml'
+      - '**.txt'
+
+  push:
+    branches:
+      - dev
+    paths-ignore:
+      - '**.md'
+      - '**/README'
+      - '**.yml'
+      - '**.txt'
+
+jobs:
+  test:
+    name: Run Unit Tests
+    runs-on: macos-15
+    if: github.repository_owner == 'nightscout'
+
+    steps:
+      - name: Select Xcode version
+        run: sudo xcode-select -s /Applications/Xcode_16.3.app/Contents/Developer
+
+      - name: Checkout code
+        uses: actions/checkout@v4
+        with:
+          fetch-depth: 1
+          submodules: recursive
+
+      - name: Restore cache
+        id: cache-restore
+        uses: actions/cache/restore@v4
+        with:
+          path: |
+            /Users/runner/Library/Developer/Xcode/DerivedData
+            .build
+          key: ${{ runner.os }}-trio-${{ hashFiles('**/*.swift', '**/*.xcodeproj', '**/*.xcworkspace') }}
+          restore-keys: |
+            ${{ runner.os }}-trio-
+
+      - name: Show cache contents before build
+        run: |
+          echo "📂 Contents of DerivedData:"
+          ls -lah /Users/runner/Library/Developer/Xcode/DerivedData || echo "Directory not found"
+          echo ""
+          echo "📂 Contents of .build:"
+          ls -lah .build || echo ".build directory not found"
+
+      - name: Build for testing
+        run: |
+          set -o pipefail && \
+          time xcodebuild build-for-testing \
+            -workspace Trio.xcworkspace \
+            -scheme "Trio Tests" \
+            -destination 'platform=iOS Simulator,name=iPhone 16,OS=18.4' \
+
+      - name: Check for uncommitted changes
+        run: |
+          CHANGES=$(git status --porcelain)
+          if [ -n "$CHANGES" ]; then
+            echo "Uncommitted changes detected:"
+            echo "$CHANGES"
+            echo "$CHANGES" | while read -r line; do
+              FILE=$(echo $line | cut -c4-)
+              echo "::warning file=$FILE::Uncommitted change detected"
+            done
+            exit 0
+          else
+            echo "No uncommitted changes detected."
+          fi
+        shell: bash
+
+      - name: Show cache contents after build
+        run: |
+          echo "📂 Updated DerivedData contents:"
+          du -sh /Users/runner/Library/Developer/Xcode/DerivedData || echo "Directory not found"
+          ls -lah /Users/runner/Library/Developer/Xcode/DerivedData || echo "Directory not found"
+          echo ""
+          echo "📂 Updated .build contents:"
+          du -sh .build || echo ".build directory not found"
+          ls -lah .build || echo ".build directory not found"
+          
+      - name: Save cache
+        if: steps.cache-restore.outputs.cache-hit != 'true'
+        uses: actions/cache/save@v4
+        with:
+          path: |
+            /Users/runner/Library/Developer/Xcode/DerivedData
+            .build
+          key: ${{ runner.os }}-trio-${{ hashFiles('**/*.swift', '**/*.xcodeproj', '**/*.xcworkspace') }}  
+
+      - name: Run tests
+        run: |
+          set -o pipefail
+          time xcodebuild test-without-building \
+            -workspace Trio.xcworkspace \
+            -scheme "Trio Tests" \
+            -destination 'platform=iOS Simulator,name=iPhone 16,OS=18.4' \
+            $([ "$ENABLE_PARALLEL_TESTING" = "true" ] && echo "-parallel-testing-enabled YES") \
+            2>&1 | tee xcodebuild.log
+
+      - name: Annotate test results
+        if: always()
+        run: |
+          if [ -f xcodebuild.log ]; then
+            if grep -q "Failing tests:" xcodebuild.log; then
+              echo "::error title=Unit Tests Failed::Some tests failed"
+              echo "## ❌ Some tests failed:" >> $GITHUB_STEP_SUMMARY
+              grep -A 20 "Failing tests:" xcodebuild.log | \
+                grep -E '^\s+[A-Za-z0-9]+\..+\(\)' | \
+                sed 's/^/  - /' >> $GITHUB_STEP_SUMMARY
+              echo "::group::Failed Test List"
+              grep -A 20 "Failing tests:" xcodebuild.log | \
+                grep -E '^\s+[A-Za-z0-9]+\..+\(\)' | \
+                sed 's/^/  - /'
+              echo "::endgroup::"
+            else
+              echo "::notice title=Unit Tests Passed::✅ All tests passed"
+              echo "✅ All tests passed" >> $GITHUB_STEP_SUMMARY
+            fi
+          else
+            echo "::warning::Test log (xcodebuild.log) not found"
+          fi

+ 22 - 7
Config.xcconfig

@@ -1,14 +1,29 @@
+// Some of the items can be modified to match the user's preference
 APP_DISPLAY_NAME = Trio
+APP_ICON = trioBlack
+APP_URL_SCHEME = Trio
+
+// DEVELOPER_TEAM will be set to your Apple Developer ID - typically using ConfigOverride.xcconfig
+DEVELOPER_TEAM = ##TEAM_ID##
+
+// Typically this is not modified unless you want to create a separate (unique) app using your ID
+// It must include $(DEVELOPMENT_TEAM)
+// For example: myOwnApp.$(DEVELOPMENT_TEAM).trio
+BUNDLE_IDENTIFIER = org.nightscout.$(DEVELOPMENT_TEAM).trio
+
+// Danger zone - do not modify these unless you know what you are doing
+
+// The TRIO_APP_GROUP_ID should not be modified - it is required to have this exact format
+// to build with GitHub actions and to work with xDrip4iOS
+TRIO_APP_GROUP_ID = group.org.nightscout.$(DEVELOPMENT_TEAM).trio.trio-app-group
+
+// The developers set the version numbers, please leave them alone
 APP_VERSION = 0.5.0
-APP_DEV_VERSION = 0.5.0.15
+APP_DEV_VERSION = 0.5.0.47
 APP_BUILD_NUMBER = 1
 COPYRIGHT_NOTICE =
-DEVELOPER_TEAM = ##TEAM_ID##
-BUNDLE_IDENTIFIER = org.nightscout.$(DEVELOPMENT_TEAM).trio
-APP_ICON = trioBlack
-APP_URL_SCHEME = Trio
 
-// Optional overrides
+// Optional overrides - these can be used to insert your TEAMID into the DEVELOPER_TEAM field
 #include? "../../ConfigOverride.xcconfig"
 #include? "../ConfigOverride.xcconfig"
-#include? "ConfigOverride.xcconfig"
+#include? "ConfigOverride.xcconfig"

+ 1 - 1
DanaKit

@@ -1 +1 @@
-Subproject commit ee9ebdd880fdcc9bc50885e60408b7c64f8834d1
+Subproject commit ca240f9df3cb5dbda9ad574161c9bbf9612908b2

+ 15 - 15
Gemfile.lock

@@ -9,26 +9,26 @@ GEM
       public_suffix (>= 2.0.2, < 7.0)
     artifactory (3.0.17)
     atomos (0.1.3)
-    aws-eventstream (1.3.2)
-    aws-partitions (1.1086.0)
-    aws-sdk-core (3.222.1)
+    aws-eventstream (1.4.0)
+    aws-partitions (1.1115.0)
+    aws-sdk-core (3.225.2)
       aws-eventstream (~> 1, >= 1.3.0)
       aws-partitions (~> 1, >= 1.992.0)
       aws-sigv4 (~> 1.9)
       base64
       jmespath (~> 1, >= 1.6.1)
       logger
-    aws-sdk-kms (1.99.0)
-      aws-sdk-core (~> 3, >= 3.216.0)
+    aws-sdk-kms (1.104.0)
+      aws-sdk-core (~> 3, >= 3.225.0)
       aws-sigv4 (~> 1.5)
-    aws-sdk-s3 (1.183.0)
-      aws-sdk-core (~> 3, >= 3.216.0)
+    aws-sdk-s3 (1.189.1)
+      aws-sdk-core (~> 3, >= 3.225.0)
       aws-sdk-kms (~> 1)
       aws-sigv4 (~> 1.5)
-    aws-sigv4 (1.11.0)
+    aws-sigv4 (1.12.1)
       aws-eventstream (~> 1, >= 1.0.2)
     babosa (1.0.4)
-    base64 (0.2.0)
+    base64 (0.3.0)
     claide (1.1.0)
     colored (1.2)
     colored2 (3.1.2)
@@ -70,7 +70,7 @@ GEM
     faraday_middleware (1.2.1)
       faraday (~> 1.0)
     fastimage (2.4.0)
-    fastlane (2.227.1)
+    fastlane (2.228.0)
       CFPropertyList (>= 2.3, < 4.0.0)
       addressable (>= 2.8, < 3.0.0)
       artifactory (~> 3.0)
@@ -157,7 +157,7 @@ GEM
     httpclient (2.9.0)
       mutex_m
     jmespath (1.6.2)
-    json (2.10.2)
+    json (2.12.2)
     jwt (2.10.1)
       base64
     logger (1.7.0)
@@ -167,13 +167,13 @@ GEM
     multipart-post (2.4.1)
     mutex_m (0.3.0)
     nanaimo (0.4.0)
-    naturally (2.2.1)
+    naturally (2.2.2)
     nkf (0.2.0)
     optparse (0.6.0)
     os (1.1.4)
     plist (3.7.2)
-    public_suffix (6.0.1)
-    rake (13.2.1)
+    public_suffix (6.0.2)
+    rake (13.3.0)
     representable (3.2.0)
       declarative (< 0.1.0)
       trailblazer-option (>= 0.1.1, < 0.2.0)
@@ -184,7 +184,7 @@ GEM
     ruby2_keywords (0.0.5)
     rubyzip (2.4.1)
     security (0.1.5)
-    signet (0.19.0)
+    signet (0.20.0)
       addressable (~> 2.8)
       faraday (>= 0.17.5, < 3.a)
       jwt (>= 1.5, < 3.0)

+ 1 - 1
Model/Helper/CarbEntryStored+helper.swift

@@ -14,7 +14,7 @@ extension NSPredicate {
 
     static var carbsForStats: NSPredicate {
         let date = Date.threeMonthsAgo
-        return NSPredicate(format: "date >= %@", date as NSDate)
+        return NSPredicate(format: "date >= %@ AND isFPU == %@", date as NSDate, false as NSNumber)
     }
 
     static var carbsNotYetUploadedToNightscout: NSPredicate {

+ 6 - 3
Model/Helper/GlucoseStored+helper.swift

@@ -20,12 +20,15 @@ extension GlucoseStored {
         return request
     }
 
-    static func glucoseIsFlat(_ glucose: [GlucoseStored]) -> Bool {
-        guard glucose.count >= 6 else { return false }
+    static func glucoseIsHIGH(_ glucose: [GlucoseStored]) -> Bool {
+        guard glucose.count >= 4 else { return false }
 
         let firstValue = glucose.first?.glucose
 
-        return glucose.allSatisfy { $0.glucose == firstValue }
+        /// 400 mg/dL covers all Dexcom CGMs as well as European Libre 2 and most readings from xDrip4iOS.
+        /// U.S. / Canadian Libres can emit up to 500 mg/dL until it reads "HI"
+        /// Our condition considers both these values, 400 and 500, as possible "flat" readings when paired CGM reads HIGH.
+        return glucose.allSatisfy { $0.glucose == firstValue && ($0.glucose == 400 || $0.glucose == 500) }
     }
 
     // Preview

+ 6 - 4
Model/Helper/OverrideStored+helper.swift

@@ -7,10 +7,12 @@ extension NSPredicate {
     }
 
     static var lastActiveOverride: NSPredicate {
-        let date = Date.oneDayAgo
-        return NSPredicate(
-            format: "date >= %@ AND enabled == %@",
-            date as NSDate,
+        // For non-indefinite overrides, we still want to filter by date
+        // For indefinite overrides, we want them regardless of date
+        NSPredicate(
+            format: "(date >= %@ OR indefinite == %@) AND enabled == %@",
+            Date.oneDayAgo as NSDate,
+            true as NSNumber,
             true as NSNumber
         )
     }

+ 27 - 11
Trio.xcodeproj/project.pbxproj

@@ -28,7 +28,7 @@
 		190EBCC829FF13AA00BA767D /* UserInterfaceSettingsStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 190EBCC729FF13AA00BA767D /* UserInterfaceSettingsStateModel.swift */; };
 		190EBCCB29FF13CB00BA767D /* UserInterfaceSettingsRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 190EBCCA29FF13CB00BA767D /* UserInterfaceSettingsRootView.swift */; };
 		191F62682AD6B05A004D7911 /* NightscoutSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 191F62672AD6B05A004D7911 /* NightscoutSettings.swift */; };
-		1935364028496F7D001E0B16 /* Oref2_variables.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1935363F28496F7D001E0B16 /* Oref2_variables.swift */; };
+		1935364028496F7D001E0B16 /* TrioCustomOrefVariables.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1935363F28496F7D001E0B16 /* TrioCustomOrefVariables.swift */; };
 		193F6CDD2A512C8F001240FD /* Loops.swift in Sources */ = {isa = PBXBuildFile; fileRef = 193F6CDC2A512C8F001240FD /* Loops.swift */; };
 		195D80B42AF6973A00D25097 /* DynamicSettingsRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 195D80B32AF6973A00D25097 /* DynamicSettingsRootView.swift */; };
 		195D80B72AF697B800D25097 /* DynamicSettingsDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 195D80B62AF697B800D25097 /* DynamicSettingsDataFlow.swift */; };
@@ -201,6 +201,7 @@
 		38FEF3FC2737E53800574A46 /* MainStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38FEF3FB2737E53800574A46 /* MainStateModel.swift */; };
 		38FEF3FE2738083E00574A46 /* CGMSettingsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38FEF3FD2738083E00574A46 /* CGMSettingsProvider.swift */; };
 		38FEF413273B317A00574A46 /* HKUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38FEF412273B317A00574A46 /* HKUnit.swift */; };
+		3B0B4E6C2DE1439F005C6627 /* LockedResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B0B4E6B2DE1439A005C6627 /* LockedResolver.swift */; };
 		3B2F77862D7E52ED005ED9FA /* TDD.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B2F77852D7E52ED005ED9FA /* TDD.swift */; };
 		3B2F77882D7E5387005ED9FA /* CurrentTDDSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B2F77872D7E5387005ED9FA /* CurrentTDDSetup.swift */; };
 		3B3B57C92DA07B3400849D16 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3B57C82DA07B3400849D16 /* GoogleService-Info.plist */; };
@@ -248,6 +249,7 @@
 		3B997DCF2DC00A3A006B6BB2 /* JSONImporterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B997DCE2DC00A3A006B6BB2 /* JSONImporterTests.swift */; };
 		3B997DD32DC02AEF006B6BB2 /* glucose.json in Resources */ = {isa = PBXBuildFile; fileRef = 3B997DD12DC02AEF006B6BB2 /* glucose.json */; };
 		3BA8D1B32DDB87150006191F /* DecimalExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BA8D1B22DDB870F0006191F /* DecimalExtensions.swift */; };
+		3BAAE60C2DE7766C0049589B /* DynamicISFEnableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BAAE60B2DE776630049589B /* DynamicISFEnableTests.swift */; };
 		3BAD36B22D7CDC1A00CC298D /* MainLoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BAD36B12D7CDC1400CC298D /* MainLoadingView.swift */; };
 		3BAD36CC2D7D420E00CC298D /* CoreDataInitializationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BAD36CB2D7D420500CC298D /* CoreDataInitializationCoordinator.swift */; };
 		3BCA5F7C2DC7B16400A7EAC7 /* pumphistory-with-external.json in Resources */ = {isa = PBXBuildFile; fileRef = 3BCA5F7B2DC7B15400A7EAC7 /* pumphistory-with-external.json */; };
@@ -842,7 +844,7 @@
 		190EBCC729FF13AA00BA767D /* UserInterfaceSettingsStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInterfaceSettingsStateModel.swift; sourceTree = "<group>"; };
 		190EBCCA29FF13CB00BA767D /* UserInterfaceSettingsRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInterfaceSettingsRootView.swift; sourceTree = "<group>"; };
 		191F62672AD6B05A004D7911 /* NightscoutSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NightscoutSettings.swift; sourceTree = "<group>"; };
-		1935363F28496F7D001E0B16 /* Oref2_variables.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Oref2_variables.swift; sourceTree = "<group>"; };
+		1935363F28496F7D001E0B16 /* TrioCustomOrefVariables.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrioCustomOrefVariables.swift; sourceTree = "<group>"; };
 		193F6CDC2A512C8F001240FD /* Loops.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Loops.swift; sourceTree = "<group>"; };
 		195D80B32AF6973A00D25097 /* DynamicSettingsRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicSettingsRootView.swift; sourceTree = "<group>"; };
 		195D80B62AF697B800D25097 /* DynamicSettingsDataFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicSettingsDataFlow.swift; sourceTree = "<group>"; };
@@ -1035,6 +1037,7 @@
 		38FEF3FB2737E53800574A46 /* MainStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainStateModel.swift; sourceTree = "<group>"; };
 		38FEF3FD2738083E00574A46 /* CGMSettingsProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGMSettingsProvider.swift; sourceTree = "<group>"; };
 		38FEF412273B317A00574A46 /* HKUnit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HKUnit.swift; sourceTree = "<group>"; };
+		3B0B4E6B2DE1439A005C6627 /* LockedResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockedResolver.swift; sourceTree = "<group>"; };
 		3B2F77852D7E52ED005ED9FA /* TDD.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TDD.swift; sourceTree = "<group>"; };
 		3B2F77872D7E5387005ED9FA /* CurrentTDDSetup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentTDDSetup.swift; sourceTree = "<group>"; };
 		3B3B57C82DA07B3400849D16 /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
@@ -1060,6 +1063,7 @@
 		3B997DCE2DC00A3A006B6BB2 /* JSONImporterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONImporterTests.swift; sourceTree = "<group>"; };
 		3B997DD12DC02AEF006B6BB2 /* glucose.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = glucose.json; sourceTree = "<group>"; };
 		3BA8D1B22DDB870F0006191F /* DecimalExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecimalExtensions.swift; sourceTree = "<group>"; };
+		3BAAE60B2DE776630049589B /* DynamicISFEnableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicISFEnableTests.swift; sourceTree = "<group>"; };
 		3BAD36B12D7CDC1400CC298D /* MainLoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainLoadingView.swift; sourceTree = "<group>"; };
 		3BAD36CB2D7D420500CC298D /* CoreDataInitializationCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataInitializationCoordinator.swift; sourceTree = "<group>"; };
 		3BCA5F7B2DC7B15400A7EAC7 /* pumphistory-with-external.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "pumphistory-with-external.json"; sourceTree = "<group>"; };
@@ -2104,8 +2108,9 @@
 			isa = PBXGroup;
 			children = (
 				38E4451D274DB04600EC9A94 /* AppDelegate.swift */,
-				388E595B25AD948C0019842D /* TrioApp.swift */,
 				BD4ED4FC2CF9D5E8000EDC9C /* AppState.swift */,
+				3B0B4E6B2DE1439A005C6627 /* LockedResolver.swift */,
+				388E595B25AD948C0019842D /* TrioApp.swift */,
 			);
 			path = Application;
 			sourceTree = "<group>";
@@ -2363,7 +2368,7 @@
 				3871F39B25ED892B0013ECB5 /* TempTarget.swift */,
 				3811DE8E25C9D80400A708ED /* User.swift */,
 				E0D4F80427513ECF00BDF1FE /* HealthKitSample.swift */,
-				1935363F28496F7D001E0B16 /* Oref2_variables.swift */,
+				1935363F28496F7D001E0B16 /* TrioCustomOrefVariables.swift */,
 				CE82E02628E869DF00473A9C /* AlertEntry.swift */,
 				19B0EF2028F6D66200069496 /* Statistics.swift */,
 				19012CDB291D2CB900FB8210 /* LoopStats.swift */,
@@ -2571,6 +2576,7 @@
 				BD8FC0552D66187700B95AED /* CoreDataTests */,
 				38FCF3F125E9028E0078B0D1 /* Info.plist */,
 				CEE9A65D2BBC9F6500EB5194 /* CalibrationsTests.swift */,
+				3BAAE60B2DE776630049589B /* DynamicISFEnableTests.swift */,
 				38FCF3F825E902C20078B0D1 /* FileStorageTests.swift */,
 				3B997DCE2DC00A3A006B6BB2 /* JSONImporterTests.swift */,
 				CE1F6DD82BADF4620064EB8D /* PluginManagerTests.swift */,
@@ -4258,6 +4264,7 @@
 				3811DE0C25C9D32F00A708ED /* BaseProvider.swift in Sources */,
 				CE95BF5A2BA62E4A00DC3DE3 /* PluginSource.swift in Sources */,
 				DD21FCB52C6952AD00AF2C25 /* DecimalPickerSettings.swift in Sources */,
+				3B0B4E6C2DE1439F005C6627 /* LockedResolver.swift in Sources */,
 				3811DE5C25C9D4D500A708ED /* Formatters.swift in Sources */,
 				3871F39F25ED895A0013ECB5 /* Decimal+Extensions.swift in Sources */,
 				CEE9A6592BBB418300EB5194 /* CalibrationsDataFlow.swift in Sources */,
@@ -4343,7 +4350,7 @@
 				110AEDEB2C51A0AE00615CC9 /* ShortcutsConfigView.swift in Sources */,
 				38DF179027733EAD00B3528F /* SnowScene.swift in Sources */,
 				DD1DB7CC2BECCA1F0048B367 /* BuildDetails.swift in Sources */,
-				1935364028496F7D001E0B16 /* Oref2_variables.swift in Sources */,
+				1935364028496F7D001E0B16 /* TrioCustomOrefVariables.swift in Sources */,
 				CE2FAD3A297D93F0001A872C /* BloodGlucoseExtensions.swift in Sources */,
 				38E4453A274E411700EC9A94 /* Disk+[UIImage].swift in Sources */,
 				DD73FA0F2D74F58E00D19D1E /* BackgroundTask+Helper.swift in Sources */,
@@ -4647,6 +4654,7 @@
 				3B997DCF2DC00A3A006B6BB2 /* JSONImporterTests.swift in Sources */,
 				BD8FC0662D661A0000B95AED /* GlucoseStorageTests.swift in Sources */,
 				BD8FC05B2D6618AF00B95AED /* DeterminationStorageTests.swift in Sources */,
+				3BAAE60C2DE7766C0049589B /* DynamicISFEnableTests.swift in Sources */,
 				CE1F6DD92BADF4620064EB8D /* PluginManagerTests.swift in Sources */,
 				38FCF3F925E902C20078B0D1 /* FileStorageTests.swift in Sources */,
 				BD8FC0602D6619DB00B95AED /* CarbsStorageTests.swift in Sources */,
@@ -4747,7 +4755,7 @@
 			baseConfigurationReference = 38F3783A2613555C009DB701 /* Config.xcconfig */;
 			buildSettings = {
 				ALWAYS_SEARCH_USER_PATHS = NO;
-				APP_GROUP_ID = "group.$(BUNDLE_IDENTIFIER).trio-app-group";
+				APP_GROUP_ID = "$(TRIO_APP_GROUP_ID)";
 				CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
 				CLANG_ANALYZER_NONNULL = YES;
 				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
@@ -4815,7 +4823,7 @@
 			baseConfigurationReference = 38F3783A2613555C009DB701 /* Config.xcconfig */;
 			buildSettings = {
 				ALWAYS_SEARCH_USER_PATHS = NO;
-				APP_GROUP_ID = "group.$(BUNDLE_IDENTIFIER).trio-app-group";
+				APP_GROUP_ID = "$(TRIO_APP_GROUP_ID)";
 				CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
 				CLANG_ANALYZER_NONNULL = YES;
 				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
@@ -4876,7 +4884,7 @@
 			isa = XCBuildConfiguration;
 			buildSettings = {
 				APP_DISPLAY_NAME = "$(APP_DISPLAY_NAME)";
-				APP_GROUP_ID = "$(APP_GROUP_ID)";
+				APP_GROUP_ID = "$(TRIO_APP_GROUP_ID)";
 				ASSETCATALOG_COMPILER_APPICON_NAME = "$(APP_ICON)";
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
@@ -4909,8 +4917,12 @@
 				PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_IDENTIFIER)";
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PROVISIONING_PROFILE_SPECIFIER = "";
+				SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
+				SUPPORTS_MACCATALYST = NO;
+				SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
+				SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
 				SWIFT_VERSION = 5.0;
-				TARGETED_DEVICE_FAMILY = "1,2";
+				TARGETED_DEVICE_FAMILY = 1;
 			};
 			name = Debug;
 		};
@@ -4918,7 +4930,7 @@
 			isa = XCBuildConfiguration;
 			buildSettings = {
 				APP_DISPLAY_NAME = "$(APP_DISPLAY_NAME)";
-				APP_GROUP_ID = "$(APP_GROUP_ID)";
+				APP_GROUP_ID = "$(TRIO_APP_GROUP_ID)";
 				ASSETCATALOG_COMPILER_APPICON_NAME = "$(APP_ICON)";
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
@@ -4950,8 +4962,12 @@
 				PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_IDENTIFIER)";
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PROVISIONING_PROFILE_SPECIFIER = "";
+				SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
+				SUPPORTS_MACCATALYST = NO;
+				SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
+				SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
 				SWIFT_VERSION = 5.0;
-				TARGETED_DEVICE_FAMILY = "1,2";
+				TARGETED_DEVICE_FAMILY = 1;
 			};
 			name = Release;
 		};

+ 19 - 0
Trio.xcodeproj/xcshareddata/xcschemes/Trio Tests.xcscheme

@@ -36,6 +36,16 @@
       debugDocumentVersioning = "YES"
       debugServiceExtension = "internal"
       allowLocationSimulation = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "388E595725AD948C0019842D"
+            BuildableName = "Trio.app"
+            BlueprintName = "Trio"
+            ReferencedContainer = "container:Trio.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
    </LaunchAction>
    <ProfileAction
       buildConfiguration = "Release"
@@ -43,6 +53,15 @@
       savedToolIdentifier = ""
       useCustomWorkingDirectory = "NO"
       debugDocumentVersioning = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "388E595725AD948C0019842D"
+            BuildableName = "Trio.app"
+            BlueprintName = "Trio"
+            ReferencedContainer = "container:Trio.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
    </ProfileAction>
    <AnalyzeAction
       buildConfiguration = "Debug">

+ 20 - 0
Trio/Resources/Assets.xcassets/app_icons/trioCircledNoBackground.appiconset/Contents.json

@@ -0,0 +1,20 @@
+{
+  "images" : [
+    {
+      "filename" : "trioCircledNoBackground.png",
+      "idiom" : "universal",
+      "platform" : "ios",
+      "size" : "1024x1024"
+    },
+    {
+      "filename" : "trioCircledNoBackground watch.png",
+      "idiom" : "universal",
+      "platform" : "watchos",
+      "size" : "1024x1024"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
Trio/Resources/Assets.xcassets/app_icons/trioCircledNoBackground.appiconset/trioCircledNoBackground watch.png


BIN
Trio/Resources/Assets.xcassets/app_icons/trioCircledNoBackground.appiconset/trioCircledNoBackground.png


BIN
Trio/Resources/Assets.xcassets/app_icons/trioCircledNoBackground.imageset/ComplicationIcon.png


+ 0 - 12
Trio/Resources/Assets.xcassets/app_icons/trioCircledNoBackground.imageset/Contents.json

@@ -1,12 +0,0 @@
-{
-  "images" : [
-    {
-      "filename" : "ComplicationIcon.png",
-      "idiom" : "universal"
-    }
-  ],
-  "info" : {
-    "author" : "xcode",
-    "version" : 1
-  }
-}

+ 12 - 0
Trio/Resources/InfoPlist.xcstrings

@@ -457,6 +457,18 @@
         }
       }
     },
+    "NSCalendarsFullAccessUsageDescription" : {
+      "comment" : "Privacy - Calendars Full Access Usage Description",
+      "extractionState" : "extracted_with_value",
+      "localizations" : {
+        "en" : {
+          "stringUnit" : {
+            "state" : "new",
+            "value" : "To create events with BG reading values, so that they can be viewed on Apple Watch and CarPlay"
+          }
+        }
+      }
+    },
     "NSCalendarsUsageDescription" : {
       "comment" : "Privacy - Calendars Usage Description",
       "extractionState" : "extracted_with_value",

Разница между файлами не показана из-за своего большого размера
+ 1 - 1
Trio/Resources/javascript/bundle/autosens.js


Разница между файлами не показана из-за своего большого размера
+ 1 - 1
Trio/Resources/javascript/bundle/autotune-core.js


Разница между файлами не показана из-за своего большого размера
+ 1 - 1
Trio/Resources/javascript/bundle/autotune-prep.js


Разница между файлами не показана из-за своего большого размера
+ 1 - 1
Trio/Resources/javascript/bundle/basal-set-temp.js


Разница между файлами не показана из-за своего большого размера
+ 1 - 1
Trio/Resources/javascript/bundle/determine-basal.js


Разница между файлами не показана из-за своего большого размера
+ 1 - 1
Trio/Resources/javascript/bundle/glucose-get-last.js


Разница между файлами не показана из-за своего большого размера
+ 1 - 1
Trio/Resources/javascript/bundle/iob.js


Разница между файлами не показана из-за своего большого размера
+ 1 - 1
Trio/Resources/javascript/bundle/meal.js


Разница между файлами не показана из-за своего большого размера
+ 1 - 1
Trio/Resources/javascript/bundle/profile.js


+ 2 - 2
Trio/Resources/javascript/prepare/autosens.js

@@ -18,9 +18,9 @@ function generate(glucose_data, pumphistory_data, basalprofile, profile_data, ca
         temptargets: temptarget_data
     };
     detection_inputs.deviations = 96;
-    var ratio8h = freeaps_autosens(detection_inputs);
+    var ratio8h = trio_autosens(detection_inputs);
     detection_inputs.deviations = 288;
-    var ratio24h = freeaps_autosens(detection_inputs);
+    var ratio24h = trio_autosens(detection_inputs);
     var lowestRatio = ratio8h.ratio < ratio24h.ratio ? ratio8h : ratio24h;
     return lowestRatio;
 }

+ 1 - 1
Trio/Resources/javascript/prepare/autotune-core.js

@@ -13,5 +13,5 @@ function generate(prepped_glucose_data,previous_autotune_data,pumpprofile_data)
       , pumpProfile: pumpprofile_data
     };
 
-    return freeaps_autotuneCore(inputs);
+    return trio_autotuneCore(inputs);
 }

+ 1 - 1
Trio/Resources/javascript/prepare/autotune-prep.js

@@ -43,5 +43,5 @@ function generate(pumphistory_data, profile_data, glucose_data, pumpprofile_data
     , tune_insulin_curve: tune_insulin_curve
     };
 
-    return freeaps_autotunePrep(inputs);
+    return trio_autotunePrep(inputs);
 }

+ 7 - 7
Trio/Resources/javascript/prepare/determine-basal.js

@@ -1,19 +1,19 @@
 //для enact/smb-suggested.json параметры: monitor/iob.json monitor/temp_basal.json monitor/glucose.json settings/profile.json settings/autosens.json --meal monitor/meal.json --microbolus --reservoir monitor/reservoir.json
 
-function generate(iob, currenttemp, glucose, profile, autosens = null, meal = null, microbolusAllowed = false, reservoir = null, clock = new Date(), pump_history, preferences, basalProfile, oref2_variables) {
+function generate(iob, currenttemp, glucose, profile, autosens = null, meal = null, microbolusAllowed = false, reservoir = null, clock = new Date(), pump_history, preferences, basalProfile, trio_custom_oref_variables) {
 
     var clock = new Date();
     
     var middleware_was_used = "";
     try {
-        var middlewareReason = middleware(iob, currenttemp, glucose, profile, autosens, meal, reservoir, clock, pump_history, preferences, basalProfile, oref2_variables);
+        var middlewareReason = middleware(iob, currenttemp, glucose, profile, autosens, meal, reservoir, clock, pump_history, preferences, basalProfile, trio_custom_oref_variables);
         middleware_was_used = (middlewareReason || "Nothing changed");
         console.log("Middleware reason: " + middleware_was_used);
     } catch (error) {
         console.log("Invalid middleware: " + error);
     };
 
-    var glucose_status = freeaps_glucoseGetLast(glucose);
+    var glucose_status = trio_glucoseGetLast(glucose);
     var autosens_data = null;
 
     if (autosens) {
@@ -40,10 +40,10 @@ function generate(iob, currenttemp, glucose, profile, autosens = null, meal = nu
         basalprofile = basalProfile;
     }
     
-    var oref2_variables_ = {};
-    if (oref2_variables) {
-        oref2_variables_ = oref2_variables;
+    var trio_custom_oref_variables_temp = {};
+    if (trio_custom_oref_variables) {
+        trio_custom_oref_variables_temp = trio_custom_oref_variables;
     }
     
-    return freeaps_determineBasal(glucose_status, currenttemp, iob, profile, autosens_data, meal_data, freeaps_basalSetTemp, microbolusAllowed, reservoir_data, clock, pumphistory, preferences, basalprofile, oref2_variables_, middleware_was_used);
+    return trio_determineBasal(glucose_status, currenttemp, iob, profile, autosens_data, meal_data, trio_basalSetTemp, microbolusAllowed, reservoir_data, clock, pumphistory, preferences, basalprofile, trio_custom_oref_variables_temp, middleware_was_used);
 }

+ 1 - 1
Trio/Resources/javascript/prepare/iob.js

@@ -11,5 +11,5 @@ function generate(pumphistory_data, profile_data, clock_data, autosens_data = nu
       if (autosens_data) {
         inputs.autosens = autosens_data;
       }
-      return freeaps_iob(inputs);
+      return trio_iob(inputs);
 }

+ 1 - 1
Trio/Resources/javascript/prepare/meal.js

@@ -23,7 +23,7 @@ function generate(pumphistory_data, profile_data, clock_data, glucose_data, basa
     , glucose: glucose_data
     };
 
-    var recentCarbs = freeaps_meal(inputs);
+    var recentCarbs = trio_meal(inputs);
 
     if (glucose_data.length < 4) {
         console.error("Not enough glucose data to calculate carb absorption; found:", glucose_data.length);

+ 6 - 6
Trio/Resources/javascript/prepare/profile.js

@@ -1,7 +1,7 @@
 //для pumpprofile.json параметры: settings/settings.json settings/bg_targets.json settings/insulin_sensitivities.json settings/basal_profile.json preferences.json settings/carb_ratios.json settings/temptargets.json settings/model.json
 //для profile.json параметры: settings/settings.json settings/bg_targets.json settings/insulin_sensitivities.json settings/basal_profile.json preferences.json settings/carb_ratios.json settings/temptargets.json settings/model.json settings/autotune.json
 
-function generate(pumpsettings_data, bgtargets_data, isf_data, basalprofile_data, preferences_input = false, carbratio_input = false, temptargets_input = false, model_input = false, autotune_input = false, freeaps_data) {
+function generate(pumpsettings_data, bgtargets_data, isf_data, basalprofile_data, preferences_input = false, carbratio_input = false, temptargets_input = false, model_input = false, autotune_input = false, trio_data) {
     if (bgtargets_data.units !== 'mg/dL') {
         if (bgtargets_data.units === 'mmol/L') {
             for (var i = 0, len = bgtargets_data.targets.length; i < len; i++) {
@@ -35,9 +35,9 @@ function generate(pumpsettings_data, bgtargets_data, isf_data, basalprofile_data
         temptargets_data = temptargets_input;
     }
     
-    var freeaps = { };
-    if (freeaps_data) {
-        freeaps = freeaps_data;
+    var trioData = { };
+    if (trio_data) {
+        trioData = trio_data;
     }
 
     var model_data = { };
@@ -98,10 +98,10 @@ function generate(pumpsettings_data, bgtargets_data, isf_data, basalprofile_data
 
     if (autotune_data) {
         if (autotune_data.basalprofile) { inputs.basals = autotune_data.basalprofile; }
-        if (!freeaps.onlyAutotuneBasals) {
+        if (!trioData.onlyAutotuneBasals) {
             if (autotune_data.isfProfile) { inputs.isf = autotune_data.isfProfile; }
             if (autotune_data.carb_ratio) { inputs.carbratio.schedule[0].ratio = autotune_data.carb_ratio; }
         }
     }
-    return freeaps_profile(inputs);
+    return trio_profile(inputs);
 }

+ 22 - 15
Trio/Sources/APS/APSManager.swift

@@ -22,7 +22,11 @@ protocol APSManager {
     func enactTempBasal(rate: Double, duration: TimeInterval) async
     func determineBasal() async throws
     func determineBasalSync() async throws
-    func simulateDetermineBasal(simulatedCarbsAmount: Decimal, simulatedBolusAmount: Decimal) async -> Determination?
+    func simulateDetermineBasal(
+        simulatedCarbsAmount: Decimal,
+        simulatedBolusAmount: Decimal,
+        simulatedCarbsDate: Date?
+    ) async -> Determination?
     func roundBolus(amount: Decimal) -> Decimal
     var lastError: CurrentValueSubject<Error?, Never> { get }
     func cancelBolus(_ callback: ((Bool, String) -> Void)?) async
@@ -436,21 +440,9 @@ final class BaseAPSManager: APSManager, Injectable {
                 return false
             }
 
-            guard !GlucoseStored.glucoseIsFlat(glucose) else {
-                debug(.apsManager, "Glucose data is too flat")
-                self.processError(APSError.glucoseError(message: String(localized: "Glucose data is too flat")))
-                return false
-            }
-
             return true
         }
 
-        guard isValidGlucoseData else {
-            debug(.apsManager, "Glucose validation failed")
-            processError(APSError.glucoseError(message: "Glucose validation failed"))
-            return
-        }
-
         do {
             let now = Date()
 
@@ -462,6 +454,10 @@ final class BaseAPSManager: APSManager, Injectable {
             try await openAPS.createProfiles()
             let determination = try await openAPS.determineBasal(currentTemp: await currentTemp, clock: now)
 
+            guard isValidGlucoseData else {
+                throw APSError.glucoseError(message: "Glucose validation failed")
+            }
+
             if let determination = determination {
                 // Capture weak self in closure
                 await MainActor.run { [weak self] in
@@ -480,7 +476,11 @@ final class BaseAPSManager: APSManager, Injectable {
         _ = try await determineBasal()
     }
 
-    func simulateDetermineBasal(simulatedCarbsAmount: Decimal, simulatedBolusAmount: Decimal) async -> Determination? {
+    func simulateDetermineBasal(
+        simulatedCarbsAmount: Decimal,
+        simulatedBolusAmount: Decimal,
+        simulatedCarbsDate: Date? = nil
+    ) async -> Determination? {
         do {
             let temp = try await fetchCurrentTempBasal(date: Date.now)
             return try await openAPS.determineBasal(
@@ -488,11 +488,12 @@ final class BaseAPSManager: APSManager, Injectable {
                 clock: Date(),
                 simulatedCarbsAmount: simulatedCarbsAmount,
                 simulatedBolusAmount: simulatedBolusAmount,
+                simulatedCarbsDate: simulatedCarbsDate,
                 simulation: true
             )
         } catch {
             debugPrint(
-                "\(DebuggingIdentifiers.failed) \(#file) \(#function) Error occurred in invokeDummyDetermineBasalSync: \(error)"
+                "\(DebuggingIdentifiers.failed) \(#file) \(#function) Error occurred in simulateDetermineBasal: \(error)"
             )
             return nil
         }
@@ -661,6 +662,12 @@ final class BaseAPSManager: APSManager, Injectable {
             throw APSError.apsError(message: "Pump not set")
         }
 
+        // Check if pump is suspended and abort if it is
+        if pump.status.pumpStatus.suspended {
+            info(.apsManager, "Skipping enactDetermination because pump is suspended")
+            return // return without throwing an error
+        }
+
         // Unable to do temp basal during manual temp basal 😁
         if isManualTempBasal {
             throw APSError.manualBasalTemp(message: "Loop not possible during the manual basal temp")

+ 8 - 8
Trio/Sources/APS/FetchTreatmentsManager.swift

@@ -35,17 +35,17 @@ final class BaseFetchTreatmentsManager: FetchTreatmentsManager, Injectable {
                         async let carbs = self.nightscoutManager.fetchCarbs()
                         async let tempTargets = self.nightscoutManager.fetchTempTargets()
 
-                        // Filter and store if not from "Trio"
-                        let filteredCarbs = await carbs.filter { $0.enteredBy != CarbsEntry.local }
-                        if filteredCarbs.isNotEmpty {
-                            try await self.carbsStorage.storeCarbs(filteredCarbs, areFetchedFromRemote: true)
+                        // Store carbs directly (no filtering, as it's done in fetchCarbs)
+                        let fetchedCarbs = await carbs
+                        if fetchedCarbs.isNotEmpty {
+                            try await self.carbsStorage.storeCarbs(fetchedCarbs, areFetchedFromRemote: true)
                         }
 
-                        // Filter and store if not from Trio
-                        let filteredTargets = await tempTargets.filter { $0.enteredBy != TempTarget.local }
-                        if filteredTargets.isNotEmpty {
+                        // Store temp targets directly (no filtering, as it's done in fetchTempTargets)
+                        let fetchedTargets = await tempTargets
+                        if fetchedTargets.isNotEmpty {
                             // Sort temp targets by creation date
-                            let sortedTargets = filteredTargets.sorted { $0.createdAt < $1.createdAt }
+                            let sortedTargets = fetchedTargets.sorted { $0.createdAt < $1.createdAt }
 
                             // Iterate and store each temp target
                             for (index, tempTarget) in sortedTargets.enumerated() {

+ 1 - 1
Trio/Sources/APS/OpenAPS/Constants.swift

@@ -53,7 +53,7 @@ extension OpenAPS {
         static let iob = "monitor/iob.json"
         static let cgmState = "monitor/cgm-state.json"
         static let podAge = "monitor/pod-age.json"
-        static let oref2_variables = "monitor/oref2_variables.json"
+        static let trio_custom_oref_variables = "monitor/trio_custom_oref_variables.json"
         static let alertHistory = "monitor/alerthistory.json"
         static let statistics = "monitor/statistics.json"
     }

+ 53 - 23
Trio/Sources/APS/OpenAPS/OpenAPS.swift

@@ -98,15 +98,15 @@ final class OpenAPS {
     }
 
     // fetch glucose to pass it to the meal function and to determine basal
-    private func fetchAndProcessGlucose() async throws -> String {
+    private func fetchAndProcessGlucose(fetchLimit: Int?) async throws -> String {
         let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: GlucoseStored.self,
             onContext: context,
             predicate: NSPredicate.predicateForOneDayAgoInMinutes,
             key: "date",
             ascending: false,
-            fetchLimit: 72,
-            batchSize: 24
+            fetchLimit: fetchLimit,
+            batchSize: 48
         )
 
         return try await context.perform {
@@ -119,7 +119,7 @@ final class OpenAPS {
         }
     }
 
-    private func fetchAndProcessCarbs(additionalCarbs: Decimal? = nil) async throws -> String {
+    private func fetchAndProcessCarbs(additionalCarbs: Decimal? = nil, carbsDate: Date? = nil) async throws -> String {
         let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: CarbEntryStored.self,
             onContext: context,
@@ -136,13 +136,16 @@ final class OpenAPS {
             var jsonArray = self.jsonConverter.convertToJSON(carbResults)
 
             if let additionalCarbs = additionalCarbs {
+                let formattedDate = carbsDate.map { ISO8601DateFormatter().string(from: $0) } ?? ISO8601DateFormatter()
+                    .string(from: Date())
+
                 let additionalEntry = [
                     "carbs": Double(additionalCarbs),
-                    "actualDate": ISO8601DateFormatter().string(from: Date()),
+                    "actualDate": formattedDate,
                     "id": UUID().uuidString,
                     "note": NSNull(),
                     "protein": 0,
-                    "created_at": ISO8601DateFormatter().string(from: Date()),
+                    "created_at": formattedDate,
                     "isFPU": false,
                     "fat": 0,
                     "enteredBy": "Trio"
@@ -278,6 +281,7 @@ final class OpenAPS {
         clock: Date = Date(),
         simulatedCarbsAmount: Decimal? = nil,
         simulatedBolusAmount: Decimal? = nil,
+        simulatedCarbsDate: Date? = nil,
         simulation: Bool = false
     ) async throws -> Determination? {
         debug(.openAPS, "Start determineBasal")
@@ -287,9 +291,9 @@ final class OpenAPS {
 
         // Perform asynchronous calls in parallel
         async let pumpHistoryObjectIDs = fetchPumpHistoryObjectIDs() ?? []
-        async let carbs = fetchAndProcessCarbs(additionalCarbs: simulatedCarbsAmount ?? 0)
-        async let glucose = fetchAndProcessGlucose()
-        async let oref2 = oref2()
+        async let carbs = fetchAndProcessCarbs(additionalCarbs: simulatedCarbsAmount ?? 0, carbsDate: simulatedCarbsDate)
+        async let glucose = fetchAndProcessGlucose(fetchLimit: 72)
+        async let prepareTrioCustomOrefVariables = prepareTrioCustomOrefVariables()
         async let profileAsync = loadFileFromStorageAsync(name: Settings.profile)
         async let basalAsync = loadFileFromStorageAsync(name: Settings.basalProfile)
         async let autosenseAsync = loadFileFromStorageAsync(name: Settings.autosense)
@@ -302,7 +306,7 @@ final class OpenAPS {
             pumpHistoryJSON,
             carbsAsJSON,
             glucoseAsJSON,
-            oref2_variables,
+            trioCustomOrefVariables,
             profile,
             basalProfile,
             autosens,
@@ -312,7 +316,7 @@ final class OpenAPS {
             try parsePumpHistory(await pumpHistoryObjectIDs, simulatedBolusAmount: simulatedBolusAmount),
             try carbs,
             try glucose,
-            try oref2,
+            try prepareTrioCustomOrefVariables,
             profileAsync,
             basalAsync,
             autosenseAsync,
@@ -364,7 +368,7 @@ final class OpenAPS {
             pumpHistory: pumpHistoryJSON,
             preferences: preferences,
             basalProfile: basalProfile,
-            oref2_variables: oref2_variables
+            trioCustomOrefVariables: trioCustomOrefVariables
         )
 
         debug(.openAPS, "\(simulation ? "[SIMULATION]" : "") OREF DETERMINATION: \(orefDetermination)")
@@ -381,11 +385,15 @@ final class OpenAPS {
 
             return determination
         } else {
+            debug(
+                .openAPS,
+                "\(DebuggingIdentifiers.failed) No determination data. orefDetermination: \(orefDetermination), Determination(from: orefDetermination): \(String(describing: Determination(from: orefDetermination))), deliverAt: \(String(describing: Determination(from: orefDetermination)?.deliverAt))"
+            )
             throw APSError.apsError(message: "No determination data.")
         }
     }
 
-    func oref2() async throws -> RawJSON {
+    func prepareTrioCustomOrefVariables() async throws -> RawJSON {
         try await context.perform {
             // Retrieve user preferences
             let userPreferences = self.storage.retrieve(OpenAPS.Settings.preferences, as: Preferences.self)
@@ -421,8 +429,10 @@ final class OpenAPS {
             let averageTDDLastTenDays = totalTDD / Decimal(totalDaysCount)
             let weightedTDD = weightPercentage * averageTDDLastTwoHours + (1 - weightPercentage) * averageTDDLastTenDays
 
-            // Prepare Oref2 variables
-            let oref2Data = Oref2_variables(
+            let glucose = try self.fetchGlucose()
+
+            // Prepare Trio's custom oref variables
+            let trioCustomOrefVariablesData = TrioCustomOrefVariables(
                 average_total_data: currentTDD > 0 ? averageTDDLastTenDays : 0,
                 weightedAverage: currentTDD > 0 ? weightedTDD : 1,
                 currentTDD: currentTDD,
@@ -442,12 +452,13 @@ final class OpenAPS {
                 start: (activeOverrides.first?.start ?? 0) as Decimal,
                 end: (activeOverrides.first?.end ?? 0) as Decimal,
                 smbMinutes: activeOverrides.first?.smbMinutes?.decimalValue ?? maxSMBBasalMinutes,
-                uamMinutes: activeOverrides.first?.uamMinutes?.decimalValue ?? maxUAMBasalMinutes
+                uamMinutes: activeOverrides.first?.uamMinutes?.decimalValue ?? maxUAMBasalMinutes,
+                shouldProtectDueToHIGH: GlucoseStored.glucoseIsHIGH(glucose)
             )
 
-            // Save and return the Oref2 variables
-            self.storage.save(oref2Data, as: OpenAPS.Monitor.oref2_variables)
-            return self.loadFileFromStorage(name: Monitor.oref2_variables)
+            // Save and return contents of Trio's custom oref variables
+            self.storage.save(trioCustomOrefVariablesData, as: OpenAPS.Monitor.trio_custom_oref_variables)
+            return self.loadFileFromStorage(name: Monitor.trio_custom_oref_variables)
         }
     }
 
@@ -457,7 +468,7 @@ final class OpenAPS {
         // Perform asynchronous calls in parallel
         async let pumpHistoryObjectIDs = fetchPumpHistoryObjectIDs() ?? []
         async let carbs = fetchAndProcessCarbs()
-        async let glucose = fetchAndProcessGlucose()
+        async let glucose = fetchAndProcessGlucose(fetchLimit: nil)
         async let getProfile = loadFileFromStorageAsync(name: Settings.profile)
         async let getBasalProfile = loadFileFromStorageAsync(name: Settings.basalProfile)
         async let getTempTargets = loadFileFromStorageAsync(name: Settings.tempTargets)
@@ -667,7 +678,7 @@ final class OpenAPS {
         pumpHistory: JSON,
         preferences: JSON,
         basalProfile: JSON,
-        oref2_variables: JSON
+        trioCustomOrefVariables: JSON
     ) async throws -> RawJSON {
         try await withCheckedThrowingContinuation { continuation in
             jsWorker.inCommonContext { worker in
@@ -696,7 +707,7 @@ final class OpenAPS {
                     pumpHistory,
                     preferences,
                     basalProfile,
-                    oref2_variables
+                    trioCustomOrefVariables
                 ])
 
                 continuation.resume(returning: result)
@@ -826,7 +837,7 @@ final class OpenAPS {
     }
 }
 
-// Non-Async fetch methods for oref2
+// Non-Async fetch methods for trio_custom_oref_variables
 extension OpenAPS {
     func fetchActiveTempTargets() throws -> [TempTargetStored] {
         try CoreDataStack.shared.fetchEntities(
@@ -860,4 +871,23 @@ extension OpenAPS {
             propertiesToFetch: ["date", "total"]
         ) as? [[String: Any]] ?? []
     }
+
+    func fetchGlucose() throws -> [GlucoseStored] {
+        let results = try CoreDataStack.shared.fetchEntities(
+            ofType: GlucoseStored.self,
+            onContext: context,
+            predicate: NSPredicate.predicateFor30MinAgo,
+            key: "date",
+            ascending: false,
+            fetchLimit: 4
+        )
+
+        return try context.perform {
+            guard let glucoseResults = results as? [GlucoseStored] else {
+                throw CoreDataError.fetchError(function: #function, file: #file)
+            }
+
+            return glucoseResults
+        }
+    }
 }

+ 13 - 15
Trio/Sources/APS/Storage/DeterminationStorage.swift

@@ -216,22 +216,20 @@ final class BaseDeterminationStorage: DeterminationStorage, Injectable {
             relationshipKeyPathsForPrefetching: ["forecastValues"]
         )
 
-        var result: [(id: UUID, forecastID: NSManagedObjectID, forecastValueIDs: [NSManagedObjectID])] = []
-
-        await context.perform {
-            if let forecasts = results as? [Forecast] {
-                for forecast in forecasts {
-                    // Use the helper property that already sorts by index
-                    let sortedValues = forecast.forecastValuesArray
-                    result.append((
-                        id: UUID(),
-                        forecastID: forecast.objectID,
-                        forecastValueIDs: sortedValues.map(\.objectID)
-                    ))
-                }
+        // Process results entirely within a single context.perform block to avoid data races
+        return await context.perform {
+            guard let forecasts = results as? [Forecast] else { return [] }
+
+            // Create and return the result array entirely within this block
+            return forecasts.map { forecast in
+                // Use the helper property that already sorts by index
+                let sortedValues = forecast.forecastValuesArray
+                return (
+                    id: UUID(),
+                    forecastID: forecast.objectID,
+                    forecastValueIDs: sortedValues.map(\.objectID)
+                )
             }
         }
-
-        return result
     }
 }

+ 10 - 5
Trio/Sources/APS/Storage/TDDStorage.swift

@@ -626,7 +626,7 @@ final class BaseTDDStorage: TDDStorage, Injectable {
 
             // Get weight percentage from preferences (default 0.65 if not set)
             let userPreferences = self.storage.retrieve(OpenAPS.Settings.preferences, as: Preferences.self)
-            let weightPercentage = userPreferences?.weightPercentage ?? Decimal(0.65) // why is this 1 as default in oref2??
+            let weightPercentage = userPreferences?.weightPercentage ?? Decimal(0.65) // why is this 1 as default in trio-oref??
 
             // Calculate weighted average using the formula:
             // weightedTDD = (weightPercentage × recent_average) + ((1 - weightPercentage) × historical_average)
@@ -643,13 +643,18 @@ final class BaseTDDStorage: TDDStorage, Injectable {
     /// - The record's date is within the last 7 days.
     /// - The total value is greater than 0.
     ///
-    /// It then checks if at least 85% of the expected data points are present,
+    /// It then checks if at least 75% of the expected data points are present,
     /// assuming at least 288 expected entries per day (one every 5 minutes).
     ///
     /// - Returns: `true` if sufficient TDD data is available, otherwise `false`.
     /// - Throws: An error if the Core Data count operation fails.
     func hasSufficientTDD() async throws -> Bool {
-        try await privateContext.perform {
+        try await BaseTDDStorage.hasSufficientTDD(context: privateContext)
+    }
+
+    /// internal function with context exposed to enable testing
+    static func hasSufficientTDD(context: NSManagedObjectContext) async throws -> Bool {
+        try await context.perform {
             let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "TDDStored")
             fetchRequest.predicate = NSPredicate(
                 format: "date > %@ AND total > 0",
@@ -657,8 +662,8 @@ final class BaseTDDStorage: TDDStorage, Injectable {
             )
             fetchRequest.resultType = .countResultType
 
-            let count = try self.privateContext.count(for: fetchRequest)
-            let threshold = Int(Double(7 * 288) * 0.85)
+            let count = try context.count(for: fetchRequest)
+            let threshold = Int(Double(7 * 288) * 0.75)
             return count >= threshold
         }
     }

+ 1 - 1
Trio/Sources/Application/AppState.swift

@@ -1,6 +1,6 @@
 import Foundation
 import Observation
-import SwiftUICore
+import SwiftUI
 import UIKit
 
 @Observable class AppState {

+ 273 - 0
Trio/Sources/Application/LockedResolver.swift

@@ -0,0 +1,273 @@
+import Foundation
+import Swinject
+
+/// This class adds a simple wrapper around a Swinject resolver to ensure that only one thread can
+/// access it at any given time.
+struct LockedResolver: Resolver {
+    let resolver: Resolver
+    let lock: NSRecursiveLock
+
+    func resolve<Service, Arg1>(_ serviceType: Service.Type, argument: Arg1) -> Service? {
+        lock.lock()
+        let service = resolver.resolve(serviceType, argument: argument)
+        lock.unlock()
+        return service
+    }
+
+    func resolve<Service, Arg1>(_ serviceType: Service.Type, name: String?, argument: Arg1) -> Service? {
+        lock.lock()
+        let service = resolver.resolve(serviceType, name: name, argument: argument)
+        lock.unlock()
+        return service
+    }
+
+    func resolve<Service, Arg1, Arg2>(_ serviceType: Service.Type, arguments arg1: Arg1, _ arg2: Arg2) -> Service? {
+        lock.lock()
+        let service = resolver.resolve(serviceType, arguments: arg1, arg2)
+        lock.unlock()
+        return service
+    }
+
+    func resolve<Service, Arg1, Arg2>(
+        _ serviceType: Service.Type,
+        name: String?,
+        arguments arg1: Arg1,
+        _ arg2: Arg2
+    ) -> Service? {
+        lock.lock()
+        let service = resolver.resolve(serviceType, name: name, arguments: arg1, arg2)
+        lock.unlock()
+        return service
+    }
+
+    func resolve<Service, Arg1, Arg2, Arg3>(
+        _ serviceType: Service.Type,
+        arguments arg1: Arg1,
+        _ arg2: Arg2,
+        _ arg3: Arg3
+    ) -> Service? {
+        lock.lock()
+        let service = resolver.resolve(serviceType, arguments: arg1, arg2, arg3)
+        lock.unlock()
+        return service
+    }
+
+    func resolve<Service, Arg1, Arg2, Arg3>(
+        _ serviceType: Service.Type,
+        name: String?,
+        arguments arg1: Arg1,
+        _ arg2: Arg2,
+        _ arg3: Arg3
+    ) -> Service? {
+        lock.lock()
+        let service = resolver.resolve(serviceType, name: name, arguments: arg1, arg2, arg3)
+        lock.unlock()
+        return service
+    }
+
+    func resolve<Service, Arg1, Arg2, Arg3, Arg4>(
+        _ serviceType: Service.Type,
+        arguments arg1: Arg1,
+        _ arg2: Arg2,
+        _ arg3: Arg3,
+        _ arg4: Arg4
+    ) -> Service? {
+        lock.lock()
+        let service = resolver.resolve(serviceType, arguments: arg1, arg2, arg3, arg4)
+        lock.unlock()
+        return service
+    }
+
+    func resolve<Service, Arg1, Arg2, Arg3, Arg4>(
+        _ serviceType: Service.Type,
+        name: String?,
+        arguments arg1: Arg1,
+        _ arg2: Arg2,
+        _ arg3: Arg3,
+        _ arg4: Arg4
+    ) -> Service? {
+        lock.lock()
+        let service = resolver.resolve(serviceType, name: name, arguments: arg1, arg2, arg3, arg4)
+        lock.unlock()
+        return service
+    }
+
+    func resolve<Service, Arg1, Arg2, Arg3, Arg4, Arg5>(
+        _ serviceType: Service.Type,
+        arguments arg1: Arg1,
+        _ arg2: Arg2,
+        _ arg3: Arg3,
+        _ arg4: Arg4,
+        _ arg5: Arg5
+    ) -> Service? {
+        lock.lock()
+        let service = resolver.resolve(serviceType, arguments: arg1, arg2, arg3, arg4, arg5)
+        lock.unlock()
+        return service
+    }
+
+    func resolve<Service, Arg1, Arg2, Arg3, Arg4, Arg5>(
+        _ serviceType: Service.Type,
+        name: String?,
+        arguments arg1: Arg1,
+        _ arg2: Arg2,
+        _ arg3: Arg3,
+        _ arg4: Arg4,
+        _ arg5: Arg5
+    ) -> Service? {
+        lock.lock()
+        let service = resolver.resolve(serviceType, name: name, arguments: arg1, arg2, arg3, arg4, arg5)
+        lock.unlock()
+        return service
+    }
+
+    func resolve<Service, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6>(
+        _ serviceType: Service.Type,
+        arguments arg1: Arg1,
+        _ arg2: Arg2,
+        _ arg3: Arg3,
+        _ arg4: Arg4,
+        _ arg5: Arg5,
+        _ arg6: Arg6
+    ) -> Service? {
+        lock.lock()
+        let service = resolver.resolve(serviceType, arguments: arg1, arg2, arg3, arg4, arg5, arg6)
+        lock.unlock()
+        return service
+    }
+
+    func resolve<Service, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6>(
+        _ serviceType: Service.Type,
+        name: String?,
+        arguments arg1: Arg1,
+        _ arg2: Arg2,
+        _ arg3: Arg3,
+        _ arg4: Arg4,
+        _ arg5: Arg5,
+        _ arg6: Arg6
+    ) -> Service? {
+        lock.lock()
+        let service = resolver.resolve(serviceType, name: name, arguments: arg1, arg2, arg3, arg4, arg5, arg6)
+        lock.unlock()
+        return service
+    }
+
+    func resolve<Service, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7>(
+        _ serviceType: Service.Type,
+        arguments arg1: Arg1,
+        _ arg2: Arg2,
+        _ arg3: Arg3,
+        _ arg4: Arg4,
+        _ arg5: Arg5,
+        _ arg6: Arg6,
+        _ arg7: Arg7
+    ) -> Service? {
+        lock.lock()
+        let service = resolver.resolve(serviceType, arguments: arg1, arg2, arg3, arg4, arg5, arg6, arg7)
+        lock.unlock()
+        return service
+    }
+
+    func resolve<Service, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7>(
+        _ serviceType: Service.Type,
+        name: String?,
+        arguments arg1: Arg1,
+        _ arg2: Arg2,
+        _ arg3: Arg3,
+        _ arg4: Arg4,
+        _ arg5: Arg5,
+        _ arg6: Arg6,
+        _ arg7: Arg7
+    ) -> Service? {
+        lock.lock()
+        let service = resolver.resolve(serviceType, name: name, arguments: arg1, arg2, arg3, arg4, arg5, arg6, arg7)
+        lock.unlock()
+        return service
+    }
+
+    func resolve<Service, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8>(
+        _ serviceType: Service.Type,
+        arguments arg1: Arg1,
+        _ arg2: Arg2,
+        _ arg3: Arg3,
+        _ arg4: Arg4,
+        _ arg5: Arg5,
+        _ arg6: Arg6,
+        _ arg7: Arg7,
+        _ arg8: Arg8
+    ) -> Service? {
+        lock.lock()
+        let service = resolver.resolve(serviceType, arguments: arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8)
+        lock.unlock()
+        return service
+    }
+
+    func resolve<Service, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8>(
+        _ serviceType: Service.Type,
+        name: String?,
+        arguments arg1: Arg1,
+        _ arg2: Arg2,
+        _ arg3: Arg3,
+        _ arg4: Arg4,
+        _ arg5: Arg5,
+        _ arg6: Arg6,
+        _ arg7: Arg7,
+        _ arg8: Arg8
+    ) -> Service? {
+        lock.lock()
+        let service = resolver.resolve(serviceType, name: name, arguments: arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8)
+        lock.unlock()
+        return service
+    }
+
+    func resolve<Service, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Arg9>(
+        _ serviceType: Service.Type,
+        arguments arg1: Arg1,
+        _ arg2: Arg2,
+        _ arg3: Arg3,
+        _ arg4: Arg4,
+        _ arg5: Arg5,
+        _ arg6: Arg6,
+        _ arg7: Arg7,
+        _ arg8: Arg8,
+        _ arg9: Arg9
+    ) -> Service? {
+        lock.lock()
+        let service = resolver.resolve(serviceType, arguments: arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9)
+        lock.unlock()
+        return service
+    }
+
+    func resolve<Service, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Arg9>(
+        _ serviceType: Service.Type,
+        name: String?,
+        arguments arg1: Arg1,
+        _ arg2: Arg2,
+        _ arg3: Arg3,
+        _ arg4: Arg4,
+        _ arg5: Arg5,
+        _ arg6: Arg6,
+        _ arg7: Arg7,
+        _ arg8: Arg8,
+        _ arg9: Arg9
+    ) -> Service? {
+        lock.lock()
+        let service = resolver.resolve(serviceType, name: name, arguments: arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9)
+        lock.unlock()
+        return service
+    }
+
+    func resolve<Service>(_ serviceType: Service.Type) -> Service? {
+        lock.lock()
+        let service = resolver.resolve(serviceType)
+        lock.unlock()
+        return service
+    }
+
+    func resolve<Service>(_ serviceType: Service.Type, name: String?) -> Service? {
+        lock.lock()
+        let service = resolver.resolve(serviceType, name: name)
+        lock.unlock()
+        return service
+    }
+}

+ 6 - 5
Trio/Sources/Application/TrioApp.swift

@@ -52,15 +52,16 @@ extension Notification.Name {
         SecurityAssembly()
     ], parent: nil, defaultObjectScope: .container)
 
+    // Simple thread-safe wrapper
+    private static let resolverLock = NSRecursiveLock()
+
     var resolver: Resolver {
-        TrioApp.assembler.resolver
+        TrioApp.resolver
     }
 
-    // Temp static var
-    // Use to backward compatibility with old Dependencies logic on Logger
-    // TODO: Remove var after update "Use Dependencies" logic in Logger
     static var resolver: Resolver {
-        TrioApp.assembler.resolver
+        // Return a simple wrapper that adds locking
+        LockedResolver(resolver: assembler.resolver, lock: resolverLock)
     }
 
     private func loadServices() {

+ 1 - 1
Trio/Sources/Helpers/MainChartHelper.swift

@@ -1,7 +1,7 @@
 import Charts
 import CoreData
 import Foundation
-import SwiftUICore
+import SwiftUI
 
 enum MainChartHelper {
     // Calculates the glucose value thats the nearest to parameter 'time'

Разница между файлами не показана из-за своего большого размера
+ 4844 - 7311
Trio/Sources/Localizations/Main/Localizable.xcstrings


+ 7 - 3
Trio/Sources/Models/Oref2_variables.swift

@@ -1,6 +1,6 @@
 import Foundation
 
-struct Oref2_variables: JSON, Equatable {
+struct TrioCustomOrefVariables: JSON, Equatable {
     var average_total_data: Decimal
     var currentTDD: Decimal
     var weightedAverage: Decimal
@@ -21,6 +21,7 @@ struct Oref2_variables: JSON, Equatable {
     var end: Decimal
     var smbMinutes: Decimal
     var uamMinutes: Decimal
+    var shouldProtectDueToHIGH: Bool
 
     init(
         average_total_data: Decimal,
@@ -42,7 +43,8 @@ struct Oref2_variables: JSON, Equatable {
         start: Decimal,
         end: Decimal,
         smbMinutes: Decimal,
-        uamMinutes: Decimal
+        uamMinutes: Decimal,
+        shouldProtectDueToHIGH: Bool
     ) {
         self.average_total_data = average_total_data
         self.weightedAverage = weightedAverage
@@ -64,10 +66,11 @@ struct Oref2_variables: JSON, Equatable {
         self.end = end
         self.smbMinutes = smbMinutes
         self.uamMinutes = uamMinutes
+        self.shouldProtectDueToHIGH = shouldProtectDueToHIGH
     }
 }
 
-extension Oref2_variables {
+extension TrioCustomOrefVariables {
     private enum CodingKeys: String, CodingKey {
         case average_total_data
         case weightedAverage
@@ -89,5 +92,6 @@ extension Oref2_variables {
         case end
         case smbMinutes
         case uamMinutes
+        case shouldProtectDueToHIGH
     }
 }

+ 1 - 1
Trio/Sources/Modules/Adjustments/AdjustmentsStateModel+Extensions/AdjustmentsStateModel+Overrides.swift

@@ -1,7 +1,7 @@
 import Combine
 import CoreData
 import Foundation
-import SwiftUICore
+import SwiftUI
 
 extension Adjustments.StateModel {
     // MARK: - Enact Overrides

+ 30 - 13
Trio/Sources/Modules/Adjustments/View/AdjustmentsRootView.swift

@@ -21,6 +21,8 @@ extension Adjustments {
         @State var isRemoveAlertPresented = false
         @State var removeAlert: Alert?
         @State var isEditingTT = false
+        @State var showCancelOverrideConfirmDialog = false
+        @State var showCancelTempTargetConfirmDialog = false
 
         private var shouldDisplayStickyOverrideStopButton: Bool {
             state.isOverrideEnabled && state.activeOverrideName.isNotEmpty
@@ -143,6 +145,32 @@ extension Adjustments {
                         EditTempTargetForm(tempTargetToEdit: tempTarget, state: state)
                     }
                 }
+                .confirmationDialog("Override to Stop", isPresented: $showCancelOverrideConfirmDialog) {
+                    Button("Stop", role: .destructive) {
+                        Task {
+                            // Save cancelled Override in OverrideRunStored Entity
+                            // Cancel ALL active Override
+                            await state.disableAllActiveOverrides(createOverrideRunEntry: true)
+                        }
+                    }
+                    Button("Cancel", role: .cancel) {}
+                } message: {
+                    Text("Stop the Override \"\(state.currentActiveOverride?.name ?? "")\"?")
+                }
+                .confirmationDialog("Temp Target to Stop", isPresented: $showCancelTempTargetConfirmDialog) {
+                    Button("Stop", role: .destructive) {
+                        Task {
+                            // Save cancelled Temp Targets in TempTargetRunStored Entity
+                            // Cancel ALL active Temp Targets
+                            await state.disableAllActiveTempTargets(createTempTargetRunEntry: true)
+                            // Update View
+                            state.updateLatestTempTargetConfiguration()
+                        }
+                    }
+                    Button("Cancel", role: .cancel) {}
+                } message: {
+                    Text("Stop the Temp Target \"\(state.currentActiveTempTarget?.name ?? "")\"?")
+                }
             }).background(appState.trioBackgroundColor(for: colorScheme))
         }
 
@@ -224,11 +252,7 @@ extension Adjustments {
             switch state.selectedTab {
             case .overrides:
                 Button(action: {
-                    Task {
-                        // Save cancelled Override in OverrideRunStored Entity
-                        // Cancel ALL active Override
-                        await state.disableAllActiveOverrides(createOverrideRunEntry: true)
-                    }
+                    showCancelOverrideConfirmDialog = true
                 }, label: {
                     Text("Stop Override")
 
@@ -239,14 +263,7 @@ extension Adjustments {
                     .tint(.white)
             case .tempTargets:
                 Button(action: {
-                    Task {
-                        // Save cancelled Temp Targets in TempTargetRunStored Entity
-                        // Cancel ALL active Temp Targets
-                        await state.disableAllActiveTempTargets(createTempTargetRunEntry: true)
-
-                        // Update View
-                        state.updateLatestTempTargetConfiguration()
-                    }
+                    showCancelTempTargetConfirmDialog = true
                 }, label: {
                     Text("Stop Temp Target")
 

+ 1 - 5
Trio/Sources/Modules/Adjustments/View/Overrides/AdjustmentsRootView+Overrides.swift

@@ -123,11 +123,7 @@ extension Adjustments.RootView {
                 .clipShape(Rectangle())
 
             Button(action: {
-                Task {
-                    // Save cancelled Override in OverrideRunStored Entity
-                    // Cancel ALL active Override
-                    await state.disableAllActiveOverrides(createOverrideRunEntry: true)
-                }
+                showCancelOverrideConfirmDialog = true
             }, label: {
                 Text("Stop Override")
                     .frame(maxWidth: .infinity, maxHeight: .infinity)

+ 1 - 7
Trio/Sources/Modules/Adjustments/View/TempTargets/AdjustmentsRootView+TempTargets.swift

@@ -144,13 +144,7 @@ extension Adjustments.RootView {
                 .clipShape(Rectangle())
 
             Button(action: {
-                Task {
-                    // Save cancelled Temp Targets in TempTargetRunStored Entity
-                    // Cancel ALL active Temp Targets
-                    await state.disableAllActiveTempTargets(createTempTargetRunEntry: true)
-                    // Update View
-                    state.updateLatestTempTargetConfiguration()
-                }
+                showCancelTempTargetConfirmDialog = true
             }, label: {
                 Text("Stop Temp Target")
                     .frame(maxWidth: .infinity, maxHeight: .infinity)

+ 9 - 7
Trio/Sources/Modules/BolusCalculatorConfig/View/BolusCalculatorConfigRootView.swift

@@ -93,13 +93,13 @@ extension BolusCalculatorConfig {
                         get: { selectedVerboseHint },
                         set: {
                             selectedVerboseHint = $0.map { AnyView($0) }
-                            hintLabel = String(localized: "Fatty Meal")
+                            hintLabel = String(localized: "Reduced Bolus")
                         }
                     ),
                     units: state.units,
                     type: .conditionalDecimal("fattyMealFactor"),
-                    label: String(localized: "Enable Fatty Meal Option"),
-                    conditionalLabel: String(localized: "Fatty Meal Bolus Percentage"),
+                    label: String(localized: "Enable Reduced Bolus Option"),
+                    conditionalLabel: String(localized: "Reduced Bolus Percentage"),
                     miniHint: String(localized: "Add and set a bolus option for meals that absorb slowly."),
                     verboseHint:
                     VStack(alignment: .leading, spacing: 10) {
@@ -107,15 +107,17 @@ extension BolusCalculatorConfig {
                         Text("Default Percent: 70%").bold()
                         Text("Do not enable this feature until you have optimized your CR (carb ratio) setting.").bold()
                         Text(
-                            "Enabling this setting adds a \"Fatty Meal\" option to the bolus calculator. Once this feature is enabled, a percentage setting will appear for you to select."
+                            "Enabling this setting adds a \"Reduced Bolus\" option to the bolus calculator. Once this feature is enabled, a percentage setting will appear for you to select."
                         )
                         Text(
-                            "When \"Fatty Meal\" is selected in the bolus calculator, the recommended bolus will be multiplied by the \"Fatty Meal Bolus Percentage\" as well as the \"Recommended Bolus Percentage\"."
+                            "When \"Reduced Bolus\" is selected in the bolus calculator, the recommended bolus will be multiplied by the \"Reduced Bolus Percentage\" as well as the \"Recommended Bolus Percentage\"."
                         )
                         Text(
-                            "If you have a \"Recommended Bolus Percentage\" of 80%, and a \"Fatty Meal Bolus Percentage\" of 70%, your recommended bolus will be multiplied by: (80 × 70) / 100 = 56%."
+                            "If you have a \"Recommended Bolus Percentage\" of 80%, and a \"Reduced Bolus Percentage\" of 70%, your recommended bolus will be multiplied by: (80 × 70) / 100 = 56%."
+                        )
+                        Text(
+                            "This is useful for slow-absorbing meals where high fat, protein, or fiber can delay carb absorption and cause a slower rise in blood sugar, so a reduced bolus helps match insulin to the delayed effect."
                         )
-                        Text("This could be useful for slow absorbing meals like pizza.")
                     }
                 )
 

+ 1 - 1
Trio/Sources/Modules/DynamicSettings/View/DynamicSettingsRootView.swift

@@ -62,7 +62,7 @@ extension DynamicSettings {
                                         localized: "Dynamically adjust insulin sensitivity using Dynamic Ratio rather than Autosens Ratio."
                                     ) :
                                     String(
-                                        localized: "Trio has only been actively used and looping for less than seven days. Cannot enable dynamic ISF."
+                                        localized: "Trio does not have enough closed-loop data to enable Dynamic ISF. This data collection can take up to 7 days."
                                     )
                                 let miniHintTextColorForDisabled: Color = colorScheme == .dark ? .orange :
                                     .accentColor

+ 25 - 7
Trio/Sources/Modules/GeneralSettings/View/UnitsLimitsSettingsRootView.swift

@@ -46,22 +46,40 @@ extension UnitsLimitsSettings {
                     verboseHint:
                     VStack(alignment: .leading, spacing: 10) {
                         Text("Default: 0 units").bold()
+
                         Text(
-                            "Warning: This must be greater than 0 for any automatic temporary basal rates or SMBs to be given."
-                        ).bold()
-                        Text(
-                            "This setting helps prevent delivering too much insulin at once. It’s typically a value close to the amount you might need for a very high blood sugar and the biggest meal of your life combined."
+                            "Note: This setting must be greater than 0 for any automatic insulin dosing by Trio (unless you currently have negative IOB)."
                         )
+                        .bold()
+                        .foregroundStyle(Color.orange)
+
                         Text(
-                            "This is the maximum amount of Insulin On Board (IOB) above profile basal rates from all sources - positive temporary basal rates, manual or meal boluses, and SMBs - that Trio is allowed to accumulate to address an above target glucose."
+                            "Choose a value that covers your highest insulin needs — think about a correction for a very high glucose reading plus your biggest meal bolus. This gives Trio room to work while keeping you safe."
                         )
+
                         Text(
-                            "If a calculated amount exceeds this limit, the suggested and / or delivered amount will be reduced so that active insulin on board (IOB) will not exceed this safety limit."
+                            "Max IOB sets a safety limit on how much insulin Trio can automatically deliver above your scheduled basal rates. This prevents the system from giving too much insulin at once."
                         )
+
+                        VStack(alignment: .leading, spacing: 0) {
+                            Text("Trio calculates your current Insulin On Board (IOB) from:")
+                            Text("• Boluses (including SMBs)")
+                            Text("• Temporary Basal Rates (TBRs)")
+                            Text("  ◦ A TBR higher than your scheduled rate will increase IOB")
+                            Text("  ◦ A TBR lower than your scheduled rate will decrease IOB")
+                        }
+
                         Text(
-                            "Note: You can still manually bolus above this limit, but the suggested bolus amount will never exceed this in the bolus calculator."
+                            "If delivering more insulin would push your IOB above this limit, Trio will reduce or skip the dose to stay within the safety boundary. This applies to SMBs, TBRs, and the recommendation from the bolus calculator."
                         )
+
+                        VStack(alignment: .leading, spacing: 0) {
+                            Text("What's NOT limited:")
+                            Text("• Manual boluses you enter yoursef")
+                            Text("• Manual temporary basal rates you set yourself")
+                        }
                     }
+                    .fixedSize(horizontal: false, vertical: true)
                 )
 
                 SettingInputSection(

+ 1 - 1
Trio/Sources/Modules/Home/View/Chart/ChartElements/SelectionPopoverView.swift

@@ -1,6 +1,6 @@
 import Charts
 import Foundation
-import SwiftUICore
+import SwiftUI
 
 struct SelectionPopoverView: ChartContent {
     let selectedGlucose: GlucoseStored

+ 2 - 1
Trio/Sources/Modules/Home/View/Header/PumpView.swift

@@ -99,7 +99,8 @@ struct PumpView: View {
                     HStack {
                         Image(systemName: hourglassIcon)
                             .font(.callout)
-                            .foregroundStyle(timerColor)
+                            .foregroundStyle(timerColor, Color.yellow)
+                            .symbolRenderingMode(.palette)
 
                         let remainingTimeString = remainingTimeString(time: date.timeIntervalSince(timerDate))
 

+ 8 - 10
Trio/Sources/Modules/Home/View/HomeRootView.swift

@@ -399,18 +399,16 @@ extension Home {
                 /// eventualBG string at bottomTrailing
 
                 if let eventualBG = state.enactedAndNonEnactedDeterminations.first?.eventualBG {
-                    let bg = eventualBG as Decimal
+                    let eventualGlucose = eventualBG as Decimal
                     HStack {
                         Image(systemName: "arrow.right.circle")
-                            .font(.callout).fontWeight(.bold)
-                        Text(
-                            Formatter.decimalFormatterWithTwoFractionDigits.string(
-                                from: (
-                                    state.units == .mmolL ? bg
-                                        .asMmolL : bg
-                                ) as NSNumber
-                            )!
-                        ).font(.callout).fontWeight(.bold).fontDesign(.rounded)
+                            .font(.callout)
+                            .fontWeight(.bold)
+
+                        Text(state.units == .mgdL ? eventualGlucose.description : eventualGlucose.formattedAsMmolL)
+                            .font(.callout)
+                            .fontWeight(.bold)
+                            .fontDesign(.rounded)
                     }
                     // aligns the evBG icon exactly with the first pixel of loop status icon
                     .padding(.leading, 12)

+ 1 - 1
Trio/Sources/Modules/Onboarding/View/OnboardingView+AlgorithmUtil.swift

@@ -356,7 +356,7 @@ enum AlgorithmSettingsSubstep: Int, CaseIterable, Identifiable {
             return VStack(alignment: .leading, spacing: 8) {
                 Text("Default: 20% increase").bold().foregroundStyle(Color.primary)
                 Text(
-                    "Maximum allowed positive percent change in glucose level to permit SMBs. If the difference in glucose is greater than this, Trio will disable SMBs."
+                    "Maximum allowed positive percent change in glucose level to permit SMBs. If the difference in glucose is greater than this, Trio will only adjust Temp Basal Rate and not deliver an SMB that loop cycle."
                 )
                 Text(
                     "This is a safety limitation to avoid high SMB doses when glucose is rising abnormally fast, such as after a meal or with a very jumpy CGM sensor."

+ 25 - 8
Trio/Sources/Modules/Onboarding/View/OnboardingView+Util.swift

@@ -386,25 +386,42 @@ enum DeliveryLimitSubstep: Int, CaseIterable, Identifiable {
     func description(units: GlucoseUnits) -> any View {
         switch self {
         case .maxIOB:
-            return VStack(alignment: .leading, spacing: 8) {
-                Text(
-                    "Note: This setting must be greater than 0 for any automatic insulin dosing by Trio."
-                ).bold().foregroundStyle(Color.orange)
+            return VStack(alignment: .leading, spacing: 10) {
+                Text("Default: 0 units").bold()
 
                 Text(
-                    "This setting helps prevent delivering too much insulin at once. It’s typically a value close to the amount you might need for a very high blood sugar and the biggest meal of your life combined."
+                    "Note: This setting must be greater than 0 for any automatic insulin dosing by Trio (unless you currently have negative IOB)."
                 )
+                .bold()
+                .foregroundStyle(Color.orange)
 
                 Text(
-                    "This is the maximum amount of Insulin On Board (IOB) above profile basal rates from all sources - positive temporary basal rates, manual or meal boluses, and SMBs - that Trio is allowed to accumulate to address an above target glucose."
+                    "Choose a value that covers your highest insulin needs — think about a correction for a very high glucose reading plus your biggest meal bolus. This gives Trio room to work while keeping you safe."
                 )
+
                 Text(
-                    "If a calculated amount exceeds this limit, the suggested and / or delivered amount will be reduced so that active insulin on board (IOB) will not exceed this safety limit."
+                    "Max IOB sets a safety limit on how much insulin Trio can automatically deliver above your scheduled basal rates. This prevents the system from giving too much insulin at once."
                 )
+
+                VStack(alignment: .leading, spacing: 0) {
+                    Text("Trio calculates your current Insulin On Board (IOB) from:")
+                    Text("• Boluses (including SMBs)")
+                    Text("• Temporary Basal Rates (TBRs)")
+                    Text("  ◦ A TBR higher than your scheduled rate will increase IOB")
+                    Text("  ◦ A TBR lower than your scheduled rate will decrease IOB")
+                }
+
                 Text(
-                    "Note: You can still manually bolus above this limit, but the suggested bolus amount will never exceed this in the bolus calculator."
+                    "If delivering more insulin would push your IOB above this limit, Trio will reduce or skip the dose to stay within the safety boundary. This applies to SMBs, TBRs, and the recommendation from the bolus calculator."
                 )
+
+                VStack(alignment: .leading, spacing: 0) {
+                    Text("What's NOT limited:")
+                    Text("• Manual boluses you enter yoursef")
+                    Text("• Manual temporary basal rates you set yourself")
+                }
             }
+            .fixedSize(horizontal: false, vertical: true)
         case .maxBolus:
             return VStack(alignment: .leading, spacing: 8) {
                 Text(

+ 1 - 1
Trio/Sources/Modules/SMBSettings/View/SMBSettingsRootView.swift

@@ -347,7 +347,7 @@ extension SMBSettings {
                     VStack(alignment: .leading, spacing: 10) {
                         Text("Default: 20% increase").bold()
                         Text(
-                            "Maximum allowed positive percent change in glucose level to permit SMBs. If the difference in glucose is greater than this, Trio will disable SMBs."
+                            "Maximum allowed positive percent change in glucose level to permit SMBs. If the difference in glucose is greater than this, Trio will only adjust Temp Basal Rate and not deliver an SMB that loop cycle."
                         )
                         Text(
                             "This is a safety limitation to avoid high SMB doses when glucose is rising abnormally fast, such as after a meal or with a very jumpy CGM sensor."

+ 6 - 8
Trio/Sources/Modules/Settings/SettingItems.swift

@@ -128,11 +128,10 @@ enum SettingItems {
             title: "Dynamic Settings",
             view: .dynamicISF,
             searchContents: [
-                "Activate Dynamic Sensitivity (ISF)",
-                "Activate Dynamic Carb Ratio (CR)",
-                "Use Sigmoid Formula",
-                "Adjustment Factor",
-                "AF",
+                "Dynamic ISF",
+                "Sigmoid",
+                "Logarithmic",
+                "Adjustment Factor (AF)",
                 "Sigmoid Adjustment Factor",
                 "Weighted Average of TDD",
                 "Adjust Basal"
@@ -162,7 +161,6 @@ enum SettingItems {
                 "Insulin Peak Time",
                 "Skip Neutral Temps",
                 "Unsuspend If No Temp",
-                "Suspend Zeros IOB",
                 "SMB Delivery Ratio",
                 "SMB Interval",
                 "Min 5m Carbimpact",
@@ -181,8 +179,8 @@ enum SettingItems {
             searchContents: [
                 "Display Meal Presets",
                 "Recommended Bolus Percentage",
-                "Enable Fatty Meal Factor",
-                "Fatty Meal Factor",
+                "Enable Reduced Bolus Factor",
+                "Reduced Bolus Factor",
                 "Enable Super Bolus",
                 "Super Bolus Factor",
                 "Very Low Glucose Warning"

+ 21 - 2
Trio/Sources/Modules/Treatments/TreatmentsStateModel.swift

@@ -89,6 +89,7 @@ extension Treatments {
         var note: String = ""
 
         var date = Date()
+        let defaultDate = Date()
 
         var carbsRequired: Decimal?
         var useFPUconversion: Bool = false
@@ -381,12 +382,26 @@ extension Treatments {
                 minPredBG
             }
 
+            // Use the cob value of the simulation if we have a simulated determination
+            var simulatedCOB: Int16?
+            if let simulatedCobValue = simulatedDetermination?.cob {
+                // Convert Decimal to Int16 and cap at maxCOB
+                let cobInt16 = Int16(truncating: NSDecimalNumber(decimal: simulatedCobValue))
+                let maxCobInt16 = Int16(truncating: NSDecimalNumber(decimal: maxCOB))
+                simulatedCOB = min(maxCobInt16, cobInt16)
+            }
+
+            // Check if this is a backdated entry by comparing with the default date using a tolerance
+            let isBackdated = abs(date.timeIntervalSince(defaultDate)) > 1.0
+
             let result = await bolusCalculationManager.handleBolusCalculation(
                 carbs: carbs,
                 useFattyMealCorrection: useFattyMealCorrectionFactor,
                 useSuperBolus: useSuperBolus,
                 lastLoopDate: apsManager.lastLoopDate,
-                minPredBG: localMinPredBG
+                minPredBG: localMinPredBG,
+                simulatedCOB: simulatedCOB,
+                isBackdated: isBackdated
             )
 
             // Update state properties with calculation results on main thread
@@ -917,7 +932,11 @@ extension Treatments.StateModel {
         } else {
             simulatedDetermination = await Task { [self] in
                 debug(.bolusState, "calling simulateDetermineBasal to get forecast data")
-                return await apsManager.simulateDetermineBasal(simulatedCarbsAmount: carbs, simulatedBolusAmount: amount)
+                return await apsManager.simulateDetermineBasal(
+                    simulatedCarbsAmount: carbs,
+                    simulatedBolusAmount: amount,
+                    simulatedCarbsDate: date
+                )
             }.value
 
             // Update evBG and minPredBG from simulated determination

+ 14 - 3
Trio/Sources/Modules/Treatments/View/ForecastChart.swift

@@ -50,10 +50,16 @@ struct ForecastChart: View {
     }
 
     private var forecastChartLabels: some View {
-        HStack {
+        // Check if this is a backdated entry by comparing with the default date using a tolerance
+        let isBackdated = abs(state.date.timeIntervalSince(state.defaultDate)) > 1.0
+
+        // When backdated, display no carbs as this label is only supposed to show current entered carbs
+        let displayedCarbs = isBackdated ? 0 : state.carbs
+
+        return HStack {
             HStack {
                 Image(systemName: "fork.knife")
-                Text("\(state.carbs.description) g")
+                Text("\(displayedCarbs.description) g")
             }
             .font(.footnote)
             .foregroundStyle(.orange)
@@ -118,6 +124,11 @@ struct ForecastChart: View {
         }
     }
 
+    private var maxGlucoseMgDl: Decimal {
+        let maxGlucose = state.glucoseFromPersistence.map({ Decimal($0.glucose) }).max() ?? 300
+        return maxGlucose > 300 ? 400 : 300
+    }
+
     private var forecastChart: some View {
         Chart {
             drawGlucose()
@@ -168,7 +179,7 @@ struct ForecastChart: View {
         .chartXAxis { forecastChartXAxis }
         .chartXScale(domain: startMarker ... endMarker)
         .chartYAxis { forecastChartYAxis }
-        .chartYScale(domain: state.units == .mgdL ? 0 ... 300 : 0.asMmolL ... 300.asMmolL)
+        .chartYScale(domain: state.units == .mgdL ? 0 ... maxGlucoseMgDl : 0.asMmolL ... maxGlucoseMgDl.asMmolL)
         .chartLegend {
             if state.forecastDisplayType == ForecastDisplayType.lines {
                 HStack(spacing: 10) {

+ 20 - 6
Trio/Sources/Modules/Treatments/View/PopupView.swift

@@ -312,7 +312,14 @@ struct PopupView: View {
     /// Don't allow total carbs to exceed Max IOB setting.
     /// Formula: (Current COB + New Carbs) / Carb Ratio = COB Correction Dose
     private var cobCardContent: some View {
-        let hasExceededMaxCOB: Bool = Decimal(state.cob) + state.carbs > state.maxCOB
+        // Check if this is a backdated entry by comparing with the default date using a tolerance
+        let isBackdated = abs(state.date.timeIntervalSince(state.defaultDate)) > 1.0
+
+        // Determine COB and carbs to display based on backdating status
+        let displayedCOB = isBackdated ? (state.simulatedDetermination?.cob ?? Decimal(state.cob)) : Decimal(state.cob)
+        let displayedCarbs = isBackdated ? 0 : state.carbs
+
+        let hasExceededMaxCOB: Bool = displayedCOB + displayedCarbs > state.maxCOB
         return Group {
             Grid(alignment: .center) {
                 // Row 1: Column headers for the COB calculation
@@ -333,11 +340,11 @@ struct PopupView: View {
                 GridRow {
                     Text("(")
                         .operatorStyle()
-                    Text(Int(state.cob).description)
+                    Text(Int(displayedCOB).description)
                         .valueStyle()
                     Text("+")
                         .operatorStyle()
-                    Text(Int(state.carbs).description)
+                    Text(Int(displayedCarbs).description)
                         .valueStyle()
                     Text(")")
                         .operatorStyle()
@@ -378,6 +385,13 @@ struct PopupView: View {
             }
             .multilineTextAlignment(.center)
 
+            if isBackdated {
+                Text("Backdated carbs (\(Int(state.carbs)) g) included in COB calculation")
+                    .font(.caption)
+                    .foregroundStyle(.orange)
+                    .padding(.top, 4)
+            }
+
             // Additional grid only displayed when Max COB limit has been exceeded
             if hasExceededMaxCOB {
                 Grid(alignment: .center) {
@@ -588,7 +602,7 @@ struct PopupView: View {
 
     /// Card showing applied factors to the final insulin calculation.
     /// Dynamically changes card based on user's selection in the Treatment view.
-    /// User can choose Fatty Meal, Super Bolus, or neither, but not both.
+    /// User can choose Reduced Bolus, Super Bolus, or neither, but not both.
     private var factorsCardContent: some View {
         Grid(alignment: .center) {
             // Choose the layout based on which options are selected
@@ -630,7 +644,7 @@ struct PopupView: View {
                 }
                 .unitStyle()
 
-            // Case: Full Bolus × Rec. Bolus % × Fatty Meal %
+            // Case: Full Bolus × Rec. Bolus % × Reduced Bolus %
             case (false, true):
                 // Row 1: Header.
                 GridRow(alignment: .lastTextBaseline) {
@@ -640,7 +654,7 @@ struct PopupView: View {
                     Text("Rec. Bolus %")
                     Text("")
                         .layoutPriority(-15)
-                    Text("Fatty %")
+                    Text("Red. Bolus %")
                 }
                 .secondaryStyle()
 

+ 9 - 1
Trio/Sources/Modules/Treatments/View/TreatmentsRootView.swift

@@ -224,6 +224,14 @@ extension Treatments {
                                         displayedComponents: [.hourAndMinute]
                                     ).controlSize(.mini)
                                         .labelsHidden()
+                                        .onChange(of: state.date) { _, _ in
+                                            // Trigger simulation when date changes to update forecasts for backdated carbs
+                                            Task {
+                                                // `updateForecasts()` does update the `simulatedDetermination` of type `Determination?` var on the main thread, so I can use this to pass its cob value into the bolus calc manager
+                                                await state.updateForecasts()
+                                                state.insulinCalculated = await state.calculateInsulin()
+                                            }
+                                        }
                                     Button {
                                         state.date = state.date.addingTimeInterval(15.minutes.timeInterval)
                                     }
@@ -247,7 +255,7 @@ extension Treatments {
                                 HStack(spacing: 10) {
                                     if state.fattyMeals {
                                         Toggle(isOn: $state.useFattyMealCorrectionFactor) {
-                                            Text("Fatty Meal")
+                                            Text("Reduced Bolus")
                                         }
                                         .toggleStyle(RadioButtonToggleStyle())
                                         .font(.footnote)

+ 28 - 17
Trio/Sources/Services/BolusCalculator/BolusCalculationManager.swift

@@ -9,9 +9,10 @@ protocol BolusCalculationManager {
         useFattyMealCorrection: Bool,
         useSuperBolus: Bool,
         lastLoopDate: Date,
-        minPredBG: Decimal?
-    ) async
-        -> CalculationResult
+        minPredBG: Decimal?,
+        simulatedCOB: Int16?,
+        isBackdated: Bool
+    ) async -> CalculationResult
 }
 
 final class BaseBolusCalculationManager: BolusCalculationManager, Injectable {
@@ -289,7 +290,9 @@ final class BaseBolusCalculationManager: BolusCalculationManager, Injectable {
         useFattyMealCorrection: Bool,
         useSuperBolus: Bool,
         lastLoopDate: Date,
-        minPredBG: Decimal?
+        minPredBG: Decimal?,
+        simulatedCOB: Int16?,
+        isBackdated: Bool
     ) async throws -> CalculationInput {
         do {
             // Get settings
@@ -337,15 +340,20 @@ final class BaseBolusCalculationManager: BolusCalculationManager, Injectable {
                 )
             }
 
+            // If the entry is backdated (user explicitly changed the date), set carbs to 0
+            // This prevents double-counting of carbs (entered carbs + COB from backdated entry)
+            let effectiveCarbs = isBackdated ? 0 : carbs
+            let effectiveCob = isBackdated ? simulatedCOB : bolusVars.cob
+
             return CalculationInput(
-                carbs: carbs,
+                carbs: effectiveCarbs,
                 currentBG: glucoseVars.currentBG,
                 deltaBG: glucoseVars.deltaBG,
                 target: bolusVars.target,
                 isf: bolusVars.isf,
                 carbRatio: bolusVars.carbRatio,
                 iob: bolusVars.iob,
-                cob: bolusVars.cob,
+                cob: effectiveCob ?? bolusVars.cob,
                 useFattyMealCorrectionFactor: useFattyMealCorrection,
                 fattyMealFactor: settings.fattyMealFactor,
                 useSuperBolus: useSuperBolus,
@@ -384,6 +392,7 @@ final class BaseBolusCalculationManager: BolusCalculationManager, Injectable {
         debug(.default, "15min insulin: \(fifteenMinutesInsulin)")
 
         // determine whole COB for which we want to dose insulin for and then determine insulin for wholeCOB
+        // we need to take backdated carbs into account - so we are using a freshly created (simulated) COB if carbs are backdated, otherwise it will default to the mostRecentDeterminations' COB value (as before)
         let wholeCob = min(Decimal(input.cob) + input.carbs, input.maxCOB)
         let wholeCobInsulin = wholeCob / input.carbRatio
         debug(.default, "Whole COB: \(wholeCob), COB insulin: \(wholeCobInsulin)")
@@ -411,11 +420,11 @@ final class BaseBolusCalculationManager: BolusCalculationManager, Injectable {
         }
 
         // apply custom factor at the end of the calculations
-        // apply custom factor if fatty meal toggle in bolus calc config settings is on and the box for fatty meals is checked (in RootView)
+        // apply custom factor if reduced bolus toggle in bolus calc config settings is on and the box for reduced bolus is checked (in RootView)
         var factoredInsulin = wholeCalc
         debug(.default, "Initial factored insulin: \(factoredInsulin)")
 
-        // Apply Recommended Bolus Percentage (input.fraction) and if selected apply Fatty Meal Bolus Percentage (input.fattyMealFactor)
+        // Apply Recommended Bolus Percentage (input.fraction) and if selected apply Reduced Bolus Percentage (input.fattyMealFactor)
         // If factoredInsulin is negative, though, don't apply either
         if factoredInsulin > 0 {
             factoredInsulin *= input.fraction
@@ -423,7 +432,7 @@ final class BaseBolusCalculationManager: BolusCalculationManager, Injectable {
 
             if input.useFattyMealCorrectionFactor {
                 factoredInsulin *= input.fattyMealFactor
-                debug(.default, "After fatty meal factor (\(input.fattyMealFactor)): \(factoredInsulin)")
+                debug(.default, "After reduced bolus factor (\(input.fattyMealFactor)): \(factoredInsulin)")
             }
         }
 
@@ -466,7 +475,6 @@ final class BaseBolusCalculationManager: BolusCalculationManager, Injectable {
             insulinCalculated: insulinCalculated,
             factoredInsulin: factoredInsulin,
             wholeCalc: wholeCalc,
-            correctionInsulin: targetDifferenceInsulin,
             iobInsulinReduction: iobInsulinReduction,
             superBolusInsulin: superBolusInsulin,
             targetDifference: targetDifference,
@@ -480,16 +488,19 @@ final class BaseBolusCalculationManager: BolusCalculationManager, Injectable {
     /// Handles the complete bolus calculation process
     /// - Parameters:
     ///   - carbs: Amount of carbohydrates to be consumed
-    ///   - useFattyMealCorrection: Whether to apply fatty meal correction
+    ///   - useFattyMealCorrection: Whether to apply reduced bolus correction
     ///   - useSuperBolus: Whether to use super bolus calculation
     ///   - minPredBG: Minimum Predicted Glucose determined by Oref
+    ///   - simulatedCOB: Optional simulated COB from backdated entries (if available)
     /// - Returns: CalculationResult containing the calculated insulin dose and details
     func handleBolusCalculation(
         carbs: Decimal,
         useFattyMealCorrection: Bool,
         useSuperBolus: Bool,
         lastLoopDate: Date,
-        minPredBG: Decimal? = nil
+        minPredBG: Decimal? = nil,
+        simulatedCOB: Int16? = nil,
+        isBackdated: Bool = false
     ) async -> CalculationResult {
         do {
             let input = try await prepareCalculationInput(
@@ -497,7 +508,9 @@ final class BaseBolusCalculationManager: BolusCalculationManager, Injectable {
                 useFattyMealCorrection: useFattyMealCorrection,
                 useSuperBolus: useSuperBolus,
                 lastLoopDate: lastLoopDate,
-                minPredBG: minPredBG
+                minPredBG: minPredBG,
+                simulatedCOB: simulatedCOB,
+                isBackdated: isBackdated
             )
             let result = await calculateInsulin(input: input)
             return result
@@ -511,7 +524,6 @@ final class BaseBolusCalculationManager: BolusCalculationManager, Injectable {
                 insulinCalculated: 0,
                 factoredInsulin: 0,
                 wholeCalc: 0,
-                correctionInsulin: 0,
                 iobInsulinReduction: 0,
                 superBolusInsulin: 0,
                 targetDifference: 0,
@@ -534,8 +546,8 @@ struct CalculationInput: Sendable {
     let carbRatio: Decimal // Carb to insulin ratio
     let iob: Decimal // Insulin on Board
     let cob: Int16 // Carbs on Board
-    let useFattyMealCorrectionFactor: Bool // Whether to apply fatty meal correction
-    let fattyMealFactor: Decimal // Factor for fatty meal adjustment
+    let useFattyMealCorrectionFactor: Bool // Whether to apply reduced bolus correction
+    let fattyMealFactor: Decimal // Factor for reduced bolus adjustment
     let useSuperBolus: Bool // Whether to use super bolus calculation
     let sweetMealFactor: Decimal // Factor for sweet meal adjustment
     let basal: Decimal // Current basal rate
@@ -552,7 +564,6 @@ struct CalculationResult: Sendable {
     let insulinCalculated: Decimal // Final calculated insulin amount which respects limits
     let factoredInsulin: Decimal // Total calculation after adjustments
     let wholeCalc: Decimal // Total calculation before adjustments
-    let correctionInsulin: Decimal // Insulin for BG correction
     let iobInsulinReduction: Decimal // IOB reduction amount
     let superBolusInsulin: Decimal // Additional insulin for super bolus
     let targetDifference: Decimal // Difference from target BG

+ 14 - 1
Trio/Sources/Services/LiveActivity/LiveActivityManager.swift

@@ -360,12 +360,21 @@ final class LiveActivityManager: Injectable, ObservableObject, SettingsObserver
                 )
                 currentActivity = ActiveActivity(activity: activity, startDate: Date.now)
                 debug(.default, "[LiveActivityManager] Created new activity: \(activity.id)")
-                await pushUpdate(state)
+
+                // Update the newly created activity with actual data
+                let updateContent = ActivityContent(
+                    state: state,
+                    staleDate: Date.now.addingTimeInterval(5 * 60)
+                )
+                await activity.update(updateContent)
+                debug(.default, "[LiveActivityManager] Updated new activity with actual data")
             } catch {
                 debug(
                     .default,
                     "\(#file): Error creating new activity: \(error)"
                 )
+                // Reset currentActivity on error to allow retry on next update
+                currentActivity = nil
             }
         }
     }
@@ -428,6 +437,10 @@ final class LiveActivityManager: Injectable, ObservableObject, SettingsObserver
             try? await Task.sleep(nanoseconds: 200_000_000) // 0.2s sleep
         }
 
+        // Add additional delay to ensure iOS has fully cleaned up the previous activity
+        debug(.default, "Waiting additional time for iOS to clean up...")
+        try? await Task.sleep(nanoseconds: 1_000_000_000) // 1s additional delay
+
         Task { @MainActor in
             await self.pushUpdate(contentState)
         }

+ 30 - 20
Trio/Sources/Services/Network/Nightscout/NightscoutAPI.swift

@@ -20,6 +20,14 @@ class NightscoutAPI {
         static let timeout: TimeInterval = 60
     }
 
+    private let excludedEnteredBy: [String] = [
+        NightscoutTreatment.local,
+        "AndroidAPS",
+        "openaps://AndroidAPS",
+        "iAPS",
+        "loop://iPhone"
+    ]
+
     enum Error: LocalizedError {
         case badStatusCode
         case missingURL
@@ -98,23 +106,29 @@ extension NightscoutAPI {
         }
     }
 
+    private func makeNeQueryItems() -> [URLQueryItem] {
+        excludedEnteredBy.enumerated().map { idx, value in
+            URLQueryItem(
+                name: "find[$and][\(idx)][enteredBy][$ne]",
+                value: value
+            )
+        }
+    }
+
     func fetchCarbs(sinceDate: Date? = nil) async throws -> [CarbsEntry] {
         var components = URLComponents()
         components.scheme = url.scheme
         components.host = url.host
         components.port = url.port
         components.path = Config.treatmentsPath
-        components.queryItems = [
-            URLQueryItem(name: "find[carbs][$exists]", value: "true"),
-            URLQueryItem(
-                name: "find[enteredBy][$ne]",
-                value: CarbsEntry.local.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)
-            ),
-            URLQueryItem(
-                name: "find[enteredBy][$ne]",
-                value: NightscoutTreatment.local.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)
-            )
+
+        var items: [URLQueryItem] = [
+            URLQueryItem(name: "find[carbs][$exists]", value: "true")
         ]
+
+        items.append(contentsOf: makeNeQueryItems())
+        components.queryItems = items
+
         if let date = sinceDate {
             let dateItem = URLQueryItem(
                 name: "find[created_at][$gt]",
@@ -137,7 +151,6 @@ extension NightscoutAPI {
             guard let httpResponse = response as? HTTPURLResponse, (200 ... 299).contains(httpResponse.statusCode) else {
                 throw URLError(.badServerResponse)
             }
-
             let carbs = try JSONCoding.decoder.decode([CarbsEntry].self, from: data)
             return carbs
         } catch {
@@ -243,18 +256,15 @@ extension NightscoutAPI {
         components.host = url.host
         components.port = url.port
         components.path = Config.treatmentsPath
-        components.queryItems = [
+
+        var items: [URLQueryItem] = [
             URLQueryItem(name: "find[eventType]", value: "Temporary+Target"),
-            URLQueryItem(
-                name: "find[enteredBy][$ne]",
-                value: TempTarget.local.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)
-            ),
-            URLQueryItem(
-                name: "find[enteredBy][$ne]",
-                value: NightscoutTreatment.local.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)
-            ),
             URLQueryItem(name: "find[duration][$exists]", value: "true")
         ]
+
+        items.append(contentsOf: makeNeQueryItems())
+        components.queryItems = items
+
         if let date = sinceDate {
             let dateItem = URLQueryItem(
                 name: "find[created_at][$gt]",

+ 3 - 1
Trio/Sources/Services/WatchManager/AppleWatchManager.swift

@@ -622,7 +622,9 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana
                         useFattyMealCorrection: false,
                         useSuperBolus: false,
                         lastLoopDate: apsManager.lastLoopDate,
-                        minPredBG: minPredBG
+                        minPredBG: minPredBG,
+                        simulatedCOB: nil,
+                        isBackdated: false // we cannot backdate carbs via watch
                     )
 
                     // Send recommendation back to watch

+ 6 - 4
Trio/Sources/Shortcuts/Bolus/BolusIntent.swift

@@ -19,7 +19,8 @@ import Swinject
         /// A preferred approach would be to just block negatives and not specify an upperBound here, since it is implemented elsewhere
         inclusiveRange: (lowerBound: 0, upperBound: 200),
         requestValueDialog: IntentDialog(
-            LocalizedStringResource(
+            stringLiteral: String(
+                localized:
                 "Bolus amount (units of insulin)?"
             )
         )
@@ -52,8 +53,9 @@ import Swinject
                 try await requestConfirmation(
                     result: .result(
                         dialog: IntentDialog(
-                            LocalizedStringResource(
-                                "Are you sure you want to bolus \(bolusFormatted) U of insulin?"
+                            stringLiteral: String(
+                                localized:
+                                "Are you sure to bolus \(bolusFormatted) U of insulin?"
                             )
                         )
                     )
@@ -62,7 +64,7 @@ import Swinject
 
             let finalBolusDisplay = try await BolusIntentRequest().bolus(amount)
             return .result(
-                dialog: IntentDialog(finalBolusDisplay)
+                dialog: IntentDialog(stringLiteral: finalBolusDisplay)
             )
 
         } catch {

+ 7 - 4
Trio/Sources/Shortcuts/Bolus/BolusIntentRequest.swift

@@ -3,26 +3,29 @@ import CoreData
 import Foundation
 
 @available(iOS 16.0,*) final class BolusIntentRequest: BaseIntentsRequest {
-    func bolus(_ bolusAmount: Double) async throws -> LocalizedStringResource {
+    func bolus(_ bolusAmount: Double) async throws -> String {
         var bolusQuantity: Decimal = 0
         switch settingsManager.settings.bolusShortcut {
         // Block boluses if they are disabled
         case .notAllowed:
-            return LocalizedStringResource(
+            return String(
+                localized:
                 "Bolusing via Shortcuts is disabled in Trio settings."
             )
 
         // Block any bolus attempted if it is larger than the max bolus in settings
         case .limitBolusMax:
             if Decimal(bolusAmount) > settingsManager.pumpSettings.maxBolus {
-                return LocalizedStringResource(
+                return String(
+                    localized:
                     "The bolus cannot be larger than the pump setting max bolus (\(settingsManager.pumpSettings.maxBolus.description))."
                 )
             } else {
                 bolusQuantity = apsManager.roundBolus(amount: Decimal(bolusAmount))
             }
             await apsManager.enactBolus(amount: Double(bolusQuantity), isSMB: false, callback: nil)
-            return LocalizedStringResource(
+            return String(
+                localized:
                 "A bolus command of \(bolusQuantity.formatted()) U of insulin was sent."
             )
         }

+ 26 - 14
Trio/Sources/Shortcuts/Carbs/AddCarbPresetIntent.swift

@@ -8,38 +8,37 @@ import Swinject
     static var title: LocalizedStringResource = "Add carbs"
 
     // Description of the action in the Shortcuts app
-    static var description = IntentDescription("Allow to add carbs in Trio.")
-
-    init() {
-        dateAdded = Date()
-    }
+    static var description = IntentDescription(LocalizedStringResource("Allow to add carbs in Trio."))
 
     @Parameter(
         title: "Quantity Carbs",
         description: "Quantity of carbs in g",
         controlStyle: .field,
         inclusiveRange: (lowerBound: 0, upperBound: 200),
-        requestValueDialog: IntentDialog("What is the numeric value of the carb to add")
+        requestValueDialog: IntentDialog(stringLiteral: String(localized: "How many grams of carbs did you eat?"))
     ) var carbQuantity: Double?
 
     @Parameter(
         title: "Quantity fat",
         description: "Quantity of fat in g",
         default: 0.0,
-        inclusiveRange: (0, 200)
+        inclusiveRange: (0, 200),
+        requestValueDialog: IntentDialog(stringLiteral: String(localized: "How many grams of fat did you eat?"))
     ) var fatQuantity: Double
 
     @Parameter(
         title: "Quantity Protein",
         description: "Quantity of Protein in g",
         default: 0.0,
-        inclusiveRange: (0, 200)
+        inclusiveRange: (0, 200),
+        requestValueDialog: IntentDialog(stringLiteral: String(localized: "How many grams of protein did you eat?"))
     ) var proteinQuantity: Double
 
     @Parameter(
         title: "Date",
-        description: "Date of adding"
-    ) var dateAdded: Date
+        description: "Date of adding",
+        requestValueDialog: IntentDialog(stringLiteral: String(localized: "When did you eat ?"))
+    ) var dateAdded: Date?
 
     @Parameter(
         title: "Notes",
@@ -76,13 +75,25 @@ import Swinject
             if let cq = carbQuantity {
                 quantityCarbs = cq
             } else {
-                quantityCarbs = try await $carbQuantity.requestValue("How many carbs ?")
+                quantityCarbs = try await $carbQuantity.requestValue("How many grams of carbs?")
+            }
+
+            let dateCarbsAdded: Date
+            let dateDefinedByUser: Bool
+            if let da = dateAdded {
+                dateCarbsAdded = da
+                dateDefinedByUser = true
+            } else {
+                dateCarbsAdded = Date()
+                dateDefinedByUser = false
             }
 
             let quantityCarbsName = quantityCarbs.toString()
             if confirmBeforeApplying {
                 try await requestConfirmation(
-                    result: .result(dialog: "Are you sure to add \(quantityCarbsName) g of carbs ?")
+                    result: .result(
+                        dialog: IntentDialog(stringLiteral: String(localized: "Add \(quantityCarbsName) grams of carbs?"))
+                    )
                 )
             }
 
@@ -90,8 +101,9 @@ import Swinject
                 quantityCarbs,
                 fatQuantity,
                 proteinQuantity,
-                dateAdded,
-                note
+                dateCarbsAdded,
+                note,
+                dateDefinedByUser
             )
             return .result(
                 dialog: IntentDialog(stringLiteral: finalQuantityCarbsDisplay)

+ 43 - 6
Trio/Sources/Shortcuts/Carbs/CarbPresetIntentRequest.swift

@@ -7,7 +7,8 @@ import Foundation
         _ quantityFat: Double,
         _ quantityProtein: Double,
         _ dateAdded: Date,
-        _ note: String?
+        _ note: String?,
+        _ dateDefinedByUser: Bool
     ) async throws -> String {
         guard quantityCarbs >= 0.0 || quantityFat >= 0.0 || quantityProtein >= 0.0 else {
             return "not adding carbs in Trio"
@@ -30,15 +31,51 @@ import Foundation
             areFetchedFromRemote: false
         )
         var resultDisplay: String
-        resultDisplay = "\(carbs) g carbs"
+        resultDisplay = String(localized: "Added \(String(format: "%.0f", Double(carbs))) g carbs")
         if quantityFat > 0.0 {
-            resultDisplay = "\(resultDisplay) and \(quantityFat) g fats"
+            resultDisplay = String(localized: "\(resultDisplay) and \(String(format: "%.0f", Double(quantityFat))) g fat")
         }
         if quantityProtein > 0.0 {
-            resultDisplay = "\(resultDisplay) and \(quantityProtein) g protein"
+            resultDisplay = String(localized: "\(resultDisplay) and \(String(format: "%.0f", Double(quantityProtein))) g protein")
         }
-        let dateName = dateAdded.formatted()
-        resultDisplay = "\(resultDisplay) added at \(dateName)"
+        if dateDefinedByUser {
+            let dateFormatter = DateFormatter()
+            dateFormatter.dateStyle = .none
+            dateFormatter.timeStyle = .short
+
+            let hourName = dateFormatter.string(from: dateAdded)
+            resultDisplay = String(localized: "\(resultDisplay) at \(hourName)")
+
+            let dayStatus = determineDateStatus(dateAdded)
+            if let dayStatus = dayStatus {
+                resultDisplay = String(localized: "\(resultDisplay)  \(dayStatus)")
+            }
+        }
+
         return resultDisplay
     }
+
+    func determineDateStatus(_ date: Date) -> LocalizedStringResource? {
+        let calendar = Calendar.current
+        let now = Date()
+
+        let dateStartOfDay = calendar.startOfDay(for: date)
+        let nowStartOfDay = calendar.startOfDay(for: now)
+
+        let components = calendar.dateComponents([.day], from: nowStartOfDay, to: dateStartOfDay)
+
+        if let dayDifference = components.day {
+            switch dayDifference {
+            case -1:
+                return LocalizedStringResource(stringLiteral: "Yesterday")
+            case 0:
+                return nil
+            case 1:
+                return LocalizedStringResource(stringLiteral: "Tomorrow")
+            default:
+                return nil
+            }
+        }
+        return nil
+    }
 }

+ 9 - 5
Trio/Sources/Shortcuts/Override/ApplyOverridePresetIntent.swift

@@ -12,7 +12,8 @@ struct ApplyOverridePresetIntent: AppIntent {
     /// The override preset to be applied.
     @Parameter(
         title: LocalizedStringResource("Override"),
-        description: LocalizedStringResource("Override choice")
+        description: LocalizedStringResource("Override choice"),
+        requestValueDialog: IntentDialog(stringLiteral: String(localized: "Which override do you want to apply?"))
     ) var preset: OverridePreset?
 
     /// A boolean parameter that determines whether confirmation is required before applying the override.
@@ -49,7 +50,7 @@ struct ApplyOverridePresetIntent: AppIntent {
                 // Request user selection if no preset is provided
                 presetToApply = try await $preset.requestDisambiguation(
                     among: await OverridePresetsIntentRequest().fetchAndProcessOverrides(),
-                    dialog: IntentDialog(LocalizedStringResource("Select override"))
+                    dialog: IntentDialog(stringLiteral: String(localized: "Select override"))
                 )
             }
 
@@ -60,7 +61,8 @@ struct ApplyOverridePresetIntent: AppIntent {
                 try await requestConfirmation(
                     result: .result(
                         dialog: IntentDialog(
-                            LocalizedStringResource(
+                            stringLiteral: String(
+                                localized:
                                 "Confirm to apply override '\(displayName)'"
                             )
                         )
@@ -72,7 +74,8 @@ struct ApplyOverridePresetIntent: AppIntent {
             if await OverridePresetsIntentRequest().enactOverride(presetToApply) {
                 return .result(
                     dialog: IntentDialog(
-                        LocalizedStringResource(
+                        stringLiteral: String(
+                            localized:
                             "Override '\(presetToApply.name)' applied"
                         )
                     )
@@ -80,7 +83,8 @@ struct ApplyOverridePresetIntent: AppIntent {
             } else {
                 return .result(
                     dialog: IntentDialog(
-                        LocalizedStringResource(
+                        stringLiteral: String(
+                            localized:
                             "Override '\(presetToApply.name)' failed"
                         )
                     )

+ 1 - 1
Trio/Sources/Shortcuts/Override/CancelOverrideIntent.swift

@@ -16,7 +16,7 @@ struct CancelOverrideIntent: AppIntent {
     @MainActor func perform() async throws -> some ProvidesDialog {
         await OverridePresetsIntentRequest().cancelOverride()
         return .result(
-            dialog: IntentDialog(LocalizedStringResource("Override canceled"))
+            dialog: IntentDialog(stringLiteral: String(localized: "Override canceled"))
         )
     }
 }

+ 14 - 4
Trio/Sources/Shortcuts/TempPresets/ApplyTempPresetIntent.swift

@@ -10,7 +10,11 @@ struct ApplyTempPresetIntent: AppIntent {
     static var description = IntentDescription("Enable a Temporary Target")
 
     /// The temporary target preset to be applied.
-    @Parameter(title: "Preset") var preset: TempPreset?
+    @Parameter(
+        title: "Preset",
+        description: "the preset to apply",
+        requestValueDialog: IntentDialog(stringLiteral: String(localized: "Which preset to apply?"))
+    ) var preset: TempPreset?
 
     /// A boolean parameter that determines whether confirmation is required before applying the temporary target.
     @Parameter(
@@ -71,7 +75,11 @@ struct ApplyTempPresetIntent: AppIntent {
             // Request confirmation before applying if required
             if confirmBeforeApplying {
                 try await requestConfirmation(
-                    result: .result(dialog: "Confirm to apply Temporary Target '\(displayName)'")
+                    result: .result(
+                        dialog: IntentDialog(
+                            stringLiteral: String(localized: "Confirm to apply Temporary Target '\(displayName)'")
+                        )
+                    )
                 )
             }
 
@@ -79,7 +87,8 @@ struct ApplyTempPresetIntent: AppIntent {
             if await intentRequest.enactTempTarget(presetToApply) {
                 return .result(
                     dialog: IntentDialog(
-                        LocalizedStringResource(
+                        stringLiteral: String(
+                            localized:
                             "Temporary Target '\(presetToApply.name)' applied"
                         )
                     )
@@ -87,7 +96,8 @@ struct ApplyTempPresetIntent: AppIntent {
             } else {
                 return .result(
                     dialog: IntentDialog(
-                        LocalizedStringResource(
+                        stringLiteral: String(
+                            localized:
                             "Temporary Target '\(presetToApply.name)' failed"
                         )
                     )

+ 1 - 1
Trio/Sources/Shortcuts/TempPresets/CancelTempPresetIntent.swift

@@ -16,7 +16,7 @@ struct CancelTempPresetIntent: AppIntent {
     @MainActor func perform() async throws -> some ProvidesDialog {
         await TempPresetsIntentRequest().cancelTempTarget()
         return .result(
-            dialog: IntentDialog(stringLiteral: "Temporary Target canceled")
+            dialog: IntentDialog(stringLiteral: String(localized: "Temporary Target canceled"))
         )
     }
 }

+ 183 - 18
TrioTests/BolusCalculatorTests/BolusCalculatorTests.swift

@@ -84,14 +84,14 @@ import Testing
         // correctionInsulin = targetDifferenceInsulin = 2U
         // iobInsulinReduction = 1U
         // superBolusInsulin = 0U (disabled)
-        // no adjustment for fatty meals (disabled)
+        // no adjustment for reduced bolus (disabled)
         // wholeCalc = round(wholeCobInsulin + correctionInsulin + fifteenMinutesInsulin - iobInsulinReduction, 3) = 11.125U
         // insulinCalculated = round(wholeCalc × fraction, 3) = 8.9U
 
-        // Calculate expected values with proper rounding using roundBolus method from the apsManager
-        let wholeCobInsulin = apsManager.roundBolus(amount: Decimal(100) / Decimal(10)) // 10U
-        let targetDifferenceInsulin = apsManager.roundBolus(amount: Decimal(80) / Decimal(40)) // 2U
-        let fifteenMinutesInsulin = apsManager.roundBolus(amount: Decimal(5) / Decimal(40)) // 0.125U
+        // Calculate expected values
+        let wholeCobInsulin = Decimal(100) / Decimal(10) // 10U
+        let targetDifferenceInsulin = Decimal(80) / Decimal(40) // 2U
+        let fifteenMinutesInsulin = Decimal(5) / Decimal(40)
         let wholeCalc = wholeCobInsulin + targetDifferenceInsulin + fifteenMinutesInsulin - Decimal(1) // 11.125U
         let expectedInsulinCalculated = apsManager.roundBolus(amount: wholeCalc * fraction) // 8.9U
 
@@ -104,7 +104,6 @@ import Testing
             Components from CalculationResult:
             - insulinCalculated: \(result.insulinCalculated)U (expected: \(expectedInsulinCalculated)U)
             - wholeCalc: \(result.wholeCalc)U (expected: \(wholeCalc)U)
-            - correctionInsulin: \(result.correctionInsulin)U (expected: \(targetDifferenceInsulin)U)
             - iobInsulinReduction: \(result.iobInsulinReduction)U (expected: 1U)
             - superBolusInsulin: \(result.superBolusInsulin)U (expected: 0U)
             - targetDifference: \(result.targetDifference) mg/dL (expected: 80 mg/dL)
@@ -121,10 +120,6 @@ import Testing
             "Final calculated insulin amount should be \(expectedInsulinCalculated)U"
         )
         #expect(result.wholeCalc == wholeCalc, "Total calculation before fraction should be \(wholeCalc)U")
-        #expect(
-            result.correctionInsulin == targetDifferenceInsulin,
-            "Insulin for BG correction should be \(targetDifferenceInsulin)U"
-        )
         #expect(result.iobInsulinReduction == -1.0, "Absolute IOB reduction amount should be 1U, hence -1U")
         #expect(result.superBolusInsulin == 0, "Additional insulin for super bolus should be 0U")
         #expect(result.targetDifference == 80, "Difference from target BG should be 80 mg/dL")
@@ -140,7 +135,7 @@ import Testing
         #expect(result.wholeCobInsulin == wholeCobInsulin, "Insulin for total carbs should be \(wholeCobInsulin)U")
     }
 
-    @Test("Calculate insulin for fatty meal") func testFattyMealCalculation() async throws {
+    @Test("Calculate insulin for reduced bolus") func testFattyMealCalculation() async throws {
         // STEP 1: Setup test scenario
         // We need to provide a CalculationInput struct
         let carbs: Decimal = 80
@@ -185,10 +180,10 @@ import Testing
             lastLoopDate: Date()
         )
 
-        // STEP 3: Calculate insulin with fatty meal enabled
+        // STEP 3: Calculate insulin with reduced bolus enabled
         let fattyMealResult = await calculator.calculateInsulin(input: input)
 
-        // STEP 4: Calculate insulin with fatty meal disabled for comparison
+        // STEP 4: Calculate insulin with reduced bolus disabled for comparison
         let standardInput = CalculationInput(
             carbs: carbs,
             currentBG: currentBG,
@@ -213,7 +208,7 @@ import Testing
         let standardResult = await calculator.calculateInsulin(input: standardInput)
 
         // STEP 5: Verify results
-        // Fatty meal should reduce the insulin amount by the fatty meal factor (0.8)
+        // Reduced bolus should reduce the insulin amount by the reduced bolus factor (0.8)
         let expectedReduction = fattyMealFactor
         let actualReduction = Decimal(
             (Double(fattyMealResult.insulinCalculated) / Double(standardResult.insulinCalculated) * 10.0).rounded() / 10.0
@@ -222,11 +217,11 @@ import Testing
         #expect(
             actualReduction == expectedReduction,
             """
-            Fatty meal calculation incorrect
+            Reduced bolus calculation incorrect
             Expected reduction factor: \(expectedReduction)
             Actual reduction factor: \(actualReduction)
             Standard calculation: \(standardResult.insulinCalculated)U
-            Fatty meal calculation: \(fattyMealResult.insulinCalculated)U
+            Reduced bolus calculation: \(fattyMealResult.insulinCalculated)U
             """
         )
     }
@@ -491,7 +486,9 @@ import Testing
             useFattyMealCorrection: false,
             useSuperBolus: false,
             lastLoopDate: Date(),
-            minPredBG: nil
+            minPredBG: nil,
+            simulatedCOB: nil,
+            isBackdated: false
         )
 
         // Then
@@ -525,7 +522,7 @@ import Testing
         // Then
         #expect(units == expectedUnits, "Units should match settings")
         #expect(fraction == expectedFraction, "Override factor should match settings")
-        #expect(fattyMealFactor == expectedFattyMealFactor, "Fatty meal factor should match settings")
+        #expect(fattyMealFactor == expectedFattyMealFactor, "Reduced bolus factor should match settings")
         #expect(sweetMealFactor == expectedSweetMealFactor, "Sweet meal factor should match settings")
         #expect(maxCarbs == expectedMaxCarbs, "Max carbs should match settings")
 
@@ -664,6 +661,174 @@ import Testing
             fileStorage.save(originalISFValues, as: OpenAPS.Settings.insulinSensitivities)
         }
     }
+
+    @Test("Calculate insulin with backdated carbs") func testHandleBolusCalculationFunction() async throws {
+        // STEP 1: Setup test scenario
+        let currentDate = Date()
+        let backdatedCarbsDate = currentDate.addingTimeInterval(-120 * 60) // 2 hours ago
+        let carbs: Decimal = 30 // 30g of carbs, backdated 2 hour
+        let cob: Int16 = 50
+
+        // Get the COB value for the backdated carbs
+        // Use the actual APS Manager to calculate simulated COB for more realistic test
+        let determination = await apsManager.simulateDetermineBasal(
+            simulatedCarbsAmount: carbs,
+            simulatedBolusAmount: 0,
+            simulatedCarbsDate: backdatedCarbsDate
+        )
+        let simulatedCOB = determination?.cob ?? Decimal(cob)
+
+        // STEP 2: Calculate results for normal and backdated carbs
+        let resultBackdated = await calculator.handleBolusCalculation(
+            carbs: carbs,
+            useFattyMealCorrection: false,
+            useSuperBolus: false,
+            lastLoopDate: Date.now.addingTimeInterval(-5.minutes.timeInterval),
+            minPredBG: 80,
+            simulatedCOB: Int16(truncating: NSDecimalNumber(decimal: simulatedCOB)),
+            isBackdated: true
+        )
+
+        let resultNormalEntry = await calculator.handleBolusCalculation(
+            carbs: carbs,
+            useFattyMealCorrection: false,
+            useSuperBolus: false,
+            lastLoopDate: Date.now.addingTimeInterval(-5.minutes.timeInterval),
+            minPredBG: 80,
+            simulatedCOB: Int16(truncating: NSDecimalNumber(decimal: simulatedCOB)),
+            isBackdated: false
+        )
+
+        // STEP 3: Compare
+        // The backdated scenario should recommend less insulin than the current time scenario
+        #expect(
+            resultBackdated.insulinCalculated < resultNormalEntry.insulinCalculated,
+            """
+            Backdated carbs should result in lower insulin recommendation
+            Current time: \(resultNormalEntry.insulinCalculated)U
+            Backdated: \(resultBackdated.insulinCalculated)U
+            Difference: \(resultNormalEntry.insulinCalculated - resultBackdated.insulinCalculated)U
+            """
+        )
+    }
+
+    @Test("Calculate insulin with backdated carbs") func testBackdatedCarbsCalculation() async throws {
+        // STEP 1: Setup test scenario
+        let currentDate = Date()
+        let backdatedCarbsDate = currentDate.addingTimeInterval(-60 * 60) // 1 hour ago
+
+        let currentBG: Decimal = 140
+        let target: Decimal = 100
+        let isf: Decimal = 40
+        let carbRatio: Decimal = 10
+        let iob: Decimal = 0.5
+        let cob: Int16 = 10 // Existing COB before adding backdated carbs
+        let carbs: Decimal = 30 // 30g of carbs, backdated 1 hour
+
+        // Get the COB value for the backdated carbs
+        // Use the actual APS Manager to calculate simulated COB for more realistic test
+        let determination = await apsManager.simulateDetermineBasal(
+            simulatedCarbsAmount: carbs,
+            simulatedBolusAmount: 0,
+            simulatedCarbsDate: backdatedCarbsDate
+        )
+
+        // Fallback to existing COB if determination is nil
+        let simulatedCOB = determination?.cob ?? Decimal(cob)
+
+        // For comparison - same scenario but with current time carbs
+        let currentTimeInput = CalculationInput(
+            carbs: carbs, // the newly entered carbs (30g)
+            currentBG: currentBG,
+            deltaBG: 0,
+            target: target,
+            isf: isf,
+            carbRatio: carbRatio,
+            iob: iob,
+            cob: cob, // the existing cob (10g)
+            useFattyMealCorrectionFactor: false,
+            fattyMealFactor: 0.8,
+            useSuperBolus: false,
+            sweetMealFactor: 1,
+            basal: 1.0,
+            fraction: 1.0,
+            maxBolus: 10,
+            maxIOB: 15,
+            maxCOB: 120,
+            minPredBG: 80,
+            lastLoopDate: currentDate
+        )
+
+        // Backdated scenario uses the same input but simulates date in the past
+        let backdatedInput = CalculationInput(
+            carbs: 0, // as the carbs are backdated we need to set the (newly entered) carbs to 0
+            currentBG: currentBG,
+            deltaBG: 0,
+            target: target,
+            isf: isf,
+            carbRatio: carbRatio,
+            iob: iob,
+            cob: Int16(truncating: NSDecimalNumber(decimal: simulatedCOB)), // current COB we got from the simulated Determination
+            useFattyMealCorrectionFactor: false,
+            fattyMealFactor: 0.8,
+            useSuperBolus: false,
+            sweetMealFactor: 1,
+            basal: 1.0,
+            fraction: 1.0,
+            maxBolus: 10,
+            maxIOB: 15,
+            maxCOB: 120,
+            minPredBG: 80,
+            lastLoopDate: currentDate
+        )
+
+        // STEP 2: Calculate insulin for both scenarios
+        let currentTimeResult = await calculator.calculateInsulin(input: currentTimeInput)
+        let backdatedResult = await calculator.calculateInsulin(input: backdatedInput)
+
+        // STEP 3: Verify results
+
+        // In the current time scenario, we expect COB to be old COB + current carbs
+        let expectedCurrentTimeCOB = Decimal(cob) + carbs
+        #expect(
+            currentTimeResult.wholeCob == expectedCurrentTimeCOB,
+            "Current time scenario should have \(expectedCurrentTimeCOB)g COB (\(cob)g existing + \(carbs)g new)"
+        )
+
+        // For backdated scenario, COB should be less than the current time scenario
+        // because some carbs have already been absorbed
+        #expect(
+            backdatedResult.wholeCob < currentTimeResult.wholeCob,
+            """
+            Backdated scenario should have less COB than current time scenario
+            Backdated: \(backdatedResult.wholeCob)g
+            Current time: \(currentTimeResult.wholeCob)g
+            Difference: \(currentTimeResult.wholeCob - backdatedResult.wholeCob)g
+            """
+        )
+
+        // The wholeCobInsulin should reflect the difference in COB
+        #expect(
+            backdatedResult.wholeCobInsulin < currentTimeResult.wholeCobInsulin,
+            """
+            Backdated scenario should require less insulin for carbs due to partial absorption
+            Backdated insulin: \(backdatedResult.wholeCobInsulin)U
+            Current time insulin: \(currentTimeResult.wholeCobInsulin)U
+            Difference: \(currentTimeResult.wholeCobInsulin - backdatedResult.wholeCobInsulin)U
+            """
+        )
+
+        // The backdated scenario should recommend less insulin than the current time scenario
+        #expect(
+            backdatedResult.insulinCalculated < currentTimeResult.insulinCalculated,
+            """
+            Backdated carbs should result in lower insulin recommendation
+            Current time: \(currentTimeResult.insulinCalculated)U
+            Backdated: \(backdatedResult.insulinCalculated)U
+            Difference: \(currentTimeResult.insulinCalculated - backdatedResult.insulinCalculated)U
+            """
+        )
+    }
 }
 
 // Copied over from BolusCalculationManager as they are not included in the protocol definition (and I don´t want them to be included)

+ 48 - 0
TrioTests/DynamicISFEnableTests.swift

@@ -0,0 +1,48 @@
+import CoreData
+import Foundation
+import Swinject
+import Testing
+
+@testable import Trio
+
+@Suite("Dynamic ISF Enable Logic Tests", .serialized) struct DynamicISFEnableTests {
+    var coreDataStack: CoreDataStack!
+    var context: NSManagedObjectContext!
+
+    init() async throws {
+        // In-memory Core Data for tests
+        coreDataStack = try await CoreDataStack.createForTests()
+        context = coreDataStack.newTaskContext()
+    }
+
+    func testEnableLogic(percentSamples: Double) async throws -> Bool {
+        let numberOfSamples = Int(288 * 7 * percentSamples)
+        let now = Date() // internal function uses Date()
+
+        try await context.perform {
+            for index in 0 ..< numberOfSamples {
+                let timeDelta = Double(index * 5 * 60)
+                let tdd = TDDStored(context: context)
+                tdd.date = now - timeDelta
+                tdd.total = 30
+                tdd.bolus = 15
+                tdd.tempBasal = 15
+                tdd.scheduledBasal = 0
+            }
+
+            try context.save()
+        }
+
+        return try await BaseTDDStorage.hasSufficientTDD(context: context)
+    }
+
+    @Test("Confirm samples from last 7 days enables Dynamic ISF") func testPercentSamplesEnablingLogic() async throws {
+        let enabled = try await testEnableLogic(percentSamples: 0.8)
+        #expect(enabled)
+    }
+
+    @Test("Confirm insufficient samples from last 7 days disables Dynamic ISF") func testPercentSamplesDisablesLogic() async throws {
+        let enabled = try await testEnableLogic(percentSamples: 0.7)
+        #expect(!enabled)
+    }
+}

+ 2 - 2
fastlane/testflight.md

@@ -166,8 +166,8 @@ _Referring to the table below, tap on each **IDENTIFIER** that has a different *
 |:--|:--|:--|
 | Trio | XC org nightscout TEAMID trio | org.nightscout.TEAMID.trio |
 | Trio LiveActivity | - | org.nightscout.TEAMID.trio.LiveActivity |
-| Trio Watch | XC IDENTIFIER | org.nightscout.TEAMID.trio.watchkitapp |
-| Trio WatchKit Extension | XC IDENTIFIER | org.nightscout.TEAMID.trio.watchkitapp.watchkitextension |
+| Trio Watch App | XC IDENTIFIER | org.nightscout.TEAMID.trio.watchkitapp |
+| Trio Watch Complication | XC IDENTIFIER | org.nightscout.TEAMID.trio.watchkitapp.TrioWatchComplication |
 
 ## Add App Group to Bundle Identifiers
 

+ 10 - 1
oref0_source_version.txt

@@ -1,6 +1,15 @@
-oref0 branch: tcd-fixes - git version: af8f79c
+oref0 branch: dev - git version: 37896e5
 
 Last commits:
+37896e5 Merge pull request #48 from nightscout/fix-bundle-naming
+f21a187 Rename output library to trio_[name]
+c0b46d3 Merge pull request #47 from nightscout/fix-400-guard
+1591b14 Remove leftover != 400 condition
+4204b12 Remove autoISF adjustments from glucose-get-last
+2596f3f Refactor 400 glucose guard: - Remove old 400 guard - Replace with check that only disables SMBs and keeps TBR at neutral == current basal rate, if shouldProtectDueToHIGH is not null/undefined and true - Remove all other 400 guards
+2ca5e20 Change folder name to Trio from iAPS
+6ad27e9 Merge pull request #46 from nightscout/tcd
+d98e4fc Merge pull request #45 from nightscout/tcd-fixes
 af8f79c Fix typo; remove enableDynamicCR setting from profile/index.js
 ffb9374 Always add minPredBG to rT object
 814b629 Remove dynamicCR

+ 54 - 59
trio-oref/lib/determine-basal/determine-basal.js

@@ -44,15 +44,15 @@ function convert_bg(value, profile)
         return Math.round(value);
     }
 }
-function enable_smb(profile, microBolusAllowed, meal_data, bg, target_bg, high_bg, oref_variables, time) {
-    if (oref_variables.smbIsScheduledOff){
+function enable_smb(profile, microBolusAllowed, meal_data, bg, target_bg, high_bg, trio_custom_variables, time) {
+    if (trio_custom_variables.smbIsScheduledOff){
         /* Below logic is related to profile overrides which can disable SMBs or disable them for a scheduled window.
          * SMBs will be disabled from [start, end), such that if an SMB is scheduled to be disabled from 10 AM to 2 PM,
          * an SMB will not be allowed from 10:00:00 until 1:59:59.
          */
         let currentHour = new Date(time.getHours());
-        let startTime = oref_variables.start;
-        let endTime = oref_variables.end;
+        let startTime = trio_custom_variables.start;
+        let endTime = trio_custom_variables.end;
 
         if (startTime < endTime && (currentHour >= startTime && currentHour < endTime)) {
             console.error("SMB disabled: current time is in SMB disabled scheduled")
@@ -79,8 +79,8 @@ function enable_smb(profile, microBolusAllowed, meal_data, bg, target_bg, high_b
         console.error("SMB disabled due to Bolus Wizard activity in the last 6 hours.");
         return false;
     // Disable if invalid CGM reading (HIGH)
-    } else if (bg == 400) {
-            console.error("Invalid CGM (HIGH). SMBs disabled.");
+    } else if (!!trio_custom_variables.shouldProtectDueToHIGH) {
+        console.error("Invalid CGM (HIGH). SMBs disabled.");
         return false;
     }
 
@@ -142,22 +142,22 @@ function enable_smb(profile, microBolusAllowed, meal_data, bg, target_bg, high_b
 }
 
 
-var determine_basal = function determine_basal(glucose_status, currenttemp, iob_data, profile, autosens_data, meal_data, tempBasalFunctions, microBolusAllowed, reservoir_data, currentTime, pumphistory, preferences, basalprofile, oref2_variables, middleWare) {
+var determine_basal = function determine_basal(glucose_status, currenttemp, iob_data, profile, autosens_data, meal_data, tempBasalFunctions, microBolusAllowed, reservoir_data, currentTime, pumphistory, preferences, basalprofile, trio_custom_variables, middleWare) {
 
     var profileTarget = profile.min_bg;
-    var overrideTarget = oref2_variables.overrideTarget;
-    if (overrideTarget != 0 && overrideTarget != 6 && oref2_variables.useOverride && !profile.temptargetSet) {
+    var overrideTarget = trio_custom_variables.overrideTarget;
+    if (overrideTarget != 0 && overrideTarget != 6 && trio_custom_variables.useOverride && !profile.temptargetSet) {
         profileTarget = overrideTarget;
     }
-    const smbIsOff = oref2_variables.smbIsOff;
-    const advancedSettings = oref2_variables.advancedSettings;
-    const isfAndCr = oref2_variables.isfAndCr;
-    const isf = oref2_variables.isf;
-    const cr_ = oref2_variables.cr;
-    const smbMinutes = oref2_variables.smbMinutes;
-    const uamMinutes = oref2_variables.uamMinutes;
+    const smbIsOff = trio_custom_variables.smbIsOff;
+    const advancedSettings = trio_custom_variables.advancedSettings;
+    const isfAndCr = trio_custom_variables.isfAndCr;
+    const isf = trio_custom_variables.isf;
+    const cr_ = trio_custom_variables.cr;
+    const smbMinutes = trio_custom_variables.smbMinutes;
+    const uamMinutes = trio_custom_variables.uamMinutes;
     // tdd past 24 hour
-    let tdd = oref2_variables.currentTDD;
+    let tdd = trio_custom_variables.currentTDD;
     var logOutPut = "";
     var tddReason = "";
 
@@ -174,12 +174,12 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
 
 
 
-    const weightedAverage = oref2_variables.weightedAverage;
+    const weightedAverage = trio_custom_variables.weightedAverage;
     var overrideFactor = 1;
     var sensitivity = profile.sens;
     var carbRatio = profile.carb_ratio;
-    if (oref2_variables.useOverride) {
-        overrideFactor = oref2_variables.overridePercentage / 100;
+    if (trio_custom_variables.useOverride) {
+        overrideFactor = trio_custom_variables.overridePercentage / 100;
         if (isfAndCr) {
             sensitivity /= overrideFactor;
             carbRatio /= overrideFactor;
@@ -189,7 +189,7 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
         }
     }
     const weightPercentage = profile.weightPercentage;
-    const average_total_data = oref2_variables.average_total_data;
+    const average_total_data = trio_custom_variables.average_total_data;
 
     // In case the autosens.min/max limits are reversed:
     const minLimitChris = Math.min(profile.autosens_min, profile.autosens_max);
@@ -390,11 +390,11 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
     var basal = profile_current_basal;
 
     // Print Current Override factor, if any
-    if (oref2_variables.useOverride) {
-        if (oref2_variables.duration == 0) {
+    if (trio_custom_variables.useOverride) {
+        if (trio_custom_variables.duration == 0) {
             console.log("Profile Override is active. Override " + round(overrideFactor * 100, 0) + "%. Override Duration: " + "Enabled indefinitely");
         } else
-            console.log("Profile Override is active. Override " + round(overrideFactor * 100, 0) + "%. Override Expires in: " + oref2_variables.duration + " min.");
+            console.log("Profile Override is active. Override " + round(overrideFactor * 100, 0) + "%. Override Expires in: " + trio_custom_variables.duration + " min.");
     }
 
     var bgTime = new Date(glucose_status.date);
@@ -425,21 +425,18 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
     if (bg <= 10 || bg === 38 || noise >= 3) {  //Dexcom is in ??? mode or calibrating, or xDrip reports high noise
         rT.reason = "CGM is calibrating, in ??? state, or noise is high";
     }
-    var tooflat=false;
-    if (bg > 60 && glucose_status.delta == 0 && glucose_status.short_avgdelta > -1 && glucose_status.short_avgdelta < 1 && glucose_status.long_avgdelta > -1 && glucose_status.long_avgdelta < 1 && bg != 400) {
+    if (bg > 60 && glucose_status.delta == 0 && glucose_status.short_avgdelta > -1 && glucose_status.short_avgdelta < 1 && glucose_status.long_avgdelta > -1 && glucose_status.long_avgdelta < 1) {
         if (glucose_status.device == "fakecgm") {
             console.error("CGM data is unchanged (" + convert_bg(bg,profile) + "+" + convert_bg(glucose_status.delta,profile)+ ") for 5m w/ " + convert_bg(glucose_status.short_avgdelta,profile) + " mg/dL ~15m change & " + convert_bg(glucose_status.long_avgdelta,2) + " mg/dL ~45m change");
             console.error("Simulator mode detected (" + glucose_status.device + "): continuing anyway");
-        } else if (bg != 400) {
-            tooflat=true;
-        }
+        } 
     }
 
     if (minAgo > 12 || minAgo < -5) { // Dexcom data is too old, or way in the future
         rT.reason = "If current system time " + systemTime + " is correct, then BG data is too old. The last BG data was read "+minAgo+"m ago at "+bgTime;
 
         // if BG is too old/noisy, or is completely unchanging, cancel any high temps and shorten any long zero temps
-    } else if ( glucose_status.short_avgdelta === 0 && glucose_status.long_avgdelta === 0 && bg != 400 ) {
+    } else if ( glucose_status.short_avgdelta === 0 && glucose_status.long_avgdelta === 0 ) {
         if ( glucose_status.last_cal && glucose_status.last_cal < 3 ) {
             rT.reason = "CGM was just calibrated";
         } else {
@@ -447,30 +444,28 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
         }
     }
 
-    if (bg != 400) {
-        if (bg <= 10 || bg === 38 || noise >= 3 || minAgo > 12 || minAgo < -5 || ( glucose_status.short_avgdelta === 0 && glucose_status.long_avgdelta === 0 ) ) {
-            if (currenttemp.rate >= basal) { // high temp is running
-                rT.reason += ". Canceling high temp basal of " + currenttemp.rate;
-                rT.deliverAt = deliverAt;
-                rT.temp = 'absolute';
-                rT.duration = 0;
-                rT.rate = 0;
-                return rT;
-                // don't use setTempBasal(), as it has logic that allows <120% high temps to continue running
-                //return tempBasalFunctions.setTempBasal(basal, 30, profile, rT, currenttemp);
-            } else if ( currenttemp.rate === 0 && currenttemp.duration > 30 ) { //shorten long zero temps to 30m
-                rT.reason += ". Shortening " + currenttemp.duration + "m long zero temp to 30m. ";
-                rT.deliverAt = deliverAt;
-                rT.temp = 'absolute';
-                rT.duration = 30;
-                rT.rate = 0;
-                return rT;
-                // don't use setTempBasal(), as it has logic that allows long zero temps to continue running
-                //return tempBasalFunctions.setTempBasal(0, 30, profile, rT, currenttemp);
-            } else { //do nothing.
-                rT.reason += ". Temp " + currenttemp.rate + " <= current basal " + basal + "U/hr; doing nothing. ";
-                return rT;
-            }
+    if (bg <= 10 || bg === 38 || noise >= 3 || minAgo > 12 || minAgo < -5 || ( glucose_status.short_avgdelta === 0 && glucose_status.long_avgdelta === 0 ) ) {
+        if (currenttemp.rate >= basal) { // high temp is running
+            rT.reason += ". Canceling high temp basal of " + currenttemp.rate;
+            rT.deliverAt = deliverAt;
+            rT.temp = 'absolute';
+            rT.duration = 0;
+            rT.rate = 0;
+            return rT;
+            // don't use setTempBasal(), as it has logic that allows <120% high temps to continue running
+            //return tempBasalFunctions.setTempBasal(basal, 30, profile, rT, currenttemp);
+        } else if ( currenttemp.rate === 0 && currenttemp.duration > 30 ) { //shorten long zero temps to 30m
+            rT.reason += ". Shortening " + currenttemp.duration + "m long zero temp to 30m. ";
+            rT.deliverAt = deliverAt;
+            rT.temp = 'absolute';
+            rT.duration = 30;
+            rT.rate = 0;
+            return rT;
+            // don't use setTempBasal(), as it has logic that allows long zero temps to continue running
+            //return tempBasalFunctions.setTempBasal(0, 30, profile, rT, currenttemp);
+        } else { //do nothing.
+            rT.reason += ". Temp " + currenttemp.rate + " <= current basal " + basal + "U/hr; doing nothing. ";
+            return rT;
         }
     }
 
@@ -782,7 +777,7 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
             bg,
             target_bg,
             high_bg,
-            oref2_variables,
+            trio_custom_variables,
             systemTime
         );
     }
@@ -1508,11 +1503,11 @@ var maxDelta_bg_threshold;
                 uamMinutesSetting = profile.maxUAMSMBBasalMinutes;
             }
 
-            if (oref2_variables.useOverride && advancedSettings && smbMinutes !== smbMinutesSetting) {
+            if (trio_custom_variables.useOverride && advancedSettings && smbMinutes !== smbMinutesSetting) {
                 console.error("SMB Max Minutes - setting overriden from " + smbMinutesSetting + " to " + smbMinutes);
                 smbMinutesSetting = smbMinutes;
             }
-            if (oref2_variables.useOverride && advancedSettings && uamMinutes !== uamMinutesSetting) {
+            if (trio_custom_variables.useOverride && advancedSettings && uamMinutes !== uamMinutesSetting) {
                 console.error("UAM Max Minutes - setting overriden from " + uamMinutesSetting + " to " + uamMinutes);
                 uamMinutesSetting = uamMinutes;
             }
@@ -1621,8 +1616,8 @@ var maxDelta_bg_threshold;
 
         var maxSafeBasal = tempBasalFunctions.getMaxSafeBasal(profile);
 
-
-        if (bg == 400) {
+        // set neutral TBR at current basal rate because glucose is considered as requiring dosing Protect due to HIGH (400 mg/dL)
+        if (!!trio_custom_variables.shouldProtectDueToHIGH) {
             return tempBasalFunctions.setTempBasal(profile.current_basal, 30, profile, rT, currenttemp);
         }
 

+ 9 - 247
trio-oref/lib/glucose-get-last.js

@@ -1,17 +1,10 @@
 function getDateFromEntry(entry) {
-return entry.date || Date.parse(entry.display_time) || Date.parse(entry.dateString);
-}
-
-function round(value, digits)
-{
-    if (! digits) { digits = 0; }
-    var scale = Math.pow(10, digits);
-    return Math.round(value * scale) / scale;
+  return entry.date || Date.parse(entry.display_time) || Date.parse(entry.dateString);
 }
 
 var getLastGlucose = function (data) {
     data = data.filter(function(obj) {
-    return obj.glucose || obj.sgv;
+      return obj.glucose || obj.sgv;
     }).map(function prepGlucose (obj) {
         //Support the NS sgv field to avoid having to convert in a custom way
         obj.glucose = obj.glucose || obj.sgv;
@@ -51,12 +44,12 @@ var getLastGlucose = function (data) {
                 //console.error(then.glucose, minutesago, avgdelta);
             //}
             // use the average of all data points in the last 2.5m for all further "now" calculations
-            if (-2 < minutesago && minutesago < 2.5) {
+            if (-2 < minutesago && minutesago <= 2.5) {
                 now.glucose = ( now.glucose + then.glucose ) / 2;
                 now_date = ( now_date + then_date ) / 2;
                 //console.error(then.glucose, now.glucose);
             // short_deltas are calculated from everything ~5-15 minutes ago
-            } else if (2.5 < minutesago && minutesago < 17.5) {
+            } else if (2.5 < minutesago && minutesago <= 17.5) {
                 //console.error(minutesago, avgdelta);
                 short_deltas.push(avgdelta);
                 // last_deltas are calculated from everything ~5 minutes ago
@@ -73,26 +66,6 @@ var getLastGlucose = function (data) {
     var last_delta = 0;
     var short_avgdelta = 0;
     var long_avgdelta = 0;
-
-    // start autoISF by https://github.com/ga-zelle/autoISF , relevant variables and functions
-    // mod 7: append 2 variables for 5% range
-    var autoISF_duration = 0;
-    var autoISF_average = 0;
-    // mod 8: append 3 variables for deltas based on regression analysis
-    var slope05 = 0;
-    var slope15 = 0;
-    var slope40 = 0;
-    // mod 14f: append results from best fitting parabola
-    var dura_p = 0;
-    var delta_pl = 0;
-    var delta_pn = 0;
-    var r_squ = 0;
-    var bg_acceleration = 0;
-    var a_0 = 0;
-    var a_1 = 0;
-    var a_2 = 0;
-    var pp_debug = "autoISF Mod14-Debug: ";
-
     if (last_deltas.length > 0) {
         last_delta = last_deltas.reduce(function(a, b) { return a + b; }) / last_deltas.length;
     }
@@ -102,228 +75,17 @@ var getLastGlucose = function (data) {
     if (long_deltas.length > 0) {
         long_avgdelta = long_deltas.reduce(function(a, b) { return a + b; }) / long_deltas.length;
     }
-    var bw = 0.05;
-    var sumBG = now.glucose;
-    var oldavg = now.glucose;
-    var minutesdur = 0;
-    for (var i = 1; i < data.length; i++) {
-        var then = data[i];
-        var then_date = getDateFromEntry(then);
-    //  mod 7c: stop the series if there was a CGM gap greater than 13 minutes, i.e. 2 regular readings
-            if (Math.round((now_date - then_date) / (1000 * 60)) - minutesdur > 13) {
-            break;
-            }
-            if (then.glucose > oldavg*(1-bw) && then.glucose < oldavg*(1+bw)) {
-            sumBG += then.glucose;
-            oldavg = sumBG / (i+1);
-            minutesdur = Math.round((now_date - then_date) / (1000 * 60));
-            } else {
-            break;
-        }
-    }
-            autoISF_average = oldavg;
-            autoISF_duration = minutesdur;
 
-            // mod 8: calculate 3 variables for deltas based on linear regression
-            // initially just test the handling of arguments
-            var slope05 = 1.05;
-            var slope15 = 1.15;
-            var slope40 = 1.40;
-
-            // mod 8a: now do the real maths based on
-            // http://www.carl-engler-schule.de/culm/culm/culm2/th_messdaten/mdv2/auszug_ausgleichsgerade.pdf
-            var sumBG  = 0;         // y
-            var sumt   = 0;         // x
-            var sumBG2 = 0;         // y^2
-            var sumt2  = 0;         // x^2
-            var sumxy  = 0;         // x*y
-            //double a;
-            var b;                   // y = a + b * x
-            var level = 7.5;
-            var minutesL;
-            // here, longer deltas include all values from 0 up the related limit
-            for (var i = 0; i < data.length; i++) {
-                var then = data[i];
-                var then_date = getDateFromEntry(then);
-                minutesL = (now_date - then_date) / (1000 * 60);
-                // watch out: the scan goes backwards in time, so delta has wrong sign
-                if(i * sumt2 == sumt * sumt) {
-                    b = 0.0;
-                }
-                else {
-                    b = (i * sumxy - sumt * sumBG) / (i * sumt2 - sumt * sumt);
-                }
-                if (minutesL > level && level == 7.5) {
-                    slope05 = -b * 5;
-                    level = 17.5;
-                }
-                if (minutesL > level && level == 17.5) {
-                    slope15 = -b * 5;
-                    level = 42.5;
-                }
-                if (minutesL > level && level == 42.5) {
-                    slope40 = -b * 5;
-                    break;
-                }
-
-                sumt   += minutesL;
-                sumt2  += minutesL * minutesL;
-                sumBG  += then.glucose;
-                sumBG2 += then.glucose * then.glucose;
-                sumxy  += then.glucose * minutesL;
-            }
-
-            // mod 14f: calculate best parabola and determine delta by extending it 5 minutes into the future
-            // nach https://www.codeproject.com/Articles/63170/Least-Squares-Regression-for-Quadratic-Curve-Fitti
-            //
-            //  y = a2*x^2 + a1*x + a0      or
-            //  y = a*x^2  + b*x  + c       respectively
-
-            // initially just test the handling of arguments
-            var dura_p  = 0;
-            var delta_pl = 0;
-            var delta_pn = 0;
-            var bg_acceleration = 0;
-            var r_squ   = 0;
-            var best_a = 0;
-            var best_b = 0;
-            var best_c = 0;
-            var a_0 = 0;
-            var a_1 = 0;
-            var a_2 = 0;
-
-            if (data.length <= 3) {                      // last 3 points make a trivial parabola
-                dura_p  = 0;
-                delta_pl = 0;
-                delta_pn = 0;
-                bg_acceleration = 0;
-                r_squ   = 0;
-                a_0 = 0;
-                a_1 = 0;
-                a_2 = 0;
-            } else {
-                //double corrMin = 0.90;                  // go backwards until the correlation coefficient goes below
-                var sy    = 0;                        // y
-                var sx    = 0;                        // x
-                var sx2   = 0;                        // x^2
-                var sx3   = 0;                        // x^3
-                var sx4   = 0;                        // x^4
-                var sxy   = 0;                        // x*y
-                var sx2y  = 0;                        // x^2*y
-                var corrMax = 0;
-                var iframe = data[0];
-                var time_0 = getDateFromEntry(iframe);
-                var ti_last = 0;
-                //# for best numerical accurarcy time and bg must be of same order of magnitude
-                var scaleTime = 300;                  //# in 5m; values are  0, 1, 2, 3, 4, ...
-                var scaleBg   =  50;                  //# TIR range is now 1.4 - 3.6
-
-                for (var i = 0; i < data.length; i++) {
-                    var then = data[i];
-                    var then_date = getDateFromEntry(then);
-                    // skip records older than 47.5 minutes
-                    var ti = (then_date - time_0) / 1000 / scaleTime;
-                    if (-ti *scaleTime > 47 * 60) {                        // skip records older than 47.5 minutes
-                        break;
-                    } else if (ti < ti_last - 7.5 * 60 / scaleTime) {       // stop scan if a CGM gap > 7.5 minutes is detected
-                        if ( i<3) {                             // history too short for fit
-                            dura_p =  -ti_last / 60;
-                            delta_pl = 0;
-                            delta_pn = 0;
-                            bg_acceleration= 0;
-                            r_squ = 0;
-                            a_0 = 0;
-                            a_1 = 0;
-                            a_2 = 0;
-                        }
-                        break;
-                    }
-                    ti_last = ti;
-                    var bg = then.glucose/scaleBg;
-                    sx += ti;
-                    sx2 += Math.pow(ti, 2);
-                    sx3 += Math.pow(ti, 3);
-                    sx4 += Math.pow(ti, 4);
-                    sy  += bg;
-                    sxy += ti * bg;
-                    sx2y += Math.pow(ti, 2) * bg;
-                    var n = i + 1;
-                    var D  = 0;
-                    var Da = 0;
-                    var Db = 0;
-                    var Dc = 0;
-                    if (n > 3) {
-                        D  = sx4 * (sx2 * n - sx * sx) - sx3 * (sx3 * n - sx * sx2) + sx2 * (sx3 * sx - sx2 * sx2);
-                        Da = sx2y* (sx2 * n - sx * sx) - sxy * (sx3 * n - sx * sx2) + sy  * (sx3 * sx - sx2 * sx2);
-                        Db = sx4 * (sxy * n - sy * sx) - sx3 * (sx2y* n - sy * sx2) + sx2 * (sx2y* sx - sxy * sx2);
-                        Dc = sx4 * (sx2 *sy - sx *sxy) - sx3 * (sx3 *sy - sx *sx2y) + sx2 * (sx3 *sxy - sx2 * sx2y);
-                    }
-                    if (D != 0) {
-                        var a = Da / D;
-                        b = Db / D;              // b initialised in linear fit !
-                        var c = Dc / D;
-                        var y_mean = sy / n;
-                        var s_squares = 0;
-                        var s_residual_squares = 0;
-                        for (var j = 0; j <= i; j++) {
-                            var before = data[j];
-                            var before_date = getDateFromEntry(before);
-                            s_squares += Math.pow(before.glucose / scaleBg - y_mean, 2);
-                            var delta_t = (before_date - time_0) / 1000 / scaleTime;
-                            var bg_j = a * Math.pow(delta_t, 2) + b * delta_t + c;
-                            s_residual_squares += Math.pow(before.glucose / scaleBg - bg_j, 2);
-                        }
-                        var r_squ = 0.64;
-                        if (s_squares != 0) {
-                            r_squ = 1 - s_residual_squares / s_squares;
-                        }
-                        if (n > 3) {
-                            if (r_squ >= corrMax) {
-                                corrMax = r_squ;
-                                // double delta_t = (then_date - time_0) / 1000;
-                                dura_p = -ti * scaleTime / 60;            // remember we are going backwards in time
-                                var delta5Min = 5 * 60 / scaleTime;
-                                delta_pl =-scaleBg * (a * Math.pow(- delta5Min, 2) - b * delta5Min);     // 5 minute slope from last fitted bg starting from last bg, i.e. t=0
-                                delta_pn = scaleBg * (a * Math.pow( delta5Min, 2) + b * delta5Min);     // 5 minute slope to next fitted bg starting from last bg, i.e. t=0
-                                bg_acceleration = 2 * a * scaleBg;             // 2nd derivative of parabola per (5min)^2
-                                a_0 = c * scaleBg;
-                                a_1 = b * scaleBg;
-                                a_2 = a * scaleBg;
-                                //r_squ = corrMax;
-                                best_a = a * scaleBg;
-                                best_b = b * scaleBg;
-                                best_c = c * scaleBg;
-                            }
-                        }
-                    }
-                }
-                pp_debug += "coeffs a/b/c=(" + round(best_a,2) + " / " + round(best_b,2) + " / " + round(best_c,2) + "); bg date=" + time_0 + "; ";
-                pp_debug += "Parabola Fits a0/a1/a2=(" + round(a_0,2) + " / " + round(a_1,2) + " / " + round(a_2,2) + "); ";
-            }
-           pp_debug += "Slopes 05/15/40=(" + round(slope05,2) + " / " + round(slope15,2) + " / " + round(slope40,2) + "); "
     return {
-        delta: Math.round( last_delta * 10000 ) / 10000
-        , glucose: Math.round( now.glucose * 10000 ) / 10000
+        delta: Math.round( last_delta * 100 ) / 100
+        , glucose: Math.round( now.glucose * 100 ) / 100
         , noise: Math.round(now.noise)
-        , short_avgdelta: Math.round( short_avgdelta * 10000 ) / 10000
-        , long_avgdelta: Math.round( long_avgdelta * 10000 ) / 10000
-        // autoISF values to return to determineBasal.js
-        , autoISF_average: Math.round( autoISF_average * 10000) / 10000
-        , autoISF_duration: Math.round(autoISF_duration * 10000) / 10000
-        , dura_p: Math.round( dura_p * 10000) / 10000
-        , delta_pl: Math.round( delta_pl * 10000) / 10000
-        , delta_pn: Math.round( delta_pn * 10000) / 10000
-        , bg_acceleration: bg_acceleration
-        , r_squ: Math.round( corrMax * 10000) / 10000
-        , parabola_fit_a0: Math.round( a_0 * 10000) / 10000
-        , parabola_fit_a1: Math.round( a_1 * 10000) / 10000
-        , parabola_fit_a2: Math.round( a_2 * 10000) / 10000
-        , pp_debug
-        // end autoISF values
+        , short_avgdelta: Math.round( short_avgdelta * 100 ) / 100
+        , long_avgdelta: Math.round( long_avgdelta * 100 ) / 100
         , date: now_date
         , last_cal: last_cal
         , device: now.device
     };
 };
 
-module.exports = getLastGlucose;
+module.exports = getLastGlucose;

+ 1 - 1
trio-oref/oref_source_file_info.txt

@@ -1,2 +1,2 @@
 These source files are copied from https://github.com/nightscout/trio-oref, and are for information purposes only.
-The algorithm is run based on minimised files in FreeAPS/Resources/javascript/bundle.
+The algorithm is run based on minimised files in Trio/Resources/javascript/bundle.