From b35b8c4b70f2bade59a366bf0030ff46a62ebe44 Mon Sep 17 00:00:00 2001
From: Jack Ord <36296721+j-or@users.noreply.github.com>
Date: Tue, 20 Jan 2026 12:32:53 +0000
Subject: [PATCH 01/11] Introduce UI labels for score variables
---
README.md | 2 ++
packages/core/src/common/types/common.ts | 4 +++
.../src/common/util/migration/schemas/17.json | 3 +++
.../context/experiment/experiment-context.tsx | 16 +++++++++---
.../context/experiment/experiment-reducers.ts | 7 +++--
packages/core/src/context/experiment/store.ts | 25 ++++++++++++++++--
.../editable-table-expanded-row.tsx | 2 +-
.../core/editable-table/editable-table.tsx | 26 +++++++++++--------
.../src/features/core/editable-table/types.ts | 1 +
.../src/features/data-points/useDataPoints.ts | 19 +++++++++-----
sample-app/src/components/home/home.tsx | 18 +++++++++++++
sample-app/src/experiment-container.tsx | 8 +++++-
12 files changed, 105 insertions(+), 26 deletions(-)
diff --git a/README.md b/README.md
index 3bfe926a..a873748a 100644
--- a/README.md
+++ b/README.md
@@ -31,6 +31,8 @@ npm run dev:app
3. Open [http://localhost:5173](http://localhost:5173) with your browser to see the result.
+Note: To build and watch a package, e.g. in /packages/ui, run `npm run build -- --watch` to make the ui package build automatically.
+
## Build and run production docker image
```bash
diff --git a/packages/core/src/common/types/common.ts b/packages/core/src/common/types/common.ts
index 61100e9a..46059eb9 100644
--- a/packages/core/src/common/types/common.ts
+++ b/packages/core/src/common/types/common.ts
@@ -6,6 +6,9 @@ import { z } from 'zod'
export const currentVersion = '17'
export const scoreName = 'Quality (0-5)'
+export const scoreNames = [scoreName, scoreName + ' 2']
+// Label is shown in UI, name is used in data
+export const scoreLabels = ['Quality (0-5)', 'Cost (0-5)']
const infoSchema = z.object({
name: z.string(),
@@ -43,6 +46,7 @@ const valueVariableSchema = z.object({
const scoreVariableSchema = z.object({
name: z.string(),
+ label: z.optional(z.string()),
description: z.string(),
enabled: z.boolean(),
})
diff --git a/packages/core/src/common/util/migration/schemas/17.json b/packages/core/src/common/util/migration/schemas/17.json
index 6115def9..ca95086a 100644
--- a/packages/core/src/common/util/migration/schemas/17.json
+++ b/packages/core/src/common/util/migration/schemas/17.json
@@ -137,6 +137,9 @@
"name": {
"type": "string"
},
+ "label": {
+ "type": "string"
+ },
"description": {
"type": "string"
},
diff --git a/packages/core/src/context/experiment/experiment-context.tsx b/packages/core/src/context/experiment/experiment-context.tsx
index 22193cf3..48547f11 100644
--- a/packages/core/src/context/experiment/experiment-context.tsx
+++ b/packages/core/src/context/experiment/experiment-context.tsx
@@ -3,7 +3,12 @@ import { useLocalStorageReducer } from '@core/storage'
import { DefaultApi } from '@boostv/process-optimizer-frontend-api'
import { Dispatch, rootReducer } from './reducers'
import { migrate } from '@core/common'
-import { initialState, State, useApi } from '@core/context/experiment'
+import {
+ initialState,
+ initialStateMultiObjective,
+ State,
+ useApi,
+} from '@core/context/experiment'
import { ExperimentType } from '@core/common/types'
import { fetchExperimentResult } from '@core/context/experiment/api'
@@ -21,17 +26,22 @@ type ExperimentProviderProps = {
experimentId: string
children?: React.ReactNode
storage?: Storage
+ isMultiObjective?: boolean
}
export const ExperimentProvider = ({
experimentId,
children,
storage,
+ isMultiObjective,
}: ExperimentProviderProps) => {
const storageKey = experimentId === undefined ? 'unknown' : experimentId
+ const initialStateToUse = isMultiObjective
+ ? initialStateMultiObjective
+ : initialState
const initialExperimentState = {
- ...initialState,
- experiment: { ...initialState.experiment, id: experimentId },
+ ...initialStateToUse,
+ experiment: { ...initialStateToUse.experiment, id: experimentId },
}
const [state, dispatch] = useLocalStorageReducer(
rootReducer,
diff --git a/packages/core/src/context/experiment/experiment-reducers.ts b/packages/core/src/context/experiment/experiment-reducers.ts
index 056ea831..af79e75d 100644
--- a/packages/core/src/context/experiment/experiment-reducers.ts
+++ b/packages/core/src/context/experiment/experiment-reducers.ts
@@ -7,7 +7,9 @@ import {
ScoreVariableType,
ValueVariableType,
experimentSchema,
+ scoreLabels,
scoreName,
+ scoreNames as scoreNamesState,
} from '@core/common/types'
import { produce } from 'immer'
import md5 from 'md5'
@@ -399,8 +401,9 @@ export const experimentReducer = produce(
if (state.scoreVariables.length < 2) {
state.scoreVariables.push({
- name: scoreName + ' 2',
- description: scoreName + ' 2',
+ name: scoreNamesState[1] ?? scoreName + ' 2',
+ label: scoreLabels[1],
+ description: scoreNamesState[1] ?? scoreName + ' 2',
enabled: true,
})
const scoreNames = state.scoreVariables.map(it => it.name)
diff --git a/packages/core/src/context/experiment/store.ts b/packages/core/src/context/experiment/store.ts
index bee6b2f3..cdc30452 100644
--- a/packages/core/src/context/experiment/store.ts
+++ b/packages/core/src/context/experiment/store.ts
@@ -1,5 +1,5 @@
import { versionInfo } from '@core/common'
-import { currentVersion, scoreName } from '@core/common/types'
+import { currentVersion, scoreLabels, scoreName } from '@core/common/types'
import { ExperimentType } from '@core/common/types'
export const emptyExperiment: ExperimentType = {
@@ -19,7 +19,8 @@ export const emptyExperiment: ExperimentType = {
scoreVariables: [
{
name: scoreName,
- description: scoreName,
+ label: scoreLabels[0],
+ description: '',
enabled: true,
},
],
@@ -58,3 +59,23 @@ export type State = {
export const initialState: State = {
experiment: emptyExperiment,
}
+
+export const initialStateMultiObjective: State = {
+ experiment: {
+ ...emptyExperiment,
+ scoreVariables: [
+ {
+ name: scoreName,
+ label: scoreLabels[0],
+ description: '',
+ enabled: true,
+ },
+ {
+ name: scoreName + ' 2',
+ label: scoreLabels[1],
+ description: '',
+ enabled: true,
+ },
+ ],
+ },
+}
diff --git a/packages/ui/src/features/core/editable-table/editable-table-expanded-row.tsx b/packages/ui/src/features/core/editable-table/editable-table-expanded-row.tsx
index 6d815e47..6d8a010e 100644
--- a/packages/ui/src/features/core/editable-table/editable-table-expanded-row.tsx
+++ b/packages/ui/src/features/core/editable-table/editable-table-expanded-row.tsx
@@ -76,7 +76,7 @@ export const EditableTableExpandedRow = ({
key={'header' + i}
className={classes.rowHeaderCell}
>
- {d.name}
+ {d.label ?? d.name}
))}
diff --git a/packages/ui/src/features/core/editable-table/editable-table.tsx b/packages/ui/src/features/core/editable-table/editable-table.tsx
index b7a08ddb..dccba503 100644
--- a/packages/ui/src/features/core/editable-table/editable-table.tsx
+++ b/packages/ui/src/features/core/editable-table/editable-table.tsx
@@ -157,9 +157,11 @@ export const EditableTable = ({
#
- {rows[0]?.dataPoints.map((item, index) => (
- {item.name}
- ))}
+ {rows
+ .find(r => r.isNew)
+ ?.dataPoints.map((item, index) => (
+ {item.label ?? item.name}
+ ))}
Edit
@@ -204,14 +206,16 @@ export const EditableTable = ({
- {rows[0]?.dataPoints.map((item, index) => (
-
- {item.name}
-
- ))}
+ {rows
+ .find(r => r.isNew)
+ ?.dataPoints.map((item, index) => (
+
+ {item.label ?? item.name}
+
+ ))}
Edit
diff --git a/packages/ui/src/features/core/editable-table/types.ts b/packages/ui/src/features/core/editable-table/types.ts
index f1e7ac1e..1d01b567 100644
--- a/packages/ui/src/features/core/editable-table/types.ts
+++ b/packages/ui/src/features/core/editable-table/types.ts
@@ -2,6 +2,7 @@ export type TableDataPointType = 'numeric' | 'options' | 'string' | 'rating'
export type TableDataPoint = {
name: string
+ label?: string
value?: string
tooltip?: string
options?: string[] | undefined
diff --git a/packages/ui/src/features/data-points/useDataPoints.ts b/packages/ui/src/features/data-points/useDataPoints.ts
index eb4eff95..0e324dba 100644
--- a/packages/ui/src/features/data-points/useDataPoints.ts
+++ b/packages/ui/src/features/data-points/useDataPoints.ts
@@ -18,7 +18,10 @@ export const useDataPoints = (
dataPoints: DataEntry[]
) => {
const scoreNames = useMemo(
- () => scoreVariables.filter(it => it.enabled).map(it => it.name),
+ () =>
+ scoreVariables
+ .filter(it => it.enabled)
+ .map(it => ({ name: it.name, label: it.label })),
[scoreVariables]
)
@@ -246,7 +249,7 @@ const mapDataPointToTableType = (
const buildEmptyRow = (
valueVariables: ValueVariableType[],
categoricalVariables: CategoricalVariableType[],
- scoreNames: string[]
+ scoreNames: { name: string; label?: string }[]
) => {
return {
dataPoints: buildCombinedVariables(valueVariables, categoricalVariables)
@@ -259,7 +262,8 @@ const buildEmptyRow = (
}))
.concat(
scoreNames.map((s, i) => ({
- name: s,
+ name: s.name,
+ label: s.label,
value: undefined,
options: undefined,
tooltip: undefined,
@@ -280,7 +284,7 @@ export const formatScore = (value: number | string) =>
const buildRows = (
valueVariables: ValueVariableType[],
categoricalVariables: CategoricalVariableType[],
- scoreNames: string[],
+ scoreNames: { name: string; label?: string }[],
dataPoints: DataEntry[]
) => {
const combinedVariables = buildCombinedVariables(
@@ -314,17 +318,20 @@ const buildRows = (
}
})
scoreNames.forEach((v, i) => {
- const existingData = item.data.find(d => d.name === v)
+ const existingData = item.data.find(d => d.name === v.name)
const type = i === 0 ? 'rating' : 'numeric'
+ const label = v.label
if (existingData !== undefined) {
vars.push({
...existingData,
value: formatScore(existingData.value),
+ label,
type,
})
} else {
vars.push({
- name: v,
+ name: v.name,
+ label,
value: undefined,
type,
})
diff --git a/sample-app/src/components/home/home.tsx b/sample-app/src/components/home/home.tsx
index b09f6cae..c7302270 100644
--- a/sample-app/src/components/home/home.tsx
+++ b/sample-app/src/components/home/home.tsx
@@ -107,6 +107,11 @@ export default function Home() {
navigate('/experiment/' + uuid())
}
+ const createNewExperimentMultiobjective = () => {
+ deleteExperiments()
+ navigate('/experiment/' + uuid() + '?multiObjective=true')
+ }
+
const openSavedExperiment = (key: string) => {
deleteExperiments()
console.log('TODO route to ' + key)
@@ -185,6 +190,19 @@ export default function Home() {
+
+
+
+
+ createNewExperimentMultiobjective()}
+ >
+
diff --git a/sample-app/src/experiment-container.tsx b/sample-app/src/experiment-container.tsx
index c1eb054e..6d0b90d5 100644
--- a/sample-app/src/experiment-container.tsx
+++ b/sample-app/src/experiment-container.tsx
@@ -3,13 +3,18 @@ import TabbedExperiment from '@sample/components/experiment/tabbed-experiment'
import Experiment from '@sample/components/experiment/experiment'
import DebugExperiment from '@sample/components/experiment/debug-experiment'
import { useGlobal } from '@sample/context/global'
-import { useParams } from 'react-router-dom'
+import { useParams, useSearchParams } from 'react-router-dom'
import { LoadingExperiment } from '@sample/components/experiment/loading-experiment'
import JsonEditor from '@sample/components/json-editor/json-editor'
import { useEffect } from 'react'
export default function ExperimentContainer() {
const { experimentId } = useParams()
+ const [searchParams] = useSearchParams()
+ const isMultiObjective = searchParams.get('multiObjective') === 'true'
+
+ console.log('ExperimentContainer render', { experimentId, isMultiObjective })
+
const {
dispatch,
state: { debug, showJsonEditor, focus },
@@ -34,6 +39,7 @@ export default function ExperimentContainer() {
experimentId={
Array.isArray(experimentId) ? (experimentId[0] ?? '') : experimentId
}
+ isMultiObjective={isMultiObjective}
>
{focus === 'legacy' ? : }
{debug && }
From 6caf8f470a9a3ce4041add2b2f6d93d91fa318b5 Mon Sep 17 00:00:00 2001
From: Jack Ord <36296721+j-or@users.noreply.github.com>
Date: Tue, 20 Jan 2026 12:37:04 +0000
Subject: [PATCH 02/11] Remove console log
---
sample-app/src/experiment-container.tsx | 2 --
1 file changed, 2 deletions(-)
diff --git a/sample-app/src/experiment-container.tsx b/sample-app/src/experiment-container.tsx
index 6d0b90d5..2584201f 100644
--- a/sample-app/src/experiment-container.tsx
+++ b/sample-app/src/experiment-container.tsx
@@ -13,8 +13,6 @@ export default function ExperimentContainer() {
const [searchParams] = useSearchParams()
const isMultiObjective = searchParams.get('multiObjective') === 'true'
- console.log('ExperimentContainer render', { experimentId, isMultiObjective })
-
const {
dispatch,
state: { debug, showJsonEditor, focus },
From f4fba354cbe578826dd0f04142c740a41ae5cdfb Mon Sep 17 00:00:00 2001
From: Jack Ord <36296721+j-or@users.noreply.github.com>
Date: Wed, 21 Jan 2026 09:09:05 +0000
Subject: [PATCH 03/11] Add changeset
---
.changeset/nervous-points-move.md | 7 +++++++
1 file changed, 7 insertions(+)
create mode 100644 .changeset/nervous-points-move.md
diff --git a/.changeset/nervous-points-move.md b/.changeset/nervous-points-move.md
new file mode 100644
index 00000000..0c7d19ae
--- /dev/null
+++ b/.changeset/nervous-points-move.md
@@ -0,0 +1,7 @@
+---
+'@boostv/process-optimizer-frontend-core': minor
+'@boostv/process-optimizer-frontend-ui': minor
+'@boostv/process-optimizer-frontend-sample-app': minor
+---
+
+Add multiobjective experiment type to UI
From 4732d708429750ba5adc47aea6cab3e7b35c03a0 Mon Sep 17 00:00:00 2001
From: Jack Ord <36296721+j-or@users.noreply.github.com>
Date: Wed, 21 Jan 2026 10:19:19 +0100
Subject: [PATCH 04/11] Change spelling
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
sample-app/src/components/home/home.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/sample-app/src/components/home/home.tsx b/sample-app/src/components/home/home.tsx
index c7302270..aa7d66a0 100644
--- a/sample-app/src/components/home/home.tsx
+++ b/sample-app/src/components/home/home.tsx
@@ -202,7 +202,7 @@ export default function Home() {
From a8616c93bb691a3bc20a1aab0cfa9505779f6828 Mon Sep 17 00:00:00 2001
From: Jack Ord <36296721+j-or@users.noreply.github.com>
Date: Wed, 21 Jan 2026 14:51:37 +0000
Subject: [PATCH 05/11] Add migration 18 with changes to score vars
---
packages/core/src/common/types/common.ts | 5 +-
.../common/util/converters/converters.test.ts | 22 +-
.../util/migration/data-formats/18.json | 103 +++++
.../common/util/migration/migration.test.ts | 146 +++++-
.../src/common/util/migration/migration.ts | 4 +-
.../common/util/migration/migrations/index.ts | 2 +
.../util/migration/migrations/migrateToV17.ts | 63 +--
.../util/migration/migrations/migrateToV18.ts | 46 ++
.../src/common/util/migration/schemas/18.json | 419 ++++++++++++++++++
.../context/experiment/experiment-reducers.ts | 5 +-
packages/core/src/context/experiment/store.ts | 8 +-
.../result-data/single-data-point.tsx | 4 +-
12 files changed, 765 insertions(+), 62 deletions(-)
create mode 100644 packages/core/src/common/util/migration/data-formats/18.json
create mode 100644 packages/core/src/common/util/migration/migrations/migrateToV18.ts
create mode 100644 packages/core/src/common/util/migration/schemas/18.json
diff --git a/packages/core/src/common/types/common.ts b/packages/core/src/common/types/common.ts
index 46059eb9..4469257a 100644
--- a/packages/core/src/common/types/common.ts
+++ b/packages/core/src/common/types/common.ts
@@ -3,10 +3,9 @@ import { z } from 'zod'
// Change the current version when doing structural
// changes to any types belonging to ExperimentType
-export const currentVersion = '17'
+export const currentVersion = '18'
-export const scoreName = 'Quality (0-5)'
-export const scoreNames = [scoreName, scoreName + ' 2']
+export const scoreNames = ['Quality', 'Cost']
// Label is shown in UI, name is used in data
export const scoreLabels = ['Quality (0-5)', 'Cost (0-5)']
diff --git a/packages/core/src/common/util/converters/converters.test.ts b/packages/core/src/common/util/converters/converters.test.ts
index 19428e3a..e46fe583 100644
--- a/packages/core/src/common/util/converters/converters.test.ts
+++ b/packages/core/src/common/util/converters/converters.test.ts
@@ -6,7 +6,7 @@ import {
ExperimentType,
ScoreVariableType,
ValueVariableType,
- scoreName,
+ scoreNames,
} from '@core/common/types'
import { initialState } from '@core/context'
import {
@@ -26,14 +26,14 @@ describe('converters', () => {
{ type: 'numeric', name: 'Peber', value: 982 },
{ type: 'numeric', name: 'Hvedemel', value: 632 },
{ type: 'categorical', name: 'Kunde', value: 'Mus' },
- { type: 'score', name: scoreName, value: 0.1 },
+ { type: 'score', name: scoreNames[0] ?? '', value: 0.1 },
] satisfies DataPointType[],
[
{ type: 'numeric', name: 'Sukker', value: 15 },
{ type: 'numeric', name: 'Peber', value: 123 },
{ type: 'numeric', name: 'Hvedemel', value: 324 },
{ type: 'categorical', name: 'Kunde', value: 'Ræv' },
- { type: 'score', name: scoreName, value: 0.2 },
+ { type: 'score', name: scoreNames[0] ?? '', value: 0.2 },
] satisfies DataPointType[],
].map((data, idx) => ({
meta: { enabled: true, id: idx + 1, valid: true },
@@ -45,16 +45,16 @@ describe('converters', () => {
{ type: 'numeric', name: 'Peber', value: 982 },
{ type: 'numeric', name: 'Hvedemel', value: 632 },
{ type: 'categorical', name: 'Kunde', value: 'Mus' },
- { type: 'score', name: scoreName, value: 0.1 },
- { type: 'score', name: scoreName + ' 2', value: 0.3 },
+ { type: 'score', name: scoreNames[0] ?? '', value: 0.1 },
+ { type: 'score', name: scoreNames[1] ?? '', value: 0.3 },
] satisfies DataPointType[],
[
{ type: 'numeric', name: 'Sukker', value: 15 },
{ type: 'numeric', name: 'Peber', value: 123 },
{ type: 'numeric', name: 'Hvedemel', value: 324 },
{ type: 'categorical', name: 'Kunde', value: 'Ræv' },
- { type: 'score', name: scoreName, value: 0.2 },
- { type: 'score', name: scoreName + ' 2', value: 0.4 },
+ { type: 'score', name: scoreNames[0] ?? '', value: 0.2 },
+ { type: 'score', name: scoreNames[1] ?? '', value: 0.4 },
] satisfies DataPointType[],
].map((data, idx) => ({
meta: { enabled: true, id: idx + 1, valid: true },
@@ -248,8 +248,8 @@ describe('converters', () => {
sampleExperiment.categoricalVariables,
sampleExperiment.valueVariables,
[
- { name: scoreName, description: '', enabled: true },
- { name: scoreName + ' 2', description: '', enabled: true },
+ { name: scoreNames[0] ?? '', description: '', enabled: true },
+ { name: scoreNames[1] ?? '', description: '', enabled: true },
],
sampleMultiObjectiveDataPoints
)
@@ -265,8 +265,8 @@ describe('converters', () => {
sampleExperiment.categoricalVariables,
sampleExperiment.valueVariables,
[
- { name: scoreName, description: '', enabled: true },
- { name: scoreName + ' 2', description: '', enabled: false },
+ { name: scoreNames[0] ?? '', description: '', enabled: true },
+ { name: scoreNames[1] ?? '', description: '', enabled: false },
],
sampleMultiObjectiveDataPoints
)
diff --git a/packages/core/src/common/util/migration/data-formats/18.json b/packages/core/src/common/util/migration/data-formats/18.json
new file mode 100644
index 00000000..b25513dc
--- /dev/null
+++ b/packages/core/src/common/util/migration/data-formats/18.json
@@ -0,0 +1,103 @@
+{
+ "id": "1234",
+ "changedSinceLastEvaluation": true,
+ "lastEvaluationHash": "never-calculated",
+ "info": {
+ "dataFormatVersion": "18",
+ "swVersion": "v1.2.0-16",
+ "name": "Cake",
+ "description": "Yummy",
+ "version": 0,
+ "extras": {}
+ },
+ "categoricalVariables": [
+ {
+ "name": "Icing",
+ "description": "Sugary",
+ "options": ["White", "Brown"],
+ "enabled": true
+ }
+ ],
+ "valueVariables": [
+ {
+ "name": "name1",
+ "description": "desc1",
+ "min": 10,
+ "max": 100,
+ "type": "discrete",
+ "enabled": true
+ },
+ {
+ "name": "name2",
+ "description": "desc2",
+ "min": 10.2,
+ "max": 100.3,
+ "type": "continuous",
+ "enabled": true
+ }
+ ],
+ "scoreVariables": [
+ {
+ "name": "Quality",
+ "label": "Quality (0-5)",
+ "description": "",
+ "enabled": true
+ }
+ ],
+ "constraints": [
+ {
+ "type": "sum",
+ "dimensions": [],
+ "value": 0
+ }
+ ],
+ "optimizerConfig": {
+ "baseEstimator": "GP",
+ "acqFunc": "EI",
+ "initialPoints": 3,
+ "kappa": 1.96,
+ "xi": 0.01
+ },
+ "results": {
+ "id": "",
+ "next": [[]],
+ "plots": [],
+ "pickled": "",
+ "expectedMinimum": [],
+ "extras": {}
+ },
+ "dataPoints": [
+ {
+ "meta": {
+ "enabled": true,
+ "valid": true,
+ "id": 1
+ },
+ "data": [
+ {
+ "type": "categorical",
+ "name": "Icing",
+ "value": "Brown"
+ },
+ {
+ "type": "numeric",
+ "name": "name1",
+ "value": 10
+ },
+ {
+ "type": "numeric",
+ "name": "name2",
+ "value": 10.2
+ },
+ {
+ "type": "score",
+ "name": "Quality",
+ "value": 0.5
+ }
+ ]
+ }
+ ],
+ "extras": {
+ "experimentSuggestionCount": 1
+ }
+}
diff --git a/packages/core/src/common/util/migration/migration.test.ts b/packages/core/src/common/util/migration/migration.test.ts
index a6016a59..3b297f3b 100644
--- a/packages/core/src/common/util/migration/migration.test.ts
+++ b/packages/core/src/common/util/migration/migration.test.ts
@@ -1,6 +1,7 @@
import { describe, expect, it } from 'vitest'
import { JSONSchemaFaker } from 'json-schema-faker'
import { migrate, _migrate, MIGRATIONS } from './migration'
+import version18 from './data-formats/18.json'
import version17 from './data-formats/17.json'
import version16 from './data-formats/16.json'
import version3 from './data-formats/3.json'
@@ -17,10 +18,12 @@ import {
ExperimentType,
ScoreVariableType,
experimentSchema,
- scoreName,
+ scoreLabels,
+ scoreNames,
} from '@core/common/types'
import { storeLatestSchema, loadTestData } from './test-utils'
-import { migrateToV17 } from './migrations/migrateToV17'
+import { scoreName17 } from './migrations/migrateToV17'
+import { migrateToV17, migrateToV18 } from './migrations'
describe('Migration of data format', () => {
storeLatestSchema()
@@ -170,7 +173,7 @@ describe('Migration of data format', () => {
{
name: 'score2',
description: 'score',
- enabled: true,
+ enabled: false,
},
] satisfies ScoreVariableType[]
@@ -184,13 +187,13 @@ describe('Migration of data format', () => {
} as unknown as ExperimentType
expect(migrateToV17(experiment16).scoreVariables).toEqual([
{
- name: scoreName,
- description: scoreName,
+ name: scoreName17,
+ description: scoreName17,
enabled: scoreVariables[0]?.enabled,
},
{
- name: scoreName + ' 2',
- description: scoreName,
+ name: scoreName17 + ' 2',
+ description: scoreName17,
enabled: scoreVariables[1]?.enabled,
},
])
@@ -243,7 +246,7 @@ describe('Migration of data format', () => {
},
{
type: 'score',
- name: scoreName,
+ name: scoreName17,
value: 0.5,
},
].concat(
@@ -251,7 +254,7 @@ describe('Migration of data format', () => {
? [
{
type: 'score',
- name: scoreName + ' 2',
+ name: scoreName17 + ' 2',
value: 2,
},
]
@@ -264,14 +267,133 @@ describe('Migration of data format', () => {
)
})
+ describe('migrateToV18', () => {
+ const scoreVarsMultiObjective = [
+ {
+ name: 'Quality (0-5)',
+ description: 'Quality (0-5)',
+ enabled: true,
+ },
+ {
+ name: 'Quality (0-5) 2',
+ description: 'Quality (0-5) 2',
+ enabled: true,
+ },
+ ] satisfies ScoreVariableType[]
+
+ const scoreVarsMultiObjectiveDisabled = [
+ {
+ name: 'Quality (0-5)',
+ description: 'Quality (0-5)',
+ enabled: true,
+ },
+ {
+ name: 'Quality (0-5) 2',
+ description: 'Quality (0-5) 2',
+ enabled: false,
+ },
+ ] satisfies ScoreVariableType[]
+
+ it.each([
+ ['multiobjective, all enabled', scoreVarsMultiObjective],
+ ['multiobjective, one disabled', scoreVarsMultiObjectiveDisabled],
+ ])('scoreVariables should be updated, %s', (_, scoreVariables) => {
+ const experiment17 = {
+ ...version17,
+ scoreVariables,
+ } as unknown as ExperimentType
+ expect(migrateToV18(experiment17).scoreVariables).toEqual([
+ {
+ name: scoreNames[0],
+ label: scoreLabels[0] ?? '',
+ description: '',
+ enabled: scoreVariables[0]?.enabled,
+ },
+ {
+ name: scoreNames[1],
+ label: scoreLabels[1] ?? '',
+ description: '',
+ enabled: scoreVariables[1]?.enabled,
+ },
+ ])
+ })
+
+ const dataPointsSingle = [...version17.dataPoints]
+ const dataPointsMulti = [...version17.dataPoints].map(dp => ({
+ ...dp,
+ data: [...dp.data].concat([
+ {
+ type: 'score',
+ name: scoreNames[1] ?? '',
+ value: 2,
+ },
+ ]),
+ }))
+
+ it.each([
+ ['one score exists', dataPointsSingle, false],
+ ['two scores exist', dataPointsMulti, true],
+ ])(
+ 'data points should be renamed, %s',
+ (_, dataPoints, isMultiObjective) => {
+ const experiment17 = {
+ ...version17,
+ dataPoints,
+ } as unknown as ExperimentType
+ const actual = [
+ {
+ meta: {
+ enabled: true,
+ valid: true,
+ id: 1,
+ },
+ data: [
+ {
+ type: 'categorical',
+ name: 'Icing',
+ value: 'Brown',
+ },
+ {
+ type: 'numeric',
+ name: 'name1',
+ value: 10,
+ },
+ {
+ type: 'numeric',
+ name: 'name2',
+ value: 10.2,
+ },
+ {
+ type: 'score',
+ name: scoreNames[0],
+ value: 0.5,
+ },
+ ].concat(
+ isMultiObjective
+ ? [
+ {
+ type: 'score',
+ name: scoreNames[1],
+ value: 2,
+ },
+ ]
+ : []
+ ),
+ },
+ ]
+ expect(migrateToV18(experiment17).dataPoints).toEqual(actual)
+ }
+ )
+ })
+
describe('experiment properties', () => {
- //TODO: More/better tests - maybe this can be mabe obsolete by schema testing
+ //TODO: More/better tests - maybe this can be made obsolete by schema testing
it('newest data format json should match default empty experiment', () => {
expect(Object.keys(emptyExperiment).length).toBe(
- Object.keys(version17).length
+ Object.keys(version18).length
)
Object.keys(emptyExperiment).forEach(p =>
- expect(version17).toHaveProperty(p)
+ expect(version18).toHaveProperty(p)
)
})
})
diff --git a/packages/core/src/common/util/migration/migration.ts b/packages/core/src/common/util/migration/migration.ts
index 3b87d0e3..7c37fc87 100644
--- a/packages/core/src/common/util/migration/migration.ts
+++ b/packages/core/src/common/util/migration/migration.ts
@@ -17,8 +17,9 @@ import {
migrateToV14,
migrateToV15,
migrateToV16,
+ migrateToV17,
+ migrateToV18,
} from './migrations'
-import { migrateToV17 } from './migrations/migrateToV17'
export const migrate = (json: any): ExperimentType => {
const migrated = _migrate(
@@ -100,4 +101,5 @@ export const MIGRATIONS: Migration[] = [
{ version: '15', converter: migrateToV15 },
{ version: '16', converter: migrateToV16 },
{ version: '17', converter: migrateToV17 },
+ { version: '18', converter: migrateToV18 },
]
diff --git a/packages/core/src/common/util/migration/migrations/index.ts b/packages/core/src/common/util/migration/migrations/index.ts
index dfc49358..ad863e51 100644
--- a/packages/core/src/common/util/migration/migrations/index.ts
+++ b/packages/core/src/common/util/migration/migrations/index.ts
@@ -12,3 +12,5 @@ export { migrateToV13 } from './migrateToV13'
export { migrateToV14 } from './migrateToV14'
export { migrateToV15 } from './migrateToV15'
export { migrateToV16 } from './migrateToV16'
+export { migrateToV17 } from './migrateToV17'
+export { migrateToV18 } from './migrateToV18'
diff --git a/packages/core/src/common/util/migration/migrations/migrateToV17.ts b/packages/core/src/common/util/migration/migrations/migrateToV17.ts
index 6f86b312..326f03d2 100644
--- a/packages/core/src/common/util/migration/migrations/migrateToV17.ts
+++ b/packages/core/src/common/util/migration/migrations/migrateToV17.ts
@@ -1,33 +1,44 @@
-import { ExperimentType, scoreName } from '@core/common/types'
+import { ExperimentType } from '@core/common/types'
import { produce } from 'immer'
+export const scoreName17 = 'Quality (0-5)'
+
export const migrateToV17 = (json: ExperimentType): ExperimentType => {
// renames all scores scores to ["scoreName", "scoreName 2"...]
- return produce(json, draft => {
- draft.info.dataFormatVersion = '17'
- draft.scoreVariables = json.scoreVariables.map((s, i) => ({
- name: getScoreName(scoreName, i),
- description: scoreName,
- enabled: s.enabled,
- }))
- draft.dataPoints = json.dataPoints.map(dp => {
- let scoreIndex = 0
- return {
- ...dp,
- data: dp.data.map(d => {
- let newName = d.name
- if (d.type === 'score') {
- newName = getScoreName(scoreName, scoreIndex)
- scoreIndex++
- }
- return {
- ...d,
- name: newName,
- }
- }),
- }
- })
- })
+ return produce(
+ json,
+ (draft: {
+ info: { dataFormatVersion: string }
+ scoreVariables: { name: string; description: string; enabled: boolean }[]
+ dataPoints: {
+ data: { name: string; type: string }[]
+ }[]
+ }) => {
+ draft.info.dataFormatVersion = '17'
+ draft.scoreVariables = json.scoreVariables.map((s, i) => ({
+ name: getScoreName(scoreName17, i),
+ description: scoreName17,
+ enabled: s.enabled,
+ }))
+ draft.dataPoints = json.dataPoints.map(dp => {
+ let scoreIndex = 0
+ return {
+ ...dp,
+ data: dp.data.map(d => {
+ let newName = d.name
+ if (d.type === 'score') {
+ newName = getScoreName(scoreName17, scoreIndex)
+ scoreIndex++
+ }
+ return {
+ ...d,
+ name: newName,
+ }
+ }),
+ }
+ })
+ }
+ )
}
const getScoreName = (name: string, index: number) =>
diff --git a/packages/core/src/common/util/migration/migrations/migrateToV18.ts b/packages/core/src/common/util/migration/migrations/migrateToV18.ts
new file mode 100644
index 00000000..3b26fb8a
--- /dev/null
+++ b/packages/core/src/common/util/migration/migrations/migrateToV18.ts
@@ -0,0 +1,46 @@
+import { ExperimentType, scoreLabels, scoreNames } from '@core/common/types'
+import { produce } from 'immer'
+
+// rename scores, change description to empty string, add labels
+export const migrateToV18 = (json: ExperimentType): ExperimentType => {
+ return produce(
+ json,
+ (draft: {
+ info: { dataFormatVersion: string }
+ scoreVariables: {
+ name: string
+ label?: string
+ description: string
+ enabled: boolean
+ }[]
+ dataPoints: {
+ data: { name: string; type: string }[]
+ }[]
+ }) => {
+ draft.info.dataFormatVersion = '18'
+ draft.scoreVariables = json.scoreVariables.map((s, i) => ({
+ name: scoreNames[i] ?? '',
+ label: scoreLabels[i] ?? '',
+ description: '',
+ enabled: s.enabled,
+ }))
+ draft.dataPoints = json.dataPoints.map(dp => {
+ let scoreIndex = 0
+ return {
+ ...dp,
+ data: dp.data.map(d => {
+ let newName = d.name
+ if (d.type === 'score') {
+ newName = scoreNames[scoreIndex] ?? ''
+ scoreIndex++
+ }
+ return {
+ ...d,
+ name: newName,
+ }
+ }),
+ }
+ })
+ }
+ )
+}
diff --git a/packages/core/src/common/util/migration/schemas/18.json b/packages/core/src/common/util/migration/schemas/18.json
new file mode 100644
index 00000000..6a2af9c1
--- /dev/null
+++ b/packages/core/src/common/util/migration/schemas/18.json
@@ -0,0 +1,419 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "lastEvaluationHash": {
+ "type": "string"
+ },
+ "changedSinceLastEvaluation": {
+ "type": "boolean"
+ },
+ "info": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "swVersion": {
+ "type": "string"
+ },
+ "dataFormatVersion": {
+ "type": "string",
+ "const": "18"
+ },
+ "version": {
+ "type": "number"
+ },
+ "extras": {
+ "type": "object",
+ "propertyNames": {
+ "type": "string"
+ },
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "name",
+ "description",
+ "swVersion",
+ "dataFormatVersion",
+ "version",
+ "extras"
+ ],
+ "additionalProperties": false
+ },
+ "extras": {
+ "type": "object",
+ "propertyNames": {
+ "type": "string"
+ },
+ "additionalProperties": {}
+ },
+ "categoricalVariables": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "options": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "enabled": {
+ "type": "boolean"
+ }
+ },
+ "required": [
+ "name",
+ "description",
+ "options",
+ "enabled"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "valueVariables": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "anyOf": [
+ {
+ "type": "string",
+ "const": "discrete"
+ },
+ {
+ "type": "string",
+ "const": "continuous"
+ }
+ ]
+ },
+ "name": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "min": {
+ "type": "number"
+ },
+ "max": {
+ "type": "number"
+ },
+ "enabled": {
+ "type": "boolean"
+ }
+ },
+ "required": [
+ "type",
+ "name",
+ "description",
+ "min",
+ "max",
+ "enabled"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "scoreVariables": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "label": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "enabled": {
+ "type": "boolean"
+ }
+ },
+ "required": [
+ "name",
+ "description",
+ "enabled"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "constraints": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "const": "sum"
+ },
+ "value": {
+ "type": "number"
+ },
+ "dimensions": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ },
+ "required": [
+ "type",
+ "value",
+ "dimensions"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "optimizerConfig": {
+ "type": "object",
+ "properties": {
+ "baseEstimator": {
+ "type": "string"
+ },
+ "acqFunc": {
+ "type": "string"
+ },
+ "initialPoints": {
+ "type": "number"
+ },
+ "kappa": {
+ "type": "number"
+ },
+ "xi": {
+ "type": "number"
+ }
+ },
+ "required": [
+ "baseEstimator",
+ "acqFunc",
+ "initialPoints",
+ "kappa",
+ "xi"
+ ],
+ "additionalProperties": false
+ },
+ "results": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "plots": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "plot": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "id",
+ "plot"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "next": {
+ "type": "array",
+ "items": {
+ "type": "array",
+ "items": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "string"
+ }
+ ]
+ }
+ }
+ },
+ "pickled": {
+ "type": "string"
+ },
+ "expectedMinimum": {
+ "type": "array",
+ "items": {
+ "anyOf": [
+ {
+ "type": "array",
+ "items": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "string"
+ }
+ ]
+ }
+ },
+ {
+ "type": "number"
+ }
+ ]
+ }
+ },
+ "extras": {
+ "type": "object",
+ "propertyNames": {
+ "type": "string"
+ },
+ "additionalProperties": {}
+ }
+ },
+ "required": [
+ "id",
+ "plots",
+ "next",
+ "pickled",
+ "expectedMinimum",
+ "extras"
+ ],
+ "additionalProperties": false
+ },
+ "dataPoints": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "meta": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "number"
+ },
+ "enabled": {
+ "type": "boolean"
+ },
+ "valid": {
+ "type": "boolean"
+ },
+ "description": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "id",
+ "enabled",
+ "valid"
+ ],
+ "additionalProperties": false
+ },
+ "data": {
+ "type": "array",
+ "items": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "const": "numeric"
+ },
+ "name": {
+ "type": "string"
+ },
+ "value": {
+ "type": "number"
+ }
+ },
+ "required": [
+ "type",
+ "name",
+ "value"
+ ],
+ "additionalProperties": false
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "const": "categorical"
+ },
+ "name": {
+ "type": "string"
+ },
+ "value": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "type",
+ "name",
+ "value"
+ ],
+ "additionalProperties": false
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "const": "score"
+ },
+ "name": {
+ "type": "string"
+ },
+ "value": {
+ "type": "number"
+ }
+ },
+ "required": [
+ "type",
+ "name",
+ "value"
+ ],
+ "additionalProperties": false
+ }
+ ]
+ }
+ }
+ },
+ "required": [
+ "meta",
+ "data"
+ ],
+ "additionalProperties": false
+ }
+ }
+ },
+ "required": [
+ "id",
+ "changedSinceLastEvaluation",
+ "info",
+ "extras",
+ "categoricalVariables",
+ "valueVariables",
+ "scoreVariables",
+ "constraints",
+ "optimizerConfig",
+ "results",
+ "dataPoints"
+ ],
+ "additionalProperties": false
+}
\ No newline at end of file
diff --git a/packages/core/src/context/experiment/experiment-reducers.ts b/packages/core/src/context/experiment/experiment-reducers.ts
index af79e75d..fb3fe682 100644
--- a/packages/core/src/context/experiment/experiment-reducers.ts
+++ b/packages/core/src/context/experiment/experiment-reducers.ts
@@ -8,7 +8,6 @@ import {
ValueVariableType,
experimentSchema,
scoreLabels,
- scoreName,
scoreNames as scoreNamesState,
} from '@core/common/types'
import { produce } from 'immer'
@@ -401,9 +400,9 @@ export const experimentReducer = produce(
if (state.scoreVariables.length < 2) {
state.scoreVariables.push({
- name: scoreNamesState[1] ?? scoreName + ' 2',
+ name: scoreNamesState[1] ?? '',
label: scoreLabels[1],
- description: scoreNamesState[1] ?? scoreName + ' 2',
+ description: '',
enabled: true,
})
const scoreNames = state.scoreVariables.map(it => it.name)
diff --git a/packages/core/src/context/experiment/store.ts b/packages/core/src/context/experiment/store.ts
index cdc30452..a5263ab1 100644
--- a/packages/core/src/context/experiment/store.ts
+++ b/packages/core/src/context/experiment/store.ts
@@ -1,5 +1,5 @@
import { versionInfo } from '@core/common'
-import { currentVersion, scoreLabels, scoreName } from '@core/common/types'
+import { currentVersion, scoreLabels, scoreNames } from '@core/common/types'
import { ExperimentType } from '@core/common/types'
export const emptyExperiment: ExperimentType = {
@@ -18,7 +18,7 @@ export const emptyExperiment: ExperimentType = {
valueVariables: [],
scoreVariables: [
{
- name: scoreName,
+ name: scoreNames[0] ?? '',
label: scoreLabels[0],
description: '',
enabled: true,
@@ -65,13 +65,13 @@ export const initialStateMultiObjective: State = {
...emptyExperiment,
scoreVariables: [
{
- name: scoreName,
+ name: scoreNames[0] ?? '',
label: scoreLabels[0],
description: '',
enabled: true,
},
{
- name: scoreName + ' 2',
+ name: scoreNames[1] ?? '',
label: scoreLabels[1],
description: '',
enabled: true,
diff --git a/packages/ui/src/features/result-data/single-data-point.tsx b/packages/ui/src/features/result-data/single-data-point.tsx
index 95887006..4726bcdd 100644
--- a/packages/ui/src/features/result-data/single-data-point.tsx
+++ b/packages/ui/src/features/result-data/single-data-point.tsx
@@ -14,7 +14,7 @@ import {
import useStyles from './single-data-point.style'
import { PNGPlot } from '@boostv/process-optimizer-frontend-plots'
import { useState } from 'react'
-import { scoreName } from '@boostv/process-optimizer-frontend-core'
+import { scoreNames } from '@boostv/process-optimizer-frontend-core'
interface SingleDataPointProps {
title: string
@@ -47,7 +47,7 @@ export const SingleDataPoint = ({
{headers
- .concat([scoreName + ' (95 % credibility interval)'])
+ .concat([(scoreNames[0] ?? '') + ' (95 % credibility interval)'])
.map((h, idx) => (
{h}
From 4ae7209f2e4b568bbaaa8fd7456cddaabef5c4e9 Mon Sep 17 00:00:00 2001
From: Jack Ord <36296721+j-or@users.noreply.github.com>
Date: Thu, 22 Jan 2026 13:52:25 +0000
Subject: [PATCH 06/11] Change score names to const
---
packages/core/src/common/types/common.ts | 6 +-
.../__snapshots__/converters.test.ts.snap | 2 +-
.../common/util/converters/converters.test.ts | 95 ++++-----
.../src/common/util/converters/converters.ts | 5 +-
.../util/migration/data-formats/18.json | 4 +-
.../common/util/migration/migration.test.ts | 180 ++++++++----------
.../util/migration/migrations/migrateToV18.ts | 87 ++++++---
.../src/common/util/migration/schemas/18.json | 12 +-
.../context/experiment/experiment-reducers.ts | 10 +-
.../src/context/experiment/reducers.test.ts | 48 +++--
packages/core/src/context/experiment/store.ts | 12 +-
.../core/src/context/experiment/test-utils.ts | 12 +-
.../src/context/experiment/validation.test.ts | 23 ++-
.../result-data/single-data-point.tsx | 10 +-
14 files changed, 267 insertions(+), 239 deletions(-)
diff --git a/packages/core/src/common/types/common.ts b/packages/core/src/common/types/common.ts
index 4469257a..101bf03e 100644
--- a/packages/core/src/common/types/common.ts
+++ b/packages/core/src/common/types/common.ts
@@ -5,7 +5,7 @@ import { z } from 'zod'
export const currentVersion = '18'
-export const scoreNames = ['Quality', 'Cost']
+export const scoreNames = ['quality', 'cost'] as const
// Label is shown in UI, name is used in data
export const scoreLabels = ['Quality (0-5)', 'Cost (0-5)']
@@ -44,8 +44,8 @@ const valueVariableSchema = z.object({
})
const scoreVariableSchema = z.object({
- name: z.string(),
- label: z.optional(z.string()),
+ name: z.literal(scoreNames[0]).or(z.literal(scoreNames[1])),
+ label: z.string(),
description: z.string(),
enabled: z.boolean(),
})
diff --git a/packages/core/src/common/util/converters/__snapshots__/converters.test.ts.snap b/packages/core/src/common/util/converters/__snapshots__/converters.test.ts.snap
index 13ddf97c..9f46277c 100644
--- a/packages/core/src/common/util/converters/__snapshots__/converters.test.ts.snap
+++ b/packages/core/src/common/util/converters/__snapshots__/converters.test.ts.snap
@@ -2,7 +2,7 @@
exports[`converters > csvToDataPoints > should fail if duplicate ids are supplied 1`] = `[Error: Duplicate or missing IDs detected in input data]`;
-exports[`converters > csvToDataPoints > should fail if header is missing 1`] = `[Error: Headers does not match Sukker,Peber,Hvedemel,Kunde,score !== Sukker,Hvedemel,Kunde,score]`;
+exports[`converters > csvToDataPoints > should fail if header is missing 1`] = `[Error: Headers does not match Sukker,Peber,Hvedemel,Kunde,quality !== Sukker,Hvedemel,Kunde,quality]`;
exports[`converters > csvToDataPoints > should fail if types does not match schema 1`] = `
[ZodError: [
diff --git a/packages/core/src/common/util/converters/converters.test.ts b/packages/core/src/common/util/converters/converters.test.ts
index e46fe583..9f98b9fc 100644
--- a/packages/core/src/common/util/converters/converters.test.ts
+++ b/packages/core/src/common/util/converters/converters.test.ts
@@ -26,14 +26,14 @@ describe('converters', () => {
{ type: 'numeric', name: 'Peber', value: 982 },
{ type: 'numeric', name: 'Hvedemel', value: 632 },
{ type: 'categorical', name: 'Kunde', value: 'Mus' },
- { type: 'score', name: scoreNames[0] ?? '', value: 0.1 },
+ { type: 'score', name: scoreNames[0], value: 0.1 },
] satisfies DataPointType[],
[
{ type: 'numeric', name: 'Sukker', value: 15 },
{ type: 'numeric', name: 'Peber', value: 123 },
{ type: 'numeric', name: 'Hvedemel', value: 324 },
{ type: 'categorical', name: 'Kunde', value: 'Ræv' },
- { type: 'score', name: scoreNames[0] ?? '', value: 0.2 },
+ { type: 'score', name: scoreNames[0], value: 0.2 },
] satisfies DataPointType[],
].map((data, idx) => ({
meta: { enabled: true, id: idx + 1, valid: true },
@@ -45,16 +45,16 @@ describe('converters', () => {
{ type: 'numeric', name: 'Peber', value: 982 },
{ type: 'numeric', name: 'Hvedemel', value: 632 },
{ type: 'categorical', name: 'Kunde', value: 'Mus' },
- { type: 'score', name: scoreNames[0] ?? '', value: 0.1 },
- { type: 'score', name: scoreNames[1] ?? '', value: 0.3 },
+ { type: 'score', name: scoreNames[0], value: 0.1 },
+ { type: 'score', name: scoreNames[1], value: 0.3 },
] satisfies DataPointType[],
[
{ type: 'numeric', name: 'Sukker', value: 15 },
{ type: 'numeric', name: 'Peber', value: 123 },
{ type: 'numeric', name: 'Hvedemel', value: 324 },
{ type: 'categorical', name: 'Kunde', value: 'Ræv' },
- { type: 'score', name: scoreNames[0] ?? '', value: 0.2 },
- { type: 'score', name: scoreNames[1] ?? '', value: 0.4 },
+ { type: 'score', name: scoreNames[0], value: 0.2 },
+ { type: 'score', name: scoreNames[1], value: 0.4 },
] satisfies DataPointType[],
].map((data, idx) => ({
meta: { enabled: true, id: idx + 1, valid: true },
@@ -248,8 +248,8 @@ describe('converters', () => {
sampleExperiment.categoricalVariables,
sampleExperiment.valueVariables,
[
- { name: scoreNames[0] ?? '', description: '', enabled: true },
- { name: scoreNames[1] ?? '', description: '', enabled: true },
+ { name: scoreNames[0], description: '', enabled: true, label: '' },
+ { name: scoreNames[1], description: '', enabled: true, label: '' },
],
sampleMultiObjectiveDataPoints
)
@@ -265,8 +265,8 @@ describe('converters', () => {
sampleExperiment.categoricalVariables,
sampleExperiment.valueVariables,
[
- { name: scoreNames[0] ?? '', description: '', enabled: true },
- { name: scoreNames[1] ?? '', description: '', enabled: false },
+ { name: scoreNames[0], description: '', enabled: true, label: '' },
+ { name: scoreNames[1], description: '', enabled: false, label: '' },
],
sampleMultiObjectiveDataPoints
)
@@ -397,7 +397,7 @@ describe('converters', () => {
},
{
type: 'score',
- name: 'score',
+ name: scoreNames[0],
value: 1,
},
],
@@ -431,14 +431,13 @@ describe('converters', () => {
},
{
type: 'score',
- name: 'score',
+ name: scoreNames[0],
value: 2,
},
],
},
]
- const expected =
- 'id;Sukker;Peber;Hvedemel;Kunde;score;enabled;valid\n1;28;982;632;Mus;1;true;true\n3;15;986;5;Mus;2;false;true'
+ const expected = `id;Sukker;Peber;Hvedemel;Kunde;${scoreNames[0]};enabled;valid\n1;28;982;632;Mus;1;true;true\n3;15;986;5;Mus;2;false;true`
const actual = dataPointsToCSV(input)
expect(actual).toEqual(expected)
})
@@ -469,7 +468,7 @@ describe('converters', () => {
},
{
type: 'score',
- name: 'score',
+ name: scoreNames[0],
value: 1,
},
],
@@ -503,14 +502,13 @@ describe('converters', () => {
},
{
type: 'score',
- name: 'score',
+ name: scoreNames[0],
value: 2,
},
],
},
]
- const expected =
- 'id;Sukker;Peber;Hvedemel;Kunde;score;enabled;valid\n1;28;;632;Mus;1;true;true\n3;15;986;5;Mus;2;false;true'
+ const expected = `id;Sukker;Peber;Hvedemel;Kunde;${scoreNames[0]};enabled;valid\n1;28;;632;Mus;1;true;true\n3;15;986;5;Mus;2;false;true`
const actual = dataPointsToCSV(input)
expect(actual).toEqual(expected)
})
@@ -531,7 +529,7 @@ describe('converters', () => {
},
{
type: 'score',
- name: 'score',
+ name: scoreNames[0],
value: 2,
},
],
@@ -550,7 +548,7 @@ describe('converters', () => {
},
{
type: 'score',
- name: 'score',
+ name: scoreNames[0],
value: 0,
},
],
@@ -569,14 +567,13 @@ describe('converters', () => {
},
{
type: 'score',
- name: 'score',
+ name: scoreNames[0],
value: 1,
},
],
},
]
- const expected =
- 'id;Sukker;score;enabled;valid\n3;282;2;true;true\n1;280;0;true;true\n2;281;1;true;true'
+ const expected = `id;Sukker;${scoreNames[0]};enabled;valid\n3;282;2;true;true\n1;280;0;true;true\n2;281;1;true;true`
const actual = dataPointsToCSV(input)
expect(actual).toEqual(expected)
})
@@ -618,7 +615,12 @@ describe('converters', () => {
},
]
const scoreVariables: ScoreVariableType[] = [
- { name: 'score', description: '', enabled: true },
+ {
+ name: scoreNames[0],
+ label: 'qualitylabel',
+ description: '',
+ enabled: true,
+ },
]
const sampleDataPoints: DataEntry[] = [
@@ -647,7 +649,7 @@ describe('converters', () => {
},
{
type: 'score',
- name: 'score',
+ name: scoreNames[0],
value: 1,
},
],
@@ -677,7 +679,7 @@ describe('converters', () => {
},
{
type: 'score',
- name: 'score',
+ name: scoreNames[0],
value: 2,
},
],
@@ -692,8 +694,7 @@ describe('converters', () => {
})
it('should convert known value', () => {
- const input =
- 'id;Sukker;Peber;Hvedemel;Kunde;score;enabled;valid\n1;28;982;632;Mus;1;true;true\n2;15;986;5;Mus;2;false;true'
+ const input = `id;Sukker;Peber;Hvedemel;Kunde;${scoreNames[0]};enabled;valid\n1;28;982;632;Mus;1;true;true\n2;15;986;5;Mus;2;false;true`
const expected = sampleDataPoints
const actual = csvToDataPoints(
input,
@@ -705,12 +706,9 @@ describe('converters', () => {
})
it('should interpret enabled field as case insensitive', () => {
- const inputWithLowerCase =
- 'id;Sukker;Peber;Hvedemel;Kunde;score;enabled;valid\n1;28;982;632;Mus;1;true\n2;15;986;5;Mus;2;false;true'
- const inputWithUpperCase =
- 'id;Sukker;Peber;Hvedemel;Kunde;score;enabled;valid\n1;28;982;632;Mus;1;TRUE\n2;15;986;5;Mus;2;FALSE;true'
- const inputWithMixedCase =
- 'id;Sukker;Peber;Hvedemel;Kunde;score;enabled;valid\n1;28;982;632;Mus;1;True\n2;15;986;5;Mus;2;False;true'
+ const inputWithLowerCase = `id;Sukker;Peber;Hvedemel;Kunde;${scoreNames[0]};enabled;valid\n1;28;982;632;Mus;1;true\n2;15;986;5;Mus;2;false;true`
+ const inputWithUpperCase = `id;Sukker;Peber;Hvedemel;Kunde;${scoreNames[0]};enabled;valid\n1;28;982;632;Mus;1;TRUE\n2;15;986;5;Mus;2;FALSE;true`
+ const inputWithMixedCase = `id;Sukker;Peber;Hvedemel;Kunde;${scoreNames[0]};enabled;valid\n1;28;982;632;Mus;1;True\n2;15;986;5;Mus;2;False;true`
const expected = sampleDataPoints
const actual = [
inputWithLowerCase,
@@ -730,8 +728,7 @@ describe('converters', () => {
})
it('should use ID column from CSV', () => {
- const input =
- 'id;Sukker;Peber;Hvedemel;Kunde;score;enabled\n42;28;982;632;Mus;1;true\n16;15;986;5;Mus;2;false'
+ const input = `id;Sukker;Peber;Hvedemel;Kunde;${scoreNames[0]};enabled\n42;28;982;632;Mus;1;true\n16;15;986;5;Mus;2;false`
const ids = [42, 16]
const expected = sampleDataPoints.map((dp, idx) => ({
...dp,
@@ -747,8 +744,7 @@ describe('converters', () => {
})
it('should fail if duplicate ids are supplied', () => {
- const input =
- 'id;Sukker;Peber;Hvedemel;Kunde;score;enabled\n2;28;982;632;Mus;1;true\n2;15;986;5;Mus;2;false'
+ const input = `id;Sukker;Peber;Hvedemel;Kunde;${scoreNames[0]};enabled\n2;28;982;632;Mus;1;true\n2;15;986;5;Mus;2;false`
expect(() =>
csvToDataPoints(
input,
@@ -760,8 +756,7 @@ describe('converters', () => {
})
it('should fail if types does not match schema', () => {
- const input =
- 'id;Sukker;Peber;Hvedemel;Kunde;score;enabled\n1;fisk;982;632;Mus;1;true\n2;15;986;5;Mus;2;false'
+ const input = `id;Sukker;Peber;Hvedemel;Kunde;${scoreNames[0]};enabled\n1;fisk;982;632;Mus;1;true\n2;15;986;5;Mus;2;false`
expect(() =>
csvToDataPoints(
input,
@@ -773,8 +768,7 @@ describe('converters', () => {
})
it('should work with no meta data columns (ID is generated based on line order)', () => {
- const input =
- 'Sukker;Peber;Hvedemel;Kunde;score\n28;982;632;Mus;1\n15;986;5;Mus;2'
+ const input = `Sukker;Peber;Hvedemel;Kunde;${scoreNames[0]}\n28;982;632;Mus;1\n15;986;5;Mus;2`
const expected = sampleDataPoints.map((dp, idx) => ({
...dp,
meta: { enabled: true, id: idx + 1, valid: true },
@@ -789,8 +783,7 @@ describe('converters', () => {
})
it('should accept shuffled columns', () => {
- const input =
- 'Sukker;score;id;Hvedemel;enabled;Peber;Kunde\n28;1;1;632;true;982;Mus\n15;2;2;5;false;986;Mus'
+ const input = `Sukker;${scoreNames[0]};id;Hvedemel;enabled;Peber;Kunde\n28;1;1;632;true;982;Mus\n15;2;2;5;false;986;Mus`
const expected = sampleDataPoints
const actual = csvToDataPoints(
input,
@@ -802,7 +795,7 @@ describe('converters', () => {
})
it('should fail if header is missing', () => {
- const input = 'Sukker;Hvedemel;Kunde;score\n28;632;Mus;1\n15;5;Mus;2'
+ const input = `Sukker;Hvedemel;Kunde;${scoreNames[0]}\n28;632;Mus;1\n15;5;Mus;2`
expect(() =>
csvToDataPoints(
input,
@@ -814,8 +807,7 @@ describe('converters', () => {
})
it('should not fail if there are extra headers', () => {
- const input =
- 'Sukker;Peber;Hvedemel;Halm;Kunde;score\n28;982;632;007;Mus;1\n15;986;5;008;Mus;2'
+ const input = `Sukker;Peber;Hvedemel;Halm;Kunde;${scoreNames[0]}\n28;982;632;007;Mus;1\n15;986;5;008;Mus;2`
const expected = sampleDataPoints.map(d => d.data)
const actual = csvToDataPoints(
input,
@@ -827,8 +819,7 @@ describe('converters', () => {
})
it('should accept whitespace in headers', () => {
- const input =
- 'id;Sukker ;Peber;Hvedemel; Kunde;score;enabled;valid\n1;28;982;632;Mus;1;true;true\n2;15;986;5;Mus;2;false;true'
+ const input = `id;Sukker ;Peber;Hvedemel; Kunde;${scoreNames[0]};enabled;valid\n1;28;982;632;Mus;1;true;true\n2;15;986;5;Mus;2;false;true`
const expected = sampleDataPoints
const actual = csvToDataPoints(
input,
@@ -840,8 +831,7 @@ describe('converters', () => {
})
it('should add extra headers to meta', () => {
- const input =
- 'Sukker;Peber;Hvedemel;Halm;Kunde;score;enabled;valid\n28;982;632;008;Mus;1;true;true\n15;986;5;008;Mus;2;false;true'
+ const input = `Sukker;Peber;Hvedemel;Halm;Kunde;${scoreNames[0]};enabled;valid\n28;982;632;008;Mus;1;true;true\n15;986;5;008;Mus;2;false;true`
const expected = sampleDataPoints.map(d => ({
...d,
meta: { ...d.meta, halm: '008' },
@@ -856,8 +846,7 @@ describe('converters', () => {
})
it('should parse optional meta data field (description)', () => {
- const input =
- 'id;Sukker;Peber;Hvedemel;Kunde;score;enabled;valid;description\n1;28;982;632;Mus;1;true;true;I am a description\n2;15;986;5;Mus;2;false;true;I am also a description'
+ const input = `id;Sukker;Peber;Hvedemel;Kunde;${scoreNames[0]};enabled;valid;description\n1;28;982;632;Mus;1;true;true;I am a description\n2;15;986;5;Mus;2;false;true;I am also a description`
const actual = csvToDataPoints(
input,
valueVariables,
diff --git a/packages/core/src/common/util/converters/converters.ts b/packages/core/src/common/util/converters/converters.ts
index 77b7f631..5df5859e 100644
--- a/packages/core/src/common/util/converters/converters.ts
+++ b/packages/core/src/common/util/converters/converters.ts
@@ -8,6 +8,7 @@ import {
SpaceType,
ValueVariableType,
dataPointSchema,
+ scoreNames,
} from '@core/common/types'
/**
@@ -78,7 +79,9 @@ export const calculateData = (
it.type === 'numeric' ? Number(it.value) : it.value
) as Array, // This type cast is valid here because only scores can be number[] and they are filtered out
yi: run
- .filter(it => enabledScoreNames.includes(it.name))
+ .filter(it =>
+ enabledScoreNames.includes(it.name as (typeof scoreNames)[number])
+ )
.map(it => it.value)
.map(it => Number(it) * -1),
})
diff --git a/packages/core/src/common/util/migration/data-formats/18.json b/packages/core/src/common/util/migration/data-formats/18.json
index b25513dc..4340e47a 100644
--- a/packages/core/src/common/util/migration/data-formats/18.json
+++ b/packages/core/src/common/util/migration/data-formats/18.json
@@ -38,7 +38,7 @@
],
"scoreVariables": [
{
- "name": "Quality",
+ "name": "quality",
"label": "Quality (0-5)",
"description": "",
"enabled": true
@@ -91,7 +91,7 @@
},
{
"type": "score",
- "name": "Quality",
+ "name": "quality",
"value": 0.5
}
]
diff --git a/packages/core/src/common/util/migration/migration.test.ts b/packages/core/src/common/util/migration/migration.test.ts
index 3b297f3b..f5871356 100644
--- a/packages/core/src/common/util/migration/migration.test.ts
+++ b/packages/core/src/common/util/migration/migration.test.ts
@@ -16,7 +16,6 @@ import { emptyExperiment } from '@core/context/experiment'
import { formatNext } from './migrations/migrateToV9'
import {
ExperimentType,
- ScoreVariableType,
experimentSchema,
scoreLabels,
scoreNames,
@@ -24,6 +23,7 @@ import {
import { storeLatestSchema, loadTestData } from './test-utils'
import { scoreName17 } from './migrations/migrateToV17'
import { migrateToV17, migrateToV18 } from './migrations'
+import { ExperimentTypeV17 } from './migrations/migrateToV18'
describe('Migration of data format', () => {
storeLatestSchema()
@@ -162,7 +162,7 @@ describe('Migration of data format', () => {
description: 'score',
enabled: true,
},
- ] satisfies ScoreVariableType[]
+ ]
const scoreVarsMultiObjectiveDisabled = [
{
@@ -175,7 +175,7 @@ describe('Migration of data format', () => {
description: 'score',
enabled: false,
},
- ] satisfies ScoreVariableType[]
+ ]
it.each([
['multiobjective, all enabled', scoreVarsMultiObjective],
@@ -268,122 +268,94 @@ describe('Migration of data format', () => {
})
describe('migrateToV18', () => {
- const scoreVarsMultiObjective = [
- {
- name: 'Quality (0-5)',
- description: 'Quality (0-5)',
- enabled: true,
- },
- {
- name: 'Quality (0-5) 2',
- description: 'Quality (0-5) 2',
- enabled: true,
- },
- ] satisfies ScoreVariableType[]
+ const experiment17 = {
+ ...version17,
+ scoreVariables: [
+ {
+ name: 'Quality (0-5)',
+ description: 'Quality (0-5)',
+ enabled: true,
+ },
+ ],
+ } as ExperimentTypeV17
- const scoreVarsMultiObjectiveDisabled = [
- {
- name: 'Quality (0-5)',
- description: 'Quality (0-5)',
- enabled: true,
- },
- {
- name: 'Quality (0-5) 2',
- description: 'Quality (0-5) 2',
- enabled: false,
- },
- ] satisfies ScoreVariableType[]
+ it('should convert single score variable name and add label', () => {
+ const result = migrateToV18(experiment17)
+ expect(result.scoreVariables).toEqual([
+ {
+ name: scoreNames[0],
+ label: scoreLabels[0],
+ description: '',
+ enabled: true,
+ },
+ ])
+ })
- it.each([
- ['multiobjective, all enabled', scoreVarsMultiObjective],
- ['multiobjective, one disabled', scoreVarsMultiObjectiveDisabled],
- ])('scoreVariables should be updated, %s', (_, scoreVariables) => {
- const experiment17 = {
+ it('should convert multiple score variables with correct indices', () => {
+ const multiObj = {
...version17,
- scoreVariables,
- } as unknown as ExperimentType
- expect(migrateToV18(experiment17).scoreVariables).toEqual([
+ scoreVariables: [
+ {
+ name: 'Quality (0-5)',
+ description: 'Quality (0-5)',
+ enabled: true,
+ },
+ {
+ name: 'Quality (0-5) 2',
+ description: 'Quality (0-5) 2',
+ enabled: false,
+ },
+ ],
+ } as ExperimentTypeV17
+
+ const result = migrateToV18(multiObj)
+ expect(result.scoreVariables).toEqual([
{
name: scoreNames[0],
- label: scoreLabels[0] ?? '',
+ label: scoreLabels[0],
description: '',
- enabled: scoreVariables[0]?.enabled,
+ enabled: true,
},
{
name: scoreNames[1],
- label: scoreLabels[1] ?? '',
+ label: scoreLabels[1],
description: '',
- enabled: scoreVariables[1]?.enabled,
+ enabled: false,
},
])
})
- const dataPointsSingle = [...version17.dataPoints]
- const dataPointsMulti = [...version17.dataPoints].map(dp => ({
- ...dp,
- data: [...dp.data].concat([
- {
- type: 'score',
- name: scoreNames[1] ?? '',
- value: 2,
- },
- ]),
- }))
+ it('should update score names in dataPoints for single objective', () => {
+ const result = migrateToV18(experiment17)
+ const scoreData = result.dataPoints[0]?.data.find(d => d.type === 'score')
+ expect(scoreData?.name).toEqual(scoreNames[0])
+ })
- it.each([
- ['one score exists', dataPointsSingle, false],
- ['two scores exist', dataPointsMulti, true],
- ])(
- 'data points should be renamed, %s',
- (_, dataPoints, isMultiObjective) => {
- const experiment17 = {
- ...version17,
- dataPoints,
- } as unknown as ExperimentType
- const actual = [
- {
- meta: {
- enabled: true,
- valid: true,
- id: 1,
- },
- data: [
- {
- type: 'categorical',
- name: 'Icing',
- value: 'Brown',
- },
- {
- type: 'numeric',
- name: 'name1',
- value: 10,
- },
- {
- type: 'numeric',
- name: 'name2',
- value: 10.2,
- },
- {
- type: 'score',
- name: scoreNames[0],
- value: 0.5,
- },
- ].concat(
- isMultiObjective
- ? [
- {
- type: 'score',
- name: scoreNames[1],
- value: 2,
- },
- ]
- : []
- ),
- },
- ]
- expect(migrateToV18(experiment17).dataPoints).toEqual(actual)
- }
- )
+ it('should update score names in dataPoints for multi-objective', () => {
+ const multiObj = {
+ ...version17,
+ scoreVariables: [
+ { name: 'Quality (0-5)', description: '', enabled: true },
+ { name: 'Quality (0-5) 2', description: '', enabled: true },
+ ],
+ dataPoints: version17.dataPoints.map(dp => ({
+ ...dp,
+ data: [
+ ...dp.data,
+ { type: 'score', name: 'Quality (0-5) 2', value: 2 },
+ ],
+ })),
+ } as ExperimentTypeV17
+
+ const result = migrateToV18(multiObj)
+ const scoreData = result.dataPoints[0]?.data.filter(
+ d => d.type === 'score'
+ )
+ expect(scoreData?.map(s => s.name)).toEqual([
+ scoreNames[0],
+ scoreNames[1],
+ ])
+ })
})
describe('experiment properties', () => {
diff --git a/packages/core/src/common/util/migration/migrations/migrateToV18.ts b/packages/core/src/common/util/migration/migrations/migrateToV18.ts
index 3b26fb8a..10f6d97d 100644
--- a/packages/core/src/common/util/migration/migrations/migrateToV18.ts
+++ b/packages/core/src/common/util/migration/migrations/migrateToV18.ts
@@ -1,46 +1,71 @@
-import { ExperimentType, scoreLabels, scoreNames } from '@core/common/types'
+import {
+ ExperimentType,
+ scoreLabels,
+ scoreNames,
+ ScoreVariableType,
+} from '@core/common/types'
import { produce } from 'immer'
+// TODO Multiobjective - do not use ScoreVariableType because this could change in the future?
+type ScoreVariableTypeV17 = Omit & {
+ name: string
+}
+
+// TODO Multiobjective - do not use Experiment because this could change in the future?
+export type ExperimentTypeV17 = Omit<
+ ExperimentType,
+ 'info' | 'scoreVariables'
+> & {
+ info: Omit & {
+ dataFormatVersion: string
+ }
+ scoreVariables: ScoreVariableTypeV17[]
+}
+
// rename scores, change description to empty string, add labels
-export const migrateToV18 = (json: ExperimentType): ExperimentType => {
- return produce(
- json,
- (draft: {
- info: { dataFormatVersion: string }
- scoreVariables: {
- name: string
- label?: string
- description: string
- enabled: boolean
- }[]
- dataPoints: {
- data: { name: string; type: string }[]
- }[]
- }) => {
- draft.info.dataFormatVersion = '18'
- draft.scoreVariables = json.scoreVariables.map((s, i) => ({
- name: scoreNames[i] ?? '',
- label: scoreLabels[i] ?? '',
+export const migrateToV18 = (json: ExperimentTypeV17): ExperimentType => {
+ return produce(json, draft => {
+ draft.info.dataFormatVersion = '18'
+ draft.scoreVariables = json.scoreVariables
+ .slice(0, scoreNames.length)
+ .map((s, i) => ({
+ name: scoreNames[i] as (typeof scoreNames)[number],
+ label: scoreLabels[i] ?? 'score' + (i + 1),
description: '',
enabled: s.enabled,
}))
- draft.dataPoints = json.dataPoints.map(dp => {
- let scoreIndex = 0
- return {
- ...dp,
- data: dp.data.map(d => {
- let newName = d.name
+ draft.dataPoints = json.dataPoints.map(dp => {
+ let scoreIndex = 0
+ return {
+ ...dp,
+ data: dp.data
+ .filter(d => {
+ // only map score data points up to scoreNames.length
if (d.type === 'score') {
- newName = scoreNames[scoreIndex] ?? ''
+ if (scoreIndex >= scoreNames.length) {
+ return false
+ }
scoreIndex++
}
+ return true
+ })
+ .map(d => {
+ let newName = d.name
+ if (d.type === 'score') {
+ // reset scoreIndex for mapping
+ const currentScoreIndex = dp.data
+ .slice(0, dp.data.indexOf(d))
+ .filter(item => item.type === 'score').length
+ newName = scoreNames[
+ currentScoreIndex
+ ] as (typeof scoreNames)[number]
+ }
return {
...d,
name: newName,
}
}),
- }
- })
- }
- )
+ }
+ })
+ }) as unknown as ExperimentType
}
diff --git a/packages/core/src/common/util/migration/schemas/18.json b/packages/core/src/common/util/migration/schemas/18.json
index 6a2af9c1..370c1eca 100644
--- a/packages/core/src/common/util/migration/schemas/18.json
+++ b/packages/core/src/common/util/migration/schemas/18.json
@@ -135,7 +135,16 @@
"type": "object",
"properties": {
"name": {
- "type": "string"
+ "anyOf": [
+ {
+ "type": "string",
+ "const": "quality"
+ },
+ {
+ "type": "string",
+ "const": "cost"
+ }
+ ]
},
"label": {
"type": "string"
@@ -149,6 +158,7 @@
},
"required": [
"name",
+ "label",
"description",
"enabled"
],
diff --git a/packages/core/src/context/experiment/experiment-reducers.ts b/packages/core/src/context/experiment/experiment-reducers.ts
index fb3fe682..20a3ee6b 100644
--- a/packages/core/src/context/experiment/experiment-reducers.ts
+++ b/packages/core/src/context/experiment/experiment-reducers.ts
@@ -400,8 +400,8 @@ export const experimentReducer = produce(
if (state.scoreVariables.length < 2) {
state.scoreVariables.push({
- name: scoreNamesState[1] ?? '',
- label: scoreLabels[1],
+ name: scoreNamesState[1],
+ label: scoreLabels[1] ?? 'score2',
description: '',
enabled: true,
})
@@ -409,7 +409,11 @@ export const experimentReducer = produce(
state.dataPoints.forEach(dataEntry => {
const dp = dataEntry.data
const containedScores = dp
- .filter(it => scoreNames.includes(it.name))
+ .filter(
+ it =>
+ it.type === 'score' &&
+ scoreNames.includes(it.name as (typeof scoreNames)[number])
+ )
.map(it => it.name)
scoreNames.forEach(scoreName => {
if (!containedScores.includes(scoreName))
diff --git a/packages/core/src/context/experiment/reducers.test.ts b/packages/core/src/context/experiment/reducers.test.ts
index e7855469..9179750e 100644
--- a/packages/core/src/context/experiment/reducers.test.ts
+++ b/packages/core/src/context/experiment/reducers.test.ts
@@ -7,6 +7,7 @@ import {
ExperimentResultType,
ExperimentType,
OptimizerConfig,
+ scoreNames,
ValueVariableType,
} from '@core/common/types'
import { emptyExperiment, State } from '@core/context/experiment'
@@ -54,7 +55,8 @@ describe('experiment reducer', () => {
],
scoreVariables: [
{
- name: 'score',
+ name: scoreNames[0],
+ label: 'qualitylabel',
description: 'score',
enabled: true,
},
@@ -97,7 +99,7 @@ describe('experiment reducer', () => {
},
{
type: 'score',
- name: 'score',
+ name: scoreNames[0],
value: 10,
},
],
@@ -372,7 +374,7 @@ describe('experiment reducer', () => {
},
{
type: 'score',
- name: 'score',
+ name: scoreNames[0],
value: 10,
},
])
@@ -510,7 +512,7 @@ describe('experiment reducer', () => {
},
{
type: 'score',
- name: 'score',
+ name: scoreNames[0],
value: 10,
},
],
@@ -606,7 +608,7 @@ describe('experiment reducer', () => {
},
{
type: 'score',
- name: 'score',
+ name: scoreNames[0],
value: 10,
},
])
@@ -792,7 +794,7 @@ describe('experiment reducer', () => {
() => {
const values = ['value1', 'value2', 'value3']
const cats = ['cat1', 'cat2', 'cat3']
- const scores = ['score', 'score2']
+ const scores = scoreNames
const testState = produce(initState, draft => {
draft.experiment.valueVariables = values.map(name =>
@@ -817,7 +819,7 @@ describe('experiment reducer', () => {
1,
values,
cats,
- scores,
+ [...scoreNames],
true
).map(dr => ({
...dr,
@@ -835,8 +837,8 @@ describe('experiment reducer', () => {
'cat1',
'cat2',
'cat3',
- 'score',
- 'score2',
+ scoreNames[0],
+ scoreNames[1],
]
const actual = rootReducer(testState, action).experiment.dataPoints.map(
dr => dr.data.map(d => d.name)
@@ -949,12 +951,14 @@ describe('experiment reducer', () => {
...initState.experiment,
scoreVariables: [
{
- name: 'score',
+ name: scoreNames[0],
+ label: 'qualitylabel',
description: 'score',
enabled: true,
},
{
- name: 'score2',
+ name: scoreNames[1],
+ label: 'costlabel',
description: 'score 2',
enabled: true,
},
@@ -986,12 +990,14 @@ describe('experiment reducer', () => {
...initState.experiment,
scoreVariables: [
{
- name: 'score',
+ name: scoreNames[0],
+ label: 'qualitylabel',
description: 'score',
enabled: true,
},
{
- name: 'score2',
+ name: scoreNames[1],
+ label: 'costlabel',
description: 'score 2',
enabled: false,
},
@@ -1024,7 +1030,7 @@ describe('experiment reducer', () => {
scores.length,
['Water'],
['Icing'],
- ['score', 'score2'],
+ [...scoreNames],
true,
scores
)
@@ -1035,12 +1041,14 @@ describe('experiment reducer', () => {
...initState.experiment,
scoreVariables: [
{
- name: 'score',
+ name: scoreNames[0],
+ label: 'qualitylabel',
description: 'score',
enabled: true,
},
{
- name: 'score2',
+ name: scoreNames[1],
+ label: 'costlabel',
description: 'score 2',
enabled: true,
},
@@ -1062,7 +1070,7 @@ describe('experiment reducer', () => {
scores.length,
['Water'],
['Icing'],
- ['score', 'score2'],
+ [...scoreNames],
true,
scores
)
@@ -1073,12 +1081,14 @@ describe('experiment reducer', () => {
...initState.experiment,
scoreVariables: [
{
- name: 'score',
+ name: scoreNames[0],
+ label: 'qualitylabel',
description: 'score',
enabled: true,
},
{
- name: 'score2',
+ name: scoreNames[1],
+ label: 'costlabel',
description: 'score 2',
enabled: true,
},
diff --git a/packages/core/src/context/experiment/store.ts b/packages/core/src/context/experiment/store.ts
index a5263ab1..b4f5eeb6 100644
--- a/packages/core/src/context/experiment/store.ts
+++ b/packages/core/src/context/experiment/store.ts
@@ -18,8 +18,8 @@ export const emptyExperiment: ExperimentType = {
valueVariables: [],
scoreVariables: [
{
- name: scoreNames[0] ?? '',
- label: scoreLabels[0],
+ name: scoreNames[0],
+ label: scoreLabels[0] ?? scoreNames[0],
description: '',
enabled: true,
},
@@ -65,14 +65,14 @@ export const initialStateMultiObjective: State = {
...emptyExperiment,
scoreVariables: [
{
- name: scoreNames[0] ?? '',
- label: scoreLabels[0],
+ name: scoreNames[0],
+ label: scoreLabels[0] ?? scoreNames[0],
description: '',
enabled: true,
},
{
- name: scoreNames[1] ?? '',
- label: scoreLabels[1],
+ name: scoreNames[1],
+ label: scoreLabels[1] ?? scoreNames[1],
description: '',
enabled: true,
},
diff --git a/packages/core/src/context/experiment/test-utils.ts b/packages/core/src/context/experiment/test-utils.ts
index 12009e04..c05c75f5 100644
--- a/packages/core/src/context/experiment/test-utils.ts
+++ b/packages/core/src/context/experiment/test-utils.ts
@@ -3,6 +3,7 @@ import {
DataEntry,
DataPointType,
ExperimentResultType,
+ scoreNames,
ScoreVariableType,
ValueVariableType,
} from '@core/common'
@@ -29,9 +30,12 @@ export const createCategoricalVariable = (
enabled: input.enabled ?? true,
}) satisfies CategoricalVariableType
-export const createScoreVariable = (input: Partial) =>
+export const createScoreVariable = (
+ input: Partial & Pick
+) =>
({
- name: input.name ?? 'name',
+ name: input.name ?? 'quality',
+ label: input.label ?? 'label',
description: input.description ?? '',
enabled: input.enabled ?? true,
}) satisfies ScoreVariableType
@@ -40,7 +44,7 @@ export const createDataPoints = (
count: number,
values = ['Water'],
categorical = ['Icing'],
- scores = ['score'],
+ scores: (typeof scoreNames)[number][] = [scoreNames[0]],
randomize = false,
scoreValues: number[] | undefined = undefined
): DataEntry[] => {
@@ -67,7 +71,7 @@ export const createDataPoints = (
return data.map((dp, i) => ({
...dp,
data: dp.data.map(d => {
- if (d.type === 'score' && d.name === 'score') {
+ if (d.type === 'score' && d.name === 'quality') {
const score = scoreValues[i]
return {
...d,
diff --git a/packages/core/src/context/experiment/validation.test.ts b/packages/core/src/context/experiment/validation.test.ts
index 01b4e165..608bdc85 100644
--- a/packages/core/src/context/experiment/validation.test.ts
+++ b/packages/core/src/context/experiment/validation.test.ts
@@ -1,5 +1,4 @@
import { describe, expect, it } from 'vitest'
-import { ExperimentType } from 'common'
import { emptyExperiment } from './store'
import {
ValidationViolations,
@@ -12,6 +11,7 @@ import {
validateLowerBoundary,
validateUpperBoundary,
} from './validation'
+import { ExperimentType, scoreNames } from '@core/common'
describe('validateUpperBoundary', () => {
it('should return empty array if no violations exist', () => {
@@ -330,7 +330,8 @@ describe('validateDataPointsUndefined', () => {
...emptyExperiment,
scoreVariables: [
{
- name: 'score',
+ name: 'quality',
+ label: 'qualitylabel',
description: '',
enabled: true,
},
@@ -344,7 +345,7 @@ describe('validateDataPointsUndefined', () => {
},
data: [
{ type: 'numeric', name: 'Water', value: 10 },
- { type: 'score', name: 'score', value: 1 },
+ { type: 'score', name: 'quality', value: 1 },
],
},
],
@@ -357,7 +358,8 @@ describe('validateDataPointsUndefined', () => {
...emptyExperiment,
scoreVariables: [
{
- name: 'score',
+ name: 'quality',
+ label: 'qualitylabel',
description: '',
enabled: true,
},
@@ -391,7 +393,8 @@ describe('validateDataPointsUndefined', () => {
],
scoreVariables: [
{
- name: 'score',
+ name: 'quality',
+ label: 'qualitylabel',
description: '',
enabled: true,
},
@@ -420,7 +423,7 @@ describe('validateDataPointsUndefined', () => {
data: [
{
type: 'score',
- name: 'score',
+ name: 'quality',
value: 1,
},
],
@@ -435,12 +438,14 @@ describe('validateDataPointsUndefined', () => {
...emptyExperiment,
scoreVariables: [
{
- name: 'score',
+ name: 'quality',
+ label: 'qualitylabel',
description: '',
enabled: true,
},
{
- name: 'score 2',
+ name: 'cost',
+ label: 'costlabel',
description: '',
enabled: false,
},
@@ -460,7 +465,7 @@ describe('validateDataPointsUndefined', () => {
},
{
type: 'score',
- name: 'score',
+ name: scoreNames[0],
value: 1,
},
],
diff --git a/packages/ui/src/features/result-data/single-data-point.tsx b/packages/ui/src/features/result-data/single-data-point.tsx
index 4726bcdd..54ade73e 100644
--- a/packages/ui/src/features/result-data/single-data-point.tsx
+++ b/packages/ui/src/features/result-data/single-data-point.tsx
@@ -14,7 +14,10 @@ import {
import useStyles from './single-data-point.style'
import { PNGPlot } from '@boostv/process-optimizer-frontend-plots'
import { useState } from 'react'
-import { scoreNames } from '@boostv/process-optimizer-frontend-core'
+import {
+ scoreLabels,
+ scoreNames,
+} from '@boostv/process-optimizer-frontend-core'
interface SingleDataPointProps {
title: string
@@ -47,7 +50,10 @@ export const SingleDataPoint = ({
{headers
- .concat([(scoreNames[0] ?? '') + ' (95 % credibility interval)'])
+ .concat([
+ (scoreLabels[0] ?? scoreNames[0]) +
+ ' (95 % credibility interval)',
+ ])
.map((h, idx) => (
{h}
From 392ff415c9a27c58078dd7a1ffb724bb5b603ba1 Mon Sep 17 00:00:00 2001
From: Jack Ord <36296721+j-or@users.noreply.github.com>
Date: Thu, 22 Jan 2026 14:32:30 +0000
Subject: [PATCH 07/11] Add entire v17 type in migration
---
.../util/migration/migrations/migrateToV18.ts | 198 ++++++++++++------
1 file changed, 136 insertions(+), 62 deletions(-)
diff --git a/packages/core/src/common/util/migration/migrations/migrateToV18.ts b/packages/core/src/common/util/migration/migrations/migrateToV18.ts
index 10f6d97d..6cc617b2 100644
--- a/packages/core/src/common/util/migration/migrations/migrateToV18.ts
+++ b/packages/core/src/common/util/migration/migrations/migrateToV18.ts
@@ -1,71 +1,145 @@
-import {
- ExperimentType,
- scoreLabels,
- scoreNames,
- ScoreVariableType,
-} from '@core/common/types'
+import { ExperimentType, scoreLabels, scoreNames } from '@core/common/types'
import { produce } from 'immer'
-// TODO Multiobjective - do not use ScoreVariableType because this could change in the future?
-type ScoreVariableTypeV17 = Omit & {
- name: string
-}
-
-// TODO Multiobjective - do not use Experiment because this could change in the future?
-export type ExperimentTypeV17 = Omit<
- ExperimentType,
- 'info' | 'scoreVariables'
-> & {
- info: Omit & {
- dataFormatVersion: string
+// Avoid using current ExperimentType as that can change in the future
+export type ExperimentTypeV17 = {
+ id: string
+ lastEvaluationHash?: string
+ changedSinceLastEvaluation: boolean
+ info: {
+ name: string
+ description: string
+ swVersion: string
+ dataFormatVersion: '17'
+ version: number
+ extras: Record
+ }
+ extras: Record
+ categoricalVariables: {
+ name: string
+ description: string
+ options: string[]
+ enabled: boolean
+ }[]
+ valueVariables: {
+ type: 'discrete' | 'continuous'
+ name: string
+ description: string
+ min: number
+ max: number
+ enabled: boolean
+ }[]
+ scoreVariables: {
+ name: string
+ label?: string
+ description: string
+ enabled: boolean
+ }[]
+ constraints: {
+ type: 'sum'
+ value: number
+ dimensions: string[]
+ }[]
+ optimizerConfig: {
+ baseEstimator: string
+ acqFunc: string
+ initialPoints: number
+ kappa: number
+ xi: number
}
- scoreVariables: ScoreVariableTypeV17[]
+ results: {
+ id: string
+ plots: {
+ id: string
+ plot: string
+ }[]
+ next: (number | string)[][]
+ pickled: string
+ expectedMinimum: ((number | string)[] | number)[]
+ extras: Record
+ }
+ dataPoints: {
+ meta: {
+ id: number
+ enabled: boolean
+ valid: boolean
+ description?: string
+ }
+ data: (
+ | {
+ type: 'numeric'
+ name: string
+ value: number
+ }
+ | {
+ type: 'categorical'
+ name: string
+ value: string
+ }
+ | {
+ type: 'score'
+ name: string
+ value: number
+ }
+ )[]
+ }[]
}
// rename scores, change description to empty string, add labels
export const migrateToV18 = (json: ExperimentTypeV17): ExperimentType => {
- return produce(json, draft => {
- draft.info.dataFormatVersion = '18'
- draft.scoreVariables = json.scoreVariables
- .slice(0, scoreNames.length)
- .map((s, i) => ({
- name: scoreNames[i] as (typeof scoreNames)[number],
- label: scoreLabels[i] ?? 'score' + (i + 1),
- description: '',
- enabled: s.enabled,
- }))
- draft.dataPoints = json.dataPoints.map(dp => {
- let scoreIndex = 0
- return {
- ...dp,
- data: dp.data
- .filter(d => {
- // only map score data points up to scoreNames.length
- if (d.type === 'score') {
- if (scoreIndex >= scoreNames.length) {
- return false
+ return produce(
+ json,
+ (draft: {
+ info: { dataFormatVersion: string }
+ scoreVariables: {
+ label?: string
+ }[]
+ dataPoints: {
+ data: { name: string; type: string }[]
+ }[]
+ }) => {
+ draft.info.dataFormatVersion = '18'
+ draft.scoreVariables = json.scoreVariables
+ .slice(0, scoreNames.length)
+ .map((s, i) => ({
+ name: scoreNames[i] as (typeof scoreNames)[number],
+ label: scoreLabels[i] ?? scoreNames[i],
+ description: '',
+ enabled: s.enabled,
+ }))
+ draft.dataPoints = json.dataPoints.map(dp => {
+ let scoreIndex = 0
+ return {
+ ...dp,
+ data: dp.data
+ .filter(d => {
+ // only map score data points up to scoreNames.length
+ if (d.type === 'score') {
+ if (scoreIndex >= scoreNames.length) {
+ return false
+ }
+ scoreIndex++
+ }
+ return true
+ })
+ .map(d => {
+ let newName = d.name
+ if (d.type === 'score') {
+ // reset scoreIndex for mapping
+ const currentScoreIndex = dp.data
+ .slice(0, dp.data.indexOf(d))
+ .filter(item => item.type === 'score').length
+ newName = scoreNames[
+ currentScoreIndex
+ ] as (typeof scoreNames)[number]
+ }
+ return {
+ ...d,
+ name: newName,
}
- scoreIndex++
- }
- return true
- })
- .map(d => {
- let newName = d.name
- if (d.type === 'score') {
- // reset scoreIndex for mapping
- const currentScoreIndex = dp.data
- .slice(0, dp.data.indexOf(d))
- .filter(item => item.type === 'score').length
- newName = scoreNames[
- currentScoreIndex
- ] as (typeof scoreNames)[number]
- }
- return {
- ...d,
- name: newName,
- }
- }),
- }
- })
- }) as unknown as ExperimentType
+ }),
+ }
+ })
+ }
+ ) as unknown as ExperimentType
}
From 2020f10535122644151f88edbfee33f365671ba1 Mon Sep 17 00:00:00 2001
From: Jack Ord <36296721+j-or@users.noreply.github.com>
Date: Thu, 22 Jan 2026 17:06:26 +0000
Subject: [PATCH 08/11] Add type guard, revert change to 17, simplify 18
migration
---
packages/core/src/common/types/common.ts | 6 +++
.../src/common/util/converters/converters.ts | 7 ++--
.../util/migration/migrations/migrateToV18.ts | 42 +++++++------------
.../src/common/util/migration/schemas/17.json | 3 --
.../context/experiment/experiment-reducers.ts | 9 ++--
.../core/src/context/experiment/test-utils.ts | 2 +-
6 files changed, 29 insertions(+), 40 deletions(-)
diff --git a/packages/core/src/common/types/common.ts b/packages/core/src/common/types/common.ts
index 101bf03e..a67666d0 100644
--- a/packages/core/src/common/types/common.ts
+++ b/packages/core/src/common/types/common.ts
@@ -9,6 +9,12 @@ export const scoreNames = ['quality', 'cost'] as const
// Label is shown in UI, name is used in data
export const scoreLabels = ['Quality (0-5)', 'Cost (0-5)']
+export const isValidScoreName = (
+ name: string
+): name is (typeof scoreNames)[number] => {
+ return (scoreNames as readonly string[]).includes(name)
+}
+
const infoSchema = z.object({
name: z.string(),
description: z.string(),
diff --git a/packages/core/src/common/util/converters/converters.ts b/packages/core/src/common/util/converters/converters.ts
index 5df5859e..67c47373 100644
--- a/packages/core/src/common/util/converters/converters.ts
+++ b/packages/core/src/common/util/converters/converters.ts
@@ -8,7 +8,7 @@ import {
SpaceType,
ValueVariableType,
dataPointSchema,
- scoreNames,
+ isValidScoreName,
} from '@core/common/types'
/**
@@ -79,8 +79,9 @@ export const calculateData = (
it.type === 'numeric' ? Number(it.value) : it.value
) as Array, // This type cast is valid here because only scores can be number[] and they are filtered out
yi: run
- .filter(it =>
- enabledScoreNames.includes(it.name as (typeof scoreNames)[number])
+ .filter(
+ it =>
+ isValidScoreName(it.name) && enabledScoreNames.includes(it.name)
)
.map(it => it.value)
.map(it => Number(it) * -1),
diff --git a/packages/core/src/common/util/migration/migrations/migrateToV18.ts b/packages/core/src/common/util/migration/migrations/migrateToV18.ts
index 6cc617b2..f8943cca 100644
--- a/packages/core/src/common/util/migration/migrations/migrateToV18.ts
+++ b/packages/core/src/common/util/migration/migrations/migrateToV18.ts
@@ -85,6 +85,7 @@ export type ExperimentTypeV17 = {
}[]
}
+// TODO: multiobjective, try without immer
// rename scores, change description to empty string, add labels
export const migrateToV18 = (json: ExperimentTypeV17): ExperimentType => {
return produce(
@@ -111,33 +112,20 @@ export const migrateToV18 = (json: ExperimentTypeV17): ExperimentType => {
let scoreIndex = 0
return {
...dp,
- data: dp.data
- .filter(d => {
- // only map score data points up to scoreNames.length
- if (d.type === 'score') {
- if (scoreIndex >= scoreNames.length) {
- return false
- }
- scoreIndex++
- }
- return true
- })
- .map(d => {
- let newName = d.name
- if (d.type === 'score') {
- // reset scoreIndex for mapping
- const currentScoreIndex = dp.data
- .slice(0, dp.data.indexOf(d))
- .filter(item => item.type === 'score').length
- newName = scoreNames[
- currentScoreIndex
- ] as (typeof scoreNames)[number]
- }
- return {
- ...d,
- name: newName,
- }
- }),
+ data: dp.data.flatMap(d => {
+ if (d.type === 'score') {
+ // empty result is filtered out by flatMap
+ return scoreIndex < scoreNames.length
+ ? {
+ ...d,
+ name: scoreNames[
+ scoreIndex++
+ ] as (typeof scoreNames)[number],
+ }
+ : []
+ }
+ return d
+ }),
}
})
}
diff --git a/packages/core/src/common/util/migration/schemas/17.json b/packages/core/src/common/util/migration/schemas/17.json
index ca95086a..6115def9 100644
--- a/packages/core/src/common/util/migration/schemas/17.json
+++ b/packages/core/src/common/util/migration/schemas/17.json
@@ -137,9 +137,6 @@
"name": {
"type": "string"
},
- "label": {
- "type": "string"
- },
"description": {
"type": "string"
},
diff --git a/packages/core/src/context/experiment/experiment-reducers.ts b/packages/core/src/context/experiment/experiment-reducers.ts
index 20a3ee6b..41048e39 100644
--- a/packages/core/src/context/experiment/experiment-reducers.ts
+++ b/packages/core/src/context/experiment/experiment-reducers.ts
@@ -7,6 +7,7 @@ import {
ScoreVariableType,
ValueVariableType,
experimentSchema,
+ isValidScoreName,
scoreLabels,
scoreNames as scoreNamesState,
} from '@core/common/types'
@@ -401,7 +402,7 @@ export const experimentReducer = produce(
if (state.scoreVariables.length < 2) {
state.scoreVariables.push({
name: scoreNamesState[1],
- label: scoreLabels[1] ?? 'score2',
+ label: scoreLabels[1] ?? scoreNamesState[1],
description: '',
enabled: true,
})
@@ -409,11 +410,7 @@ export const experimentReducer = produce(
state.dataPoints.forEach(dataEntry => {
const dp = dataEntry.data
const containedScores = dp
- .filter(
- it =>
- it.type === 'score' &&
- scoreNames.includes(it.name as (typeof scoreNames)[number])
- )
+ .filter(it => it.type === 'score' && isValidScoreName(it.name))
.map(it => it.name)
scoreNames.forEach(scoreName => {
if (!containedScores.includes(scoreName))
diff --git a/packages/core/src/context/experiment/test-utils.ts b/packages/core/src/context/experiment/test-utils.ts
index c05c75f5..5708c82f 100644
--- a/packages/core/src/context/experiment/test-utils.ts
+++ b/packages/core/src/context/experiment/test-utils.ts
@@ -34,7 +34,7 @@ export const createScoreVariable = (
input: Partial & Pick
) =>
({
- name: input.name ?? 'quality',
+ name: input.name,
label: input.label ?? 'label',
description: input.description ?? '',
enabled: input.enabled ?? true,
From 592b2b024b08d89300eea78b9ff6d9000be8f58d Mon Sep 17 00:00:00 2001
From: Jack Ord <36296721+j-or@users.noreply.github.com>
Date: Fri, 23 Jan 2026 07:07:43 +0000
Subject: [PATCH 09/11] Derive type from sample json
---
.../util/migration/migrations/migrateToV18.ts | 158 +++++-------------
1 file changed, 46 insertions(+), 112 deletions(-)
diff --git a/packages/core/src/common/util/migration/migrations/migrateToV18.ts b/packages/core/src/common/util/migration/migrations/migrateToV18.ts
index f8943cca..3d8ecce4 100644
--- a/packages/core/src/common/util/migration/migrations/migrateToV18.ts
+++ b/packages/core/src/common/util/migration/migrations/migrateToV18.ts
@@ -1,73 +1,22 @@
import { ExperimentType, scoreLabels, scoreNames } from '@core/common/types'
-import { produce } from 'immer'
+import sampleV17 from '../data-formats/17.json'
+// Use 17.json to define ExperimentTypeV17. Explicitly define string literals to make ts happy.
// Avoid using current ExperimentType as that can change in the future
-export type ExperimentTypeV17 = {
- id: string
- lastEvaluationHash?: string
- changedSinceLastEvaluation: boolean
- info: {
- name: string
- description: string
- swVersion: string
- dataFormatVersion: '17'
- version: number
- extras: Record
- }
- extras: Record
- categoricalVariables: {
- name: string
- description: string
- options: string[]
- enabled: boolean
- }[]
- valueVariables: {
+type ExperimentTypeV17 = Omit<
+ typeof sampleV17,
+ 'valueVariables' | 'constraints' | 'dataPoints'
+> & {
+ valueVariables: (Omit<(typeof sampleV17.valueVariables)[number], 'type'> & {
type: 'discrete' | 'continuous'
- name: string
- description: string
- min: number
- max: number
- enabled: boolean
- }[]
- scoreVariables: {
- name: string
- label?: string
- description: string
- enabled: boolean
- }[]
- constraints: {
+ })[]
+ constraints: (Omit<(typeof sampleV17.constraints)[number], 'type'> & {
type: 'sum'
- value: number
- dimensions: string[]
- }[]
- optimizerConfig: {
- baseEstimator: string
- acqFunc: string
- initialPoints: number
- kappa: number
- xi: number
- }
- results: {
- id: string
- plots: {
- id: string
- plot: string
- }[]
- next: (number | string)[][]
- pickled: string
- expectedMinimum: ((number | string)[] | number)[]
- extras: Record
- }
- dataPoints: {
- meta: {
- id: number
- enabled: boolean
- valid: boolean
- description?: string
- }
+ })[]
+ dataPoints: (Omit<(typeof sampleV17.dataPoints)[number], 'data'> & {
data: (
| {
- type: 'numeric'
+ type: 'numeric' | 'score'
name: string
value: number
}
@@ -76,58 +25,43 @@ export type ExperimentTypeV17 = {
name: string
value: string
}
- | {
- type: 'score'
- name: string
- value: number
- }
)[]
- }[]
+ })[]
}
-// TODO: multiobjective, try without immer
// rename scores, change description to empty string, add labels
export const migrateToV18 = (json: ExperimentTypeV17): ExperimentType => {
- return produce(
- json,
- (draft: {
- info: { dataFormatVersion: string }
- scoreVariables: {
- label?: string
- }[]
- dataPoints: {
- data: { name: string; type: string }[]
- }[]
- }) => {
- draft.info.dataFormatVersion = '18'
- draft.scoreVariables = json.scoreVariables
- .slice(0, scoreNames.length)
- .map((s, i) => ({
- name: scoreNames[i] as (typeof scoreNames)[number],
- label: scoreLabels[i] ?? scoreNames[i],
- description: '',
- enabled: s.enabled,
- }))
- draft.dataPoints = json.dataPoints.map(dp => {
- let scoreIndex = 0
- return {
- ...dp,
- data: dp.data.flatMap(d => {
- if (d.type === 'score') {
- // empty result is filtered out by flatMap
- return scoreIndex < scoreNames.length
- ? {
- ...d,
- name: scoreNames[
- scoreIndex++
- ] as (typeof scoreNames)[number],
- }
- : []
- }
- return d
- }),
- }
- })
- }
- ) as unknown as ExperimentType
+ return {
+ ...json,
+ info: {
+ ...json.info,
+ dataFormatVersion: '18',
+ },
+ scoreVariables: json.scoreVariables
+ .slice(0, scoreNames.length)
+ .map((s, i) => ({
+ name: scoreNames[i] as (typeof scoreNames)[number],
+ label: scoreLabels[i] ?? (scoreNames[i] as (typeof scoreNames)[number]),
+ description: '',
+ enabled: s.enabled,
+ })),
+ dataPoints: json.dataPoints.map(dp => {
+ let scoreIndex = 0
+ return {
+ ...dp,
+ data: dp.data.flatMap(d => {
+ if (d.type === 'score') {
+ // empty result is filtered out by flatMap
+ return scoreIndex < scoreNames.length
+ ? {
+ ...d,
+ name: scoreNames[scoreIndex++] as (typeof scoreNames)[number],
+ }
+ : []
+ }
+ return d
+ }),
+ }
+ }),
+ }
}
From ca271c6ddda18f312f5dd4d28e72a29a1e21e33b Mon Sep 17 00:00:00 2001
From: Jack Ord <36296721+j-or@users.noreply.github.com>
Date: Fri, 23 Jan 2026 07:34:20 +0000
Subject: [PATCH 10/11] Remove complex typing
---
.../util/migration/migrations/migrateToV18.ts | 32 ++-----------------
1 file changed, 2 insertions(+), 30 deletions(-)
diff --git a/packages/core/src/common/util/migration/migrations/migrateToV18.ts b/packages/core/src/common/util/migration/migrations/migrateToV18.ts
index 3d8ecce4..0f1eaa09 100644
--- a/packages/core/src/common/util/migration/migrations/migrateToV18.ts
+++ b/packages/core/src/common/util/migration/migrations/migrateToV18.ts
@@ -1,36 +1,8 @@
import { ExperimentType, scoreLabels, scoreNames } from '@core/common/types'
-import sampleV17 from '../data-formats/17.json'
-
-// Use 17.json to define ExperimentTypeV17. Explicitly define string literals to make ts happy.
-// Avoid using current ExperimentType as that can change in the future
-type ExperimentTypeV17 = Omit<
- typeof sampleV17,
- 'valueVariables' | 'constraints' | 'dataPoints'
-> & {
- valueVariables: (Omit<(typeof sampleV17.valueVariables)[number], 'type'> & {
- type: 'discrete' | 'continuous'
- })[]
- constraints: (Omit<(typeof sampleV17.constraints)[number], 'type'> & {
- type: 'sum'
- })[]
- dataPoints: (Omit<(typeof sampleV17.dataPoints)[number], 'data'> & {
- data: (
- | {
- type: 'numeric' | 'score'
- name: string
- value: number
- }
- | {
- type: 'categorical'
- name: string
- value: string
- }
- )[]
- })[]
-}
// rename scores, change description to empty string, add labels
-export const migrateToV18 = (json: ExperimentTypeV17): ExperimentType => {
+// Note: Json is v17 but casted as ExperimentType
+export const migrateToV18 = (json: ExperimentType): ExperimentType => {
return {
...json,
info: {
From 9d3291e9fd0b99733d293ec93bf5cfd74dfc550d Mon Sep 17 00:00:00 2001
From: Jack Ord <36296721+j-or@users.noreply.github.com>
Date: Fri, 23 Jan 2026 07:58:51 +0000
Subject: [PATCH 11/11] Hardcode v17 type
---
.../util/migration/migrations/migrateToV18.ts | 86 ++++++++++++++++++-
1 file changed, 84 insertions(+), 2 deletions(-)
diff --git a/packages/core/src/common/util/migration/migrations/migrateToV18.ts b/packages/core/src/common/util/migration/migrations/migrateToV18.ts
index 0f1eaa09..cd03ab52 100644
--- a/packages/core/src/common/util/migration/migrations/migrateToV18.ts
+++ b/packages/core/src/common/util/migration/migrations/migrateToV18.ts
@@ -1,8 +1,90 @@
import { ExperimentType, scoreLabels, scoreNames } from '@core/common/types'
+export type ExperimentTypeV17 = {
+ id: string
+ lastEvaluationHash?: string
+ changedSinceLastEvaluation: boolean
+ info: {
+ name: string
+ description: string
+ swVersion: string
+ dataFormatVersion: '17'
+ version: number
+ extras: Record
+ }
+ extras: Record
+ categoricalVariables: {
+ name: string
+ description: string
+ options: string[]
+ enabled: boolean
+ }[]
+ valueVariables: {
+ type: 'discrete' | 'continuous'
+ name: string
+ description: string
+ min: number
+ max: number
+ enabled: boolean
+ }[]
+ scoreVariables: {
+ name: string
+ label?: string
+ description: string
+ enabled: boolean
+ }[]
+ constraints: {
+ type: 'sum'
+ value: number
+ dimensions: string[]
+ }[]
+ optimizerConfig: {
+ baseEstimator: string
+ acqFunc: string
+ initialPoints: number
+ kappa: number
+ xi: number
+ }
+ results: {
+ id: string
+ plots: {
+ id: string
+ plot: string
+ }[]
+ next: (number | string)[][]
+ pickled: string
+ expectedMinimum: ((number | string)[] | number)[]
+ extras: Record
+ }
+ dataPoints: {
+ meta: {
+ id: number
+ enabled: boolean
+ valid: boolean
+ description?: string
+ }
+ data: (
+ | {
+ type: 'numeric'
+ name: string
+ value: number
+ }
+ | {
+ type: 'categorical'
+ name: string
+ value: string
+ }
+ | {
+ type: 'score'
+ name: string
+ value: number
+ }
+ )[]
+ }[]
+}
+
// rename scores, change description to empty string, add labels
-// Note: Json is v17 but casted as ExperimentType
-export const migrateToV18 = (json: ExperimentType): ExperimentType => {
+export const migrateToV18 = (json: ExperimentTypeV17): ExperimentType => {
return {
...json,
info: {