Add AI mood report feature with PDF export for therapist sharing
Adds a Reports tab to the Insights view with date range selection, two report types (Quick Summary / Detailed), Foundation Models AI generation with batched concurrent processing, and clinical PDF export via WKWebView HTML rendering. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
146
Shared/Models/AIReport.swift
Normal file
146
Shared/Models/AIReport.swift
Normal file
@@ -0,0 +1,146 @@
|
||||
//
|
||||
// AIReport.swift
|
||||
// Reflect
|
||||
//
|
||||
// Data models for AI-generated mood reports with PDF export.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import FoundationModels
|
||||
|
||||
// MARK: - Report Type
|
||||
|
||||
enum ReportType: String, CaseIterable {
|
||||
case quickSummary = "Quick Summary"
|
||||
case detailed = "Detailed Report"
|
||||
|
||||
var icon: String {
|
||||
switch self {
|
||||
case .quickSummary: return "doc.text"
|
||||
case .detailed: return "doc.text.magnifyingglass"
|
||||
}
|
||||
}
|
||||
|
||||
var description: String {
|
||||
switch self {
|
||||
case .quickSummary: return "AI overview of your mood patterns"
|
||||
case .detailed: return "Week-by-week analysis with full data"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Report Entry (decoupled from SwiftData)
|
||||
|
||||
struct ReportEntry {
|
||||
let date: Date
|
||||
let mood: Mood
|
||||
let notes: String?
|
||||
let weather: WeatherData?
|
||||
|
||||
init(from model: MoodEntryModel) {
|
||||
self.date = model.forDate
|
||||
self.mood = model.mood
|
||||
self.notes = model.notes
|
||||
self.weather = model.weatherJSON.flatMap { WeatherData.decode(from: $0) }
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Report Overview Stats
|
||||
|
||||
struct ReportOverviewStats {
|
||||
let totalEntries: Int
|
||||
let averageMood: Double
|
||||
let moodDistribution: [Mood: Int]
|
||||
let trend: String
|
||||
let dateRange: String
|
||||
}
|
||||
|
||||
// MARK: - Report Week
|
||||
|
||||
struct ReportWeek: Identifiable {
|
||||
let id = UUID()
|
||||
let weekNumber: Int
|
||||
let startDate: Date
|
||||
let endDate: Date
|
||||
let entries: [ReportEntry]
|
||||
var aiSummary: String?
|
||||
}
|
||||
|
||||
// MARK: - Report Month Summary
|
||||
|
||||
struct ReportMonthSummary: Identifiable {
|
||||
let id = UUID()
|
||||
let month: Int
|
||||
let year: Int
|
||||
let entryCount: Int
|
||||
let averageMood: Double
|
||||
var aiSummary: String?
|
||||
|
||||
var title: String {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = "MMMM yyyy"
|
||||
var components = DateComponents()
|
||||
components.month = month
|
||||
components.year = year
|
||||
let date = Calendar.current.date(from: components) ?? Date()
|
||||
return formatter.string(from: date)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Report Year Summary
|
||||
|
||||
struct ReportYearSummary: Identifiable {
|
||||
let id = UUID()
|
||||
let year: Int
|
||||
let entryCount: Int
|
||||
let averageMood: Double
|
||||
var aiSummary: String?
|
||||
}
|
||||
|
||||
// MARK: - Assembled Report
|
||||
|
||||
struct MoodReport {
|
||||
let reportType: ReportType
|
||||
let generatedAt: Date
|
||||
let overview: ReportOverviewStats
|
||||
let weeks: [ReportWeek]
|
||||
let monthlySummaries: [ReportMonthSummary]
|
||||
let yearlySummaries: [ReportYearSummary]
|
||||
var quickSummary: String?
|
||||
}
|
||||
|
||||
// MARK: - @Generable AI Response Structs
|
||||
|
||||
@available(iOS 26, *)
|
||||
@Generable
|
||||
struct AIWeeklySummary: Equatable {
|
||||
@Guide(description: "A clinical, factual summary of the user's mood patterns for this week. Use third-person perspective (e.g., 'The individual'). 2-3 sentences covering mood trends, notable patterns, and any significant changes. Neutral, professional tone suitable for a therapist to read.")
|
||||
var summary: String
|
||||
}
|
||||
|
||||
@available(iOS 26, *)
|
||||
@Generable
|
||||
struct AIMonthSummary: Equatable {
|
||||
@Guide(description: "A clinical, factual summary of the user's mood patterns for this month. Use third-person perspective. 3-4 sentences covering overall mood trend, week-over-week changes, and notable patterns. Professional tone suitable for clinical review.")
|
||||
var summary: String
|
||||
}
|
||||
|
||||
@available(iOS 26, *)
|
||||
@Generable
|
||||
struct AIYearSummary: Equatable {
|
||||
@Guide(description: "A clinical, factual summary of the user's mood patterns for this year. Use third-person perspective. 3-5 sentences covering seasonal patterns, long-term trends, and significant periods. Professional tone suitable for clinical review.")
|
||||
var summary: String
|
||||
}
|
||||
|
||||
@available(iOS 26, *)
|
||||
@Generable
|
||||
struct AIQuickSummaryResponse: Equatable {
|
||||
@Guide(description: "A comprehensive clinical summary of the user's mood data for the selected period. Use third-person perspective (e.g., 'The individual'). 4-6 sentences covering: overall mood patterns, notable trends, day-of-week patterns, and any areas of concern or improvement. Factual, neutral, professional tone suitable for sharing with a therapist.")
|
||||
var summary: String
|
||||
|
||||
@Guide(description: "2-3 key clinical observations as brief bullet points. Each should be a single factual sentence about a pattern or trend observed in the data.")
|
||||
var keyObservations: [String]
|
||||
|
||||
@Guide(description: "1-2 brief, neutral recommendations based on observed patterns. Frame as observations rather than prescriptions (e.g., 'Mood data suggests weekday routines may benefit from...').")
|
||||
var recommendations: [String]
|
||||
}
|
||||
Reference in New Issue
Block a user