Skip to content

Latest commit

 

History

20 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

NimPulse

NimPulse

Self-hosted Familien-Gesundheitsplattform: Apple-Health-Sync, Mehrbenutzer, Auswertung und ein KI-Assistent, der die eigenen Gesundheitsdaten erklärt — nicht diagnostiziert. Azure-basiert, .NET 8, iOS-App mit HealthKit.

Deploy to Azure Visualize

Stand

  • .NET-8-Solution mit /api/v1-Struktur
  • Web-Dashboard im selben Prozess (Blazor Server, static rendering) — Login/Registrierung, Health-Übersicht, Berichte, Benutzerverwaltung, KI-Gateway, alles auch im Browser nutzbar (siehe unten)
  • AI-Provider-Abstraktion: Claude (Sonnet 5, Anthropic API) + GPT-5.6 (Azure OpenAI Service), Standard admin-konfigurierbar über das KI-Gateway (siehe unten)
  • iOS-Grundgerüst (xcodegen, Team KQGPPH4S33, Bundle email.nimtz.nimpulse)
  • HealthKit: Autorisierungsanfrage für praktisch alle iOS-Gesundheitsdatentypen (siehe docs/HEALTHKIT.md)
  • Benutzerverwaltung: Registrierung/Login (JWT für die API, Cookie fürs Web-Dashboard — dasselbe Backend), Rollen Admin/Member, erster registrierter Account wird automatisch Admin
  • Health-Daten-Sync: iOS liest alle Quantity-/Category-HealthKit-Typen und lädt sie userbezogen hoch (SQLite, Upsert nach HealthKit-UUID)
  • Reports: Aggregation (Summe/Durchschnitt/Min/Max) pro Typ, Tag/Woche/Monat
  • KI-Gateway: Admin wählt Standard-AI-Provider/-Modell/-Keys zur Laufzeit (Settings-Screen in App und Web-Dashboard) — keine Keys mehr im 1-Click-Deploy-Formular
  • KI-Coach: fortlaufender Chat mit Gesundheitsdaten-Kontext, /coach (Web) + Chat-Screen (iOS)
  • EF Core Migrations statt EnsureCreated() (Baseline-Bootstrap für Bestandsdaten)
  • Dashboard: Tages-Score (v1-Formel), Tag-für-Tag-Navigation, Dark Mode — Web (/) + eigener iOS-Dashboard-Tab (Neubau, existierte vorher nicht)
  • PDF-Export der Reports (QuestPDF, direkt gestreamt) — Web (/reports) + iOS (Sharesheet im Dashboard)
  • Proaktive Wochenzusammenfassung: Hintergrund-Dienst generiert einmal pro Woche und Nutzer eine KI-Zusammenfassung, erscheint als Nachricht im KI-Coach
  • 1-Click-Azure-Deploy-Template (App Service + Azure Files/SQLite + Blob für Berichte)
  • Invite-Links für Familienmitglieder statt Admin-legt-direkt-an (später, falls gewünscht)

Sprachumfang: Deutsch + Englisch (bewusst kein EFIGS+NL für dieses Projekt).

Benutzerverwaltung

  • POST /api/v1/auth/register — offene Selbstregistrierung; der erste Account im System wird automatisch Admin, alle danach Member.
  • POST /api/v1/auth/login — E-Mail/Passwort, liefert ein JWT (Gültigkeit konfigurierbar über Auth:TokenLifetimeHours, Standard 30 Tage).
  • GET /api/v1/auth/me — aktueller Nutzer ([Authorize]).
  • GET/POST/DELETE /api/v1/admin/users — Admin-only: Familienmitglieder direkt anlegen (mit Initial-Passwort) statt auf Selbstregistrierung angewiesen zu sein.

Alle /api/v1/health/*- und /api/v1/ai/*-Endpoints brauchen ein Authorization: Bearer <token>-Header. Passwort-Hashing über PasswordHasher<User> (ASP.NET Core Identity Core, ohne den vollen Identity-Unterbau). Kein E-Mail-Versand/Passwort-Reset bisher — kein E-Mail-Gateway vorhanden.

Web-Dashboard

Läuft im selben Prozess/Container wie die API (Blazor Server, static server rendering — kein SignalR-Circuit, klassische Formular-Posts/Redirects reichen für dieses Admin-/Family-Scale-UI). Kein separates Hosting/Deploy nötig.

Route Zugriff Zweck
/login, /register Öffentlich Anmeldung/Erstanlage. /login leitet automatisch zu /register weiter, solange noch kein Benutzer existiert.
/ Angemeldet Dashboard — Tages-Score, Kopf-Kacheln und 7-Tage-Chart für den gewählten Tag, Tag-für-Tag-Navigation (?date=yyyy-MM-dd).
/reports Angemeldet Dieselbe Aggregation wie GET /api/v1/health/reports, als Tabelle mit Typ-/Zeitraum-Filter.
/admin Admin Benutzerliste, neue Benutzer anlegen, löschen.
/settings Admin KI-Gateway-Konfiguration (siehe unten).

Auth: Cookie fürs Web-Dashboard, Bearer/JWT für die iOS-App und andere API-Clients — beide Schemes laufen nebeneinander (Program.cs, "Smart"-Policy-Scheme wählt anhand des Authorization-Headers), teilen sich dieselben Claims/Rollen.

Dark Mode folgt standardmäßig prefers-color-scheme, der "Design"-Knopf in der Nav-Leiste überschreibt das per localStorage (kein Blazor-Interactivity nötig, reines Inline-JS in App.razor).

Dashboard: Tages-Score

GET /api/v1/health/score?date=yyyy-MM-dd (Datum optional, Default heute) — v1-Formel aus Schritten (Ziel 10.000/Tag), aktiver Energie (Ziel 500 kcal/Tag) und Ruhepuls (100 Punkte bei ≤60 bpm, linear fallend auf 50 bei 100 bpm), gewichtet 40/30/30. Fehlt ein Metrik-Typ am gewählten Tag, verteilt sich das Gewicht proportional auf die übrigen; ganz ohne Daten gibt es keinen Score statt einer erfundenen Zahl. Ausdrücklich ein transparenter Startpunkt, keine medizinische Bewertung (DailyScoreService.cs). Web (/) und iOS (Dashboard-Tab) zeigen denselben Score über denselben Endpoint. GET /api/v1/health/reports akzeptiert optional denselben date-Parameter, um den 7-Tage-Chart auf einen vergangenen Tag zu verankern statt auf "jetzt".

KI-Gateway

GET/PUT /api/v1/settings/ai (Admin-only), auch als Formular unter /settings — legt fest, welcher Provider standardmäßig antwortet (claude, azure-openai oder openai), welches Modell/Deployment, und die API-Keys selbst. Drei Provider zur Wahl: Claude (Anthropic), Azure OpenAI (eigene Azure-Subscription, deployment-name-basiert) und OpenAI direkt (eigener OpenAI-Account, freies Modell-Textfeld z. B. gpt-5 — kein Azure-Resource nötig). Alles DB-backed (SQLite, AiGatewaySettings-Tabelle) statt appsettings/Umgebungsvariablen — der 1-Click-Deploy fragt keine AI-Keys mehr ab, ein Admin setzt sie einmalig nach dem ersten Login. GET maskiert die Keys (nur hasClaudeApiKey/hasAzureOpenAiApiKey/hasOpenAiApiKey, nie der Wert selbst); ein leeres Key-Feld beim Speichern lässt den bestehenden Key unangetastet. In der iOS-App: Zahnrad-Symbol → Einstellungen (nur für Admins sichtbar).

Reports

GET /api/v1/health/reports?type=stepCount&period=day|week|month&days=30&date=yyyy-MM-dd — aggregiert Quantity-Samples in Zeit-Buckets (Anzahl, Summe, Durchschnitt, Min, Max). Dieselbe Basis trägt Tages-, Wochen- und Monatsübersichten. date ist optional und verankert das Fenster auf einen bestimmten Tag statt "die letzten N Tage ab jetzt" (für Tag-Navigation im Dashboard).

GET /api/v1/health/reports/pdf (dieselben Parameter) liefert dieselbe Aggregation als PDF (ReportPdfService, QuestPDF) — direkt gestreamt, nicht auf dem Server gespeichert. Web: "PDF exportieren"-Link auf /reports. iOS: Button im Dashboard, öffnet das System-Sharesheet. Der ARM-Template-Blob-Container reports (infra/azuredeploy.json) ist dafür vorbereitet, aber v1 nutzt ihn noch nicht — ein Later-Schritt für teilbare PDF-Links.

Lokal entwickeln

API

dotnet build
ASPNETCORE_URLS="http://0.0.0.0:5289" dotnet run --project src/NimPulse.Api

0.0.0.0 binden (nicht 127.0.0.1), sonst erreicht ein Gerät im selben WLAN (z. B. das iPhone beim Testen) den Server nicht.

AI-Keys lokal setzen, statt sie in appsettings.json einzutragen:

export Ai__Claude__ApiKey="sk-ant-..."
export Ai__AzureOpenAi__Endpoint="https://<resource>.openai.azure.com/"
export Ai__AzureOpenAi__ApiKey="..."
export Ai__AzureOpenAi__DeploymentName="<deployment-name>"

Auth__JwtSigningKey unbedingt für echte Deployments setzen — der appsettings-Default ist absichtlich ein erkennbar unsicherer Platzhalter.

iOS

cd ios
xcodegen generate
open NimPulse.xcodeproj

Nach jeder neuen .swift-Datei erneut xcodegen generate ausführen. Sources/Networking/APIConfig.swift zeigt auf die LAN-IP des Rechners, auf dem die API läuft (ipconfig getifaddr en0) — bei Netzwerkwechsel anpassen.

1-Click Deploy

Klick den Deploy to Azure-Button oben. Pflichtparameter: siteName. AI-Provider/-Keys werden nicht beim Deploy abgefragt — nach dem ersten Login unter /settings (Web) oder Einstellungen (iOS) setzen. jwtSigningKey wird automatisch pro Deployment generiert, wenn leer gelassen.

Bekannte Einschränkung: SQLite + DateTimeOffset

Der EF-Core-SQLite-Provider übersetzt Where/OrderBy/Max/Min auf DateTimeOffset-Spalten (z. B. HealthSample.StartDate) server-seitig nicht zuverlässig — teils mit NotSupportedException, teils mit InvalidOperationException: ... could not be translated, sobald ein Vergleich mit einem weiteren Prädikat kombiniert wird. Betroffene Stellen (ReportService, HealthController.GetSamples, AdminUsersController.List) filtern/sortieren deshalb bewusst erst nach ToListAsync() clientseitig. Bei neuen Queries auf StartDate/CreatedAt/SyncedAt denselben Zweischritt verwenden — direkt in der DB-Query vergleichen/sortieren bricht.

Schema-Änderungen: EF Core Migrations

Seit v0.7.0 läuft das Schema über echte dotnet ef migrations (src/NimPulse.Core/Migrations/) statt Database.EnsureCreated() + manuellem ALTER TABLE (das hatte zuvor schon einmal einen echten Vorfall verursacht: nachträglich hinzugefügte AiGatewaySettings-Spalten fehlten auf dem laufenden Azure-Deployment). Beim Start entscheidet BootstrapDatabase in Program.cs: existieren bereits Tabellen, aber keine __EFMigrationsHistory (= eine alte EnsureCreated()-DB), wird die Baseline-Migration 20260804104900_InitialBaseline als "bereits angewendet" markiert, ohne ihre CREATE TABLEs erneut auszuführen — danach läuft Database.Migrate() normal für alles Weitere. Bei einer komplett neuen DB läuft die Baseline-Migration regulär durch. Jede künftige Schema-Änderung ist ab jetzt eine echte Migration, kein manueller ALTER TABLE-Block mehr.

KI-Coach

/coach (Web) bzw. das Sprechblasen-Symbol oben links in der iOS-App — ein fortlaufender Chat pro Nutzer (ChatMessages-Tabelle), nicht nur ein zustandsloser Einzel-Request. Vor jeder Antwort baut ChatCoachService (src/NimPulse.Core/Ai/ChatCoachService.cs) automatisch einen Kontext aus den eigenen Gesundheitsdaten der letzten 7 Tage (Schritte, aktive Energie, Herzfrequenz, Ruhepuls, Körpergewicht) in den System-Prompt ein, damit der gewählte AI-Provider (Claude/Azure OpenAI/OpenAI, siehe KI-Gateway oben) über echte Trends spricht statt nur auf getippten Text zu reagieren. GET /api/v1/ai/chat/history + POST /api/v1/ai/chat bedienen iOS und andere API-Clients; Coach.razor im Web ruft denselben ChatCoachService direkt auf (keine Zwischen-HTTP-Runde). /coach ist aktuell die einzige Seite mit @rendermode InteractiveServer — alle anderen Seiten bleiben bewusst Static SSR.

Proaktive Wochenzusammenfassung: WeeklyInsightBackgroundService (src/NimPulse.Api/BackgroundServices/) tickt alle 6h, prüft pro Nutzer über GeneratedInsight (Unique-Index UserId+WeekStart), ob diese ISO-Woche schon eine Zusammenfassung existiert, und generiert sonst eine über WeeklyInsightService (denselben ChatCoachService-Gesundheitskontext, anderer Prompt). Kein E-Mail-Gateway, keine Push-Notifications nötig — die Zusammenfassung landet einfach als neue Assistenten-Nachricht in der bestehenden Chat-Historie, sichtbar beim nächsten Öffnen von /coach bzw. dem iOS-Chat. KI-Provider-Fehler (z. B. kein Key konfiguriert) lassen den Tick einfach ohne neuen Eintrag durchlaufen, statt den Hintergrund-Dienst abzuwürgen.

Projektstruktur

src/NimPulse.Api/     # ASP.NET Core Web-API (/api/v1), Auth/Admin/Health/AI-Gateway-Controller
src/NimPulse.Core/    # Domänenlogik: Users, Auth (JWT), Health (EF Core/SQLite), Ai, Settings
ios/                  # SwiftUI-App, xcodegen-verwaltet (Login, Health-Sync, Settings)
infra/azuredeploy.json # 1-Click ARM-Template
docs/                 # HealthKit-Datenumfang, weitere Notizen

About

Self-hosted family health platform — Apple Health sync, multi-user, AI insights (Claude + GPT-5.6). .NET 8, Azure, iOS/HealthKit.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages