feat(core,ui): score functions — per-objective computed scores - #393
Open
j-or wants to merge 58 commits into
Open
feat(core,ui): score functions — per-objective computed scores#393j-or wants to merge 58 commits into
j-or wants to merge 58 commits into
Conversation
Reconcile 253 commits of main. Conflicts resolved: - packages/ui/package.json: took main's dependency versions, kept mathjs - package-lock.json: regenerated from main's lockfile + mathjs
…state Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…in data table Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rows to manual, harden response/expression handling Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
It is a CJS-only dependency; when bundled, rolldown rewrites its internal
require("react") to a runtime __require shim that throws in an ESM browser
environment. Externalizing it (like the other third-party deps) lets the
consumer resolve it (it is a declared dependency) and its bundler handle
the CJS/ESM interop.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Vite's optimized-dep interop exposes the CJS module's default export as the
whole exports object ({ __esModule: true, default: Component }) rather than the
component, so <Editor> rendered as an object -> React 'Element type is invalid'.
Unwrap .default defensively; still yields the component under vitest/bundled
interop where the default import is already the component.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…expression registerAndInsert fired two setDraft calls in one handler (add the variable, then insert its symbol into the expression) both built from the same stale `draft` closure, so the expression update clobbered the variable addition. Result: a clicked factor (or response) ended up referenced in the expression but missing from `variables`, so computeScore hit an undefined mathjs symbol and returned undefined -> the factor never affected the score. Make setDraft a functional updater so the two updates compose. +regression test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…, error-on-blur, close-on-save - Data-point Save was gated on validation violations, which come from persisted state and can't clear without saving -> unbreakable deadlock once the missing-response warning fired. Gate on isModified only; the row is still excluded from the optimizer via meta.valid and the warning still shows. - Playground built a test input for every symbol in the expression, so typing bare identifiers spawned phantom fields. Only build inputs for declared variables. - Expression parse error now shows on blur, not on every keystroke. - Settings 'Save' now closes the panel (caller onSave was a no-op). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The data-point RESPONSE header rendered the internal score name ("quality",
CSS-capitalized) instead of the objective's human label ("Quality (0-5)") used
everywhere else. Carry sv.label through TableDataRow.scoreFunctions and render it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Clicking an insert button blurred the expression field, firing the on-blur parse error; its InfoBox renders above the buttons and shifted them down between mousedown and mouseup, cancelling the click so the symbol was never inserted. preventDefault on mousedown keeps focus (and cursor) on the textarea: no blur error, no layout shift, click fires, symbol inserts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…title Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tion A score function that uses only factors (no response variables) rendered its label with no inputs beside it (e.g. a factor-only 'Cost'). Only show an objective when it has response vars, and hide the whole section when none do. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…function' button Playground is hidden by default behind a Science-icon 'Test your function' toggle; clicking reveals it. It resets to collapsed whenever the settings panel is closed (that unmounts the component). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The 'Test your function' toggle already labels the section, so the inner 'Playground' header and 'Test your score function here' info box were redundant. Expanding now shows the playground inputs directly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The help question-mark icon (Help tooltip) now opens a dialog explaining how score functions work: factors vs responses, building the expression, the playground, and the per-row f(x)/manual toggle. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
recomputeScore now creates the {type:'score'} entry when a data point has
none yet (a new function-mode point's blank score column is dropped on
conversion), so its computed value is persisted and the point validates. The
created entry is inserted in canonical (defaultSorted) order so multi-objective
score columns stay aligned across points. Reviewed for the yi/calculateXi/
request-hash ordering invariant.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Row score-function state (useDataPoints/buildScoreFunctions): a data point with no stored responses defaults to manual (keeps its entered value), while the 'add new point' template defaults to function mode — so new points use a defined function by default while pre-existing points are left untouched. Also carries the objective's human label through to the table row. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Help icon opens a 'How score functions work' dialog. - 'Score function' + 'Test your function' section titles; playground always visible (no collapse), Result shown in a labelled read-only field. - Typing a factor's name auto-registers it (highlights + shows in playground), matching the button. - Buttons/inputs show the factor's real name/casing; playground inputs labelled by variable name, not the mathjs symbol. - Expression parse error shows on blur, not per keystroke. - Insert buttons no longer steal focus (preventDefault) so a blur-error layout shift can't cancel the click. - Expression field height-capped with scroll and minWidth:0 so a long expression wraps instead of overlapping the playground. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Responses laid out in a grid so each objective's Nth input aligns in a column; inputs content-sized with a maxWidth cap (not stretched). - Objectives with no response variables are hidden (and the whole section when none apply). - Response inputs are plain text and accept a comma decimal separator (normalised to a dot on save), matching factor inputs. - Row Save is gated only on isModified, not on validation violations, so a row with a missing-response warning can still be saved to fix it. - Settings Save closes the panel. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ight field Adds a decoupled, generic HighlightField (@ui/common): a transparent textarea stacked over a highlighted <pre> in one CSS-grid cell, taking a highlight(code) => html fn and exposing insertAtCursor. ScoreFunctionField becomes a thin wrapper supplying the blue-symbol highlighting; its public API is unchanged. Removes the react-simple-code-editor dependency entirely (package.json + lockfile), so it no longer needs externalizing from the ui build and the dev:local symlink workaround is gone — the highlighter now bundles into the dist. Also drops the two CJS-interop workarounds the dependency required. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… the score entry Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…Names, drop dead code - Gate the quality 0-5 InfoBox on scoreNames[0] instead of a bare 'quality' literal (keeps the objective identity check in one place). - Remove the leftover commented-out Result Typography block in the playground. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The 'does not recompute a row in manual mode' test set no manual value and only asserted .not.toBe(200), which passed merely because the score stayed 0. Now it seeds a real manual score of 9 and asserts it is preserved exactly (toBe(9)) after the function changes — catching any recompute, not just the value 200. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…icon Renames the header tooltip and the settings-panel title from Settings to Score functions, swaps the cog for a star icon (matching the quality rating star in the editor), and drops the now-redundant 'Score function' subtitle inside the tabs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… on save Adds a "For existing data points" segmented control (Leave unchanged / Use for all / Turn off for all) to each score-function tab, defaulted to unchanged. Selecting "Use for all" shows an inline warning with the count of data points missing required responses (using the draft function, since that's what Save will persist and apply). The choice is applied on Save via setDataPointsUseFunction, after the draft functions are persisted via updateScoreFunction so recomputation uses the just-saved function.
…fill toggle Move the bulk score-function control below the two side-by-side columns so it spans the panel's full width, and style the segmented toggle like the Brownie Bee /settings toggles (selected option filled with the brand colour). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…margin) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds validateDataPointsScoreUncomputable: function-mode rows whose responses are complete but computeScore still returns undefined (e.g. a factor the function uses is disabled/removed) are now marked invalid with a dedicated table message, distinct from the existing responses-missing violation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds findDisabledFactors (factors the expression uses that aren't enabled value variables) + an inline warning in the Score functions panel, so a function silently depending on a disabled factor is no longer invisible. Also softens the uncomputable-point message to reference both factors and the expression. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ansfer defaults) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…n with no data points Adds a disabled guard to the Data-points "Score functions" star button matching the condition under which DataPointsSettings would render nothing, and hides the "For existing data points" bulk section when the experiment has no data points. Adds a focused test (separate mock file) covering the hidden case.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Points created by copySuggestedToDataPoints (Transfer all / per-row Transfer to data points) now get a responses entry with useFunction:true for every scoreVariable with a scoreFunction, and an initial recomputeScore pass — matching the defaulting behavior a table-added row gets via setDataPointsUseFunction.
…ction Mirrors the table-add flow: after transferring a Pareto point's factor settings into a new data point row, dispatch updateDataPointResponses with useFunction: true for each score variable that has a scoreFunction, so the reducer recomputes the score using the function rather than leaving it unscored.
Matches the table-add default path (buildScoreFunctions filters enabled), so copy-suggested and Pareto transfers don't set up a responses entry for a disabled objective that still carries a scoreFunction. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add an 8px bottom margin to the score-functions settings panel so it does not sit flush against the data-points table, and force white text on the selected 'For existing data points' toggle (the bee theme's amber primary yields a black contrastText that reads poorly on the fill). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds per-objective score functions: define a mathjs expression over named
"responses" (values measured per data point) and existing factors that computes
an objective's quality/cost score, instead of typing it by hand — with a per-row
toggle to fall back to a manually entered value.
Approach
Purely additive to the experiment data model in
@boostv/process-optimizer-frontend-core, behind a v21 data-formatmigration:
scoreVariable.scoreFunction+dataEntry.responses. The computed(or manual) score keeps living in the existing
{ type: 'score', name, value }entry, so the optimizer request, Pareto selection, plots, and CSV export are all
unchanged. See ADR 0003.
Highlights
computeScore/deriveSymbol/usedSymbols; reducer actions (updateScoreFunction,updateDataPointResponses) with recompute + canonical score-entry ordering;validation that a function-mode row with missing responses is invalid and
excluded from the optimizer.
factor/response insert buttons, live playground, help dialog); data-point
editor (
f(x)toggle, manual person-icon, grid-aligned response inputs, commadecimals); an in-house
HighlightField— noreact-simple-code-editordependency.
Behaviour notes
manual, so existing hand-entered scores are never overwritten.
Testing
dev:local.🤖 Generated with Claude Code