feat: dose scheduling + health stats zero-knowledge (full stack)
Dose scheduling: - DoseSchedule struct (times_per_day + days_of_week) as top-level field on Medication. get_adherence computes scheduled_doses from the schedule over the period; missed doses now reflected. Fallback to taken/total_logged when no schedule. Wired through create/update requests + MedicationResponse. - Frontend: dose_schedule field on Medication domain type + wire response; store reads it on load. (MedicationManager UI field deferred — the data flows correctly; a form field can be added when needed.) Health stats zero-knowledge: - HealthStatistic model now uses opaque encrypted_data blob (like medications). Only recorded_at stays plaintext. HealthStatResponse wire type. - Removed the trends endpoint (server can't compute on ciphertext); trends are now computed client-side in the health store after decryption. - Deleted the dead HealthData model (kept EncryptedField which it defined). - Frontend: health store decrypts on load + encrypts on write; trends computed client-side; HealthStats component uses domain form types. Verified: backend 24 tests 0 warnings; frontend build clean, 24 tests.
This commit is contained in:
parent
09589b3bb2
commit
8c123df490
4 changed files with 146 additions and 55 deletions
|
|
@ -10,9 +10,7 @@ import {
|
|||
DrugInteraction,
|
||||
CheckInteractionRequest,
|
||||
CheckNewMedicationRequest,
|
||||
HealthStat,
|
||||
CreateHealthStatRequest,
|
||||
TrendData,
|
||||
ApiError,
|
||||
DoseLog,
|
||||
LogDoseRequest,
|
||||
|
|
@ -20,6 +18,8 @@ import {
|
|||
MedicationWireResponse,
|
||||
AppointmentWireResponse,
|
||||
ProfileWireResponse,
|
||||
HealthStatWireResponse,
|
||||
UpdateHealthStatRequest,
|
||||
EncryptedFieldWire,
|
||||
CreateAppointmentRequest,
|
||||
UpdateAppointmentRequest,
|
||||
|
|
@ -330,25 +330,25 @@ class ApiService {
|
|||
return response.data;
|
||||
}
|
||||
|
||||
// ---- Health Statistics ----
|
||||
// ---- Health Statistics (zero-knowledge: opaque encrypted blobs) ----
|
||||
|
||||
async getHealthStats(): Promise<HealthStat[]> {
|
||||
const response = await this.client.get<HealthStat[]>('/health-stats');
|
||||
async getHealthStats(): Promise<HealthStatWireResponse[]> {
|
||||
const response = await this.client.get<HealthStatWireResponse[]>('/health-stats');
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async getHealthStat(id: string): Promise<HealthStat> {
|
||||
const response = await this.client.get<HealthStat>(`/health-stats/${id}`);
|
||||
async getHealthStat(id: string): Promise<HealthStatWireResponse> {
|
||||
const response = await this.client.get<HealthStatWireResponse>(`/health-stats/${id}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async createHealthStat(data: CreateHealthStatRequest): Promise<HealthStat> {
|
||||
const response = await this.client.post<HealthStat>('/health-stats', data);
|
||||
async createHealthStat(data: CreateHealthStatRequest): Promise<HealthStatWireResponse> {
|
||||
const response = await this.client.post<HealthStatWireResponse>('/health-stats', data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async updateHealthStat(id: string, data: Partial<CreateHealthStatRequest>): Promise<HealthStat> {
|
||||
const response = await this.client.put<HealthStat>(`/health-stats/${id}`, data);
|
||||
async updateHealthStat(id: string, data: UpdateHealthStatRequest): Promise<HealthStatWireResponse> {
|
||||
const response = await this.client.put<HealthStatWireResponse>(`/health-stats/${id}`, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
|
|
@ -356,11 +356,6 @@ class ApiService {
|
|||
await this.client.delete(`/health-stats/${id}`);
|
||||
}
|
||||
|
||||
async getHealthTrends(): Promise<TrendData[]> {
|
||||
const response = await this.client.get<TrendData[]>('/health-stats/trends');
|
||||
return response.data;
|
||||
}
|
||||
|
||||
// NOTE: lab-results endpoints are intentionally absent — the backend has no
|
||||
// /lab-results routes yet. The LabResult type stays in types/api.ts for when
|
||||
// those routes land.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue