LoopInsights: AI-powered therapy settings analysis#2405
Open
taylorpatterson-T1D wants to merge 180 commits intoLoopKit:devfrom
Open
LoopInsights: AI-powered therapy settings analysis#2405taylorpatterson-T1D wants to merge 180 commits intoLoopKit:devfrom
taylorpatterson-T1D wants to merge 180 commits intoLoopKit:devfrom
Conversation
FoodFinder adds barcode scanning, AI camera analysis, voice search, and text-based food lookup to Loop's carb entry workflow. All feature code lives in dedicated FoodFinder/ subdirectories with FoodFinder_ prefixed filenames for clean isolation and portability to other Loop forks. Integration touchpoints: ~29 lines across 3 existing files (CarbEntryView, SettingsView, FavoriteFoodDetailView). Feature is controlled by a single toggle in FoodFinder_FeatureFlags.swift. New files: 34 (11 views, 3 models, 13 services, 2 view models, 1 feature flags, 1 documentation, 3 tests)
Voice search (microphone button) now uses the AI analysis pipeline instead of USDA text search, enabling natural language food descriptions like "a medium bowl of spicy ramen and a side of gyoza". Text-typed searches continue using USDA/OpenFoodFacts as before. Changes: - SearchBar: Add mic button with voice search callback - SearchRouter: Add analyzeFoodByDescription() routing through AI providers - SearchViewModel: Add performVoiceSearch() async method - EntryPoint: Wire VoiceSearchView sheet to AI analysis pipeline
Replace the separate mic button with automatic natural language detection. When the user dictates into the search field via iOS keyboard dictation, the text is analyzed: short queries (1-3 words like "apple") use USDA, while longer descriptive phrases (4+ words like "a medium bowl of spicy ramen and a side of gyoza") automatically route to the AI analysis path. Changes: - SearchBar: Remove mic button and onVoiceSearchTapped parameter - SearchViewModel: Add isNaturalLanguageQuery() heuristic, route detected natural language through performVoiceSearch in performFoodSearch - EntryPoint: Remove voice search sheet, wire onGenerativeSearchResult callback to handleAIFoodAnalysis
The Python script created group definitions but didn't properly attach all of them to their parent groups. Fixes: - Services group → now child of Loop app root (was orphaned) - Resources group → now child of Loop app root (was orphaned) - Documentation group → now child of project root (was orphaned) - ViewModels/FoodFinder → moved from Loop root to View Models group - Tests/FoodFinder → moved from project root to LoopTests group
…, analysis history - Fix triple barcode fire by consuming scan result immediately in Combine sink - Replace AsyncImage with pre-downloaded thumbnail to avoid SwiftUI rebuild issues - Use smallest OFF thumbnail (100px) with static food icon fallback for slow servers - Add secure Keychain storage for AI provider API keys - Add analysis history tracking with FoodFinder_AnalysisRecord - Consolidate AI provider settings and remove BYOTestConfig
- Remove barcode connectivity pre-check that added 3+ seconds latency per scan - Add NSCache to ImageDownloader for thumbnail deduplication (50 items, 10MB) - Remove artificial minimumSearchDuration delay from search and error paths - Merge duplicate Combine observers into single combineLatest for AI recomputation - Decode image_thumb_url from OpenFoodFacts API for smallest available thumbnail - Wrap 369 bare print() calls in #if DEBUG across 8 FoodFinder files
…eaders File consolidations (6 files removed, 2 new files created): 1. FoodFinder_ScanResult.swift + FoodFinder_VoiceResult.swift → FoodFinder_InputResults.swift 2. FoodFinder_FavoriteDetailView.swift + FoodFinder_FavoriteEditView.swift + FoodFinder_FavoritesView.swift → FoodFinder_FavoritesHelpers.swift 3. FoodFinder_AISettingsManager.swift → absorbed into FoodFinder_AIProviderConfig.swift 4. FoodFinder_FavoritesViewModel.swift → absorbed into FoodFinder_SearchViewModel.swift Other changes: - Fix long analysis titles overflowing the screen by programmatically truncating picker row names and constraining food type to 20 chars - Improve AI prompts for menu/recipe/text image analysis - Add text-only AI analysis path in AIServiceManager - Increase AI token budget for multi-item responses - Standardize all 26 FoodFinder file headers with consistent format
- Add originalAICarbs and aiConfidencePercent fields to FoodFinder_AnalysisRecord for tracking AI estimate accuracy - Add Notification.Name.foodFinderMealLogged for real-time meal event observation - Add MealDataProvider protocol with date-range query interface and AnalysisHistoryStore conformance - Add "Last 30 days" retention option to Analysis History settings
- Add originalAICarbs and aiConfidencePercent fields to FoodFinder_AnalysisRecord for tracking AI estimate accuracy - Add Notification.Name.foodFinderMealLogged for real-time meal event observation - Add MealDataProvider protocol with date-range query interface and AnalysisHistoryStore conformance - Add "Last 30 days" retention option to Analysis History settings
- Absorption time model: conservative adjustments anchored to Loop's 3-hour default. FPU adds +0/+0.5/+1.0 hr (was +1/+2.5/+4), fiber +0/+0.25/+0.5 (was +0/+1/+2), meal size +0/+0.25/+0.5 (was +0/+1/+2). Cap reduced from 8 to 5 hours. Updated AI prompt and 3 examples. - OCR routing fix: raised menu detection threshold from 1 to 5 significant lines and always include image on menu path to prevent food photo misclassification (fixes "Unidentifiable Food Item" on food photos). - Inline "Why X hrs?" pill on Absorption Time row replaces standalone DisclosureGroup row. Purple centered pill with fixed width, expands reasoning on tap. Uses AIAbsorptionTimePickerRow when AI-generated.
Add LoopInsights feature: an AI-driven therapy settings advisor that analyzes glucose, insulin, and carb data to suggest adjustments to Carb Ratios, Insulin Sensitivity Factors, and Basal Rates. Core components: - Dashboard with therapy settings overview, pattern detection, and AI suggestions - Configurable AI provider (OpenAI, Anthropic, Gemini, Grok, self-hosted) - Data aggregation pipeline with test data fixtures from Tidepool - Suggestion lifecycle: pending → applied/dismissed with full history - AI personality settings (Supportive Coach, Clinical Expert, Dry Wit, Tough Love) - Developer mode with auto-apply and test data toggles - Secure API key storage via Keychain - Safety guardrails: max 20% change per adjustment, one setting at a time - Unit tests for models, data aggregation, and suggestion store 22 new files, 4 modified files across Views, View Models, Models, Services, Managers, Resources, and Tests.
…ng, and UI refinements - Wire real therapy settings writes via LoopInsightsSettingsWriter closure - Schedule splitting: insert new entries when AI suggests times not in user's schedule - Revert feature: restore pre-apply settings from suggestion history - Settings Score (0-100) with TIR, Safety, Stability, GMI breakdown - Clinical reasoning framework: AI now understands AID-specific patterns (corrections/day, basal/bolus ratio, time-of-day analysis, cross-setting interactions) - All three settings visible in every AI prompt for cross-setting reasoning - Pre-computed red flags injected into prompt (algorithm workload, basal % alerts) - Stale-data guard: excludes manually reverted changes from recent context - Suggestion merge: consolidates split AI responses into single cards - Pre-Fill Editor: editable proposed values before applying - Auto-applied notification banner - Debug log with Copy Full Log for troubleshooting AI behavior - Temperature forced to 0.0 for deterministic analysis
… advisor UI Add Ask LoopInsights chat with AI advisor powered by therapy context and glucose data. Background monitoring with configurable frequency and notification banners. New Trends & Insights view with Daily/Weekly/Monthly/Stats/Advisor tabs. Dark gradient styling for chat and trends views. Banner now includes Ask button to open chat directly.
…ports Add clinical goal tracking (TIR, A1C, below-range, custom) with progress bars, AI-powered 30-day pattern discovery with sick day and negative basal detection, timestamped reflection journal with mood tags, and HTML-to-PDF report generation with share sheet. Goals & Patterns accessible from the Dashboard navigation section.
… analysis Add HealthKit biometric data (heart rate, HRV, steps, sleep, active energy, weight) to the AI analysis and chat pipelines. Biometrics are read-only, independently authorized, and gracefully degrade when individual types are unavailable. New file: LoopInsights_HealthKitManager.swift Modified: Models, DataAggregator, AIAnalysis, ChatViewModel, Coordinator, FeatureFlags, SettingsView, DashboardView, pbxproj, Localizable.xcstrings
…nsights, Nightscout import - Ambulatory Glucose Profile (AGP) chart with percentile bands and median line - Clarity-style dashboard redesign: Glucose card, Time in Range 5-zone stacked bar, capsule period picker with exact Clarity colors (#C14F0C, #F0CA4C, #74A52E, #D36265, #7F0302) - Caffeine tracker with half-life decay modeling and glucose correlation - Meal insights with food response analysis and per-meal glucose impact - Nightscout data import support - Advanced analyzers for pattern detection - 5-zone TIR breakdown (Very High/High/In Range/Low/Very Low) replacing 3-zone model - Compact list section spacing for tighter dashboard layout - Chat view UI refinements
…card fixes P1: Parallel HealthKit queries via async let (6 concurrent fetches) P2: Single-pass TIR zone counting (5-zone) replacing multiple filter passes P3: Pre-fetch raw data in DataAggregator, cache for cross-component reuse P4: Binary search for glucose lookups in FoodResponseAnalyzer P5: Pre-sorted glucose samples with binary search in AdvancedAnalyzers P6: Pre-compute AGP data in ViewModel instead of SwiftUI view body P7: Static DateFormatter in LoopInsightsTimeBlock.formatTime P8: Pre-sort schedule items before dose loops, pre-sort in ViewModel P9: Pre-convert glucose to parallel arrays avoiding repeated doubleValue calls P10: Pass precomputed hourly averages to circadian profile builder Also: enhanced step/activity data in AI prompts with time-of-day breakdowns and activity-glucose correlation analysis (2h lag), and meal card layout cleanup. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…y fixes Glucose chart now operates in two modes: standard Ambulatory Glucose Profile (24-hour overlay with percentile bands) for 14-day lookback, and Glucose Profile (multi-day time series) for all other periods. Both modes include an info button explaining the visualization. HealthKit glucose data supplements Loop store for longer analysis periods. Chart data clears on period change to prevent stale labels. Additional fixes across 22 files: improved HealthKit data pipeline reliability, enhanced test data provider, refined food response analysis, and minor bug fixes in background monitor, coordinator, caffeine tracker, and goals/trends views. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…y fixes Glucose chart now operates in two modes: standard Ambulatory Glucose Profile (24-hour overlay with percentile bands) for 14-day lookback, and Glucose Profile (multi-day time series) for all other periods. Both modes include an info button explaining the visualization. HealthKit glucose data supplements Loop store for longer analysis periods. Chart data clears on period change to prevent stale labels. Additional fixes across 22 files: improved HealthKit data pipeline reliability, enhanced test data provider, refined food response analysis, and minor bug fixes in background monitor, coordinator, caffeine tracker, and goals/trends views.
Bump all body text, headers, and stat values to full white for readability on dark backgrounds. Replace .toolbarColorScheme (iOS 16+) with manual toolbar principal title for compatibility. Restore UINavigationBarAppearance approach in ChatView. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
3e26483 to
1d61573
Compare
The 20-char limit was truncating food names (e.g. "Baked pastry with f…") which made them unreadable in LoopInsights Meal Insights. The RowEmojiTextField maxLength only restricts keyboard input, so longer programmatic values are safe. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Added steps for creating and using test data in developer mode for demos and feature functionality testing.
1d61573 to
c03d6c0
Compare
…ivity CoreMotion-based activity detection that automatically applies user-selected override presets when walking or running is detected. 7 new files, 2 modified. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Safety guardrails (3 layers of defense against dangerous therapy values): - LoopInsights_SafetyGuardrails struct with clinical bounds mirroring LoopKit (CR 4-28 recommended/2-150 absolute, ISF 16-400/10-500, Basal 0.05-10/0.05-30) - Post-parse validation rejects values outside absolute bounds and >25% changes - AI prompt now includes absolute bounds with clamping instructions - confirmApply() hard-blocks absolute violations - applyEditedSuggestion() validates edited blocks against absolute bounds - autoApplySuggestion() blocks anything outside recommended range (stricter) - SuggestionDetailView shows orange warning banner and color-coded values - DashboardView alert changes to "Safety Warning" with specific warnings - Suggestion cards show orange triangle badge for guardrail warnings Data-first AI prompts (all 4 AI interaction points): - Chat, Analysis, Goals/Patterns, and Trends prompts now require every answer to cite the user's specific numbers — no generic diabetes advice - Added "#1 RULE" blocks emphasizing real data over textbook answers Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…volume date wrapping
…ide generationConfig)
…ase and breaks non-thinking models
…rompts - Success criteria: AI now tells users what to watch for after applying a change - Outcome evaluation: AI evaluates its own past suggestions before making new ones - Toned down system prompt: corrections and basal/bolus ratio are context, not red flags - Tough Love personality rewritten to be direct without being cruel - Removed editorial RED FLAG/ELEVATED annotations from user prompt data - Added DataLayer disclosure to README (enabled by default, how to disable)
# Conflicts: # Loop/Services/LoopInsights/LoopInsights_AIAnalysis.swift
Gemini 2.5 Flash/Pro thinking models return thinking in parts[0] and the actual response in subsequent parts. Our key path read parts[0], getting thinking text instead of the JSON response. Now extracts the last non-thought part. Also bumps maxTokens from 2048 to 8192 to prevent response truncation with expanded success criteria fields.
Existing UserDefaults configs have maxTokens=2048 which truncates Gemini responses. Override to 8192 minimum on load, same pattern as temperature enforcement.
Gemini thinking models consume output tokens for thinking, causing JSON responses to be truncated. Added repairTruncatedJSON to close unclosed brackets/braces. Bumped maxTokens to 16384 to leave room for thinking overhead.
Gemini 2.5+ thinking models consume output tokens for chain-of-thought, leaving too few for the actual JSON response. Set thinkingBudget: 0 to disable thinking since we only need structured JSON output.
The thinkingConfig field causes HTTP 400 on endpoints that don't support it. Rely on high maxTokens (16384) + JSON repair to handle thinking models that consume output tokens for chain-of-thought.
Replaces brittle key path extraction with a 3-strategy approach: 1. Standard key path (works for normal models) 2. Deep recursive search (finds text in any response structure) 3. Full response stringify (last resort, lets JSON parser handle it) Handles thinking models, API version differences, and unexpected response formats without needing model-specific knowledge.
Thinking models can use 30K+ tokens for thinking. 65536 leaves plenty of room for both thinking and the actual response. Deep search now collects ALL text strings and picks the one containing JSON markers or the longest one, instead of returning the first one found.
When thinking models (Gemini 2.5) spend all output tokens on reasoning and produce no actual response, detect the API envelope and show a clear error suggesting non-thinking alternatives.
When a thinking model returns an empty response, show a clear error with an (i) "View Supported Models" link that opens a sheet listing all confirmed working models by provider, as of March 2026.
Two bugs fixed: 1. Removed 2048 maxTokens cap that overrode the configured 65536 default, causing thinking models to exhaust output tokens on reasoning. 2. Added Gemini-specific non-thought extraction that skips thought:true parts and throws emptyThinkingResponse when no real content exists.
Two bugs fixed: 1. Removed 2048 maxTokens cap that overrode the configured 65536 default, causing thinking models to exhaust output tokens on reasoning. 2. Added Gemini-specific non-thought extraction that skips thought:true parts and throws emptyThinkingResponse when no real content exists.
65536 gave thinking models too much runway, causing very long response times. 8192 provides enough headroom for thinking (~6K) plus the JSON response (~1.5K) without excessive delays.
65536 gave thinking models too much runway, causing very long response times. 8192 provides enough headroom for thinking (~6K) plus the JSON response (~1.5K) without excessive delays.
Switch API base URL from .org to .net (503 outage). Rewrite relevance scoring with tiered name matching, category awareness, and fetch-50-return-15 strategy so generic queries like "banana" return the whole food first instead of branded chips/snacks.
# Conflicts: # Loop/Localizable.xcstrings
computeGlucoseStats was writing to self.lastGlucoseForAGP from within
an async let concurrent context, causing heap corruption on newer Swift
runtimes ("freed pointer was not the last allocation"). Returns AGP
data via struct instead of mutating self during concurrent execution.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The three therapy settings that cause a lot of confusion are Carb Rates, Insulin Sensitivity and Basal Rates. They all interoperate with each other and setting the right baselines for these often involves fasting and precise documentation. I wanted AI to solve this problem for us.
The Problem We're Solving
Managing diabetes with a closed-loop system like Loop requires ongoing tuning of three core therapy settings — Carb Ratio (CR), Insulin Sensitivity Factor (ISF), and Basal Rate (BR). Today, users must manually interpret CGM data, identify patterns (dawn phenomenon, post-meal spikes, overnight lows), and translate those patterns into schedule adjustments. This process is:
Endocrinologists do this work in 15-minute appointments a few times per year. LoopInsights brings that analytical capability to every Loop user, on demand, using their own data.
New Feature Impact
LoopInsights is an AI-powered therapy settings advisor that lives inside Loop's Settings screen. It reads glucose, insulin, and carbohydrate data (read-only), sends aggregated statistics to a user-configured AI provider, and returns specific, time-block-level setting adjustment suggestions with clinical reasoning.
What It Does
User-Configurable Settings
gpt-4o,claude-sonnet-4-5-20250929)Safety Considerations
Requirements
Requirements to Run LoopInsights
schedule
Everything LoopInsights uses (HealthKit, Keychain, Networking, Push Notifications, CommonCrypto) is already in Loop's existing entitlements and linked frameworks.
Feature Architecture
Loop Integration Footprint
LoopInsights has been optimized for performance and designed with a minimal file footprint in mind. Only 3 existing Loop files are modified, with minimal changes:
SettingsView.swiftSettingsViewModel.swiftloopInsightsDataStoresclosure propertyStatusTableViewController.swiftTotal integration footprint: ~28 lines across 3 files.
New Files (43 files)
All LoopInsights code uses the
LoopInsights_prefix and lives inLoopInsights/subdirectories. No LoopKit framework modifications required.Data Flow
Screenshots
Video Demo
Check out how it works here: https://youtu.be/P-xfHt0AVTM
🎬 A walkthrough video demonstrating the full LoopInsights workflow — from enabling the feature, configuring an AI provider, running analysis, reviewing suggestions, and applying a change — will be linked here.
Test Data for Development & Demos
LoopInsights includes a Test Data mode for development, demos, and evaluation without needing live CGM data. A Python script pulls real data from a Tidepool account and converts it to fixture files that LoopInsights can load.
Generating Test Data from Tidepool
Prerequisites:
pip3 install requestsThe script authenticates with the Tidepool API, pulls CGM glucose, insulin doses, carb entries, and pump settings, converts them to Loop's native JSON format, and saves four fixture files:
tidepool_glucose_samples.jsontidepool_dose_entries.jsontidepool_carb_entries.jsontidepool_therapy_settings.json(optional)Loading Test Data
Fixtures can be placed in two locations (checked in order):
Documents/LoopInsights/on device (no rebuild; drag-drop via Finder on physical device)Resources/LoopInsights/TestData/(requires rebuild)Enabling Test Data Mode
Creating Fixtures Manually
If you don't have a Tidepool account, you can create JSON fixtures by hand. See
Documentation/LoopInsights/LoopInsights_README.mdfor the exact JSON schema for each fixture file (glucose samples, dose entries, carb entries, and therapy settings).Beta Test Plan
Prerequisites
feat/LoopInsightsbranch running on a physical devicePhase 1: Basic Functionality
Phase 2: Glucose Charts
Phase 3: Suggestion Workflow
Phase 4: Secondary Features
Phase 5: Edge Cases & Safety
Phase 6: Performance
Requesting review by @marionbarker based on availability.