Documentation source
Computed Fields & Insights View Parity
Add computed/derived fields to entity type schemas and enhance chart block config so the block-based Transformation Insights view matches the hardcoded /insights page.
## Problem
The hardcoded `/insights` page looks polished because it uses server-side computed fields (`payback_months`, `payback_band`, `net_annual_value`) and domain-specific presentation (enum color maps, ordered stages, multi-dimension tooltips). The block-based Transformation Insights view can't replicate this because:
1. **No computed fields** — `payback_months = implementation_cost / estimated_annual_value * 12` can't be expressed in the schema. Charts referencing these fields show empty data.
2. **No enum color maps** — chart blocks cycle through a palette by index. The insights page maps `status` values to specific colors and `implementation_effort` to semantic colors (green/amber/red).
3. **No custom sort order** — pipeline stages must appear in business order (identified → deployed), not alphabetical.
## Solution
Two additions to the platform, scoped to what's needed for insights parity:
### 1. Computed Fields on Entity Type Schemas
Add `x-computed` extension to JSON Schema field definitions. Computed fields are virtual — evaluated at block resolution time, never stored.
```json
{
"payback_months": {
"type": "number",
"title": "Payback (months)",
"x-computed": {
"expr": "implementation_cost / estimated_annual_value * 12",
"format": "number"
}
},
"payback_band": {
"type": "string",
"title": "Payback Band",
"x-computed": {
"expr": "payback_months",
"classify": [
{ "max": 3, "label": "under_3_months" },
{ "max": 6, "label": "three_to_six_months" },
{ "max": 12, "label": "six_to_twelve_months" },
{ "default": "over_12_months" }
]
}
}
}
```
**Expression language:** Arithmetic only (`+`, `-`, `*`, `/`, parentheses, numeric literals). Field names are bare identifiers. No function calls, no string operations, no property access.
**Evaluation rules:**
- If any referenced field is null/undefined/NaN, the result is null (null propagation).
- Division by zero returns null.
- `classify` takes the numeric result and maps it to the first range where `value <= max`. If no range matches, uses `default`.
**Guardrails:**
- **Save-time validation:** When an entity type schema is saved, parse all `x-computed` expressions, extract field references, and validate they exist in the same schema. Reject if any reference is missing or circular.
- **Dependency tracking:** `x-computed` fields declare dependencies implicitly via the expression. The validator builds a DAG and rejects cycles.
- **Archive protection:** When a field is archived/removed, check if any `x-computed` fields reference it. Block the removal with an error listing the dependent computed fields.
- **Resolution-time only:** Computed values are never written to `entity.content`. They're injected into the entity row during block resolution, so they're always fresh.
**Implementation:** A single pure function `computeFields(content, schema)` that:
1. Topologically sorts computed fields by dependency order
2. Evaluates each expression against `content` (with prior computed values available)
3. Returns an augmented content object with computed fields merged in
This function is called once per entity in the block resolution pipeline, before chart grouping / stat aggregation / table display.
### 2. Chart Config Enhancements
Three additions to `ChartConfigSchema` and the chart block renderer:
#### a. Enum Color Map (`colorMap`)
```json
{
"colorMap": {
"identified": "hsl(221, 83%, 53%)",
"researching": "hsl(263, 70%, 50%)",
"scoped": "hsl(38, 92%, 50%)",
"building": "hsl(160, 60%, 45%)",
"deployed": "hsl(142, 72%, 35%)"
}
}
```
When `colorMap` is present, bar/pie colors are looked up by the item's `key` (the raw enum value before humanization). Falls back to palette cycling for unmapped values.
#### b. Custom Sort Order (`sortValues`)
```json
{
"sortValues": ["identified", "researching", "scoped", "building", "validating", "deployed"]
}
```
When present, chart items are ordered by position in this array instead of by value or label. Items not in the array sort to the end.
#### c. Tooltip Value Label (`tooltipLabel`)
```json
{
"tooltipLabel": "Annual Value"
}
```
When present, the tooltip shows this label instead of the generic "value" label. Combined with `valueFormat: "currency"`, this gives tooltips like `"Annual Value: $150,000"` instead of `"value: 150000"`.
### 3. Updated Seed Data
Update the Transformation Insights view blocks to use:
- Computed fields for `payback_months` and `payback_band`
- `colorMap` for pipeline stages and effort levels
- `sortValues` for stage ordering
- `tooltipLabel` on value charts
- Correct field names throughout
Also update the opportunity entity type schema to add computed field definitions.
### 4. Admin UI for Computed Fields
The existing entity type admin page (`/admin/entity-types/[slug]` Fields tab) gets a computed fields section. This lives alongside the existing schema editor and field config editor.
**Grid view addition:** Computed fields appear as rows in the fields grid with a "Computed" badge instead of a type selector. The expression and classify config are shown inline.
**Card view addition:** Each computed field gets a collapsible card with:
- Field name (read-only after creation, same as regular fields)
- Expression input (text field with placeholder like `implementation_cost / estimated_annual_value * 12`)
- Output type selector (number or string — string only when classify is used)
- Classify rules editor (optional, only shown when output type is string): ordered list of `{ max, label }` entries + default label
- Live validation: expression is parsed on change, dependency errors shown inline
- Dependent field warning: if this field is referenced by other computed fields, show them
**Save flow:** Computed fields are saved as part of `json_schema.properties` with the `x-computed` extension. The existing PATCH `/api/entity-types/[slug]` route handles this — no new API routes needed. The `updateEntityType` server action's schema validation step is extended to validate computed field expressions and detect cycles.
### 5. Agent Tools for Computed Fields
The existing `updateEntityTypeSchema` admin tool already supports `addFields` and `updateFields`. Computed fields are added the same way — the agent passes schema properties with `x-computed`:
```json
{
"tool": "updateEntityTypeSchema",
"input": {
"entityTypeSlug": "opportunity",
"addFields": {
"payback_months": {
"type": "number",
"title": "Payback (months)",
"x-computed": {
"expr": "implementation_cost / estimated_annual_value * 12",
"format": "number"
}
}
}
}
}
```
**Changes to `updateEntityTypeSchema`:**
- Validate `x-computed` expressions in added/updated fields
- Extract dependencies and check they exist in the schema (including other fields being added in the same call)
- Reject cycles
- Block removal of fields that are dependencies of computed fields (same archive protection as the UI)
**Changes to `updateFieldConfig`:**
- Skip computed fields (they have no extraction config — they're virtual)
- Return a clear error if an agent tries to set extraction config on a computed field
**No new tool needed.** The existing `updateEntityTypeSchema` tool is the right interface. Agents already know how to use it.
### 6. API Access
The existing PATCH `/api/entity-types/[slug]` route accepts `json_schema` in the body. Computed fields are part of the schema, so they're already API-accessible. The validation layer (extended in this spec) runs on all save paths — UI, agent tool, and direct API.
API key access (with `entity-types:write` scope) works the same way. No new routes.
## What This Does NOT Include
- No standalone formula editor component (expressions are edited as text in the field card)
- No stored computed values (always virtual, always fresh)
- No custom tooltip templates (single label override is enough for parity)
- No custom footer stat computation (the existing `footerStats` config with pre-set values is sufficient)
- No changes to the renderer plugin system (this is config-only)
## Acceptance Criteria
1. Opportunity entity type has `payback_months` and `payback_band` as `x-computed` fields
2. Transformation Insights view charts show real data with correct field values
3. Pipeline chart shows stages in business order with stage-specific colors
4. Value charts format as currency with labeled tooltips
5. Payback Profile chart shows bucketed data from computed `payback_band`
6. Removing a field referenced by a computed field is blocked with a clear error
7. Circular computed field dependencies are rejected at save time
8. Admin UI shows computed fields with expression editor and classify config
9. `updateEntityTypeSchema` agent tool validates and accepts `x-computed` fields
10. All existing tests pass, new tests cover computed field evaluation and guardrails
## Files
| File | Change |
|------|--------|
| `features/entities/types.ts` | Add `ComputedFieldConfig` type, extend schema property types |
| `features/schemas/computed.ts` | NEW — expression parser, evaluator, dependency validator, `computeFields()` |
| `features/schemas/computed.test.ts` | NEW — unit tests for expression eval, null propagation, classify, cycles, archive protection |
| `features/entities/server/entity-type-actions.ts` | Extend schema validation to check `x-computed` expressions, deps, cycles |
| `features/entities/components/admin/schema-editor.tsx` | Add computed field rows with expression editor and classify UI |
| `features/entities/components/admin/computed-field-editor.tsx` | NEW — expression input, classify rules editor, live validation |
| `app/(app)/admin/entity-types/[slug]/fields-grid-editor.tsx` | Show computed fields with badge and inline expression |
| `features/tools/admin/entity-type-tools.ts` | Validate `x-computed` in `updateEntityTypeSchema`, block computed fields in `updateFieldConfig` |
| `features/blocks/server/resolvers/index.ts` | Call `computeFields()` on entity rows before dispatching to per-type resolvers |
| `features/blocks/server/resolvers/chart.ts` | Pass augmented entity rows to chart data builder |
| `features/blocks/server/resolvers/stat-cards.ts` | Pass augmented content to field-level stats |
| `features/blocks/server/resolvers/data-table.ts` | Pass augmented entities to table data |
| `features/blocks/components/chart-block-renderers.tsx` | Support `colorMap`, `sortValues` in bar/pie renderers |
| `features/blocks/config-schemas.ts` | Add `colorMap`, `sortValues`, `tooltipLabel` to `ChartConfigSchema` |
| `features/blocks/lib/chart-data.ts` | Support `sortValues` ordering in `buildGroupedChartItems` |
| `scripts/fix-oci-empty-views.ts` | Update block configs with colorMap, sortValues, tooltipLabel; add computed fields to opportunity schema |
| `scripts/seed-dashboard-views.ts` | Same updates for canonical seed |