Requirements — metrics & status tracking v1
At a glance
- What it is: a metrics store + append-only status-snapshot ledger — automatic health capture, honest tracking history for both instances, generated dashboards
- Status: Draft v1 — current focus
- Depends on: North Star (kernel/instance model), vault-indexer, project-dashboard spec (ledger design adopted here), 2026 PRD (signal list only), retrieval v2 Phase 0 (tags table pattern; no hard ordering dependency)
- Ceiling-bound: Foundation work — phases sized for ≤5 hrs/week, each independently shippable, auto-pause honored
- Serves: Job B, under Principles 1–2. Scope is tracking + sensemaking; the capacity layer is designed (Capacity layer) but explicitly out of v1, gated on the data this doc produces (S8)
1. Background and problem statement
Two tracking gaps, one substrate:
- The life instance has no runtime. The 2026 PRD defined states, ceilings, and a burnout early-warning rule (“any two signals → downshift”) — but nothing captured the signals, nothing evaluated the rule, nothing showed state. Per the North Star its modules dissolve into standard kernel entities (projects and routines); what v1 owes the life instance is the same thing it owes the work instance: honest tracking with history.
- OrionUpOS status has no history. Project status lives scattered in frontmatter with an inconsistent vocabulary; a Dataview board would show “now” but nothing survives moves/deletes. The paused project-dashboard spec already designed the fix (append-only snapshot ledger keyed by stable
project-id); it lands here.
Both reduce to the same primitives on the existing SQLite substrate: time-series metrics and append-only status snapshots, with generated visual output.
2. Goals and non-goals
Goals
- G1. Metrics store fed primarily by automatic sources (Apple Health export first), secondarily by ≤10-second manual capture.
- G2. Status snapshot ledger for entities in both instances — work projects, life projects and routines — keyed by stable IDs, surviving file churn.
- G3. Generated visual dashboards — heatmaps, trends, status board — written into the vault as artifacts; never hand-maintained.
- G4. The PRD’s burnout rule evaluated automatically, surfaced as an explicit warning (“2 signals active → downshift”), with the evaluation logged.
- G5. Zero new required infrastructure: SQLite + Python + existing indexer patterns. Grafana/Postgres is a Layer-2 upgrade path, not a dependency.
Non-goals
- NG1. No redesign of LaquaOS or OrionUpOS — vocabularies, states, and rules come from their existing docs verbatim.
- NG2. No enforcement actuators in v1 (calendar blocking, app locking). v1 detects and tells; teeth are a v2 question pending North Star O-A.
- NG3. No cloud services, no third-party analytics. Health data flows device → owned store only.
- NG4. No general-purpose quantified-self platform. The schema serves the PRD signal list + chosen habits (North Star O-C); nothing speculative.
- NG5. No changes to retrieval v2 scope; shared DB, separate concerns.
3. Current state
vault-index.db:filestable with proven migration pattern (open_db), content-hash change detection,content_mtimesemantics;tagstable arriving via retrieval v2 Phase 0.- Project-dashboard spec (paused, in-vault):
project-idfrontmatter convention, OrionUpOS 6-status vocabulary (inbox|active|review|blocked|paused|done),project_snapshotstable sketch, weekly cadence hook. Verified finding: 20+ inconsistent status values across 71 files — vocabulary lock is a prerequisite, not a nice-to-have. - 2026 PRD (historical source, per North Star): supplies the burnout signal list verbatim (sleep < 6.5 h × 5 d, craving spikes, irritability/numbness, perfectionist rigidity, “I’ll rest after this”). Its former modules map to kernel entities: certs and the hike → projects; weight, savings → routines.
- Apple Health: captures sleep/activity/weight today; exportable on schedule via Health Auto Export (JSON/CSV to an endpoint or file drop) — no wearable assumptions beyond what’s already worn.
4. Functional requirements
FR-1x — Stores
- FR-10 — New tables in
vault-index.dbvia the existingopen_dbmigration pattern:
metrics(date TEXT, key TEXT, value REAL, source TEXT, recorded_at REAL, PRIMARY KEY(date, key, source))— one row per metric per day per source; re-imports upsert idempotently.status_snapshots(snapshot_date TEXT, entity_type TEXT, entity_id TEXT, status TEXT, detail TEXT, PRIMARY KEY(snapshot_date, entity_type, entity_id))—entity_type ∈ {project, routine}(instance recorded indetail);detailis a small JSON blob (priority, next-action, and — reserved for the capacity layer — cost/peak…). Append-only by convention: rows are never updated after their snapshot date passes.- FR-11 —
entity_idis the stable slug in frontmatter (project-idper the dashboard spec; the same convention extends to routines and life-instance entities, e.g.cert-cka,hike-20mi). Never a file path.- FR-12 — A
sourcesregistry table records each ingest source and its last-seen timestamp, sostatscan report “sleep data last arrived 2026-08-29” — silent pipeline death must be visible (Principle: honest signals).
FR-2x — Automatic ingest
- FR-20 —
ingest_health.pyconsumes Health Auto Export output (JSON and CSV) from a watched drop folder (synced via existing means — iCloud/Syncthing — no new service): maps configured HK types → metric keys (sleep_hours,weight_lbs,active_energy,steps, …), upserts, records source freshness.- FR-21 — Mapping is config-driven (
metrics.yaml): HK identifier → key, unit conversion, aggregation (e.g. sleep samples → nightly total). Adding a metric is config, not code.- FR-22 — Ingest is idempotent and fail-silent in the house style: a malformed file is skipped with a logged warning, never a crash; partial files never half-import (transaction per file).
- FR-23 — Screen-time/doom-scroll (the Vice metric) is included iff exportable from the device without manual daily action; otherwise it moves to quick capture and the doc’s mapping table says so honestly. (Spike in Phase 2 — Screen Time has no clean export API; do not burn ceiling-hours forcing it.)
FR-3x — Quick capture
- FR-30 —
metrics.py log <key> <value> [--date]CLI for desk-side capture, writingsource='manual'.- FR-31 — Subjective signals are boolean metrics with fixed keys from the PRD:
signal_craving,signal_irritability,signal_rigidity,signal_rest_after_this, plushabit_<name>booleans once O-C names them.- FR-32 — Phone-side capture in v1 is the thinnest owned path: an Apple Shortcut appending a line (
date,key,value) to a file in the synced drop folder, ingested by the same pipeline as FR-20. ≤10 s, works offline, zero new services. (The mobile app inherits this as a proper feature later; the pipeline doesn’t change.)- FR-33 — Missing data is absence, never fabricated zeros — an unlogged day renders as a gap. No streak-shaming semantics anywhere (PRD: no moral framing).
FR-4x — Status snapshots
- FR-40 —
snapshotsubcommand (extendsvault_indexer.py, per the dashboard spec): readstype: project-indexnotes, validates status against the locked 6-value vocabulary, writes project rows for today. Invalid statuses are reported (file, value) and skipped — the ledger stays clean while the vault converges.- FR-41 — Life-instance entities are not special: former PRD modules become project/routine notes carrying the same frontmatter convention (
type, stable id, locked status vocabulary), snapshotted by the same command. No separate module registry exists — one mechanism, two instances.- FR-42 — Snapshot cadence: manual invocation + a weekly hook in the existing work-cadence routine. No daemon.
- FR-43 — Transitions are derivable (state at date X vs. X-1); a
transitionsview answers “when did CKS go active” and “how long has X been blocked” without a separate log-on-write mechanism.
FR-5x — Evaluation & alerts
- FR-50 —
evaluatesubcommand implements the PRD rule verbatim: sleep signal =sleep_hours < 6.5on ≥5 of the last 5 days with data; each subjective signal = logged true within its window (default 7 days). ≥2 active signals → downshift warning.- FR-51 — Evaluation output: console + a generated
warningblock at the top of the metrics dashboard note + an entry inmetrics(key='downshift_warning')so warnings themselves have history.- FR-52 — Evaluation runs at ingest time and snapshot time — no separate schedule to maintain. Insufficient data states are reported as such (“sleep: 2/5 days of data”), never treated as “all clear.”
- FR-53 — Capacity mechanics (declaration, admission, eviction — Capacity layer) are out of v1 by decision, gated on S8: budgets and costs get calibrated from the tracking data this doc produces. The schema reserves room (
detailJSON keys, metric namespace) so activation later is additive, not a migration.
FR-6x — Visual output
- FR-60 —
reportsubcommand generates dashboard notes into the vault (same generated-artifact pattern as daily summaries):
Dashboards/metrics.md— per-metric GitHub-style yearly heatmap (SVG, embedded or linked), 90-day trend sparklines, current burnout-signal status, source freshness.Dashboards/status-board.md— current board grouped by status (the dashboard spec’s Phase 1 view, rendered statically from the ledger rather than live Dataview — one mechanism for both OSes) + a transitions/aging section (“blocked 3 weeks”).- FR-61 — Heatmaps are self-contained SVG (no plugin dependency, renders in Obsidian preview and the future mobile app alike). Color scales configurable; gaps visibly distinct from zeros (FR-33).
- FR-62 — Regeneration is idempotent and full-overwrite — dashboards are outputs, never edited by hand (a hand-edit is overwritten without mercy; the doc header says so).
- FR-63 — Grafana path (Layer 2, optional, not v1): a
--export postgrestarget mirroringmetrics+status_snapshotsto the homelab Postgres, at which point Grafana panels and true push alerts replace/augment generated notes. Schema above is designed to mirror cleanly (flat, date-keyed, no vault-path dependencies).
5. Non-functional requirements
Non-functional requirements
- NFR-1 Boring to operate: after setup, the steady-state weekly manual load is ≤ a status touch-up on active entity notes during the existing weekly review + subjective-signal taps. Everything else is automatic or piggybacks existing routines.
- NFR-2 Fail-silent, loudly visible: no component may block or crash another (house style), but silent failure is forbidden — source freshness (FR-12) and insufficient-data states (FR-52) make death visible.
- NFR-3 Privacy: health data exists only in the synced drop folder and owned stores; dashboards live in the vault (already private). The Postgres export target is same-infrastructure only.
- NFR-4 Portability & test style: stdlib + existing deps only (SVG generated by hand-rolled template, no matplotlib requirement); suite runs with no Ollama, no
mcp, no device — fixtures are files + in-memory SQLite, matching the existing suites.- NFR-5 Longevity: stores are dump-able to CSV with one command (
export --csv); Job C’s “survives tool death” applies to metrics too.
6. Interface specification
vault_indexer.py snapshot [--date] # project + routine entities → status_snapshots
metrics.py ingest [--drop-dir] # health export + quick-capture files → metrics
metrics.py log <key> <value> [--date] # manual capture
metrics.py evaluate [--window 7] # burnout rule → warning states
metrics.py report [--out Dashboards/] # regenerate dashboard notes (runs evaluate)
metrics.py export --csv <dir> | --postgres <dsn> # longevity / Layer-2 path
Config: metrics.yaml (HK mappings, metric keys, habit list once O-C lands, heatmap options). Env: SECOND_BRAIN_DB reused; METRICS_DROP_DIR for ingest.
7. Implementation phases
Each ≤ a few ceiling-respecting sessions; each shippable; stop-anywhere is a feature.
- Phase 0 — conventions (mostly editing, not code): lock status vocabulary; add
project-id+type: project-indexto the 11 project notes (dashboard spec Phase 1’s authoring half, statuses drafted for review); translate the PRD’s former modules into project/routine notes with the standard frontmatter (the dissolution made concrete); fix the tracked-metric list (resolves O-C — write the real list). Exit: the vault says true things for tools to read. - Phase 1 — stores + snapshot: FR-10/11/12 migrations;
snapshotacross both instances’ entities; transitions view. Exit: a week of snapshots queryable; “how long blocked” answerable. - Phase 2 — ingest: Health Auto Export → drop folder →
metrics; source freshness; Screen Time spike (FR-23) with an honest yes/no. Exit: sleep + weight + activity flowing hands-free (S2’s pipeline). - Phase 3 — capture + evaluate:
logCLI; Shortcut → drop-file path; burnout evaluation with insufficient-data honesty. Exit: a drill fires the downshift warning end-to-end (S4). - Phase 4 — dashboards: heatmap SVGs, metrics + status-board notes, warning surfacing. Exit: the 21-day autopilot test starts counting (S2).
- Phase 5 — docs + longevity: README (new subcommands, mapping guide), CSV export, CHANGELOG. Postgres/Grafana export deferred until the autopilot test passes — earn Layer 2.
8. Testing requirements
Testing requirements
- T-1 Migrations: fresh DB and existing-DB upgrade both yield the schema; re-running is a no-op.
- T-2 Ingest: fixture Health exports (JSON + CSV, incl. malformed and duplicate files) → correct upserts, idempotent re-runs, per-file transactionality, freshness updates.
- T-3 Aggregation: sleep-sample fixtures spanning midnight → correct nightly totals (the classic bug, pinned).
- T-4 Quick-capture file format: valid/garbage lines, out-of-order dates, timezone-naive handling documented and tested.
- T-5 Snapshot: vocabulary validation (junk statuses reported + skipped), stable-id keying survives a path move fixture, routine-entity parsing, idempotent same-day re-run.
- T-6 Transitions: state-change detection and “days in status” against a constructed history.
- T-7 Evaluate: signal windows, the ≥2 rule, insufficient-data states, warning-history rows; a fixture reproducing “5 short nights + one craving” fires.
- T-8 Report: SVG well-formed, gaps ≠ zeros visually (distinct fill asserted), full-overwrite semantics, deterministic output (golden files).
- T-9 Export: CSV round-trips back to identical tables.
9. Acceptance criteria
Acceptance criteria
- AC-1. Autopilot: 21 consecutive days of sleep/weight/activity in the store and on the heatmap with zero manual pipeline touches (North Star S2).
- AC-2. Ten-second capture: a subjective signal logged from the phone lock screen in ≤10 s, visible on the dashboard after next ingest (S3’s mechanism).
- AC-3. The rule has an actuator: a drill (or reality) produces the downshift warning with the contributing signals named (S4).
- AC-4. History survives churn: rename/move a project folder — its snapshot history remains intact and continuous under its
project-id(S5).- AC-5. Honest gaps: stop the health export for 3 days — dashboard shows gaps and stale-source warning, not zeros or silence.
- AC-6. Suite passes with no device, no Ollama, no
mcp, no network.
10. Risks and open questions
Risks and open questions
- R-1 Health Auto Export reliability/format drift → pin fixtures to observed formats; freshness alarm (FR-12) is the detection net; worst case is manual re-export, never data loss.
- R-2 Screen-time export may be infeasible without manual action (FR-23 spike) → accept quick-capture fallback or drop the vice metric from v1; do not build a scraping science project.
- R-3 Snapshot honesty depends on frontmatter being updated → mitigated by Phase 0 (11 notes fixed once), the weekly routine hook, and staleness surfacing on the board; if statuses still rot, that’s O-A evidence (“surfacing” vs “teeth”), not a tooling bug to code around yet.
- R-4 Scope creep toward quantified-self maximalism → NG4 + the
metrics.yamlallowlist are the fence; new keys require naming their Job B purpose in the config comment.- Q-1 North Star O-A (where the PRD died) — v1 hedges capture and surfacing; if the answer is “teeth,” the capacity layer’s eviction step gains actuators (calendar blocks, state-flipping) when it activates.
- Q-2 Status granularity: is a weekly review touch enough for life-instance entities, or do transitions need same-day capture? Decide after a month of real use.
- Q-3 Does the mobile app’s capture screen (mobile doc, parked) supersede the Shortcut path? Only if the Shortcut proves insufficient — don’t build the fancier thing first.