Skip to content

Commit a35fc66

Browse files
os-billclaude
andauthored
fix(service-analytics): a consolidated measure over the home field works unchanged (proof), and the record-currency refusal names the home field (multi-currency M3-1) (#30176)
<!-- claude-projects-attribution: {"github_login":"os-bill"} --> _Requested by **Bill** · [project thread](https://claude.ai/code/project/chan_016iMXWSZDzqWRWvWzdNo8wg?thread=cmsg_016iMXWSZDzqWRWvWzdNo8wgNdDwFvZLCevqYiyR8iafDv)_ epic:#8345#1 · os-bill · session_01Utp6CZWjnSeed8H4dMjt9d Fixes #27195 Ruling-ref: 6465431966 Clause-②: yes (widening) — `AnalyticsServiceConfig.sourceFieldMeta` (an exported config type) may answer one more optional key, `homeAmountField`, which `AnalyticsServicePlugin` relays; nothing an author writes changes Metadata-protocol: none ## 维护者速读 - **改了什么:** 新增证明测试:三种记录币种(USD/EUR/CNY)的行经写入投影存入影子列后,数据集度量 `sum` 影子列在内存预览、SQLite、Postgres 上都直接得出本位币合计,代码路径零改动;未分组汇总原始金额的拒绝文案现在同时给出两个药方:指向影子列(宿主透传时点名),或按币种码分组。 - **为什么改:** 仪表盘是客户看合并数的地方;原文案只教「按币种分组」,没告诉作者要一个合计该指向影子列。 - **风险与回滚:** 小。错误码、状态、member/field/object 信封不变,只改文案;`sourceFieldMeta` 多一个可选键(Clause-② widening),宿主不答时文案不点名字段。回滚即撤 PR。 - **先看:** `packages/services/service-analytics/src/cube-record-currency-door.ts` 的文案,再看 `home-amount-consolidated-measure.test.ts`。 - **两分钟验证:** `OS_TEST_POSTGRES_URL=… npx vitest run src/__tests__/home-amount-consolidated-measure.test.ts`(36 条);在 main 上跑同一文件,证明部分全绿、文案断言红。 - **需要您做的:** 无。 ## What changes **The proof (no product code).** `src/__tests__/home-amount-consolidated-measure.test.ts` writes four invoices in three record currencies (USD, EUR and the home CNY) through the engine, whose write-time projection (ADR-0144 D5, `home-amount-projection.ts`) stores each amount converted into the home currency in the declared `homeAmount.field`. A dataset measure `sum` over that home field answers the home-currency total through `queryDataset` (dataset compile + query), on: - SQLite and PostgreSQL, each on both strategies (`NativeSQLStrategy`; `ObjectQLStrategy`, narrowed to the engine aggregate); - the draft-preview in-memory evaluator, over the very rows the projection stored (read back from the store). This "memory" cell is that evaluator, not `driver-memory`. Pinned per cell: the projection fixture (home amounts and rates as stored); the ungrouped KPI (2010 CNY); per-currency subtotals grouped by the code field and a `totals` grand total equal to the KPI; the column labelled with the home currency and no per-row `currencyField` (the home field is `dynamic`); the inferred cube's `amount_home_sum`; a relationship path (a line cube joined to the invoice: `invoice.amount` refused naming `invoice.amount_home` and `invoice.currency_code`, `invoice.amount_home` served on the native strategy); `INVALID_FIELD` and status 400 on every refusal path (dataset, inferred cube, joined cube, draft preview). Control: the raw amount grouped by code is served per currency and adds to a different number (270). On `main` the proof assertions already pass, so the consolidated path is the unchanged code path. **The door message (text only).** `assertRecordCurrencyAggregatesGrouped` (#27187, PR #27338) refused an ungrouped `sum` / `avg` / `min` / `max` over a record-mode amount with one prescription: group by the code field. It now names both: - for one total in the home currency, point the measure at the amount's home field, by name (`homeAmount.field`) when the host relays it, or else as "the field its `currencyConfig.homeAmount.field` names", with the declaration to add if it has none (true whether or not the object declares one); - for one row per currency, add the dimension over the code field. Code `INVALID_FIELD`, status 400, `member` / `param` / `cube` / `field` / `object` are unchanged; no new refusal code. The home field reaches the door through `sourceFieldMeta`, which now answers `homeAmountField` beside `currencyField` (record mode only), relayed by `AnalyticsServicePlugin` from `currencyConfig.homeAmount.field`. **Not in this diff.** The engine's own `aggregate()` door (`packages/objectql/src/record-currency-aggregate-door.ts`) already names a stored home-currency amount field generically; it is outside this card's file surface. ⛔ No `ui/**` / `plugin-dashboard` change (ui lane off). #27193 untouched. ## Why ADR-0144 D6: a consolidated total aggregates the stored home field. The proof shows that needs nothing new; the message makes the refusal prescribe it. ## Verification - `home-amount-consolidated-measure.test.ts`: 36/36 at the current head (30/30 at `e430075a`) with `OS_TEST_POSTGRES_URL` set (PostgreSQL 16, local). Ablation at `e430075a`: the same file on `main`'s three source files: 14 failed, every one a message assertion; every proof assertion passed. - `pnpm --filter @objectstack/service-analytics typecheck`: exit 0. `eslint` on the changed files: exit 0. - Package suite with `OS_TEST_POSTGRES_URL` (at `e430075a`): 5267 passed, 1 failed: `read-scope-temporal-coercion.test.ts` "the server is not on UTC (the premise of the PostgreSQL cells)", a premise check on the local server's zone (local PostgreSQL runs on `Etc/UTC`; CI provisions a skewed zone). Not touched by this diff. - `dispatch-gates --commands` (at `e430075a`): 62 commands, exit codes captured before any pipe: 61 exit 0; `pnpm check:dual-build-cjs-loads` exit 3, NOT MEASURED (it reads every package's `dist/`, and this worktree built only this package's closure), left to CI. `check-plugin-teardown-shape --self-test` first exited 3 on the shallow clone and passed (exit 0) after fetching its pinned fixture commit. One changeset, `@objectstack/service-analytics` patch. ## Human Minimum - [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic. (spec not touched) - [x] 2. Every name I added uses the casing for its kind. - [x] 3. No formatter ran; eslint passes on every changed file (whole-repo `pnpm lint` left to CI). - [x] 4. One issue, one branch off an up-to-date `main`, one pull request. - [x] 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset. - [x] 6. `packages/spec` touched: n/a. - [x] 7. No hand edits to `content/docs/references/`, `packages/*/CHANGELOG.md` or `content/docs/releases/`. - [x] 8. Title, body and comments are in English. - [x] The *Before Submitting* list in CONTRIBUTING.md is done. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01Utp6CbmDUh2Sx7fvaGgu7r --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 968d645 commit a35fc66

5 files changed

Lines changed: 487 additions & 17 deletions

File tree

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@objectstack/service-analytics': patch
3+
---
4+
5+
fix(service-analytics): the refusal of an ungrouped aggregate over a record-currency amount names the home field as the remedy for a consolidated total (ADR-0144 D6)
6+
7+
Clause-②: yes (widening)
8+
Metadata-protocol: none
9+
10+
- **Now.** A `sum`, `avg`, `min` or `max` over a `currencyMode: 'record'` amount that does not group by its code field is still refused `INVALID_FIELD` / 400 before anything is read, with the same code, status, member, field and object. Its message now names both remedies. For one total in the home currency, point the measure at the amount's home field: by name (`homeAmount.field`) when the host relays it, or else as "the field its `currencyConfig.homeAmount.field` names", with the declaration to add if it has none. For one row per currency, add the dimension over the code field. Before, the message named only the grouping.
11+
- **Unchanged.** A measure over the home field is no record-mode amount, so it is served unchanged: ungrouped, grouped by the code field, and as a `totals` grand total, labelled with the home currency.
12+
- **The widening.** `AnalyticsServiceConfig.sourceFieldMeta` may answer one more optional key, `homeAmountField`: the amount's `currencyConfig.homeAmount.field`, beside a `currencyField` only. `AnalyticsServicePlugin` relays it. A host that does not answer it gets the message without the field's name.

0 commit comments

Comments
 (0)