Architecture¶
C4 Level 1 — System Context¶
C4Context
title Nutri-E — System Context
Person(user, "User", "Tracks daily supplement intake,\nscans food labels, monitors nutrients")
System(app, "Nutri-E iOS App", "Supplement tracker with AI-powered\nfood analysis, schedule management,\nand nutrient insights. Offline-first,\nCore Data backed.")
System_Ext(appstore, "Apple App Store", "App distribution and\nin-app purchase processing")
System_Ext(revenuecat, "RevenueCat", "Subscription lifecycle management.\nHandles purchase validation,\nentitlement tracking, and\nwebhook delivery.")
System_Ext(dsld_nih, "NIH DSLD API", "Dietary Supplement Label Database\n(api.ods.od.nih.gov/dsld/v9).\nSearchable database of supplement\nproduct labels and nutrient data.")
System_Ext(openai, "OpenAI API", "GPT-4 Vision for food and\nsupplement label analysis.\nParses photos into structured\nnutrient data.")
System_Ext(healthkit, "Apple HealthKit", "On-device health data store.\nNutri-E reads/writes nutrient\nand supplement consumption data.")
System_Ext(apns, "Apple Push Notification\nService (APNs)", "Delivers supplement reminders\nand dosage schedule notifications\nto the device.")
System(cf_workers, "Cloudflare Workers\n(Backend)", "Serverless API layer hosted\non Cloudflare's edge network.\nHandles auth, rate limiting,\ncaching, and webhooks.")
Rel(user, app, "Logs supplements,\nscans labels, views\nnutrient history")
Rel(app, healthkit, "Reads/writes nutrient\nconsumption data", "HealthKit API")
Rel(app, appstore, "Purchases premium\nsubscription", "StoreKit 2")
Rel(appstore, revenuecat, "Sends purchase\nserver notifications", "Apple S2S")
Rel(revenuecat, cf_workers, "Sends subscription\nlifecycle events", "HTTPS webhook")
Rel(app, cf_workers, "DSLD search,\nOpenAI image analysis,\nsubscription verification", "HTTPS + device auth")
Rel(cf_workers, dsld_nih, "Proxies supplement\nsearch queries", "HTTPS")
Rel(cf_workers, openai, "Sends image + prompt\nfor AI analysis", "HTTPS")
Rel(app, apns, "Schedules supplement\nreminder notifications", "UserNotifications")
C4 Level 2 — Containers¶
C4Container
title Nutri-E — Containers
Person(user, "User", "iOS device running Nutri-E")
Container_Boundary(ios, "iOS Application") {
Container(swiftui_views, "SwiftUI Views", "SwiftUI", "UI layer — supplement list,\nschedule view, nutrient charts,\ncamera scanner, settings")
Container(services, "Service Layer", "Swift singletons", "Business logic:\nSupplementService,\nDSLDSearchService,\nOpenAIService,\nStoreKitManager,\nDeviceAuthService,\nConsumptionPredictionService")
ContainerDb(coredata, "Core Data", "SQLite via Core Data", "Local persistent store.\nSupplements, doses, schedules,\nnutrient history. Offline-first.")
Container(healthkit_client, "HealthKit Client", "HealthKit framework", "Reads/writes nutrient\nconsumption to on-device\nHealthKit store")
Container(notifications, "Notification Scheduler", "UserNotifications", "Schedules local push\nnotifications for supplement\nreminders and dose schedules")
}
Container_Boundary(cf, "Cloudflare Workers (Edge)") {
Container(dsld_worker, "DSLD Worker\n(nutrie-dsld-worker)", "Cloudflare Worker / JS", "Proxies NIH DSLD supplement\nsearch API. Device auth,\nrate limiting (KV), 30-day\nresponse cache (KV).")
Container(openai_worker, "OpenAI Worker\n(nutrie-openai-worker)", "Cloudflare Worker / JS", "Proxies OpenAI GPT-4 Vision.\nDevice auth, rate limiting,\n7-day cache for supplement\nlabels (skips cache for food).")
Container(apple_worker, "Apple Webhook Worker\n(nutrie-apple-webhook-worker)", "Cloudflare Worker / JS", "Receives RevenueCat webhooks.\nVerifies REVENUECAT_WEBHOOK_SECRET.\nUpdates SUBSCRIPTIONS KV.\nArchives cancellations to\nSUBSCRIPTIONS_DELETED KV.")
Container(stats_worker, "Stats RSS Worker\n(nutrie-stats-rss)", "Cloudflare Worker / JS", "Internal monitoring feed.\nExposes /subscriptions and\n/devices RSS endpoints.\nReads SUBSCRIPTIONS KV and\nDEVICE_ACTIVITY KV.")
ContainerDb(kv_subscriptions, "SUBSCRIPTIONS KV", "Cloudflare KV", "Active subscription records\nkeyed by device ID.\n{plan, expiresAt, updatedAt}")
ContainerDb(kv_rate_limit, "RATE_LIMIT KV", "Cloudflare KV", "Per-device daily quota\ntracking for DSLD and\nOpenAI workers.")
ContainerDb(kv_cache, "CACHE_KV", "Cloudflare KV", "Response cache for DSLD\n(30 days) and OpenAI\nsupplement labels (7 days).")
ContainerDb(kv_device_activity, "DEVICE_ACTIVITY KV", "Cloudflare KV", "Unique device timestamps\nfor DAU tracking (stats\nworker reads)")
ContainerDb(kv_deleted, "SUBSCRIPTIONS_DELETED KV", "Cloudflare KV", "Archived cancelled/expired\nsubscriptions for audit trail\nand customer support.")
}
System_Ext(dsld_nih, "NIH DSLD API", "api.ods.od.nih.gov/dsld/v9")
System_Ext(openai_api, "OpenAI API", "GPT-4 Vision")
System_Ext(revenuecat, "RevenueCat", "Subscription webhooks")
System_Ext(healthkit, "Apple HealthKit", "On-device health store")
Rel(user, swiftui_views, "Interacts with", "Touch / SwiftUI")
Rel(swiftui_views, services, "Calls", "Swift")
Rel(services, coredata, "Reads/writes\nsupplement data", "Core Data API")
Rel(services, healthkit_client, "Logs nutrient\nconsumption", "HealthKit API")
Rel(services, notifications, "Schedules\nreminders", "UserNotifications")
Rel(services, dsld_worker, "Searches supplements\n+ device signature", "HTTPS GET")
Rel(services, openai_worker, "Sends image + prompt\n+ device signature", "HTTPS POST")
Rel(services, kv_subscriptions, "Verifies subscription\nentitlement", "via DSLD/OpenAI workers")
Rel(revenuecat, apple_worker, "POST subscription\nlifecycle events", "HTTPS webhook")
Rel(apple_worker, kv_subscriptions, "Writes/deletes\nsubscription record", "KV put/delete")
Rel(apple_worker, kv_deleted, "Archives cancelled\nsubscriptions", "KV put")
Rel(dsld_worker, kv_rate_limit, "Checks + increments\ndaily quota", "KV get/put")
Rel(dsld_worker, kv_cache, "Read/write\nresponse cache", "KV get/put")
Rel(dsld_worker, dsld_nih, "Proxies search\nrequest", "HTTPS")
Rel(openai_worker, kv_rate_limit, "Checks + increments\ndaily quota", "KV get/put")
Rel(openai_worker, kv_cache, "Caches supplement\nanalysis results", "KV get/put")
Rel(openai_worker, openai_api, "Sends image\nanalysis request", "HTTPS")
Rel(stats_worker, kv_subscriptions, "Counts active\nsubscriptions", "KV list")
Rel(stats_worker, kv_device_activity, "Counts unique\ndevices (24h)", "KV list")
Pattern: MVVM with Service Layer¶
| Layer | Implementation | Responsibility |
|---|---|---|
| Views | SwiftUI Views | UI rendering, user input |
| Services | Singletons (.shared) |
Business logic, API calls |
| Persistence | Core Data | Local storage |
| Models | Core Data entities, enums | Data structures |
SOLID Principles¶
| Principle | Application |
|---|---|
| Single Responsibility | Each service has ONE job (DSLDSearchService vs DosageFormService) |
| Open/Closed | Services extended via new enum cases (UnitType) |
| Interface Segregation | Focused service APIs |
| Dependency Inversion | Views depend on services via .shared |
Other Principles¶
- KISS: Minimal abstraction; direct Core Data access via
@FetchRequest - DRY: Shared formatters (
SupplementDisplayFormatter,LocaleNumberFormatter) - YAGNI: MVP-first; features added only when needed
- Explicit UI: All functionality through visible controls (no hidden gestures)
- Offline-First: Core Data for local persistence; works without network
Core Data Setup¶
PersistenceControllermanages the stack as a singletonmanagedObjectContextinjected via SwiftUI environment- Model name:
Nutri_E(references.xcdatamodeld)
Service Access Pattern¶
DosageFormService.shared.getDosageForm(for: "Capsule")
DSLDSearchService.shared.searchSupplements(query: "vitamin d")
SupplementDisplayFormatter.shared.formatDoseDisplay(for: supplement)
Core Data Access Pattern¶
@Environment(\.managedObjectContext) private var viewContext
@FetchRequest(sortDescriptors: [...]) private var supplements: FetchedResults<Supplement>
DataManager.shared.save()
C4 Level 3 — Components (iOS App)¶
Zooms into the iOS Application container from Level 2, showing the internal components and their dependencies.
C4Component
title Nutri-E — iOS App Components
Person(user, "User", "iPhone user tracking supplements")
Container_Boundary(ios, "iOS Application (no.invotek.nutrie)") {
%% ── View layer ───────────────────────────────────────────────
Component(supplementsView, "SupplementsView", "SwiftUI", "Main supplement list. Tap to log dose, view detail, edit.")
Component(scheduleView, "ScheduleManagementView / ScheduleProfileView", "SwiftUI", "Manage schedule profiles and per-supplement time slots.")
Component(nutrientsView, "NutrientsView / NutrientDetailView", "SwiftUI", "Aggregated nutrient totals with RDA progress bars. Drill into individual nutrients.")
Component(scannerView, "ScanLabelView / BarcodeScannerView / CameraCaptureView", "SwiftUI + AVFoundation", "Camera scanner for barcode lookup and AI label photo capture.")
Component(foodView, "FoodView", "SwiftUI", "Log food items. Uses OpenAIService for photo-based nutrient extraction.")
Component(todayView, "TodayView", "SwiftUI", "Daily intake dashboard — doses taken, schedule adherence, streak.")
Component(analyticsView, "AnalyticsView / SymptomTrendsView", "SwiftUI + Swift Charts", "Charts for dose history, nutrient trends, symptom correlations.")
Component(settingsView, "SettingsView / MoreView", "SwiftUI", "Preferences, HealthKit, notifications, subscription, export, feedback.")
%% ── Core services ────────────────────────────────────────────
Component(supplementService, "SupplementService", "Swift / ObservableObject", "CRUD for Supplement and SupplementDose. Records doses, calculates units per dose, manages stock count.")
Component(scheduleService, "ScheduleService", "Swift / ObservableObject", "CRUD for ScheduleProfile and SupplementSchedule. Activates profiles, resolves today's schedule, fires notification reschedule on change.")
Component(nutrientAgg, "NutrientAggregationService", "Swift", "Aggregates nutrients across all supplements for the current day. Applies unit normalization before summation.")
Component(consumptionPred, "ConsumptionPredictionService", "Swift / ObservableObject", "Predicts remaining doses needed today based on schedule. Feeds stock-depletion estimates.")
Component(streakService, "StreakTrackingService", "Swift", "Counts consecutive schedule-completion days from Core Data dose history.")
Component(symptomService, "SymptomTrackingService / SymptomCorrelationService", "Swift / ObservableObject", "Logs daily symptom ratings. Correlates symptom trends with supplement changes.")
Component(stockService, "StockManagementService", "Swift", "Monitors stockCount thresholds. Triggers low-stock notifications via NotificationService.")
%% ── External integrations ───────────────────────────────────
Component(dsldService, "DSLDSearchService", "Swift / ObservableObject", "Searches NIH supplement database via Cloudflare DSLD Worker. Caches results in memory. Converts DSLDSupplement to local Supplement.")
Component(openaiService, "OpenAIService", "Swift / ObservableObject", "Sends food/label photos to Cloudflare OpenAI Worker (GPT-4 Vision). Parses AISupplementInfo and FoodData from JSON response. Includes retry logic (3 attempts, exp. backoff).")
Component(hkService, "HealthKitService", "Swift / HealthKit", "Requests auth for dietary write types (via HealthKitNutrientMapping) and health metric read types (steps, resting HR, energy, sleep). Writes nutrient samples after each dose log.")
Component(notifService, "NotificationService", "Swift / UNUserNotificationCenter", "Schedules repeating daily reminders per supplement schedule slot. Fires low-stock alerts, morning summary, evening recap, and weekly digest notifications.")
Component(subscriptionMgr, "SubscriptionManager", "Swift / RevenueCat SDK", "Configures RevenueCat, logs in with DeviceAuthService deviceId. Publishes currentTier (free/basic/pro/premium/ultimate). Guards premium features.")
Component(deviceAuth, "DeviceAuthService", "Swift", "Generates and persists a stable device UUID used as RevenueCat app_user_id and HMAC key for Cloudflare Worker request signing.")
Component(aiUsageTracker, "AIUsageTracker", "Swift / UserDefaults", "Tracks daily OpenAI scan usage. Enforces per-tier scan limits.")
%% ── Data / persistence ──────────────────────────────────────
Component(coreDataStack, "PersistenceController", "Core Data / SQLite", "NSPersistentContainer singleton. viewContext for reads, performBackgroundTask for writes. No CloudKit sync.")
Component(exportService, "DataExportService", "Swift", "Serialises all Core Data entities to ExportData JSON for backup/restore. Supports full round-trip import.")
Component(pdfService, "PDFReportService", "Swift / UIKit", "Generates multi-page US Letter PDF practitioner report from supplement and dose history.")
%% ── Nutrient utilities ──────────────────────────────────────
Component(rdaService, "NutrientRDAService / UserRDAService", "Swift", "Loads RDA database from bundled JSON. Computes personalised DRI targets from UserProfile (age, sex, health goals).")
Component(unitConverter, "NutrientUnitConverter / NutrientNormalizationService", "Swift", "Converts between mg, mcg, IU, g across nutrient names. Runs once at startup via NutrientUnitMigrationService to normalise stored values.")
}
%% ── External systems ─────────────────────────────────────────
System_Ext(dsldWorker, "Cloudflare DSLD Worker", "Proxies NIH DSLD API with device auth + cache")
System_Ext(openaiWorker, "Cloudflare OpenAI Worker", "Proxies GPT-4 Vision with device auth + rate limit")
System_Ext(healthkit, "Apple HealthKit", "On-device health data store")
System_Ext(notifications, "Apple Notifications (APNs)", "Local notification delivery")
System_Ext(revenuecat, "RevenueCat", "Subscription entitlement management")
%% ── Relationships ────────────────────────────────────────────
Rel(user, supplementsView, "Logs doses, manages supplements")
Rel(user, scannerView, "Scans barcodes and label photos")
Rel(user, scheduleView, "Configures daily reminder schedules")
Rel(user, nutrientsView, "Reviews nutrient totals vs RDA")
Rel(user, foodView, "Logs food intake via photo or manual entry")
Rel(user, analyticsView, "Views trends and symptom correlations")
Rel(user, settingsView, "Manages preferences and subscription")
Rel(supplementsView, supplementService, "recordDose(), fetchSupplements()")
Rel(supplementsView, dsldService, "Search DSLD on add supplement")
Rel(supplementsView, openaiService, "Scan label photo → AISupplementInfo")
Rel(scannerView, dsldService, "Barcode lookup")
Rel(scannerView, openaiService, "Photo → AISupplementInfo or FoodData")
Rel(foodView, openaiService, "Photo → FoodData list")
Rel(scheduleView, scheduleService, "CRUD schedule profiles")
Rel(nutrientsView, nutrientAgg, "Today's aggregated totals")
Rel(nutrientsView, rdaService, "Per-nutrient RDA targets")
Rel(analyticsView, symptomService, "Symptom logs and correlations")
Rel(settingsView, subscriptionMgr, "Purchase / restore subscription")
Rel(settingsView, exportService, "Export JSON backup")
Rel(settingsView, pdfService, "Generate practitioner PDF")
Rel(settingsView, hkService, "Toggle HealthKit sync")
Rel(settingsView, notifService, "Configure notification permission")
Rel(supplementService, coreDataStack, "NSManagedObjectContext read/write")
Rel(scheduleService, coreDataStack, "NSManagedObjectContext read/write")
Rel(scheduleService, notifService, "Reschedule reminders on profile change")
Rel(supplementService, hkService, "Write nutrient sample on dose log")
Rel(supplementService, stockService, "Decrement stockCount on dose")
Rel(stockService, notifService, "Trigger low-stock alert")
Rel(aiUsageTracker, openaiService, "Enforce daily scan quota")
Rel(subscriptionMgr, deviceAuth, "Get deviceId for RevenueCat login")
Rel(openaiService, deviceAuth, "Sign worker requests with HMAC")
Rel(dsldService, deviceAuth, "Sign worker requests with HMAC")
Rel(dsldService, dsldWorker, "Supplement search", "HTTPS + device auth header")
Rel(openaiService, openaiWorker, "Image analysis", "HTTPS + device auth header, base64 image")
Rel(hkService, healthkit, "Write dietary nutrient samples", "HealthKit API")
Rel(notifService, notifications, "Schedule local notifications", "UNUserNotificationCenter")
Rel(subscriptionMgr, revenuecat, "Purchase + entitlement check", "RevenueCat SDK")
Key Services¶
| Service | Purpose |
|---|---|
SupplementService |
Core supplement operations |
ConsumptionPredictionService |
Schedule-aware usage prediction |
DSLDSearchService |
NIH supplement database integration |
OpenAIService |
GPT-4 Vision for food/supplement analysis |
DeviceAuthService |
Device authentication for API security |
StoreKitManager |
StoreKit 2 in-app purchases |
StarredNutrientsService |
Nutrient favorites via UserDefaults |
NutrientUnitMigrationService |
Startup data migrations |
Unit System¶
Unified unit system in UnitType.swift:
- Weight: mg, g, mcg (with auto-conversion thresholds)
- Volume: mL, L
- Energy: kcal, cal (always use
.kcalfor calories display) - Count: tablets, capsules, softgels, gummies, vials, units
- International Units: IU (converted to standard units)
When to Add Abstraction¶
DO when: logic repeated 3+ times, external dependency needs isolation, complex business rules.
DON'T when: "we might need it later", single use case, no concrete variation.