Store PR diff stats at settle

Task 5391 · Done · Coder · created · updated

## Original requirement (verbatim, epic) > Create Operator dashboard with high-level metrics of the entire system to analyze usage, efficiency, resources spent, results achieved - LOCs, docs - quantitative and qualitative. Some trends. Make it very robust - less words, more information that can be zoomed in. > > Do a web research to find out what competition does, but also come up with innovative ideas that will help observe and grow the business. Operator interview answers (verbatim): "Suggest other things too" · "When the runner merges a PR we can capture lines at that point" · "We are already calculating dollar cost from tokens and our price table in settings. BTW update the table with the latest data" Full settled inputs: PRD = Task 5386 `prd` artifact; architecture = Task 5387 `architecture` artifact (read with `task_read_artifact`). Section refs below (PRD §n / Arch §n) point there. Metric attribution rules (Arch §3), the pinned API contract (Arch §6) and the algorithms (Arch §7) are BINDING — do not invent alternatives. ## Requirement R9 — LOC capture at merge (PRD, verbatim) - **AC9.1** Every task settled with a `merged_pr` terminal after ship records lines added, lines removed and files changed for that PR, on every supported forge (GitHub, GitLab, Bitbucket, Azure DevOps). → *Verify:* merge a test PR on dev via a runner; SQL shows the three numbers equal to the forge's PR diff stats. - **AC9.2** If the stats cannot be obtained, the settle still succeeds and the PR is marked "LOC not captured" with a reason — capture never blocks a settle. → *Verify:* test with forge stats unavailable → settle succeeds, row marked. - **AC9.3** No backfill of historic PRs. (This child owns the CP storage half; AC9.1's end-to-end oracle is completed by the jaahd stamping child.) ## Scope & approach (this child) CP half of R9 (Arch §4.1 steps 4-6, §4.3, §5.1). Pinned contract — implement EXACTLY: ``` pr_stats = {status: "captured"|"partial"|"not_captured", reason: str|null (required when status != captured), additions, deletions (after E3 exclusion), files_changed (ALL files), excluded_additions, excluded_deletions: int|null, forge: "github"|"gitlab"|"bitbucket"|"azure_devops"} table task_pr_stats(task_id bigint PK FK tasks(id) ON DELETE CASCADE, org_id uuid NOT NULL (OrgScoped, stamped from the task), pr_url text NOT NULL, forge text NOT NULL, status text NOT NULL CHECK in ('captured','partial','not_captured'), reason text NULL, additions/deletions/files_changed/excluded_additions/excluded_deletions int NULL, captured_at timestamptz NOT NULL default now()) ``` - Additive alembic migration + model in `apps/api/app/models.py`. - `SettleIn` (`apps/api/app/routes/agent.py:5159`) gains OPTIONAL `pr_stats`. `settle_coder_on_merge` (`apps/api/app/routes/tasks.py:4144`) gains a `pr_stats` argument (default None) and upserts one `task_pr_stats` row BEFORE its own `session.commit()` (same transaction) when terminal is `merged_pr` and `pr_stats` is present. Malformed `pr_stats` → stored `not_captured`, `reason="malformed"`; it NEVER fails or delays the settle. Absent `pr_stats` → no row (read as "not captured"). - Compliance-group mirrors (`routes/agent.py:3930-3938`) settle without their own call — do not create duplicate rows for mirrors: callers that pass no `pr_stats` (e.g. the mirror path at `routes/agent.py:3933`) write no row. - The node-side stamping (jaahd + forge adapters) is a separate child; this child only accepts and stores. Out of scope: - Do NOT touch `apps/jaahd/` (node half is a sibling child). - No backfill migration of historic PRs; no CP→forge call of any kind (ADR-0112). Epic out-of-scope (PRD §5, verbatim): - No tenant-facing dashboard or data exposure: no new route, nav entry or API readable by any non-operator; tenant `/` (onboarding `HomeLanding`) unchanged. - Do NOT change Audit › Sessions › Efficiency, task run-metrics, `/clients` billing, account/node pages — reuse their definitions; any metric they already compute must not be re-implemented with a different formula. - Do NOT change cost arithmetic in `app/model_prices.py` (only its rate data, R10). - No LOC backfill, no git-clone-and-count on nodes, no LOC for non-merged work. - No doc-file LOC split (docs = artifacts only, D10). - No star-rating panels, no derived "quality signals" panels (D9 picked AI review only); existing ratings feature untouched. - No brush-select, no period-over-period deltas, no drill-down record lists (D8). Clicking an anomaly applies a slice — that is not a record list. - No Slack/email/push of insights, digests or anomalies (D11). - No change to task lifecycle semantics: recording state-transition times must be additive and must not alter transitions, guards or the `task_lifecycle.py` table. - No billing changes (plans, Stripe, wallets) — read only. - Never block a settle on LOC capture. Done means: - Settle with a valid `pr_stats` → exactly one `task_pr_stats` row with matching fields — API test asserting row contents. - Settle with malformed `pr_stats` (wrong types, unknown status) → settle returns success and task is `done`; row `status='not_captured', reason='malformed'` — API test. - Settle with no `pr_stats` → success, no row — API test. - Settle with `pr_stats` on a non-`merged_pr` terminal → ignored, no row — API test. - Migration is additive and upgrades/downgrades cleanly — `test_alembic_head.py` + migration test green. - No migration or script touches historic rows — code review of the diff (AC9.3). - `./dc check full` green; PR merged to develop.

Outcome

Built task_pr_stats storage: settle accepts optional pr_stats, upserts one row in the settle transaction; malformed→not_captured/malformed, never blocks settle. Migration 0388 additive. Full gate green; PR #4697 merged, Jaah Deploy succeeded.

Pull request

Artifacts

Activity

  • Draft
  • Ready
  • Runner started
  • Runner ended (done)