feat: typed computed fields - ComputedNumber and ComputedText (#2197)#2199
feat: typed computed fields - ComputedNumber and ComputedText (#2197)#2199Caedous wants to merge 5 commits into
Conversation
Signed-off-by: Caedous <tombevs_2@hotmail.com>
E2E ✅ passed
After downloading and extracting the .zip file, open Updated for commit 656d8d3 |
…uted field editor Signed-off-by: Caedous <tombevs_2@hotmail.com>
PeterBaker0
left a comment
There was a problem hiding this comment.
Some documentation updates.
We should also write a migration to move the old ComputedValue -> ComputedNumber. While there might not be any in the wild, would be good to do so to follow best practise here for backwards compatibility. There is existing hooks for 'migrate notebooks on startup' which run these migrations.
| The Computed Number's key feature is the **Expression** text area, which | ||
| defines the calculated value. Each referenced field is written by | ||
| wrapping its Field ID in braces, e.g. | ||
| `{Wet-Soil-Mass-g} * {Number-of-Samples}`. Below the expression, a chip |
There was a problem hiding this comment.
"Chip is shown" is out of date
| | **Expression** | A text area where you define the calculation using literals, operators, and field references in single-brace syntax (e.g., `{Width} * {Height}`). | | ||
| | **Insert field** | A searchable picker of the referenceable fields in the form. Selecting one inserts its braced reference into the expression. | | ||
|
|
||
| The expression is typed and checked when the {{notebook}} is designed — |
There was a problem hiding this comment.
"rather than a silent wrong answer" reads like an agent correcting an earlier prompt
|
|
||
| ## Tips | ||
|
|
||
| - **Use the field picker to build expressions** rather than typing Field |
| - **The result stays blank until all referenced fields have values.** | ||
| This is intentional — a partially complete record shows no value rather | ||
| than a misleading partial calculation. | ||
| - **Type mismatches are reported when the {{notebook}} is designed**, not |
There was a problem hiding this comment.
rewrite to not refer to previous lesser implementations - "not hidden at collection time" is not necessary
Signed-off-by: Caedous <tombevs_2@hotmail.com>
Signed-off-by: Caedous <tombevs_2@hotmail.com>
stevecassidy
left a comment
There was a problem hiding this comment.
One thing that's missing is a check when fields are deleted that they don't occur in a computed expression. We have this for conditions, see findFieldDependencyReferences, and field delete checks for references before allowing deletion. We should have the same for computed fields.
| <FormHelperText> | ||
| Reference other numeric fields by wrapping their ID in braces. | ||
| {numericFieldCount > 0 && | ||
| Reference other fields by wrapping their ID in braces. |
There was a problem hiding this comment.
I think some inline help would be useful on writing expressions, maybe just the list of operators you have in the docs might be enough.
Ticket
#2197
Description
Extends the computed field expression engine to handle strings, numbers and booleans in and out, and splits the field into ComputedNumber and ComputedText so each has a fixed
type-returned. Typing is strict with no implicit coercion, per Peter's direction on the issue: ill-typed expressions are rejected when the notebook is designed rather than producing a silently wrong value, and the two-field split avoids union return types that would complicate export flows such as GeoPackage.Operators are discrete and unambiguous:
+ - * / %are numeric only,&is string concatenation,&& || !are boolean only,< > <= >=compare two numbers or two strings,== !=require matching types, and the ternary requires a boolean condition with same-typed branches. Mixed-type inputs are fine so long as the overall result matches the field's type —{vegType} == 'test' || {flag} ? 3 : 5is a valid ComputedNumber, while... ? 3 : 'Not valid'is rejected. Field references keep the brace syntax ({Field-ID}).Proposed Changes
The engine in data-model compiles each expression bottom-up into a typed closure, inferring every node's type at compile time and selecting operator implementations once. Field reference types come from the spec's
type-returnedvalues via a sharedFAIMS_TYPE_TO_EXPR_TYPEmap, built at notebook load incompileUiSpecConditionals, and each component's expression is validated against its required return type (number for ComputedNumber, string for ComputedText). Mismatches throwExpressionErrornaming the types involved. The source cache is removed, since the same source legitimately compiles differently against different field types and compile-once-at-load makes it redundant.In forms, ComputedField is renamed to ComputedNumber and ComputedText is added: one shared read-only component with per-type value schemas in a single
ComputedFieldsfolder, following theDateFieldsmulti-spec pattern. The recompute resolves scope values per each referenced field's declared type instead of coercing everything toNumber; an empty string counts as missing, so partially filled forms stay blank rather than showing a partial result.The Designer gains two chooser cards (Computed Number under NUMBERS, Computed Text under TEXT) sharing one editor. The insert chips now list all referenceable fields — number, text and checkbox, with derived fields excluded — and the helper text shows a per-variant example. Invalid expressions are compiled as the user types and the error is shown inline under the expression input; if one still gets through via raw JSON, the uiSpec compilation logs it and the field stays blank.
The expression tests are rewritten for the typed engine (26 cases) covering the ticket's fireRisk example, concatenation, string comparison, mixed-type inputs with a matching result type, and the compile-time error battery: type mismatches per operator, ternary rules, wrong return type, unknown fields, and brace validation. The user documentation is updated, replacing the Computed Value page with Computed Number and adding a Computed Text page under text fields.
How to Test
vegTypeand a number fieldvegHeight, using the expression{vegType} == 'hedge' ? ({vegHeight} < 3 ? 'Low' : ({vegHeight} < 5 ? 'Medium' : 'High')) : 'Medium'. Entering hedge/2 shows Low, hedge/4 Medium, hedge/6 High, and tree with any height Medium.{vegType} & '-test'renderstree-testand stays blank until vegType has a value.{vegHeight} & 'x'as a Computed Text expression. The console reportsExpressionError: & (concatenation) requires strings, got number and stringat compile time and the field stays blank.Checklist