Add period prediction accuracy feedback (P4.5 complete)
All checks were successful
Deploy / deploy (push) Successful in 1m36s
All checks were successful
Deploy / deploy (push) Successful in 1m36s
Implements visual feedback for cycle prediction accuracy in ICS calendar feeds: - Add predictedDate field to PeriodLog type for tracking predicted vs actual dates - POST /api/cycle/period now calculates and stores predictedDate based on previous lastPeriodDate + cycleLength, returns daysEarly/daysLate in response - ICS feed generates "(Predicted)" events when actual period start differs from predicted, with descriptions like "period arrived 2 days early" - Calendar route fetches period logs and passes them to ICS generator This creates an accuracy feedback loop helping users understand their cycle variability over time per calendar.md spec. 807 tests passing across 43 test files. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
// ABOUTME: Creates subscribable calendar with phase blocks and warnings.
|
||||
import { createEvents, type EventAttributes } from "ics";
|
||||
|
||||
import type { PeriodLog } from "@/types";
|
||||
import { getCycleDay, getPhase, PHASE_CONFIGS } from "./cycle";
|
||||
|
||||
const PHASE_EMOJIS: Record<string, string> = {
|
||||
@@ -16,10 +17,16 @@ interface IcsGeneratorOptions {
|
||||
lastPeriodDate: Date;
|
||||
cycleLength: number;
|
||||
monthsAhead?: number;
|
||||
periodLogs?: PeriodLog[];
|
||||
}
|
||||
|
||||
export function generateIcsFeed(options: IcsGeneratorOptions): string {
|
||||
const { lastPeriodDate, cycleLength, monthsAhead = 3 } = options;
|
||||
const {
|
||||
lastPeriodDate,
|
||||
cycleLength,
|
||||
monthsAhead = 3,
|
||||
periodLogs = [],
|
||||
} = options;
|
||||
const events: EventAttributes[] = [];
|
||||
|
||||
const endDate = new Date();
|
||||
@@ -68,6 +75,43 @@ export function generateIcsFeed(options: IcsGeneratorOptions): string {
|
||||
// Close final phase
|
||||
events.push(createPhaseEvent(currentPhase, phaseStartDate, currentDate));
|
||||
|
||||
// Add predicted vs actual events from period logs
|
||||
for (const log of periodLogs) {
|
||||
if (!log.predictedDate) {
|
||||
continue; // Skip logs without prediction (first log)
|
||||
}
|
||||
|
||||
const actual = new Date(log.startDate);
|
||||
const predicted = new Date(log.predictedDate);
|
||||
|
||||
// Calculate difference in days
|
||||
const diffMs = predicted.getTime() - actual.getTime();
|
||||
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
||||
|
||||
// Only show predicted event if dates differ
|
||||
if (diffDays === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Generate predicted menstrual event
|
||||
const predictedEnd = new Date(predicted);
|
||||
predictedEnd.setDate(predicted.getDate() + 3); // Menstrual phase is 3 days
|
||||
|
||||
let description: string;
|
||||
if (diffDays > 0) {
|
||||
description = `Original prediction - period arrived ${diffDays} days early`;
|
||||
} else {
|
||||
description = `Original prediction - period arrived ${Math.abs(diffDays)} days late`;
|
||||
}
|
||||
|
||||
events.push({
|
||||
start: dateToArray(predicted),
|
||||
end: dateToArray(predictedEnd),
|
||||
title: "🔵 MENSTRUAL (Predicted)",
|
||||
description,
|
||||
});
|
||||
}
|
||||
|
||||
const { value, error } = createEvents(events);
|
||||
if (error) {
|
||||
throw new Error(`ICS generation error: ${error}`);
|
||||
|
||||
Reference in New Issue
Block a user