ADR-0014: DailyReflection Data Model — Structured Answers + Optional Notes
DateFebruary 8, 2026
CategoryData Architecture
Context
- Phase 3 introduces a Daily Reflection feature with prompts and a
DailyReflection entity.
- Roadmap open question: Structured vs free-text reflection data.
- Phase 4/5 (Weekly Synthesis / Morning Briefing) will later package reflection history into an LLM context window to generate weekly guidance and advice.
- We need a data shape that is:
- Easy to render in UI (prompt → answer)
- Aggregatable (week-level rollups, trends)
- Stable for long-term exports and LLM packaging
- Flexible enough for users to write freely on low-energy days
Constraints:
- Local-first, offline-only (no server-side schema evolution helpers).
- Room-backed persistence; migrations must be manageable.
Decision
We will store Daily Reflections as answers to a living set of questions, plus an optional free-form notes field.
- Each reflection consists of:
- A
DailyReflection record representing the day/session.
- A list of
DailyReflectionAnswer rows: one per question.
- Optional
notes for unstructured, user-written text not tied to a specific question.
We will treat the reflection as ultimately renderable into free text for weekly overview/LLM packaging. The question/answer structure exists to support a consistent UI and gentle guidance during daily entry.
Scope boundaries:
- This ADR defines the logical data model and invariants.
- Exact Room entity names/column types are deferred to the schema migration ADRs.
Suggested Persistence Shape (logical)
daily_reflections
id
day_key (see ADR-0017)
status (DRAFT | SUBMITTED)
notes (nullable)
created_at, updated_at
reflection_questions (see ADR-0015)
id
question_text (unique)
is_default
is_active
created_at, updated_at
daily_reflection_answers
id
daily_reflection_id (FK)
question_id (FK → reflection_questions)
position (int, stable ordering within the active question list)
question_text_snapshot (TEXT; optional but recommended)
answer_text (nullable/empty allowed)
created_at, updated_at
Invariants:
- A reflection may have zero answers while in DRAFT.
- On SUBMITTED, answers are expected to exist for each active question at the time the reflection is created, but may be empty (skipping is allowed).
question_text_snapshot (if stored) is written at answer creation time to preserve readability even if the question later changes.
Rationale
- LLM packaging friendliness: question-answer pairs can be rendered deterministically into a free-text block (question text + answer + ordering), while still allowing weekly overview to treat the result as plain text.
- Aggregation and analytics (future): structured answers enable simple counts and heuristics (e.g., common “learned” themes) without NLP.
- User expressiveness:
notes provides a “just write” escape hatch without turning the whole model into unstructured blobs.
- Question evolution without heavy machinery: questions can be edited/curated over time; storing
question_text_snapshot preserves historical readability without needing versioning.
- Room pragmatism: a parent row + child rows maps naturally to Room relations; avoids complex JSON migrations while still being flexible.
Consequences
Positive effects:
- Clean UI mapping: render prompts in order and bind answers directly.
- Weekly synthesis can operate on a stable data contract: ordered list of (prompt, answer) plus optional notes.
- Export/backup can serialize reflections consistently and reproducibly.
Known downsides:
- More tables and joins than a single free-text column.
- If questions are edited in-place and
question_text_snapshot is not stored, historical answers may become harder to interpret.
Follow-up work:
- Define the schema migration(s) for these entities.
- Define a canonical “LLM packet” rendering format, e.g.:
- Day header (local date + zone)
- Bulleted questions + answers
- Notes section
Alternatives Considered
- Free-text only (single TEXT column) — rejected: hard to aggregate, weaker for deterministic LLM packaging, and prompt semantics are lost.
- Structured only (no notes) — rejected: reduces expressiveness and increases friction on low-energy days.
- Single JSON column for answers — deferred: simpler schema, but migrations and partial updates become harder; can be revisited if relational overhead becomes painful.
Notes
- Skipping is explicitly allowed to preserve the principle “Reflection is lightweight and repeatable”.
- “Structured” here means “question-indexed”, not rigid validation.