feat(api): weekly review analytics and career floor evaluation #77

Merged
yuris merged 1 commit from feat/issue-30-review into main 2026-09-23 04:27:53 +00:00
Owner

Motivation

Phase 4 ends with the weekly review: an honest, read-only account of what was recorded against what was planned. The obvious implementations all lie — counting allocated calendar blocks as work done, reading an unobserved day as a zero, presenting planned block length as if it were effort, folding service-confirmed facts together with externally observed differences, and dressing patterns up as diagnoses. The specification (docs/technical/workflows.md#weekly-analytics) demands the opposite: every metric states its numerator, denominator, observation interval and coverage; an unobserved period is a gap, never a zero; allocation is not attainment; external differences are not deliberate acts; a corrected fact is folded once and stays traceable; and nothing here reschedules, queues or cancels anything.

Changes

  • src/api/reviews.rs (new) — POST /v1/reviews (read scope: no operation key, no calendar write, no journal row). It validates {period: "week", anchor_date?} before any read, then assembles the metrics, evidence and advisory flags from the reviewed week's live reads and one folded journal read.
  • src/domain/analytics/ (new) — six focused modules:
    • mod.rs — the one metric shape, Metric { name, numerator, denominator, unknown_count, coverage }, and MetricCoverage { from, to, view, attribution, unit, observed, unknown, detail }, so a consumer never special-cases a metric;
    • interval.rs — the Monday–Monday local week and its seven half-open days;
    • career_floor.rs — weekday attainment, classified by #22's own CareerFloor::from_day_outcomes, so the review and the next-action policy cannot disagree;
    • completions.rs — service-confirmed completions and observed external completion differences as two metrics over one denominator;
    • duration.rs — planned block minutes and reported effort minutes kept apart, with unknown actual durations excluded and counted;
    • deferrals.rs — items past deferral_review_count, raising #25's existing DeferralReview prompt (its message, its four options) and deciding nothing.
  • src/history/timeline.rs — new journal reads: effective_outcomes() (the corrected view, one entry per original fact, carrying the standing assertion plus the original/correction link), deferrals_in() (only the evidence classes that establish a deliberate deferral, grouped per item, with unattributable facts disclosed), and completions_in() (confirmed vs observed, split by evidence class). day_outcomes() is now defined over the folded read, so the career floor has exactly one definition for /v1/next and the review.
  • src/api/mod.rs — route registration plus the read-scope handler.
  • src/cli.rs — life review --period week [--anchor-date YYYY-MM-DD] [--json], a read-scope POST through post_read_envelope, with a human renderer that prints each ratio beside its observed/unknown coverage, then the flags and the evidence.
  • skills/life/references/commands.md — life review moved into the Reads table; it is no longer advertised as unimplemented.
  • Tests and fixtures: tests/review_tests.rs, unit tests in interval.rs/reviews.rs; the CLI/API fixtures that pinned /v1/reviews as a 501 stub now probe /v1/sync, and the skill package's unimplemented-command count reflects the remaining two.

Verification

nix develop --command cargo test — every suite green, with 16 new integration cases in tests/review_tests.rs covering, per acceptance criterion:

  • Career floor from recorded outcomes (W12) — a qualifying weekday, an observed non-qualifying weekday and three unobserved weekdays produce 1/2 with unknown_count 3; a live Friday block that was never reported changes neither the numerator nor the unknown count, and contributes only to the planned half of the duration metric.
  • Observed zero vs not observed — five observed non-qualifying weekdays answer 0/5, unknown 0 with no history_incomplete warning, while a week with nothing recorded answers 0/0, unknown 5 with the gap named.
  • Per-metric interval and coverage — every metric carries its own from/to, view: corrected, attribution, unit, observed/unknown and detail lists.
  • Confirmed vs observed completions — two metrics over one denominator, an observed difference that is not a completion excluded entirely.
  • Planned duration vs reported effort — 120m planned against 30m reported, one unknown duration counted, one block reported and one never reported; a transparent event is not a block.
  • Deferrals (W10) — two confirmed deferrals raise the prompt with its four options and its evidence IDs, one deferral does not, an observed_external difference is not a deferral, and an unattributable deferral is disclosed rather than attributed.
  • Correction-aware totals (W14) — a corrected duration counts once at the corrected value, a fact corrected into the week counts once, and the evidence carries event_id, original_event_id and corrected_by.
  • Advisory flags — a reachable provider yields a review_flag citing the inputs it was asked about and jev-1.13.0/review_flag_v1, with no diagnostic language; an unreachable provider yields judgment_unavailable with byte-identical metrics and evidence.
  • Zero writes — every calendar request the daemon makes is a REPORT, and every journal table's row count is unchanged.

Closes #30

## Motivation Phase 4 ends with the weekly review: an honest, read-only account of what was recorded against what was planned. The obvious implementations all lie — counting allocated calendar blocks as work done, reading an unobserved day as a zero, presenting planned block length as if it were effort, folding service-confirmed facts together with externally observed differences, and dressing patterns up as diagnoses. The specification (`docs/technical/workflows.md#weekly-analytics`) demands the opposite: every metric states its numerator, denominator, observation interval and coverage; an unobserved period is a gap, never a zero; allocation is not attainment; external differences are not deliberate acts; a corrected fact is folded once and stays traceable; and nothing here reschedules, queues or cancels anything. ## Changes - `src/api/reviews.rs` (new) — `POST /v1/reviews` (read scope: no operation key, no calendar write, no journal row). It validates `{period: "week", anchor_date?}` before any read, then assembles the metrics, evidence and advisory flags from the reviewed week's live reads and one folded journal read. - `src/domain/analytics/` (new) — six focused modules: - `mod.rs` — the one metric shape, `Metric { name, numerator, denominator, unknown_count, coverage }`, and `MetricCoverage { from, to, view, attribution, unit, observed, unknown, detail }`, so a consumer never special-cases a metric; - `interval.rs` — the Monday–Monday local week and its seven half-open days; - `career_floor.rs` — weekday attainment, classified by #22's own `CareerFloor::from_day_outcomes`, so the review and the next-action policy cannot disagree; - `completions.rs` — service-confirmed completions and observed external completion differences as two metrics over one denominator; - `duration.rs` — planned block minutes and reported effort minutes kept apart, with unknown actual durations excluded and counted; - `deferrals.rs` — items past `deferral_review_count`, raising #25's existing `DeferralReview` prompt (its message, its four options) and deciding nothing. - `src/history/timeline.rs` — new journal reads: `effective_outcomes()` (the corrected view, one entry per original fact, carrying the standing assertion plus the original/correction link), `deferrals_in()` (only the evidence classes that establish a deliberate deferral, grouped per item, with unattributable facts disclosed), and `completions_in()` (confirmed vs observed, split by evidence class). `day_outcomes()` is now defined over the folded read, so the career floor has exactly one definition for `/v1/next` and the review. - `src/api/mod.rs` — route registration plus the read-scope handler. - `src/cli.rs` — `life review --period week [--anchor-date YYYY-MM-DD] [--json]`, a read-scope POST through `post_read_envelope`, with a human renderer that prints each ratio beside its observed/unknown coverage, then the flags and the evidence. - `skills/life/references/commands.md` — `life review` moved into the Reads table; it is no longer advertised as unimplemented. - Tests and fixtures: `tests/review_tests.rs`, unit tests in `interval.rs`/`reviews.rs`; the CLI/API fixtures that pinned `/v1/reviews` as a 501 stub now probe `/v1/sync`, and the skill package's unimplemented-command count reflects the remaining two. ## Verification `nix develop --command cargo test` — every suite green, with 16 new integration cases in `tests/review_tests.rs` covering, per acceptance criterion: - **Career floor from recorded outcomes (W12)** — a qualifying weekday, an observed non-qualifying weekday and three unobserved weekdays produce `1/2` with `unknown_count 3`; a live Friday block that was never reported changes neither the numerator nor the unknown count, and contributes only to the planned half of the duration metric. - **Observed zero vs not observed** — five observed non-qualifying weekdays answer `0/5, unknown 0` with no `history_incomplete` warning, while a week with nothing recorded answers `0/0, unknown 5` with the gap named. - **Per-metric interval and coverage** — every metric carries its own `from`/`to`, `view: corrected`, `attribution`, `unit`, `observed`/`unknown` and detail lists. - **Confirmed vs observed completions** — two metrics over one denominator, an observed difference that is not a completion excluded entirely. - **Planned duration vs reported effort** — `120m` planned against `30m` reported, one unknown duration counted, one block reported and one never reported; a transparent event is not a block. - **Deferrals (W10)** — two confirmed deferrals raise the prompt with its four options and its evidence IDs, one deferral does not, an `observed_external` difference is not a deferral, and an unattributable deferral is disclosed rather than attributed. - **Correction-aware totals (W14)** — a corrected duration counts once at the corrected value, a fact corrected *into* the week counts once, and the evidence carries `event_id`, `original_event_id` and `corrected_by`. - **Advisory flags** — a reachable provider yields a `review_flag` citing the inputs it was asked about and `jev-1.13.0`/`review_flag_v1`, with no diagnostic language; an unreachable provider yields `judgment_unavailable` with byte-identical metrics and evidence. - **Zero writes** — every calendar request the daemon makes is a REPORT, and every journal table's row count is unchanged. ## Related Issues Closes #30
Author
Owner

Coordinator review — APPROVED

Verified 0cdbfa7 myself: cargo test 736 passed / 0 failed (16 new review tests), cargo clippy --all-targets -- -D warnings 0 errors, cargo fmt --check clean.

What I checked beyond the summary — this is the slice where "say what you measured" is easiest to fake, so the assertions matter:

  • Observed zero is distinguished from never observed, and the asymmetry is observable rather than implied: five observed non-qualifying weekdays give 0/5 with unknown 0, the five dates listed in detail.observed_days, and no warning; a week with nothing recorded gives 0/0 with unknown 5, an empty observed_days, and a history_incomplete warning naming the days. That is precisely the distinction workflows.md demands, and it is carried by the warning as well as the counts.
  • An allocated block is not attainment, proven by where it does show up: the live Friday block appears only in focus_block_effort's planned_minutes, never in the career-floor numerator, and the non-qualifying Tuesday lands in unmet_days while unobserved days land in unknown_days — so each day is classified once, explicitly. Reusing #22's CareerFloor::from_day_outcomes keeps one definition of the floor in the codebase.
  • Confirmed and observed completions share a denominator but keep separate numerators, with each numerator naming its own evidence, and an observed_external return to NEEDS-ACTION correctly excluded — so an external difference is reported as an observation rather than promoted to a service fact.
  • Planned versus actual effort is separated with the transparent event excluded (denominator 120 from two opaque blocks, not three events) and the unknown actual duration excluded and counted (unreported_blocks 1), which is the disclosure the doc asks for.
  • Corrections are folded once and stay traceable (VIEW_CORRECTED), deferrals count only deliberate recorded ones and reuse #25's own review prompt and its four options rather than inventing a second one, and the advisory flag is review_flag.v1's interpretation with a judgment_unavailable path that leaves every deterministic metric unchanged.
  • Supporting fixtures that assumed life review was unimplemented were moved to a still-unimplemented verb (sync) in api_tests.rs, cli_tests.rs and skill_package_tests.rs, and the skill's command reference moved review to Reads — the coordinated-update habit this codebase has kept throughout.

Merging. This closes the last issue of the original 36.

## Coordinator review — APPROVED Verified `0cdbfa7` myself: `cargo test` **736 passed / 0 failed** (16 new review tests), `cargo clippy --all-targets -- -D warnings` 0 errors, `cargo fmt --check` clean. What I checked beyond the summary — this is the slice where "say what you measured" is easiest to fake, so the assertions matter: - **Observed zero is distinguished from never observed, and the asymmetry is observable rather than implied:** five observed non-qualifying weekdays give `0/5` with `unknown 0`, the five dates listed in `detail.observed_days`, and **no** warning; a week with nothing recorded gives `0/0` with `unknown 5`, an empty `observed_days`, and a `history_incomplete` warning naming the days. That is precisely the distinction `workflows.md` demands, and it is carried by the warning as well as the counts. - **An allocated block is not attainment, proven by where it *does* show up:** the live Friday block appears only in `focus_block_effort`'s `planned_minutes`, never in the career-floor numerator, and the non-qualifying Tuesday lands in `unmet_days` while unobserved days land in `unknown_days` — so each day is classified once, explicitly. Reusing `#22`'s `CareerFloor::from_day_outcomes` keeps one definition of the floor in the codebase. - **Confirmed and observed completions share a denominator but keep separate numerators,** with each numerator naming its own evidence, and an `observed_external` return to `NEEDS-ACTION` correctly excluded — so an external difference is reported as an observation rather than promoted to a service fact. - **Planned versus actual effort is separated with the transparent event excluded** (denominator 120 from two opaque blocks, not three events) and the unknown actual duration excluded *and counted* (`unreported_blocks 1`), which is the disclosure the doc asks for. - **Corrections are folded once and stay traceable** (`VIEW_CORRECTED`), deferrals count only deliberate recorded ones and reuse #25's own review prompt and its four options rather than inventing a second one, and the advisory flag is `review_flag.v1`'s interpretation with a `judgment_unavailable` path that leaves every deterministic metric unchanged. - Supporting fixtures that assumed `life review` was unimplemented were **moved** to a still-unimplemented verb (`sync`) in `api_tests.rs`, `cli_tests.rs` and `skill_package_tests.rs`, and the skill's command reference moved `review` to Reads — the coordinated-update habit this codebase has kept throughout. Merging. This closes the last issue of the original 36.
yuris merged commit 37c81014ba into main 2026-09-23 04:27:53 +00:00
yuris changed title from feat(api): weekly review analytics and career floor evaluation (#30) to feat(api): weekly review analytics and career floor evaluation 2026-09-23 05:11:58 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
yuris/life-service!77
No description provided.