File size: 4,907 Bytes
092334a 4748aae 37d92f0 ea7b176 c3e4cb4 f5ffed6 d349fee 092334a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 | // ---------------------------------------------------------------------------
// viz / seriesData.ts β EXIT wave 2 (W2-7/W2-9). The Y1 chart block's data
// preparation, pure and React-free so `verify_ui.py` can run it under node.
//
// Both functions here guard a MEASURED trap rather than a hypothetical one:
// Β· `inServerOrder` β the engine sorts a non-date category axis by VALUE
// DESCENDING, so a trend renders as a revenue-ordered sawtooth unless the
// server's chronology is reapplied (Y1 amendment 7).
// Β· `asFields` β a chart field carries the viz `FieldType` vocabulary, NOT
// Y1's `fmt` enum, and mixing them typechecks nowhere and renders as raw
// text (Y1 rule 1).
// ---------------------------------------------------------------------------
import type { Bucket } from "./chartData";
import type { Field, FieldType } from "./types";
/** The Y1 `fields[]` entry β the server's minimal field descriptor. */
export interface WireField {
key: string;
label: string;
type: string;
source?: string;
}
/** The viz field-type vocabulary as a runtime set. β `money` is deliberately
* absent: it belongs to Y1's `fmt` enum, and a chart field carrying it is the
* mix-up rule 1 warns about. It lands on `text`, which is the visible symptom. */
export const VIZ_FIELD_TYPES: ReadonlySet<string> = new Set([
"text", "status", "currency", "int", "date", "pct", "select", "user",
"multiselect", "checkbox", "phone", "email", "url", "rating", "created_time", "formula",
// Wave 18 C5 β£b β the RUNTIME copy of the vocabulary. A `Set` is not a `Record`: the
// compiler stays green when this goes stale and `asFields` silently downgrades the column
// to text. When a `FieldType` joins the union, this line moves WITH the batch.
"automation",
// Wave 22 C7 β `metric` is numeric (a measure over a snapshot window) and PLOTS.
"metric",
// Wave 19 R7 β `image`. Listed for the reason the comment above gives, not because a chart
// plots pictures: it does not. `isNumericFieldType` leaves it categorical, so it groups like
// any other reference string. What listing it BUYS is honesty β without it `asFields` would
// report the column as `text`, and the one thing a viz field type decides is what the surface
// is allowed to claim about the column.
"image",
// Wave 23 C7 β `json`. THE BATCH LINE the union's own comment names: a `Set` is not a
// `Record`, so the compiler stays green when this goes stale and `asFields` silently reports
// a json column as `text`. Same reasoning as `image` β the type is categorical either way,
// and what listing it buys is that the surface stops mis-naming the column.
"json",
// 2026-08-07 β the relational pair. THE BATCH LINE again: a `Set` is not a `Record`, so
// without this `asFields` would report a rollup column as `text` and the chart builder would
// refuse to plot the one column the feature exists to produce.
"link", "rollup",
// β Wave-27 item 13 (R13) β `code`. THE BATCH LINE a fourth time, and this one was caught by
// `verify_ui` rather than by the compiler, which is the whole point of that gate existing: the
// union in `viz/types.ts` went red under `tsc` the moment `code` joined the grid's, and this
// Set stayed green while `asFields` quietly reported a code column as `text`. Categorical
// either way (`isNumericFieldType` does not name it); what listing it buys is that the surface
// stops mis-naming the column.
"code",
]);
/**
* Y1 `fields[]` β viz `Field[]`.
*
* β An UNRECOGNISED `type` falls back to `text` rather than throwing β the
* server may learn a field type before this client does (Y1 rule 7's reasoning,
* one level down), and one unknown type must not blank a whole chart. `text` is
* the safe landing: it groups, and it never claims to be measurable.
*/
export function asFields(wire: WireField[]): Field[] {
return wire.map((f) => ({
key: f.key,
label: f.label,
type: (VIZ_FIELD_TYPES.has(f.type) ? f.type : "text") as FieldType,
source: f.source === "overlay" ? "overlay" : "odoo",
}));
}
/**
* Re-order buckets to the server's declared chronology.
*
* β ANYTHING THE LIST DOES NOT NAME IS APPENDED, NEVER DROPPED. Filtering to
* `x_order` would silently delete a period the server plotted but forgot to
* order β a bar disappearing from a revenue trend, with nothing anywhere going
* red. Sorting unnamed keys to the end keeps every value on screen and makes
* the omission visible instead.
*/
export function inServerOrder(buckets: Bucket[], xOrder?: string[]): Bucket[] {
if (!xOrder || xOrder.length === 0) return buckets;
const rank = new Map(xOrder.map((k, i) => [k, i]));
return [...buckets].sort(
(a, b) =>
(rank.get(a.key) ?? Number.MAX_SAFE_INTEGER) -
(rank.get(b.key) ?? Number.MAX_SAFE_INTEGER)
);
}
|