Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 33 additions & 7 deletions src/dashboards/executive.dashboard.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import type { Dashboard } from '@objectstack/spec/ui';

/**
* 管理层 — DESIGN.md §09's second dashboard: 生效合同额 · 90 天内到期 ·
* 高风险合同 · 审批瓶颈(各台阶平均停留)· 按方向的合同额趋势.
* 管理层 — DESIGN.md §09's second dashboard.
*
* §09's brick list is deliberately NOT copied here, for the reason PR #74 gave
* when it took the sibling copy off `legal.dashboard.ts`: a copy drifts, a
* pointer cannot. This one had already drifted. It advertised one brick this
* file has never built — 审批瓶颈(各台阶平均停留)— and §09 stopped asking for
* that brick on 2026-09-09 (维护者裁定, PR #40 `1127e52`), replacing it with the
* per-rung routing volume the four `route_*` tiles below deliver. §09 as merged
* (PR #70, `30fd863`) is the source; every widget below carries its own comment
* for its own shape.
*
* Every widget binds `contract_metrics`, so the two global filters below reach
* a column that exists on every one of them. See `legal.dashboard.ts` for the
Expand Down Expand Up @@ -136,21 +144,39 @@ export const ExecutiveDashboard: Dashboard = {
colorVariant: 'danger',
layout: { x: 9, y: 0, w: 3, h: 2 },
},
// ─── §09's 审批瓶颈, as the four rungs of DESIGN.md §04's ladder ───────
// ─── The four rungs of DESIGN.md §04's ladder, read as ROUTING VOLUME ───
//
// These four tiles are the brick §09 puts at this position, and they build
// it in full: one tile per rung, reading the four `route_*` flags F2 stamps
// on the contract. §09 says of this position that it carries traffic and
// not dwell — the board and the design agree here, and §09 (PR #70,
// `30fd863`) is where that wording lives rather than a copy of it here.
//
// §09 asks for 各台阶平均停留 — average DWELL per approval step. Two
// independent facts rule the dwell out, both measured rather than assumed:
// The average DWELL per rung that §09 listed BEFORE 2026-09-09 is not
// deliverable, and §09 no longer asks for it: the maintainer ruling in
// PR #40 (`1127e52`) placed it outside the V1.0 surface, and §09 now gives
// these two measurements — both taken on this board — as the reasons it
// declines:
//
// 1. There is no dwell to read. `sys_approval_request` holds 0 rows on a
// stock `pnpm demo` — the seed stamps `approval_status` directly and
// the ladder (F5) never ran — so no rung has a start or an end.
// 2. Even with rows, a duration is not expressible in the semantic layer:
// see the measurement in `cycle-time.dataset.ts`.
//
// ⚠️ Neither reads as a TODO. Fact 1 is independent of the platform and
// survives any upgrade; and the in-flight platform work
// (`objectstack-ai/objectstack#16737`) makes the wrong path ERROR rather
// than adding date arithmetic, so the dwell does not arrive when it closes
// either. ⛔ Do not re-promise the metric on the strength of that issue, and
// ⛔ do not stamp it from an application-side daily job — §09 forbids that
// route in as many words, as replicating a platform rule in the app.
//
// What IS persisted is the routing: F2 stamps four booleans on the contract,
// one per rung. These four tiles read them — how much traffic each rung
// carries. Each title names its rung and the group title says "Routing
// Load", never "bottleneck", because volume is not dwell.
// carries. Each tile is titled `Routes: <rung>`; no title or description on
// this board — here or in either translation bundle — says "bottleneck" or
// "dwell", because volume is neither.
//
// FOUR TILES, not one four-measure chart, and the shape was forced by a
// measurement. The rungs are four COLUMNS, not four values of one column, so
Expand Down
61 changes: 42 additions & 19 deletions src/dashboards/legal.dashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,15 +140,29 @@ export const LegalDashboard: Dashboard = {
},
{
/**
* §09's 超 SLA tile, with the one honest change its threshold had to
* take. The SLA is PER TYPE (`clm_contract_type.review_sla_days`: 2, 3, 5
* or 10 days across the nine seeded types), and a breach is a row-wise
* comparison of `review_started_at` against another object's column.
* Analytics cannot express that: there is no formula filter (§12 gap
* #10), and a cross-object filter is refused outright on the strategy
* every bucketed query lands on. The alternative — a persisted
* `review_due_at` stamped by the daily job — is the §12 gap-#7 shape and
* belongs to card 09, not here.
* The review-ageing tile, at a FIXED 30-day threshold written into its
* own title — which is the brick §09 prescribes here, not a degraded
* stand-in for one. §09 places the per-type「超 SLA」 outside the V1.0
* delivery surface (维护者裁定 2026-09-09, PR #40 `1127e52`; §09 as
* merged, PR #70 `30fd863`) and asks in its place for exactly this: a
* fixed threshold, self-declared in the brick's title.
*
* The measurement behind that ruling was taken here, which is why it is
* recorded here. The SLA is PER TYPE (`clm_contract_type.review_sla_days`:
* 2, 3, 5 or 10 days across the nine seeded types), and a breach is a
* row-wise comparison of `review_started_at` against another object's
* column. Analytics cannot express that: there is no formula filter
* (§12 gap #10), and a cross-object filter is refused outright on the
* strategy every bucketed query lands on.
*
* ⛔ The remaining route — a persisted `review_due_at` stamped by a daily
* job, the §12 gap-#7 shape — is closed for this metric, and not merely
* unbuilt: §09 forbids filling the per-type breach from an
* application-side job because that replicates a platform rule inside the
* application, and decision #31 ruled the same way on the notification
* side (1C + 2B, 2026-09-09 — see `legal-review-sla.flow.ts`, whose F3
* reminder fires on this same fixed 30 days so the tile and the reminder
* cannot tell two stories).
*
* So the threshold is FIXED, stated in the title, and above every seeded
* SLA: a review older than 30 days is late under any of the nine. The
Expand Down Expand Up @@ -179,20 +193,29 @@ export const LegalDashboard: Dashboard = {
// ─── Row 2: throughput, period over period ───────────────────────────
{
/**
* §09 asks for 平均周转(本月 vs 上月) — an AVERAGE TURNAROUND compared
* with the previous period. The comparison half is delivered exactly as
* the card requires, with the platform primitive (`compareTo`, the one
* shape `DatasetSelection.compareTo` implements) rather than a hardcoded
* delta. The average-duration half is NOT delivered, and this tile
* measures THROUGHPUT instead: how many contracts legal got to `approved`
* in the window.
* §09's 审批吞吐 brick: how many contracts legal got to `approved` in the
* window, against the previous period — and the period comparison uses
* the platform primitive §09 names (`compareTo`, the one shape
* `DatasetSelection.compareTo` implements) rather than a hardcoded delta.
* This tile IS that brick; it is not a substitute for a missing one.
*
* Why: a duration cannot be computed in the semantic layer on this
* §09 did ask for 平均周转 — an AVERAGE TURNAROUND — until 2026-09-09,
* when the maintainer ruling in PR #40 (`1127e52`) placed 各段时长 outside
* the V1.0 delivery surface and rewrote this brick to throughput (§09 as
* merged: PR #70, `30fd863`). The measurement behind that ruling was
* taken here: a duration cannot be computed in the semantic layer on this
* platform version — `Field.datetime` persists ISO text, so `AVG()` over
* a stage stamp answers the average YEAR (2025.9166…), and the dataset
* layer has no expression in which to subtract two dates. The full
* measurement is in `cycle-time.dataset.ts`. Substituting a number that
* is honest and saying so beats rendering a plausible one that is not.
* measurement is in `cycle-time.dataset.ts`. A number that is honest and
* says so beats a plausible one that is not, which is the reasoning §09
* itself now carries.
*
* ⚠️ This tile does not become an average turnaround when
* `objectstack-ai/objectstack#16737` closes. That in-flight fix makes the
* wrong path ERROR instead of returning a plausible fake number; it does
* not add date arithmetic, so the duration stays uncomputable after it
* lands. ⛔ Do not read the tracker as a queue this metric is waiting in.
*
* The window is stated HERE, on the widget, and it is bounded on both
* ends on purpose: `DatasetWidget` lowers only a `{ $gte, $lte }` pair
Expand Down
28 changes: 22 additions & 6 deletions src/datasets/cycle-time.dataset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,15 @@ import { defineDataset } from '@objectstack/spec/ui';
* How far contracts get, and when — DESIGN.md §09's second dataset, by
* contract type and legal owner.
*
* ## ⚠️ §09 asks for 各段时长 (per-stage DURATIONS). This dataset does not carry
* one, because on this platform version none can be computed honestly.
* ## ⚠️ No duration measure — and §09 does not ask for one
*
* §09's bullet for this dataset asks for stage-reached counts and rules out any
* duration measure, which is exactly what is declared below. 各段时长 (per-stage
* DURATIONS) was placed outside the V1.0 delivery surface by the maintainer
* ruling of 2026-09-09 (PR #40, `1127e52`; §09 as merged, PR #70, `30fd863`),
* and the measurements in this comment are the ones §09 now cites as its
* reason. They were taken here, on this dataset — which is why they are
* recorded here, and not because this file falls short of the design.
*
* Measured on `@objectstack/spec` 17.3.0 + `@objectstack/service-analytics`
* 17.3.0 + better-sqlite3 13.0.3, against the 820-row demo:
Expand All @@ -27,10 +34,19 @@ import { defineDataset } from '@objectstack/spec/ui';
* form is the one that renders a clean, plausible number. Shipping it would be
* exactly the "seeded number that looks computed" AGENTS.md forbids.
*
* A duration needs a PERSISTED numeric — the same shape DESIGN.md §12 already
* prescribes for the other date-arithmetic gap (#7: "到期、逾期由日任务盖戳字段"),
* i.e. a daily job stamping `clm_contract`. That field does not exist and
* creating it is a schema change, not an analytics one. Raised on the PR.
* ⚠️ None of this changes when `objectstack-ai/objectstack#16737` closes. The
* in-flight platform fix makes the wrong path ERROR instead of returning that
* plausible number; it does not add date arithmetic, so a duration remains
* uncomputable after it lands. The unlock criterion decision #31 records is
* "this repo upgrades to a version where the wrong path actually errors" — the
* end of a fake number, not the arrival of the metric.
*
* A duration would need a PERSISTED numeric — the shape DESIGN.md §12 gives the
* other date-arithmetic gap (#7), a daily job stamping the column, as the
* obligation and payment sweeps do for 到期 / 逾期. ⛔ That route is closed for
* this one, and not merely unbuilt: §09 forbids filling 各段时长 or the per-type
* 「超 SLA」 from an application-side daily job, because it replicates a platform
* rule inside the application.
*
* ## What it DOES carry
*
Expand Down