Files
Reflect/Shared/Models/GuidedReflection.swift
Trey t 99314b8e6a Add CBT-based guided reflection questions with clinical info sheet
Replace generic journaling prompts with evidence-based therapeutic
techniques: CBT thought record for negative moods, ACT cognitive
defusion for neutral, and behavioral activation for positive. Each
question now shows a clinical step label (e.g. SITUATION, REFRAME).
Added info button linking to a new sheet explaining the techniques
with citations to Beck, Harris, and Martell/Dimidjian.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 22:39:30 -05:00

146 lines
4.5 KiB
Swift

//
// GuidedReflection.swift
// Reflect
//
// Codable model for guided reflection responses, stored as JSON in MoodEntryModel.
//
import Foundation
// MARK: - Mood Category
enum MoodCategory: String, Codable {
case positive // great, good 3 questions (Behavioral Activation)
case neutral // average 4 questions (ACT Cognitive Defusion)
case negative // bad, horrible 4 questions (CBT Thought Record)
init(from mood: Mood) {
switch mood {
case .great, .good: self = .positive
case .average: self = .neutral
case .horrible, .bad: self = .negative
default: self = .neutral
}
}
var questionCount: Int {
switch self {
case .positive: return 3
case .neutral, .negative: return 4
}
}
/// The therapeutic technique name for display purposes.
var techniqueName: String {
switch self {
case .positive: return "Behavioral Activation"
case .neutral: return "Acceptance & Commitment Therapy"
case .negative: return "Cognitive Behavioral Therapy"
}
}
/// Short CBT step labels shown above each question.
var stepLabels: [String] {
switch self {
case .negative:
return [
String(localized: "Situation"),
String(localized: "Automatic Thought"),
String(localized: "Perspective Check"),
String(localized: "Reframe"),
]
case .neutral:
return [
String(localized: "Awareness"),
String(localized: "Thought"),
String(localized: "Defusion"),
String(localized: "Values"),
]
case .positive:
return [
String(localized: "Activity"),
String(localized: "Awareness"),
String(localized: "Planning"),
]
}
}
}
// MARK: - Guided Reflection
struct GuidedReflection: Codable, Equatable {
struct Response: Codable, Equatable, Identifiable {
var id: Int // question index (0-based)
let question: String
var answer: String
}
let moodCategory: MoodCategory
var responses: [Response]
var completedAt: Date?
// MARK: - Computed Properties
var isComplete: Bool {
responses.count == moodCategory.questionCount &&
responses.allSatisfy { !$0.answer.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty }
}
var answeredCount: Int {
responses.filter { !$0.answer.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty }.count
}
var totalQuestions: Int {
moodCategory.questionCount
}
// MARK: - Factory
static func createNew(for mood: Mood) -> GuidedReflection {
let category = MoodCategory(from: mood)
let questionTexts = questions(for: category)
let responses = questionTexts.enumerated().map { index, question in
Response(id: index, question: question, answer: "")
}
return GuidedReflection(moodCategory: category, responses: responses, completedAt: nil)
}
static func questions(for category: MoodCategory) -> [String] {
switch category {
case .positive:
return [
String(localized: "guided_reflection_positive_q1"),
String(localized: "guided_reflection_positive_q2"),
String(localized: "guided_reflection_positive_q3"),
]
case .neutral:
return [
String(localized: "guided_reflection_neutral_q1"),
String(localized: "guided_reflection_neutral_q2"),
String(localized: "guided_reflection_neutral_q3"),
String(localized: "guided_reflection_neutral_q4"),
]
case .negative:
return [
String(localized: "guided_reflection_negative_q1"),
String(localized: "guided_reflection_negative_q2"),
String(localized: "guided_reflection_negative_q3"),
String(localized: "guided_reflection_negative_q4"),
]
}
}
// MARK: - JSON Helpers
func encode() -> String? {
guard let data = try? JSONEncoder().encode(self) else { return nil }
return String(data: data, encoding: .utf8)
}
static func decode(from json: String) -> GuidedReflection? {
guard let data = json.data(using: .utf8) else { return nil }
return try? JSONDecoder().decode(GuidedReflection.self, from: data)
}
}