From 673565744652e5484310f48ca9efc35fdb93e912 Mon Sep 17 00:00:00 2001 From: DetachHead <57028336+DetachHead@users.noreply.github.com> Date: Tue, 12 May 2026 02:45:30 +1000 Subject: [PATCH 1/8] uv: add `system-certs` and mark `native-tls` as deprecated (#5667) see https://github.com/astral-sh/uv/releases/tag/0.11.9 --- src/schemas/json/uv.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/schemas/json/uv.json b/src/schemas/json/uv.json index efe8e6ff12a..4a0e592b63f 100644 --- a/src/schemas/json/uv.json +++ b/src/schemas/json/uv.json @@ -322,10 +322,14 @@ "description": "Whether the project is managed by uv. If `false`, uv will ignore the project when\n`uv run` is invoked.", "type": ["boolean", "null"] }, - "native-tls": { + "system-certs": { "description": "Whether to load TLS certificates from the platform's native certificate store.\n\nBy default, uv loads certificates from the bundled `webpki-roots` crate. The\n`webpki-roots` are a reliable set of trust roots from Mozilla, and including them in uv\nimproves portability and performance (especially on macOS).\n\nHowever, in some cases, you may want to use the platform's native certificate store,\nespecially if you're relying on a corporate trust root (e.g., for a mandatory proxy) that's\nincluded in your system's certificate store.", "type": ["boolean", "null"] }, + "native-tls": { + "description": "(Deprecated: use `system-certs` instead.) Whether to load TLS certificates from the platform's native certificate store.\n\nBy default, uv loads certificates from the bundled `webpki-roots` crate. The\n`webpki-roots` are a reliable set of trust roots from Mozilla, and including them in uv\nimproves portability and performance (especially on macOS).\n\nHowever, in some cases, you may want to use the platform's native certificate store,\nespecially if you're relying on a corporate trust root (e.g., for a mandatory proxy) that's\nincluded in your system's certificate store.", + "type": ["boolean", "null"] + }, "no-binary": { "description": "Don't install pre-built wheels.\n\nThe given packages will be built and installed from source. The resolver will still use\npre-built wheels to extract package metadata, if available.", "type": ["boolean", "null"] From 6e11618130792e0b04f6c839b4b329907b83d515 Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Mon, 11 May 2026 17:45:49 +0100 Subject: [PATCH 2/8] Add Agent Permission Policy schema (.agents/permissions.json) (#5666) * Add Agent Permission Policy schema (.agents/permissions.json) * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- src/api/json/catalog.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/api/json/catalog.json b/src/api/json/catalog.json index d88efdc6134..70c2ee0e90c 100644 --- a/src/api/json/catalog.json +++ b/src/api/json/catalog.json @@ -9709,6 +9709,15 @@ "fileMatch": ["*.nexusrpc.yaml", "*.nexusrpc.yml"], "url": "https://raw.githubusercontent.com/nexus-rpc/nexus-rpc-gen/main/schemas/nexus-rpc-gen.json" }, + { + "name": "Agent Permission Policy", + "description": "Cross-agent permission policy for AI coding agents", + "fileMatch": [ + "**/.agents/permissions.json", + "**/.agents/permissions.local.json" + ], + "url": "https://raw.githubusercontent.com/Mearman/agent-permissions/main/agent-permissions.schema.json" + }, { "name": "AgentCore CLI", "description": "Configuration file for Amazon Bedrock AgentCore CLI projects", From 34644afc90182ae6e2970c6597cbb1ce8a3c29ca Mon Sep 17 00:00:00 2001 From: Michael Osofsky Date: Tue, 12 May 2026 00:46:10 +0800 Subject: [PATCH 3/8] Added schema abc-supply-plan-13.0.0.json (#5668) * Added schema abc-supply-plan-13.0.0.json - Added abc-supply-plan-13.0.0.json schema file - Updated catalog.json to include version 13.0.0 and set as default - Updated schema-validation.jsonc with validation configuration - Added positive test case for version 13.0.0 - Copied negative test cases from version 12.0.0 with updated schema references Key changes in version 13.0.0: - Added required sourceLink property to demand row metadata (oneOf: null, Clinical Demand Forecast link, or Supply Plan link) Testing: - Validated schema-specific: node ./cli.js check --schema-name=abc-supply-plan-13.0.0.json - Validated full test suite: node ./cli.js check (802 schemas pass) Co-Authored-By: Claude Opus 4.7 (1M context) * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: Michael Osofsky Co-authored-by: Claude Opus 4.7 (1M context) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- src/api/json/catalog.json | 5 +- .../abc-supply-plan-extraneous-property.json | 15 + ...pply-plan-invalid-fractional-lot-size.json | 48 + .../abc-supply-plan-invalid-plan-date.json | 12 + ...upply-plan-invalid-strings-as-numbers.json | 48 + ...c-supply-plan-missing-schema-property.json | 11 + .../abc-supply-plan-missing-tabs.json | 11 + src/schema-validation.jsonc | 22 + src/schemas/json/abc-supply-plan-13.0.0.json | 1553 +++++++++++++++++ .../abc-supply-plan.json | 550 ++++++ 10 files changed, 2273 insertions(+), 2 deletions(-) create mode 100644 src/negative_test/abc-supply-plan-13.0.0/abc-supply-plan-extraneous-property.json create mode 100644 src/negative_test/abc-supply-plan-13.0.0/abc-supply-plan-invalid-fractional-lot-size.json create mode 100644 src/negative_test/abc-supply-plan-13.0.0/abc-supply-plan-invalid-plan-date.json create mode 100644 src/negative_test/abc-supply-plan-13.0.0/abc-supply-plan-invalid-strings-as-numbers.json create mode 100644 src/negative_test/abc-supply-plan-13.0.0/abc-supply-plan-missing-schema-property.json create mode 100644 src/negative_test/abc-supply-plan-13.0.0/abc-supply-plan-missing-tabs.json create mode 100644 src/schemas/json/abc-supply-plan-13.0.0.json create mode 100644 src/test/abc-supply-plan-13.0.0/abc-supply-plan.json diff --git a/src/api/json/catalog.json b/src/api/json/catalog.json index 70c2ee0e90c..9d02367df14 100644 --- a/src/api/json/catalog.json +++ b/src/api/json/catalog.json @@ -230,7 +230,7 @@ "name": "ABCSupplyPlan", "description": "ABCSupplyPlan representing all the state for performing inventory optimization and expiry analysis in ABC-Plan MasterPlanner", "fileMatch": ["abc-supply-plan-*.json"], - "url": "https://www.schemastore.org/abc-supply-plan-12.0.0.json", + "url": "https://www.schemastore.org/abc-supply-plan-13.0.0.json", "versions": { "1.0.0": "https://www.schemastore.org/abc-supply-plan-1.0.0.json", "2.0.0": "https://www.schemastore.org/abc-supply-plan-2.0.0.json", @@ -248,7 +248,8 @@ "11.2.0": "https://www.schemastore.org/abc-supply-plan-11.2.0.json", "11.3.0": "https://www.schemastore.org/abc-supply-plan-11.3.0.json", "11.4.0": "https://www.schemastore.org/abc-supply-plan-11.4.0.json", - "12.0.0": "https://www.schemastore.org/abc-supply-plan-12.0.0.json" + "12.0.0": "https://www.schemastore.org/abc-supply-plan-12.0.0.json", + "13.0.0": "https://www.schemastore.org/abc-supply-plan-13.0.0.json" } }, { diff --git a/src/negative_test/abc-supply-plan-13.0.0/abc-supply-plan-extraneous-property.json b/src/negative_test/abc-supply-plan-13.0.0/abc-supply-plan-extraneous-property.json new file mode 100644 index 00000000000..2024cbcd88d --- /dev/null +++ b/src/negative_test/abc-supply-plan-13.0.0/abc-supply-plan-extraneous-property.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://json.schemastore.org/abc-supply-plan-13.0.0.json", + "abcMaterialsMap": {}, + "analytics": { + "items": [], + "layouts": [], + "tabs": [] + }, + "planDate": "2020-03-01", + "planNotes": "{\"blocks\":[{\"key\":\"8o58p\",\"text\":\"Plan for March 2020\",\"type\":\"unstyled\",\"depth\":0,\"inlineStyleRanges\":[],\"entityRanges\":[],\"data\":{}}],\"entityMap\":{}}", + "recipeMap": {}, + "this_is_an_invalid_property": { + "this_is_an_invalid_object_property": "this_is_an_invalid_object_value" + } +} diff --git a/src/negative_test/abc-supply-plan-13.0.0/abc-supply-plan-invalid-fractional-lot-size.json b/src/negative_test/abc-supply-plan-13.0.0/abc-supply-plan-invalid-fractional-lot-size.json new file mode 100644 index 00000000000..fc1fb253664 --- /dev/null +++ b/src/negative_test/abc-supply-plan-13.0.0/abc-supply-plan-invalid-fractional-lot-size.json @@ -0,0 +1,48 @@ +{ + "$schema": "https://json.schemastore.org/abc-supply-plan-13.0.0.json", + "abcMaterialsMap": { + "1": { + "abcMaterialName": "FDP", + "actuals": {}, + "currency": "USD", + "decimalPrecision": 0, + "demand": { + "2020-07-01": 100 + }, + "doExpiryCarryover": false, + "expiryAdjustments": {}, + "firmOrders": [], + "firmRelease": {}, + "firmingPeriod": 0, + "inventory": {}, + "leadTime": 3, + "lifetime": 10, + "lotSizes": [ + { + "endDate": null, + "startDate": null, + "timeDependentValue": 100.5 + } + ], + "maximumInventories": [], + "minimumInventories": [], + "ordering": 1, + "otherDemand": {}, + "otherDemandAnnotations": {}, + "plannedOrders": {}, + "plannedRelease": {}, + "productionMethod": "CumulativeLeadTime", + "timeAggregateType": "Monthly", + "x": 249, + "y": 127 + } + }, + "analytics": { + "items": [], + "layouts": [], + "tabs": [] + }, + "planDate": "2020-03-01", + "planNotes": "{\"blocks\":[{\"key\":\"8o58p\",\"text\":\"Plan for March 2020\",\"type\":\"unstyled\",\"depth\":0,\"inlineStyleRanges\":[],\"entityRanges\":[],\"data\":{}}],\"entityMap\":{}}", + "recipeMap": {} +} diff --git a/src/negative_test/abc-supply-plan-13.0.0/abc-supply-plan-invalid-plan-date.json b/src/negative_test/abc-supply-plan-13.0.0/abc-supply-plan-invalid-plan-date.json new file mode 100644 index 00000000000..faefe466f75 --- /dev/null +++ b/src/negative_test/abc-supply-plan-13.0.0/abc-supply-plan-invalid-plan-date.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://json.schemastore.org/abc-supply-plan-13.0.0.json", + "abcMaterialsMap": {}, + "analytics": { + "items": [], + "layouts": [], + "tabs": [] + }, + "planDate": "March 1st, 2020", + "planNotes": "{\"blocks\":[{\"key\":\"8o58p\",\"text\":\"Plan for March 2020\",\"type\":\"unstyled\",\"depth\":0,\"inlineStyleRanges\":[],\"entityRanges\":[],\"data\":{}}],\"entityMap\":{}}", + "recipeMap": {} +} diff --git a/src/negative_test/abc-supply-plan-13.0.0/abc-supply-plan-invalid-strings-as-numbers.json b/src/negative_test/abc-supply-plan-13.0.0/abc-supply-plan-invalid-strings-as-numbers.json new file mode 100644 index 00000000000..db4a63ed5f0 --- /dev/null +++ b/src/negative_test/abc-supply-plan-13.0.0/abc-supply-plan-invalid-strings-as-numbers.json @@ -0,0 +1,48 @@ +{ + "$schema": "https://json.schemastore.org/abc-supply-plan-13.0.0.json", + "abcMaterialsMap": { + "1": { + "abcMaterialName": "FDP", + "actuals": {}, + "currency": "USD", + "decimalPrecision": "zero", + "demand": { + "2020-07-01": "one hundred" + }, + "doExpiryCarryover": false, + "expiryAdjustments": {}, + "firmOrders": [], + "firmRelease": {}, + "firmingPeriod": "0", + "inventory": {}, + "leadTime": "three", + "lifetime": 10, + "lotSizes": [ + { + "endDate": null, + "startDate": null, + "timeDependentValue": "2000" + } + ], + "maximumInventories": [], + "minimumInventories": [], + "ordering": 1, + "otherDemand": {}, + "otherDemandAnnotations": {}, + "plannedOrders": {}, + "plannedRelease": {}, + "productionMethod": "CumulativeLeadTime", + "timeAggregateType": "Monthly", + "x": 249, + "y": 127 + } + }, + "analytics": { + "items": [], + "layouts": [], + "tabs": [] + }, + "planDate": "2020-03-01", + "planNotes": "{\"blocks\":[{\"key\":\"8o58p\",\"text\":\"Plan for March 2020\",\"type\":\"unstyled\",\"depth\":0,\"inlineStyleRanges\":[],\"entityRanges\":[],\"data\":{}}],\"entityMap\":{}}", + "recipeMap": {} +} diff --git a/src/negative_test/abc-supply-plan-13.0.0/abc-supply-plan-missing-schema-property.json b/src/negative_test/abc-supply-plan-13.0.0/abc-supply-plan-missing-schema-property.json new file mode 100644 index 00000000000..5bed16811ea --- /dev/null +++ b/src/negative_test/abc-supply-plan-13.0.0/abc-supply-plan-missing-schema-property.json @@ -0,0 +1,11 @@ +{ + "abcMaterialsMap": {}, + "analytics": { + "items": [], + "layouts": [], + "tabs": [] + }, + "planDate": "2020-03-01", + "planNotes": "{\"blocks\":[{\"key\":\"8o58p\",\"text\":\"Plan for March 2020\",\"type\":\"unstyled\",\"depth\":0,\"inlineStyleRanges\":[],\"entityRanges\":[],\"data\":{}}],\"entityMap\":{}}", + "recipeMap": {} +} diff --git a/src/negative_test/abc-supply-plan-13.0.0/abc-supply-plan-missing-tabs.json b/src/negative_test/abc-supply-plan-13.0.0/abc-supply-plan-missing-tabs.json new file mode 100644 index 00000000000..eb49cc263a1 --- /dev/null +++ b/src/negative_test/abc-supply-plan-13.0.0/abc-supply-plan-missing-tabs.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://json.schemastore.org/abc-supply-plan-13.0.0.json", + "abcMaterialsMap": {}, + "analytics": { + "items": [], + "layouts": [] + }, + "planDate": "2020-03-01", + "planNotes": "{\"blocks\":[{\"key\":\"8o58p\",\"text\":\"Plan for March 2020\",\"type\":\"unstyled\",\"depth\":0,\"inlineStyleRanges\":[],\"entityRanges\":[],\"data\":{}}],\"entityMap\":{}}", + "recipeMap": {} +} diff --git a/src/schema-validation.jsonc b/src/schema-validation.jsonc index 839bde39eeb..d379f49ec82 100644 --- a/src/schema-validation.jsonc +++ b/src/schema-validation.jsonc @@ -782,6 +782,28 @@ "abcArePlanningFrequencyChangesOnPlanningMonths" ] }, + "abc-supply-plan-13.0.0.json": { + "unknownFormat": ["abc-draft-js_RawDraftContentState"], + "unknownKeywords": [ + "abcIsFirstDayOfMonth", + "abcIsLastDayOfMonth", + "abcIsAfter0001-01-01", + "abcIsBefore9999-12-31", + "abcDoMaterialIDsExist", + "abcIsAcyclic", + "abcAreAllocationMethodsHomogeneous", + "abcIsValidColor", + "abcNoDuplicateValuesForOrderingProperty", + "abcHasNonOverlappingTimeDependentValues", + "abcHasUninterruptedTimeDependentValues", + "abcIsExpirationDateOnOrAfterManufactureDate", + "abcIsReleaseDateOnOrAfterPlanDate", + "abcIsReleaseDateOnOrAfterManufactureDate", + "abcIsExpirationDateOnOrAfterReleaseDate", + "abcDemandDetailsMatchDemandRows", + "abcArePlanningFrequencyChangesOnPlanningMonths" + ] + }, "anywork-ac-1.0.json": { "externalSchema": ["base.json"] }, diff --git a/src/schemas/json/abc-supply-plan-13.0.0.json b/src/schemas/json/abc-supply-plan-13.0.0.json new file mode 100644 index 00000000000..ac6a70e97b8 --- /dev/null +++ b/src/schemas/json/abc-supply-plan-13.0.0.json @@ -0,0 +1,1553 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://json.schemastore.org/abc-supply-plan-13.0.0.json", + "$comment": "AUTO-GENERATED by scripts/assemble-schemas.js — DO NOT EDIT BY HAND", + "title": "ABCSupplyPlan JSON Schema", + "description": "Schema defining the structure of ABCSupplyPlan used for managing plan data in ABC-Plan's MasterPlanner.", + "properties": { + "$schema": { + "description": "Link to https://json.schemastore.org/abc-supply-plan-13.0.0.json", + "type": "string", + "enum": ["https://json.schemastore.org/abc-supply-plan-13.0.0.json"] + }, + "planDate": { + "type": "string", + "format": "date", + "title": "Plan Date", + "description": "The start date for the plan. Format: first day of a month.", + "abcIsFirstDayOfMonth": true, + "abcIsAfter0001-01-01": true, + "abcIsBefore9999-12-31": true + }, + "planNotes": { + "type": "string", + "format": "abc-draft-js_RawDraftContentState", + "title": "Plan Notes", + "description": "Notes or comments about the plan in a specified format. Since there is no JSON Schema for draft-js, the underlying component for mui-rte, format abc-draft-js_RawDraftContentState is enforced by calling https://draftjs.org/docs/api-reference-data-conversion/#convertfromraw. see also: https://github.com/facebookarchive/draft-js/issues/2071 and https://github.com/facebookarchive/draft-js/issues/1544" + }, + "analytics": { + "$comment": "SINGLE SOURCE OF TRUTH for analytics configuration. Used by scripts/assemble-schemas.js to replace __ABC_JSON_SCHEMA_PLACEHOLDER_ANALYTICS__ in each template. DO NOT reference this file directly — it is inlined into parent schemas at build time.", + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "title": "Analytics Item", + "description": "An analytics item that can be either a time series or an analytics note", + "oneOf": [ + { + "type": "object", + "title": "Time Series", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "type": { + "type": "string", + "enum": ["TIME_SERIES"] + }, + "subtitle": { + "type": "string" + }, + "excludeMonthsFromBeginning": { + "type": "number", + "description": "Number of months to exclude from the beginning of the time series. Positive values trim, negative values add months before the plan start date." + }, + "excludeMonthsFromEnd": { + "type": "number", + "description": "Number of months to exclude from the end of the time series. Positive values trim, negative values add months after the plan end date." + }, + "timeAggregateType": { + "type": "string", + "enum": ["Annual", "Quarterly", "Monthly"], + "title": "Time Aggregate Type", + "description": "The aggregation level for time series data display" + }, + "metrics": { + "type": "array", + "items": { + "type": "object", + "properties": { + "metricType": { + "type": "string", + "enum": [ + "demand", + "demandDetail", + "actuals", + "otherDemand", + "firmOrders", + "plannedOrders", + "firmRelease", + "plannedRelease", + "expiryAdjustments", + "inventory", + "mfc", + "targetMFC", + "capacityUtilization", + "workInProgress" + ] + }, + "abcMaterialIDs": { + "type": "array", + "items": { + "type": "string" + } + }, + "visualization": { + "type": "object", + "oneOf": [ + { + "properties": { + "type": { + "type": "string", + "enum": ["line"] + }, + "strokeWidth": { + "type": "number" + }, + "strokeDasharray": { + "type": "string" + }, + "dotSize": { + "type": "number", + "minimum": 0 + }, + "dotFill": { + "type": "string" + }, + "showDataPointValues": { + "type": "boolean", + "description": "Display data point values directly on the chart" + } + }, + "required": [ + "type", + "strokeWidth", + "strokeDasharray", + "dotSize", + "dotFill", + "showDataPointValues" + ], + "additionalProperties": false + }, + { + "properties": { + "type": { + "type": "string", + "enum": ["bar"] + }, + "barWidth": { + "type": "number" + }, + "radius": { + "type": "number" + }, + "stackId": { + "type": "string" + }, + "showAsPercent": { + "type": "boolean" + }, + "showDataPointValues": { + "type": "boolean", + "description": "Display data point values directly on the chart" + } + }, + "required": [ + "type", + "barWidth", + "radius", + "stackId", + "showAsPercent", + "showDataPointValues" + ], + "additionalProperties": false + }, + { + "properties": { + "type": { + "type": "string", + "enum": ["area"] + }, + "fillOpacity": { + "type": "number" + }, + "strokeWidth": { + "type": "number" + }, + "stackId": { + "type": "string" + }, + "showAsPercent": { + "type": "boolean" + }, + "showDataPointValues": { + "type": "boolean", + "description": "Display data point values directly on the chart" + } + }, + "required": [ + "type", + "fillOpacity", + "strokeWidth", + "stackId", + "showAsPercent", + "showDataPointValues" + ], + "additionalProperties": false + } + ] + }, + "yAxisIndex": { + "type": "number" + }, + "color": { + "type": "string" + }, + "zIndex": { + "type": "number" + }, + "label": { + "type": "string" + }, + "showQuantitiesAs": { + "type": ["string", "null"], + "oneOf": [ + { + "type": "string", + "enum": ["Units", "Lots", "Monetary"] + }, + { + "type": "null" + } + ], + "title": "Show Quantities As", + "description": "How to display the quantities for this metric" + }, + "comparisonPlanID": { + "type": ["string", "null"], + "title": "Comparison Plan ID", + "description": "The ID of the comparison plan to use for this metric or null if no comparison plan is used" + }, + "regionID": { + "type": "string", + "title": "Region ID", + "description": "Optional region filter for Clinical Demand Forecast metrics" + }, + "armID": { + "type": "string", + "title": "Arm ID", + "description": "Optional treatment arm filter for Clinical Demand Forecast metrics" + }, + "kitID": { + "type": "string", + "title": "Kit ID", + "description": "Optional kit filter for Clinical Demand Forecast metrics" + }, + "itemID": { + "type": "string", + "title": "Item ID", + "description": "Optional item filter for Clinical Demand Forecast metrics" + }, + "isCumulative": { + "type": "boolean", + "title": "Is Cumulative", + "description": "Whether to display cumulative values" + } + }, + "required": [ + "metricType", + "abcMaterialIDs", + "visualization", + "yAxisIndex", + "color", + "zIndex", + "label", + "showQuantitiesAs", + "comparisonPlanID" + ], + "allOf": [ + { + "if": { + "properties": { + "metricType": { + "const": "mfc" + } + } + }, + "then": { + "properties": { + "showQuantitiesAs": { + "type": "null" + } + } + } + }, + { + "if": { + "properties": { + "metricType": { + "const": "targetMFC" + } + } + }, + "then": { + "properties": { + "showQuantitiesAs": { + "type": "null" + } + } + } + }, + { + "if": { + "properties": { + "metricType": { + "const": "otherDemand" + } + } + }, + "then": { + "properties": { + "showQuantitiesAs": { + "enum": ["Units"] + } + } + } + }, + { + "if": { + "properties": { + "metricType": { + "const": "capacityUtilization" + } + } + }, + "then": { + "properties": { + "showQuantitiesAs": { + "type": "null" + } + } + } + }, + { + "if": { + "properties": { + "metricType": { + "const": "demand" + } + } + }, + "then": { + "properties": { + "showQuantitiesAs": { + "enum": ["Units", "Monetary"] + } + } + } + }, + { + "if": { + "properties": { + "metricType": { + "const": "actuals" + } + } + }, + "then": { + "properties": { + "showQuantitiesAs": { + "enum": ["Units", "Monetary"] + } + } + } + }, + { + "if": { + "properties": { + "metricType": { + "const": "firmRelease" + } + } + }, + "then": { + "properties": { + "showQuantitiesAs": { + "enum": ["Units", "Lots", "Monetary"] + } + } + } + }, + { + "if": { + "properties": { + "metricType": { + "const": "plannedRelease" + } + } + }, + "then": { + "properties": { + "showQuantitiesAs": { + "enum": ["Units", "Lots", "Monetary"] + } + } + } + } + ], + "additionalProperties": false + } + } + }, + "required": [ + "id", + "name", + "type", + "subtitle", + "excludeMonthsFromBeginning", + "excludeMonthsFromEnd", + "timeAggregateType", + "metrics" + ], + "additionalProperties": false + }, + { + "type": "object", + "title": "Analytics Note Item", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "type": { + "type": "string", + "enum": ["ANALYTICS_NOTE"] + }, + "subtitle": { + "type": "string" + }, + "backgroundColor": { + "type": "string" + }, + "imageUrl": { + "type": "string" + }, + "text": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "fontFamily": { + "type": "string" + }, + "fontSize": { + "type": "number" + }, + "color": { + "type": "string" + }, + "horizontalAlignment": { + "type": "string", + "enum": ["left", "center", "right"] + }, + "verticalAlignment": { + "type": "string", + "enum": ["top", "center", "bottom"] + } + }, + "required": [ + "content", + "fontFamily", + "fontSize", + "color", + "horizontalAlignment", + "verticalAlignment" + ], + "additionalProperties": false + } + }, + "required": ["id", "name", "type", "subtitle"], + "additionalProperties": false + }, + { + "type": "object", + "title": "Demand/Consumption Allocation Item", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "type": { + "type": "string", + "enum": ["DEMAND_OR_CONSUMPTION_ALLOCATION"] + }, + "subtitle": { + "type": "string" + }, + "materialId": { + "type": "string", + "description": "Single material ID for demand details analysis" + }, + "visualizationType": { + "type": "string", + "enum": ["PIE_CHART", "TIME_SERIES"], + "description": "Visualization type for the demand details" + }, + "chartType": { + "type": "string", + "enum": ["line", "bar", "area"], + "description": "Chart type for time series visualization (defaults to bar)" + }, + "excludeMonthsFromBeginning": { + "type": "number", + "description": "Months to exclude from plan start (default: 0)" + }, + "excludeMonthsFromEnd": { + "type": "number", + "description": "Months to exclude from plan end (default: 0)" + }, + "timeAggregateType": { + "type": "string", + "enum": ["Annual", "Quarterly", "Monthly"], + "description": "Time aggregation type for TIME_SERIES mode" + }, + "displayConfig": { + "type": "object", + "properties": { + "showTopN": { + "type": "number", + "description": "Show top N categories" + }, + "showOther": { + "type": "boolean", + "description": "Group remaining into Other" + }, + "showPercentages": { + "type": "boolean", + "description": "Show percentages in labels" + }, + "showLegend": { + "type": "boolean", + "description": "Show legend" + }, + "minSlicePercentage": { + "type": "number", + "description": "Min percentage to show separately" + } + }, + "required": [ + "showTopN", + "showOther", + "showPercentages", + "showLegend", + "minSlicePercentage" + ], + "additionalProperties": false + }, + "planId": { + "type": "string", + "description": "Optional plan ID for comparison" + } + }, + "required": [ + "id", + "name", + "type", + "subtitle", + "materialId", + "visualizationType" + ], + "additionalProperties": false + } + ] + } + }, + "layouts": { + "type": "array", + "items": { + "type": "object", + "properties": { + "contentId": { + "type": "string" + }, + "x": { + "type": "number", + "minimum": 0, + "maximum": 11, + "description": "Grid column position (0-11 for 12-column grid)" + }, + "y": { + "type": "number", + "minimum": 0, + "description": "Grid row position" + }, + "tabID": { + "type": "string", + "description": "ID of the tab this layout item belongs to" + }, + "w": { + "type": "number", + "minimum": 1, + "maximum": 12, + "description": "Width in grid units (1-12)" + }, + "h": { + "type": "number", + "minimum": 1, + "description": "Height in grid units" + }, + "minH": { + "type": "number", + "minimum": 1, + "description": "Minimum height in grid units" + }, + "maxH": { + "type": "number", + "minimum": 1, + "description": "Maximum height in grid units" + }, + "minW": { + "type": "number", + "minimum": 1, + "description": "Minimum width in grid units" + }, + "maxW": { + "type": "number", + "minimum": 1, + "description": "Maximum width in grid units" + }, + "isDraggable": { + "type": "boolean", + "description": "Whether the item can be dragged" + }, + "isResizable": { + "type": "boolean", + "description": "Whether the item can be resized" + } + }, + "required": ["contentId", "x", "y", "tabID", "w", "h"], + "additionalProperties": false + } + }, + "tabs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "ordering": { + "type": "number" + } + }, + "required": ["id", "name", "ordering"], + "additionalProperties": false + } + } + }, + "required": ["items", "layouts", "tabs"], + "additionalProperties": false + }, + "abcMaterialsMap": { + "type": "object", + "patternProperties": { + "^\\d+$": { + "$ref": "#/definitions/ABCMaterialState" + } + }, + "additionalProperties": false, + "title": "ABC Materials Map", + "description": "A mapping of material IDs to their respective states within the ABC system.", + "abcNoDuplicateValuesForOrderingProperty": true + }, + "recipeMap": { + "type": "object", + "patternProperties": { + "^\\d+-\\d+$": { + "$ref": "#/definitions/RecipeState" + } + }, + "additionalProperties": false, + "title": "Recipe Map", + "description": "A mapping of recipes, representing the acyclic relationships among materials. abcAreAllocationMethodsHomogeneous requires no mixing of Allocation Methods. A downstream material cannot have mixed upstream recipes. An upstream material cannot have mixed downstream recipes.", + "abcDoMaterialIDsExist": true, + "abcIsAcyclic": true, + "abcAreAllocationMethodsHomogeneous": true + } + }, + "required": [ + "$schema", + "planDate", + "planNotes", + "analytics", + "abcMaterialsMap", + "recipeMap" + ], + "additionalProperties": false, + "type": "object", + "definitions": { + "ABCMaterialState": { + "type": "object", + "title": "ABC Material State", + "description": "Represents the state of a material in the system including its attributes and planning parameters.", + "abcDemandDetailsMatchDemandRows": true, + "properties": { + "x": { + "type": "number", + "title": "X Coordinate", + "description": "The X coordinate position of the material in a graphical representation." + }, + "y": { + "type": "number", + "title": "Y Coordinate", + "description": "The Y coordinate position of the material in a graphical representation." + }, + "ordering": { + "type": "number", + "title": "Ordering", + "description": "Numeric value representing the order or sequence of the material." + }, + "abcMaterialName": { + "type": "string", + "minLength": 1, + "title": "Material Name", + "description": "The name of the material." + }, + "uom": { + "type": "string", + "minLength": 1, + "title": "Unit of Measure", + "description": "The unit of measure used for the material." + }, + "materialShape": { + "type": "string", + "enum": [ + "circle", + "square", + "diamond", + "rectangle", + "parallelogram", + "trapezoid", + "triangle", + "pentagon", + "hexagon" + ], + "title": "Material Shape", + "description": "The shape of the material represented graphically." + }, + "materialColor": { + "$ref": "#/definitions/Color" + }, + "doExpiryCarryover": { + "type": "boolean", + "title": "Expiry Carryover", + "description": "Indicates whether to carry over the expiry information for the material." + }, + "isCapacityConstraintNode": { + "type": "boolean", + "title": "Capacity Constraint Node", + "description": "Determines if the material is a node where capacity constraints are applied." + }, + "inventoryMethod": { + "type": "string", + "enum": ["TargetMFC", "MinimumInventory"], + "title": "Inventory Method", + "description": "The method used for managing inventory levels, either target months forward coverage or minimum inventory." + }, + "decimalPrecision": { + "type": "integer", + "minimum": 0, + "maximum": 5, + "title": "Decimal Precision", + "description": "The precision of decimal places allowed for numerical entries related to the material." + }, + "currency": { + "type": "string", + "minLength": 1, + "title": "Currency", + "description": "The currency used for monetary calculations of the material." + }, + "manufacturingCost": { + "type": "number", + "minimum": 0, + "title": "Manufacturing Cost", + "description": "The direct manufacturing cost per unit of the material." + }, + "standardCost": { + "type": "number", + "minimum": 0, + "title": "Standard Cost", + "description": "The standard cost per unit including overhead of the material." + }, + "salesPrice": { + "type": "number", + "minimum": 0, + "title": "Sales Price", + "description": "The sales price per unit of the material." + }, + "lotSize": { + "$comment": "Require integer until we fix https://gitlab.com/abc-plan/abc-plan-server/-/issues/9", + "type": "integer", + "minimum": 1, + "title": "Lot Size", + "description": "Batch size for orders. Must be greater than 0 to plan, etc." + }, + "leadTime": { + "type": "integer", + "minimum": 0, + "title": "Lead Time", + "description": "Delay between Manufacture Date and Release Date. Format: non-negative integer." + }, + "firmingPeriod": { + "type": "integer", + "minimum": 0, + "title": "Firming Period", + "description": "Time during which no Planned Orders are allowed. Format: non-negative integer." + }, + "targetMFCs": { + "$ref": "#/definitions/NonNegativeIntegerTimeDependentValues", + "title": "Target MFC", + "description": "Target Months Forward Coverage refers to a dynamic safety stock level—a buffer quantity of inventory designed to mitigate the risk of stock-outs caused by variability in Demand. In essence, it represents the number of months of Demand that could be satisfied assuming no additional material is manufactured. Each value is defined for a specific period of time." + }, + "minimumInventories": { + "$ref": "#/definitions/NonNegativeIntegerTimeDependentValues", + "title": "Minimum Inventory", + "description": "List of Minimum Inventory values, each defined for a specific period of time. Minimum Inventory denotes the lowest stock level to prevent outages, triggering restock." + }, + "planningFrequencies": { + "$ref": "#/definitions/PositiveIntegerTimeDependentValues", + "title": "Planning Frequencies", + "description": "List of Planning Frequency values, each defined for a specific period of time.", + "abcArePlanningFrequencyChangesOnPlanningMonths": true + }, + "shelfLives": { + "$ref": "#/definitions/NonNegativeIntegerTimeDependentValues", + "title": "Shelf Lives", + "description": "List of Shelf Life values, each defined for a specific period of time." + }, + "stopshipBuffers": { + "$ref": "#/definitions/NonNegativeIntegerTimeDependentValues", + "title": "Stopship Buffers", + "description": "Buffers to account for Stopship scenarios, listed for different periods." + }, + "initialInventories": { + "type": "array", + "items": { + "$ref": "#/definitions/InitialInventory" + }, + "title": "Initial Inventories", + "description": "List of Initial Inventory records, each associated with specific lot and dates." + }, + "firmOrders": { + "type": "array", + "items": { + "$ref": "#/definitions/FirmOrder" + }, + "title": "Firm Orders", + "description": "List of Firm Orders with their respective quantities and dates." + }, + "demand": { + "$ref": "#/definitions/PositiveDateMapMap", + "title": "Demand", + "description": "Map of demand rows, where each key is a unique ID and value is a DateMap containing demand values for specific dates." + }, + "demandDetails": { + "$ref": "#/definitions/ABCDemandDetailsMap", + "title": "Demand/Consumption Allocation", + "description": "Mapping of demand row IDs to their metadata including labels and ordering." + }, + "otherDemand": { + "$ref": "#/definitions/PositiveDateMap", + "title": "Other Demand", + "description": "Map of other types of demand not included in the primary demand values." + }, + "otherDemandAnnotation": { + "$ref": "#/definitions/AnnotationMap", + "title": "Other Demand Annotation", + "description": "Annotations related to other demand entries, providing additional context." + }, + "actuals": { + "$ref": "#/definitions/PositiveDateMap", + "title": "Actuals", + "description": "Map of actual quantities, corresponding to real data collected." + }, + "plannedOrders": { + "$ref": "#/definitions/PositiveDateMap", + "title": "Planned Orders", + "description": "Map of planned order quantities, anticipated ahead of time." + }, + "expiryAdjustments": { + "$ref": "#/definitions/NegativeDateMap", + "title": "Expiry Adjustments", + "description": "Adjustments made to account for expired materials, reducing quantities." + }, + "expiryAdjustmentsAnnotation": { + "$ref": "#/definitions/AnnotationMap", + "title": "Expiry Adjustments Annotation", + "description": "Annotations related to expiry adjustment entries, providing additional context such as lot numbers being written off." + }, + "timeAggregateType": { + "type": "string", + "enum": ["Annual", "Quarterly", "Monthly"], + "title": "Time Aggregate Type", + "description": "The aggregation level for planning and reporting, e.g., annual, quarterly, or monthly." + }, + "showQuantitiesAs": { + "type": "string", + "enum": ["Units", "Lots", "Monetary"], + "title": "Show Quantities As", + "description": "Defines how quantities are represented, e.g., in units, lots, or monetary value." + }, + "expiryAnalysisType": { + "type": "string", + "enum": ["Expiration", "Stopship"], + "title": "Expiry Analysis Type", + "description": "Determines the type of analysis to be performed on expiry data, focusing on expiration or stopship scenarios." + }, + "timeDependentPlanningParameters": { + "type": "boolean", + "title": "Time Dependent Planning Parameters", + "description": "Indicates whether planning parameters are dependent on time, necessitating different values at different periods." + }, + "inventorySystemMaterialNumber": { + "title": "Material Number in the inventory management system", + "description": "For pulling inventory from the inventory management system into Initial Inventory and Firm Orders & Releases", + "type": ["string", "null"] + }, + "inventorySystemLocationName": { + "title": "Location in the inventory management system", + "description": "For pulling inventory from the inventory management system into Initial Inventory and Firm Orders & Releases and filtering it by location", + "type": ["string", "null"] + } + }, + "required": [ + "x", + "y", + "ordering", + "abcMaterialName", + "uom", + "materialShape", + "materialColor", + "doExpiryCarryover", + "isCapacityConstraintNode", + "inventoryMethod", + "decimalPrecision", + "currency", + "manufacturingCost", + "standardCost", + "salesPrice", + "lotSize", + "leadTime", + "firmingPeriod", + "targetMFCs", + "minimumInventories", + "planningFrequencies", + "shelfLives", + "stopshipBuffers", + "initialInventories", + "firmOrders", + "demand", + "demandDetails", + "actuals", + "expiryAdjustments", + "expiryAdjustmentsAnnotation", + "otherDemand", + "otherDemandAnnotation", + "plannedOrders", + "timeAggregateType", + "showQuantitiesAs", + "expiryAnalysisType", + "timeDependentPlanningParameters", + "inventorySystemMaterialNumber", + "inventorySystemLocationName" + ], + "additionalProperties": false, + "allOf": [ + { + "if": { + "properties": { + "timeDependentPlanningParameters": { + "const": false + } + } + }, + "then": { + "properties": { + "targetMFCs": { + "type": "array", + "maxItems": 1, + "minItems": 1 + }, + "minimumInventories": { + "type": "array", + "maxItems": 1, + "minItems": 1 + }, + "planningFrequencies": { + "type": "array", + "maxItems": 1, + "minItems": 1 + }, + "shelfLives": { + "type": "array", + "maxItems": 1, + "minItems": 1 + }, + "stopshipBuffers": { + "type": "array", + "maxItems": 1, + "minItems": 1 + } + } + } + }, + { + "if": { + "properties": { + "timeDependentPlanningParameters": { + "const": true + } + } + }, + "then": { + "properties": { + "targetMFCs": { + "type": "array", + "minItems": 1 + }, + "minimumInventories": { + "type": "array", + "minItems": 1 + }, + "planningFrequencies": { + "type": "array", + "minItems": 1 + }, + "shelfLives": { + "type": "array", + "minItems": 1 + }, + "stopshipBuffers": { + "type": "array", + "minItems": 1 + } + } + } + } + ] + }, + "RecipeState": { + "type": "object", + "title": "Recipe State", + "description": "Defines a recipe within the system, including its components and yields.", + "properties": { + "recipe": { + "type": "number", + "exclusiveMinimum": 0, + "title": "Recipe ID", + "description": "Unique identifier of the recipe." + }, + "allocationMethod": { + "type": "string", + "enum": ["PercentAllocation", "PriorityAllocation"], + "title": "Consumption Allocation", + "description": "Method for allocating downstream consumption to upstream materials." + }, + "percentAllocations": { + "$ref": "#/definitions/PercentTimeDependentValues", + "title": "Percent Allocations", + "description": "Percentage allocations of materials to the recipe over different periods." + }, + "priorityAllocations": { + "$ref": "#/definitions/NonNegativeNumberTimeDependentValues", + "title": "Priority Allocations", + "description": "Priority allocations of materials to the recipe over different periods." + }, + "percentYield": { + "type": "number", + "minimum": 0, + "title": "Percent Yield", + "description": "The yield percentage of the recipe, indicating efficiency." + } + }, + "required": [ + "recipe", + "allocationMethod", + "percentAllocations", + "priorityAllocations", + "percentYield" + ], + "additionalProperties": false + }, + "PositiveDateMap": { + "type": "object", + "title": "Positive Date Map", + "description": "Mapping of dates to positive numerical values, typically representing demand or supply quantities.", + "patternProperties": { + "^\\d{4}-(0[1-9]|1[0-2])-01$": { + "type": "number", + "minimum": 0 + } + }, + "additionalProperties": false + }, + "PositiveDateMapMap": { + "type": "object", + "title": "Positive Date Map Map", + "description": "Mapping of unique IDs to date maps, supporting multiple demand rows per material.", + "patternProperties": { + "^.*$": { + "$ref": "#/definitions/PositiveDateMap" + } + }, + "additionalProperties": false + }, + "NegativeDateMap": { + "type": "object", + "title": "Negative Date Map", + "description": "Mapping of dates to negative numerical values, typically representing adjustments or reductions.", + "patternProperties": { + "^\\d{4}-(0[1-9]|1[0-2])-01$": { + "type": "number", + "maximum": 0 + } + }, + "additionalProperties": false + }, + "AnnotationMap": { + "type": "object", + "title": "Annotation Map", + "description": "Mapping of dates to annotations or notes about specific entries.", + "patternProperties": { + "^\\d{4}-(0[1-9]|1[0-2])-01$": { + "type": "string" + } + }, + "additionalProperties": false + }, + "Color": { + "type": "string", + "title": "Color", + "description": "Colors may be specified in any string-based format supported by the Color constructor documented at https://www.npmjs.com/package/color", + "abcIsValidColor": "true" + }, + "ABCDemandDetailsMap": { + "type": "object", + "title": "ABC Demand/Consumption Allocation Map", + "description": "Mapping of demand row IDs to their metadata including label, ordering, and optional source plan linkage.", + "patternProperties": { + "^.*$": { + "type": "object", + "properties": { + "label": { + "type": "string", + "title": "Label", + "description": "User-visible label for the demand row." + }, + "ordering": { + "type": "number", + "title": "Ordering", + "description": "Display order of the demand row within a material." + }, + "sourceLink": { + "title": "Source Link", + "description": "Link to the source plan this demand row was copied from. Null if not linked.", + "oneOf": [ + { + "type": "null" + }, + { + "type": "object", + "title": "Clinical Demand Forecast Link", + "description": "Link to a specific kit and region within a Clinical Demand Forecast.", + "properties": { + "planType": { + "type": "string", + "const": "clinicalDemandForecast" + }, + "planID": { + "type": "string", + "title": "Plan ID", + "description": "The ID of the source Clinical Demand Forecast." + }, + "kitID": { + "type": "string", + "title": "Kit ID", + "description": "The kit ID within the source Clinical Demand Forecast." + }, + "regionID": { + "type": "string", + "title": "Region ID", + "description": "The region ID within the source Clinical Demand Forecast." + }, + "armID": { + "type": ["string", "null"], + "title": "Arm ID", + "description": "The treatment arm ID. Null includes all arms." + }, + "itemID": { + "type": ["string", "null"], + "title": "Item ID", + "description": "The kit item ID. Null includes all items." + }, + "isPlacebo": { + "type": ["boolean", "null"], + "title": "Is Placebo", + "description": "Whether this demand is for placebo kits. Null or false for commercial demand." + }, + "includeDemand": { + "type": ["boolean", "null"], + "title": "Include Demand", + "description": "Whether kit demand (treatment-driven) is included." + }, + "includeSiteSeeding": { + "type": ["boolean", "null"], + "title": "Include Site Seeding", + "description": "Whether site seeding demand is included." + } + }, + "required": ["planType", "planID", "kitID", "regionID"], + "additionalProperties": false + }, + { + "type": "object", + "title": "Supply Plan Link", + "description": "Link to a specific material within a Supply Plan.", + "properties": { + "planType": { + "type": "string", + "const": "supplyPlan" + }, + "planID": { + "type": "string", + "title": "Plan ID", + "description": "The ID of the source Supply Plan." + }, + "materialID": { + "type": "string", + "title": "Material ID", + "description": "The material ID within the source Supply Plan." + } + }, + "required": ["planType", "planID", "materialID"], + "additionalProperties": false + } + ] + } + }, + "required": ["label", "ordering", "sourceLink"], + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "TemplateTimeDependentValue": { + "$comment": "additionalProperties is true because this object acts as a template and is merged with other type definitions to specify valid date ranges for time-dependent values.", + "type": "object", + "title": "Template Time Dependent Value", + "description": "Base template for defining time-dependent values, specifying the valid date ranges for such values.", + "properties": { + "startDate": { + "title": "Start Date", + "description": "The start date for the time-dependent value. Must be the first day of a month and within a valid date range.", + "oneOf": [ + { + "type": "string", + "format": "date", + "abcIsFirstDayOfMonth": true, + "abcIsAfter0001-01-01": true, + "abcIsBefore9999-12-31": true + }, + { + "type": "null" + } + ] + }, + "endDate": { + "title": "End Date", + "description": "The end date for the time-dependent value. Must be the last day of a month and within a valid date range.", + "oneOf": [ + { + "type": "string", + "format": "date", + "abcIsLastDayOfMonth": true, + "abcIsAfter0001-01-01": true, + "abcIsBefore9999-12-31": true + }, + { + "type": "null" + } + ] + } + }, + "required": ["startDate", "endDate"], + "additionalProperties": true + }, + "PercentValueConstraints": { + "$comment": "additionalProperties is true because it is a template and gets merged with other types", + "type": "object", + "title": "Percent Value Constraints", + "description": "Constraints for percentage values, defining the valid range as 0% to 100%.", + "properties": { + "timeDependentValue": { + "description": "During a particular period of time for this recipe, how much of the downstream consumption is allocated to the upstream material. Format: 0-1 which correspond to 0%-100%.", + "type": "number", + "minimum": 0, + "maximum": 1 + } + }, + "required": ["timeDependentValue"], + "additionalProperties": true + }, + "PercentTimeDependentValue": { + "allOf": [ + { + "$ref": "#/definitions/TemplateTimeDependentValue" + }, + { + "$ref": "#/definitions/PercentValueConstraints" + } + ], + "title": "Percent Time Dependent Value", + "description": "Defines a time-specific percentage value within a valid date range, adhering to percentage constraints." + }, + "PercentTimeDependentValues": { + "type": "array", + "items": { + "$ref": "#/definitions/PercentTimeDependentValue" + }, + "title": "Percent Time Dependent Values", + "description": "Array of time-dependent percentage values, each specifying the allocation for a given period.", + "abcHasNonOverlappingTimeDependentValues": "true", + "abcHasUninterruptedTimeDependentValues": "true" + }, + "NonNegativeIntegerConstraints": { + "$comment": "additionalProperties is true because this object acts as a template and is merged with other type definitions to enforce non-negative integer constraints in various scenarios.", + "type": "object", + "title": "Non-Negative Integer Constraints", + "description": "Defines constraints for integer values to ensure they are non-negative, used in various time-dependent value configurations.", + "properties": { + "timeDependentValue": { + "type": "integer", + "minimum": 0, + "title": "Time Dependent Value", + "description": "An integer value that cannot be negative, typically representing quantities or counts in a time-dependent context." + } + }, + "required": ["timeDependentValue"], + "additionalProperties": true + }, + "NonNegativeIntegerTimeDependentValue": { + "allOf": [ + { + "$ref": "#/definitions/TemplateTimeDependentValue" + }, + { + "$ref": "#/definitions/NonNegativeIntegerConstraints" + } + ], + "title": "Non-Negative Integer Time Dependent Value", + "description": "Defines a time-specific integer value within a valid date range, ensuring it is non-negative." + }, + "NonNegativeIntegerTimeDependentValues": { + "type": "array", + "items": { + "$ref": "#/definitions/NonNegativeIntegerTimeDependentValue" + }, + "title": "Non-Negative Integer Time Dependent Values", + "description": "Array of time-dependent integer values, ensuring each is non-negative.", + "abcHasNonOverlappingTimeDependentValues": "true", + "abcHasUninterruptedTimeDependentValues": "true" + }, + "NonNegativeNumberConstraints": { + "$comment": "additionalProperties is true to allow merging this object with other type definitions to enforce non-negative number constraints in various scenarios.", + "type": "object", + "title": "Non-Negative Number Constraints", + "description": "Defines constraints for numbers to ensure they are non-negative, used in various contexts.", + "properties": { + "timeDependentValue": { + "type": "number", + "minimum": 0, + "title": "Non-Negative Number", + "description": "A non-negative number, used in various contexts to represent quantities or counts." + } + }, + "required": ["timeDependentValue"], + "additionalProperties": true + }, + "NonNegativeNumberTimeDependentValue": { + "allOf": [ + { + "$ref": "#/definitions/TemplateTimeDependentValue" + }, + { + "$ref": "#/definitions/NonNegativeNumberConstraints" + } + ], + "title": "Non-Negative Number Time Dependent Value", + "description": "Defines a time-specific number within a valid range, ensuring it is non-negative." + }, + "NonNegativeNumberTimeDependentValues": { + "type": "array", + "items": { + "$ref": "#/definitions/NonNegativeNumberTimeDependentValue" + }, + "title": "Non-Negative Number Time Dependent Values", + "description": "Array of time-dependent non-negative numbers.", + "abcHasNonOverlappingTimeDependentValues": "true", + "abcHasUninterruptedTimeDependentValues": "true" + }, + "PositiveIntegerConstraints": { + "$comment": "additionalProperties is set to true because this object acts as a template and is merged with other type definitions to enforce positive integer constraints in various scenarios.", + "type": "object", + "title": "Positive Integer Constraints", + "description": "Defines constraints for integer values to ensure they are positive, used in various configurations where a strictly positive value is required.", + "properties": { + "timeDependentValue": { + "type": "integer", + "minimum": 1, + "title": "Time Dependent Value", + "description": "An integer value that must be positive, typically representing quantities or counts in contexts where zero is not a valid value." + } + }, + "required": ["timeDependentValue"], + "additionalProperties": true + }, + "PositiveIntegerTimeDependentValue": { + "allOf": [ + { + "$ref": "#/definitions/TemplateTimeDependentValue" + }, + { + "$ref": "#/definitions/PositiveIntegerConstraints" + } + ], + "title": "Positive Integer Time Dependent Value", + "description": "Defines a time-specific integer value that must always be positive, ensuring it meets the requirements of scenarios where zero or negative numbers are not permitted." + }, + "PositiveIntegerTimeDependentValues": { + "type": "array", + "items": { + "$ref": "#/definitions/PositiveIntegerTimeDependentValue" + }, + "title": "Positive Integer Time Dependent Values", + "description": "Array of time-dependent integer values, ensuring each is positive. This setup is intended for scenarios where values must always be greater than zero.", + "abcHasNonOverlappingTimeDependentValues": "true", + "abcHasUninterruptedTimeDependentValues": "true" + }, + "InitialInventory": { + "type": "object", + "title": "Initial Inventory", + "description": "Defines the initial inventory of a material, including lot number and associated dates.", + "properties": { + "lotNumber": { + "type": "string", + "minLength": 1, + "title": "Lot Number", + "description": "The identifier for the lot number of the inventory item. It must be at least 1 character in length." + }, + "initialInventoryQuantity": { + "type": "number", + "minimum": 0, + "title": "Initial Inventory Quantity", + "description": "The quantity of the inventory item when first recorded. This must be a non-negative number." + }, + "manufactureDate": { + "type": "string", + "format": "date", + "title": "Manufacture Date", + "description": "The date the item was manufactured. This date must be the first day of a month and fall within a valid date range.", + "abcIsFirstDayOfMonth": true, + "abcIsAfter0001-01-01": true, + "abcIsBefore9999-12-31": true + }, + "expirationDate": { + "type": "string", + "format": "date", + "title": "Expiration Date", + "description": "The date the item will expire. This date must be the last day of a month and fall within a valid date range.", + "abcIsLastDayOfMonth": true, + "abcIsAfter0001-01-01": true, + "abcIsBefore9999-12-31": true, + "abcIsExpirationDateOnOrAfterManufactureDate": true + } + }, + "required": [ + "lotNumber", + "initialInventoryQuantity", + "manufactureDate", + "expirationDate" + ], + "additionalProperties": false + }, + "FirmOrder": { + "type": "object", + "title": "Firm Order", + "description": "Defines a firm order within the system, including order details and relevant dates.", + "properties": { + "firmOrderName": { + "type": "string", + "title": "Firm Order Name", + "description": "The name or identifier of the firm order." + }, + "firmOrderQuantity": { + "type": "number", + "minimum": 0, + "title": "Firm Order Quantity", + "description": "The quantity specified in the firm order. Must be a non-negative value." + }, + "manufactureDate": { + "type": "string", + "format": "date", + "title": "Manufacture Date", + "description": "The date the goods are scheduled to be manufactured. Must be the first day of the month and within valid date range.", + "abcIsFirstDayOfMonth": true, + "abcIsAfter0001-01-01": true, + "abcIsBefore9999-12-31": true + }, + "releaseDate": { + "type": "string", + "format": "date", + "title": "Release Date", + "description": "The date the goods are scheduled to be released. Must be the first day of the month and within valid date range.", + "abcIsFirstDayOfMonth": true, + "abcIsAfter0001-01-01": true, + "abcIsBefore9999-12-31": true, + "abcIsReleaseDateOnOrAfterPlanDate": true, + "abcIsReleaseDateOnOrAfterManufactureDate": true + }, + "expirationDate": { + "type": "string", + "format": "date", + "title": "Expiration Date", + "description": "The expiration date of the product. Must be the last day of the month and within valid date range.", + "abcIsLastDayOfMonth": true, + "abcIsAfter0001-01-01": true, + "abcIsBefore9999-12-31": true, + "abcIsExpirationDateOnOrAfterReleaseDate": true + } + }, + "required": [ + "firmOrderName", + "firmOrderQuantity", + "manufactureDate", + "releaseDate", + "expirationDate" + ], + "additionalProperties": false + } + } +} diff --git a/src/test/abc-supply-plan-13.0.0/abc-supply-plan.json b/src/test/abc-supply-plan-13.0.0/abc-supply-plan.json new file mode 100644 index 00000000000..2a995afdbdf --- /dev/null +++ b/src/test/abc-supply-plan-13.0.0/abc-supply-plan.json @@ -0,0 +1,550 @@ +{ + "$schema": "https://json.schemastore.org/abc-supply-plan-13.0.0.json", + "abcMaterialsMap": { + "1": { + "abcMaterialName": "FDP", + "actuals": {}, + "currency": "USD", + "decimalPrecision": 0, + "demand": { + "1": { + "2020-03-01": 0, + "2020-04-01": 2117, + "2020-05-01": 2214, + "2020-06-01": 2285, + "2020-07-01": 2516, + "2020-08-01": 2675, + "2020-09-01": 2833, + "2020-10-01": 3006, + "2020-11-01": 3196, + "2020-12-01": 3414, + "2021-01-01": 3630, + "2021-02-01": 3859, + "2021-03-01": 4105, + "2021-04-01": 4369, + "2021-05-01": 4651, + "2021-06-01": 4948, + "2021-07-01": 5263, + "2021-08-01": 5600, + "2021-09-01": 5959, + "2021-10-01": 6341, + "2021-11-01": 6748, + "2021-12-01": 7180, + "2022-01-01": 7639, + "2022-02-01": 8128, + "2022-03-01": 8648, + "2022-04-01": 9202, + "2022-05-01": 9791, + "2022-06-01": 10417 + } + }, + "demandDetails": { + "1": { + "label": "Default Demand", + "ordering": 1, + "sourceLink": null + } + }, + "doExpiryCarryover": false, + "expiryAdjustments": { + "2020-03-01": 0, + "2020-04-01": 0, + "2020-05-01": 0, + "2020-06-01": 0, + "2020-07-01": 0, + "2020-08-01": 0, + "2020-09-01": 0, + "2020-10-01": 0, + "2020-11-01": 0, + "2020-12-01": 0, + "2021-01-01": 0, + "2021-02-01": 0, + "2021-03-01": 0, + "2021-04-01": 0, + "2021-05-01": 0, + "2021-06-01": 0, + "2021-07-01": 0, + "2021-08-01": 0, + "2021-09-01": 0, + "2021-10-01": 0, + "2021-11-01": 0, + "2021-12-01": 0, + "2022-01-01": 0, + "2022-02-01": 0, + "2022-03-01": 0, + "2022-04-01": 0, + "2022-05-01": 0, + "2022-06-01": 0 + }, + "expiryAdjustmentsAnnotation": {}, + "expiryAnalysisType": "Expiration", + "firmOrders": [ + { + "expirationDate": "2021-03-31", + "firmOrderName": "Sep Firm Order", + "firmOrderQuantity": 2000, + "manufactureDate": "2020-09-01", + "releaseDate": "2020-10-01" + }, + { + "expirationDate": "2021-05-31", + "firmOrderName": "Nov Firm Order", + "firmOrderQuantity": 4000, + "manufactureDate": "2020-11-01", + "releaseDate": "2020-12-01" + }, + { + "expirationDate": "2021-08-31", + "firmOrderName": "Feb Firm Order", + "firmOrderQuantity": 4000, + "manufactureDate": "2021-02-01", + "releaseDate": "2021-03-01" + }, + { + "expirationDate": "2021-07-31", + "firmOrderName": "Jan Firm Order", + "firmOrderQuantity": 4000, + "manufactureDate": "2021-01-01", + "releaseDate": "2021-02-01" + }, + { + "expirationDate": "2021-06-30", + "firmOrderName": "Dec Firm Order", + "firmOrderQuantity": 4000, + "manufactureDate": "2020-12-01", + "releaseDate": "2021-01-01" + } + ], + "firmingPeriod": 6, + "initialInventories": [ + { + "expirationDate": "2020-07-31", + "initialInventoryQuantity": 8000, + "lotNumber": "old lot", + "manufactureDate": "2020-01-01" + } + ], + "inventoryMethod": "TargetMFC", + "inventorySystemLocationName": null, + "inventorySystemMaterialNumber": null, + "isCapacityConstraintNode": false, + "leadTime": 1, + "lotSize": 2000, + "manufacturingCost": 0, + "materialColor": "#3D85C6", + "materialShape": "circle", + "minimumInventories": [ + { + "endDate": null, + "startDate": null, + "timeDependentValue": 0 + } + ], + "ordering": 0, + "otherDemand": { + "2020-03-01": 0, + "2020-04-01": 0, + "2020-05-01": 0, + "2020-06-01": 0, + "2020-07-01": 0, + "2020-08-01": 0, + "2020-09-01": 0, + "2020-10-01": 0, + "2020-11-01": 0, + "2020-12-01": 0, + "2021-01-01": 0, + "2021-02-01": 0, + "2021-03-01": 0, + "2021-04-01": 0, + "2021-05-01": 0, + "2021-06-01": 0, + "2021-07-01": 0, + "2021-08-01": 0, + "2021-09-01": 0, + "2021-10-01": 0, + "2021-11-01": 0, + "2021-12-01": 0, + "2022-01-01": 0, + "2022-02-01": 0, + "2022-03-01": 0, + "2022-04-01": 0, + "2022-05-01": 0, + "2022-06-01": 0 + }, + "otherDemandAnnotation": {}, + "plannedOrders": { + "2020-09-01": 16000, + "2020-10-01": 4000, + "2020-11-01": 0, + "2020-12-01": 2000, + "2021-01-01": 0, + "2021-02-01": 2000, + "2021-03-01": 6000, + "2021-04-01": 4000, + "2021-05-01": 8000, + "2021-06-01": 6000, + "2021-07-01": 8000, + "2021-08-01": 6000, + "2021-09-01": 10000, + "2021-10-01": 8000, + "2021-11-01": 8000, + "2021-12-01": 10000, + "2022-01-01": 12000, + "2022-02-01": 0, + "2022-03-01": 0, + "2022-04-01": 0, + "2022-05-01": 0 + }, + "planningFrequencies": [ + { + "endDate": null, + "startDate": null, + "timeDependentValue": 1 + } + ], + "salesPrice": 0, + "shelfLives": [ + { + "endDate": null, + "startDate": null, + "timeDependentValue": 6 + } + ], + "showQuantitiesAs": "Units", + "standardCost": 0, + "stopshipBuffers": [ + { + "endDate": null, + "startDate": null, + "timeDependentValue": 0 + } + ], + "targetMFCs": [ + { + "endDate": null, + "startDate": null, + "timeDependentValue": 4 + } + ], + "timeAggregateType": "Monthly", + "timeDependentPlanningParameters": false, + "uom": "bottle", + "x": 258, + "y": 75 + }, + "2": { + "abcMaterialName": "DP", + "actuals": {}, + "currency": "USD", + "decimalPrecision": 0, + "demand": {}, + "demandDetails": {}, + "doExpiryCarryover": false, + "expiryAdjustments": {}, + "expiryAdjustmentsAnnotation": {}, + "expiryAnalysisType": "Expiration", + "firmOrders": [], + "firmingPeriod": 0, + "initialInventories": [], + "inventoryMethod": "TargetMFC", + "inventorySystemLocationName": null, + "inventorySystemMaterialNumber": null, + "isCapacityConstraintNode": false, + "leadTime": 0, + "lotSize": 333221, + "manufacturingCost": 0, + "materialColor": "#3D85C6", + "materialShape": "circle", + "minimumInventories": [ + { + "endDate": null, + "startDate": null, + "timeDependentValue": 0 + } + ], + "ordering": 1, + "otherDemand": {}, + "otherDemandAnnotation": {}, + "plannedOrders": { + "2020-03-01": 0, + "2020-04-01": 0, + "2020-05-01": 0, + "2020-06-01": 0, + "2020-07-01": 0, + "2020-08-01": 0, + "2020-09-01": 666442, + "2020-10-01": 0, + "2020-11-01": 333221, + "2020-12-01": 0, + "2021-01-01": 333221, + "2021-02-01": 0, + "2021-03-01": 333221, + "2021-04-01": 0, + "2021-05-01": 333221, + "2021-06-01": 0, + "2021-07-01": 333221, + "2021-08-01": 333221, + "2021-09-01": 333221, + "2021-10-01": 0, + "2021-11-01": 333221, + "2021-12-01": 333221, + "2022-01-01": 333221, + "2022-02-01": 0, + "2022-03-01": 0, + "2022-04-01": 0, + "2022-05-01": 0, + "2022-06-01": 0 + }, + "planningFrequencies": [ + { + "endDate": null, + "startDate": null, + "timeDependentValue": 1 + } + ], + "salesPrice": 0, + "shelfLives": [ + { + "endDate": null, + "startDate": null, + "timeDependentValue": 0 + } + ], + "showQuantitiesAs": "Units", + "standardCost": 0, + "stopshipBuffers": [ + { + "endDate": null, + "startDate": null, + "timeDependentValue": 0 + } + ], + "targetMFCs": [ + { + "endDate": null, + "startDate": null, + "timeDependentValue": 0 + } + ], + "timeAggregateType": "Monthly", + "timeDependentPlanningParameters": false, + "uom": "tab", + "x": 258, + "y": 275 + }, + "3": { + "abcMaterialName": "API", + "actuals": {}, + "currency": "USD", + "decimalPrecision": 0, + "demand": {}, + "demandDetails": {}, + "doExpiryCarryover": false, + "expiryAdjustments": {}, + "expiryAdjustmentsAnnotation": {}, + "expiryAnalysisType": "Expiration", + "firmOrders": [], + "firmingPeriod": 0, + "initialInventories": [], + "inventoryMethod": "TargetMFC", + "inventorySystemLocationName": null, + "inventorySystemMaterialNumber": null, + "isCapacityConstraintNode": false, + "leadTime": 0, + "lotSize": 333221, + "manufacturingCost": 0, + "materialColor": "#3D85C6", + "materialShape": "circle", + "minimumInventories": [ + { + "endDate": null, + "startDate": null, + "timeDependentValue": 0 + } + ], + "ordering": 2, + "otherDemand": {}, + "otherDemandAnnotation": {}, + "plannedOrders": { + "2020-03-01": 0, + "2020-04-01": 0, + "2020-05-01": 0, + "2020-06-01": 0, + "2020-07-01": 0, + "2020-08-01": 0, + "2020-09-01": 333221, + "2020-10-01": 0, + "2020-11-01": 333221, + "2020-12-01": 0, + "2021-01-01": 0, + "2021-02-01": 0, + "2021-03-01": 333221, + "2021-04-01": 0, + "2021-05-01": 0, + "2021-06-01": 0, + "2021-07-01": 333221, + "2021-08-01": 0, + "2021-09-01": 333221, + "2021-10-01": 0, + "2021-11-01": 0, + "2021-12-01": 333221, + "2022-01-01": 0, + "2022-02-01": 0, + "2022-03-01": 0, + "2022-04-01": 0, + "2022-05-01": 0, + "2022-06-01": 0 + }, + "planningFrequencies": [ + { + "endDate": null, + "startDate": null, + "timeDependentValue": 1 + } + ], + "salesPrice": 0, + "shelfLives": [ + { + "endDate": null, + "startDate": null, + "timeDependentValue": 0 + } + ], + "showQuantitiesAs": "Units", + "standardCost": 0, + "stopshipBuffers": [ + { + "endDate": null, + "startDate": null, + "timeDependentValue": 0 + } + ], + "targetMFCs": [ + { + "endDate": null, + "startDate": null, + "timeDependentValue": 0 + } + ], + "timeAggregateType": "Monthly", + "timeDependentPlanningParameters": false, + "uom": "mg", + "x": 258, + "y": 475 + } + }, + "analytics": { + "items": [ + { + "excludeMonthsFromBeginning": 0, + "excludeMonthsFromEnd": 0, + "id": "1738852054957", + "metrics": [ + { + "abcMaterialIDs": ["1"], + "color": "#1976d2", + "comparisonPlanID": null, + "label": "Inventory", + "metricType": "inventory", + "showQuantitiesAs": "Units", + "visualization": { + "barWidth": 20, + "radius": 0, + "showAsPercent": false, + "showDataPointValues": false, + "stackId": "", + "type": "bar" + }, + "yAxisIndex": 0, + "zIndex": 0 + }, + { + "abcMaterialIDs": ["1"], + "color": "#E69138", + "comparisonPlanID": null, + "label": "MFC", + "metricType": "mfc", + "showQuantitiesAs": null, + "visualization": { + "dotFill": "#ffffff", + "dotSize": 4, + "showDataPointValues": false, + "strokeDasharray": "", + "strokeWidth": 2, + "type": "line" + }, + "yAxisIndex": 1, + "zIndex": 0 + } + ], + "name": "Inventory vs. MFC", + "subtitle": "FDP", + "timeAggregateType": "Monthly", + "type": "TIME_SERIES" + } + ], + "layouts": [ + { + "contentId": "1738852054957", + "h": 6, + "isDraggable": true, + "isResizable": true, + "minH": 2, + "minW": 1, + "tabID": "1", + "w": 7, + "x": 0, + "y": 0 + } + ], + "tabs": [ + { + "id": "1", + "name": "Overview", + "ordering": 1 + } + ] + }, + "planDate": "2020-03-01", + "planNotes": "{\"blocks\":[{\"key\":\"8o58p\",\"text\":\"\u00c6\",\"type\":\"unstyled\",\"depth\":0,\"inlineStyleRanges\":[],\"entityRanges\":[],\"data\":{}}],\"entityMap\":{}}", + "recipeMap": { + "1-2": { + "allocationMethod": "PercentAllocation", + "percentAllocations": [ + { + "endDate": null, + "startDate": null, + "timeDependentValue": 1 + } + ], + "percentYield": 1, + "priorityAllocations": [ + { + "endDate": null, + "startDate": null, + "timeDependentValue": 1 + } + ], + "recipe": 30 + }, + "2-3": { + "allocationMethod": "PercentAllocation", + "percentAllocations": [ + { + "endDate": null, + "startDate": null, + "timeDependentValue": 1 + } + ], + "percentYield": 1, + "priorityAllocations": [ + { + "endDate": null, + "startDate": null, + "timeDependentValue": 1 + } + ], + "recipe": 0.5 + } + } +} From cb2e47776122d13510b40f9b8c4150695b81ed93 Mon Sep 17 00:00:00 2001 From: Michael Osofsky Date: Tue, 12 May 2026 00:46:41 +0800 Subject: [PATCH 4/8] Feature/publish abc clinical demand forecast 3.0.0 (#5670) * Added schema abc-clinical-demand-forecast-2.0.0.json - Added abc-clinical-demand-forecast-2.0.0.json schema file - Updated catalog.json to include version 2.0.0 and set as default - Updated schema-validation.jsonc with validation configuration - Added positive test case for version 2.0.0 with required dispensationIntervals/dispensationIntervalUnit fields - Copied negative test cases from version 1.0.0 Key changes in version 2.0.0: - Added required dispensationIntervals (non-empty array of numbers >= 1) on dosingRegimenDefinition - Added required dispensationIntervalUnit (enum: days/weeks/months/years) on dosingRegimenDefinition Testing: - Validated schema-specific: node ./cli.js check --schema-name=abc-clinical-demand-forecast-2.0.0.json - Validated full test suite: node ./cli.js check (all 802 tested schemas pass) Co-Authored-By: Claude Opus 4.7 (1M context) * Added schema abc-clinical-demand-forecast-3.0.0.json - Added abc-clinical-demand-forecast-3.0.0.json schema file - Updated catalog.json to include version 3.0.0 and set as default - Updated schema-validation.jsonc with validation configuration - Added positive test case for version 3.0.0 - Copied negative test cases from version 2.0.0 Key changes in version 3.0.0 (breaking): - Replaced dosingFrequency string enum (once_daily_qd, twice_daily_bid, every_other_day, etc.) with a structured DosingFrequency object that supports multi-phase regimens. Each DosingFrequency has a description (also used as the AI generation prompt) and an ordered phases array (1-10 phases). Each DosingPhase defines a repeating cycle via cycleDoses and cycleUnit (days or months), and a repeat duration via repeatFor and repeatForUnit (days, weeks, months, or years). repeatFor: 0 marks a terminal phase that runs for the remainder of the treatment. This enables regimens such as capecitabine 14-on/7-off and "weekly for 6 weeks then monthly" that the old flat enum could not express. - Positive test data updated: replaced the old once_daily_qd string with an equivalent single terminal-phase DosingFrequency object (cycleDoses: [1], cycleUnit: days, repeatFor: 0). Testing: - Validated schema-specific: node ./cli.js check --schema-name=abc-clinical-demand-forecast-3.0.0.json (PASS) - Validated full test suite: node ./cli.js check (PASS, all 803 tested schemas) Stacked-branch note: this branch was created from feature/publish-abc-clinical-demand-forecast-2.0.0 (not master) because the 2.0.0 PR has not yet been merged upstream. When the 2.0.0 PR merges, rebase this branch onto master before merging. Co-Authored-By: Claude Opus 4.7 (1M context) * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: Michael Osofsky Co-authored-by: Claude Opus 4.7 (1M context) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- src/api/json/catalog.json | 6 +- ...mand-forecast-missing-schema-property.json | 10 + ...mand-forecast-missing-schema-property.json | 10 + src/schema-validation.jsonc | 6 + .../abc-clinical-demand-forecast-2.0.0.json | 1840 ++++++++++++++++ .../abc-clinical-demand-forecast-3.0.0.json | 1885 +++++++++++++++++ .../abc-clinical-demand-forecast.json | 239 +++ .../abc-clinical-demand-forecast.json | 249 +++ 8 files changed, 4243 insertions(+), 2 deletions(-) create mode 100644 src/negative_test/abc-clinical-demand-forecast-2.0.0/abc-clinical-demand-forecast-missing-schema-property.json create mode 100644 src/negative_test/abc-clinical-demand-forecast-3.0.0/abc-clinical-demand-forecast-missing-schema-property.json create mode 100644 src/schemas/json/abc-clinical-demand-forecast-2.0.0.json create mode 100644 src/schemas/json/abc-clinical-demand-forecast-3.0.0.json create mode 100644 src/test/abc-clinical-demand-forecast-2.0.0/abc-clinical-demand-forecast.json create mode 100644 src/test/abc-clinical-demand-forecast-3.0.0/abc-clinical-demand-forecast.json diff --git a/src/api/json/catalog.json b/src/api/json/catalog.json index 9d02367df14..411b7933080 100644 --- a/src/api/json/catalog.json +++ b/src/api/json/catalog.json @@ -221,9 +221,11 @@ "name": "ABCClinicalDemandForecast", "description": "ABCClinicalDemandForecast defining the structure of clinical trial demand forecasting data in ABC-Plan", "fileMatch": ["abc-clinical-demand-forecast-*.json"], - "url": "https://www.schemastore.org/abc-clinical-demand-forecast-1.0.0.json", + "url": "https://www.schemastore.org/abc-clinical-demand-forecast-3.0.0.json", "versions": { - "1.0.0": "https://www.schemastore.org/abc-clinical-demand-forecast-1.0.0.json" + "1.0.0": "https://www.schemastore.org/abc-clinical-demand-forecast-1.0.0.json", + "2.0.0": "https://www.schemastore.org/abc-clinical-demand-forecast-2.0.0.json", + "3.0.0": "https://www.schemastore.org/abc-clinical-demand-forecast-3.0.0.json" } }, { diff --git a/src/negative_test/abc-clinical-demand-forecast-2.0.0/abc-clinical-demand-forecast-missing-schema-property.json b/src/negative_test/abc-clinical-demand-forecast-2.0.0/abc-clinical-demand-forecast-missing-schema-property.json new file mode 100644 index 00000000000..98b90a2643c --- /dev/null +++ b/src/negative_test/abc-clinical-demand-forecast-2.0.0/abc-clinical-demand-forecast-missing-schema-property.json @@ -0,0 +1,10 @@ +{ + "analytics": { + "items": [], + "layouts": [], + "tabs": [] + }, + "clinicalDemandCalculatedForecast": null, + "clinicalDemandForecastConfiguration": {}, + "planNotes": "" +} diff --git a/src/negative_test/abc-clinical-demand-forecast-3.0.0/abc-clinical-demand-forecast-missing-schema-property.json b/src/negative_test/abc-clinical-demand-forecast-3.0.0/abc-clinical-demand-forecast-missing-schema-property.json new file mode 100644 index 00000000000..98b90a2643c --- /dev/null +++ b/src/negative_test/abc-clinical-demand-forecast-3.0.0/abc-clinical-demand-forecast-missing-schema-property.json @@ -0,0 +1,10 @@ +{ + "analytics": { + "items": [], + "layouts": [], + "tabs": [] + }, + "clinicalDemandCalculatedForecast": null, + "clinicalDemandForecastConfiguration": {}, + "planNotes": "" +} diff --git a/src/schema-validation.jsonc b/src/schema-validation.jsonc index d379f49ec82..beef8e6e8b7 100644 --- a/src/schema-validation.jsonc +++ b/src/schema-validation.jsonc @@ -447,6 +447,12 @@ "abc-clinical-demand-forecast-1.0.0.json": { "unknownKeywords": ["abcHasValidKitItemReferences"] }, + "abc-clinical-demand-forecast-2.0.0.json": { + "unknownKeywords": ["abcHasValidKitItemReferences"] + }, + "abc-clinical-demand-forecast-3.0.0.json": { + "unknownKeywords": ["abcHasValidKitItemReferences"] + }, "abc-supply-plan-1.0.0.json": { "unknownFormat": ["abc-draft-js_RawDraftContentState"], "unknownKeywords": [ diff --git a/src/schemas/json/abc-clinical-demand-forecast-2.0.0.json b/src/schemas/json/abc-clinical-demand-forecast-2.0.0.json new file mode 100644 index 00000000000..df6fa632fab --- /dev/null +++ b/src/schemas/json/abc-clinical-demand-forecast-2.0.0.json @@ -0,0 +1,1840 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://json.schemastore.org/abc-clinical-demand-forecast-2.0.0.json", + "$comment": "AUTO-GENERATED by scripts/assemble-schemas.js — DO NOT EDIT BY HAND", + "title": "ABCClinicalDemandForecast JSON Schema", + "description": "Schema defining the structure of ABCClinicalDemandForecast used for clinical trial demand forecasting in ABC-Plan.", + "type": "object", + "properties": { + "$schema": { + "description": "Link to https://json.schemastore.org/abc-clinical-demand-forecast-2.0.0.json", + "type": "string", + "enum": [ + "https://json.schemastore.org/abc-clinical-demand-forecast-2.0.0.json" + ] + }, + "planNotes": { + "type": "string", + "title": "Plan Notes", + "description": "Optional notes about the clinical demand forecast plan." + }, + "analytics": { + "$comment": "SINGLE SOURCE OF TRUTH for analytics configuration. Used by scripts/assemble-schemas.js to replace __ABC_JSON_SCHEMA_PLACEHOLDER_ANALYTICS__ in each template. DO NOT reference this file directly — it is inlined into parent schemas at build time.", + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "title": "Analytics Item", + "description": "An analytics item that can be either a time series or an analytics note", + "oneOf": [ + { + "type": "object", + "title": "Time Series", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "type": { + "type": "string", + "enum": ["TIME_SERIES"] + }, + "subtitle": { + "type": "string" + }, + "excludeMonthsFromBeginning": { + "type": "number", + "description": "Number of months to exclude from the beginning of the time series. Positive values trim, negative values add months before the plan start date." + }, + "excludeMonthsFromEnd": { + "type": "number", + "description": "Number of months to exclude from the end of the time series. Positive values trim, negative values add months after the plan end date." + }, + "timeAggregateType": { + "type": "string", + "enum": ["Annual", "Quarterly", "Monthly"], + "title": "Time Aggregate Type", + "description": "The aggregation level for time series data display" + }, + "metrics": { + "type": "array", + "items": { + "type": "object", + "properties": { + "metricType": { + "type": "string", + "enum": [ + "enrolled", + "active", + "completed", + "kitDemand", + "sites", + "screened", + "screenedOut", + "droppedOut", + "siteSeedingKitDemand", + "kitItemDemand" + ] + }, + "abcMaterialIDs": { + "type": "array", + "items": { + "type": "string" + } + }, + "visualization": { + "type": "object", + "oneOf": [ + { + "properties": { + "type": { + "type": "string", + "enum": ["line"] + }, + "strokeWidth": { + "type": "number" + }, + "strokeDasharray": { + "type": "string" + }, + "dotSize": { + "type": "number", + "minimum": 0 + }, + "dotFill": { + "type": "string" + }, + "showDataPointValues": { + "type": "boolean", + "description": "Display data point values directly on the chart" + } + }, + "required": [ + "type", + "strokeWidth", + "strokeDasharray", + "dotSize", + "dotFill", + "showDataPointValues" + ], + "additionalProperties": false + }, + { + "properties": { + "type": { + "type": "string", + "enum": ["bar"] + }, + "barWidth": { + "type": "number" + }, + "radius": { + "type": "number" + }, + "stackId": { + "type": "string" + }, + "showAsPercent": { + "type": "boolean" + }, + "showDataPointValues": { + "type": "boolean", + "description": "Display data point values directly on the chart" + } + }, + "required": [ + "type", + "barWidth", + "radius", + "stackId", + "showAsPercent", + "showDataPointValues" + ], + "additionalProperties": false + }, + { + "properties": { + "type": { + "type": "string", + "enum": ["area"] + }, + "fillOpacity": { + "type": "number" + }, + "strokeWidth": { + "type": "number" + }, + "stackId": { + "type": "string" + }, + "showAsPercent": { + "type": "boolean" + }, + "showDataPointValues": { + "type": "boolean", + "description": "Display data point values directly on the chart" + } + }, + "required": [ + "type", + "fillOpacity", + "strokeWidth", + "stackId", + "showAsPercent", + "showDataPointValues" + ], + "additionalProperties": false + } + ] + }, + "yAxisIndex": { + "type": "number" + }, + "color": { + "type": "string" + }, + "zIndex": { + "type": "number" + }, + "label": { + "type": "string" + }, + "showQuantitiesAs": { + "type": ["string", "null"], + "oneOf": [ + { + "type": "string", + "enum": ["Units", "Lots", "Monetary"] + }, + { + "type": "null" + } + ], + "title": "Show Quantities As", + "description": "How to display the quantities for this metric" + }, + "comparisonPlanID": { + "type": ["string", "null"], + "title": "Comparison Plan ID", + "description": "The ID of the comparison plan to use for this metric or null if no comparison plan is used" + }, + "regionID": { + "type": "string", + "title": "Region ID", + "description": "Optional region filter for Clinical Demand Forecast metrics" + }, + "armID": { + "type": "string", + "title": "Arm ID", + "description": "Optional treatment arm filter for Clinical Demand Forecast metrics" + }, + "kitID": { + "type": "string", + "title": "Kit ID", + "description": "Optional kit filter for Clinical Demand Forecast metrics" + }, + "itemID": { + "type": "string", + "title": "Item ID", + "description": "Optional item filter for Clinical Demand Forecast metrics" + }, + "isCumulative": { + "type": "boolean", + "title": "Is Cumulative", + "description": "Whether to display cumulative values" + } + }, + "required": [ + "metricType", + "abcMaterialIDs", + "visualization", + "yAxisIndex", + "color", + "zIndex", + "label", + "showQuantitiesAs", + "comparisonPlanID" + ], + "additionalProperties": false + } + } + }, + "required": [ + "id", + "name", + "type", + "subtitle", + "excludeMonthsFromBeginning", + "excludeMonthsFromEnd", + "timeAggregateType", + "metrics" + ], + "additionalProperties": false + }, + { + "type": "object", + "title": "Analytics Note Item", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "type": { + "type": "string", + "enum": ["ANALYTICS_NOTE"] + }, + "subtitle": { + "type": "string" + }, + "backgroundColor": { + "type": "string" + }, + "imageUrl": { + "type": "string" + }, + "text": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "fontFamily": { + "type": "string" + }, + "fontSize": { + "type": "number" + }, + "color": { + "type": "string" + }, + "horizontalAlignment": { + "type": "string", + "enum": ["left", "center", "right"] + }, + "verticalAlignment": { + "type": "string", + "enum": ["top", "center", "bottom"] + } + }, + "required": [ + "content", + "fontFamily", + "fontSize", + "color", + "horizontalAlignment", + "verticalAlignment" + ], + "additionalProperties": false + } + }, + "required": ["id", "name", "type", "subtitle"], + "additionalProperties": false + } + ] + } + }, + "layouts": { + "type": "array", + "items": { + "type": "object", + "properties": { + "contentId": { + "type": "string" + }, + "x": { + "type": "number", + "minimum": 0, + "maximum": 11, + "description": "Grid column position (0-11 for 12-column grid)" + }, + "y": { + "type": "number", + "minimum": 0, + "description": "Grid row position" + }, + "tabID": { + "type": "string", + "description": "ID of the tab this layout item belongs to" + }, + "w": { + "type": "number", + "minimum": 1, + "maximum": 12, + "description": "Width in grid units (1-12)" + }, + "h": { + "type": "number", + "minimum": 1, + "description": "Height in grid units" + }, + "minH": { + "type": "number", + "minimum": 1, + "description": "Minimum height in grid units" + }, + "maxH": { + "type": "number", + "minimum": 1, + "description": "Maximum height in grid units" + }, + "minW": { + "type": "number", + "minimum": 1, + "description": "Minimum width in grid units" + }, + "maxW": { + "type": "number", + "minimum": 1, + "description": "Maximum width in grid units" + }, + "isDraggable": { + "type": "boolean", + "description": "Whether the item can be dragged" + }, + "isResizable": { + "type": "boolean", + "description": "Whether the item can be resized" + } + }, + "required": ["contentId", "x", "y", "tabID", "w", "h"], + "additionalProperties": false + } + }, + "tabs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "ordering": { + "type": "number" + } + }, + "required": ["id", "name", "ordering"], + "additionalProperties": false + } + } + }, + "required": ["items", "layouts", "tabs"], + "additionalProperties": false + }, + "clinicalDemandForecastConfiguration": { + "$ref": "#/definitions/ClinicalDemandForecastConfiguration", + "title": "Clinical Demand Forecast Configuration", + "description": "All algorithm inputs that affect forecast calculation. Any change to this object invalidates clinicalDemandCalculatedForecast." + }, + "clinicalDemandCalculatedForecast": { + "$ref": "#/definitions/ClinicalDemandCalculatedForecast", + "title": "Clinical Demand Calculated Forecast", + "description": "Calculated demand forecast with atomic monthly data per-region, per-arm." + } + }, + "required": [ + "planNotes", + "analytics", + "clinicalDemandForecastConfiguration", + "clinicalDemandCalculatedForecast" + ], + "abcHasValidKitItemReferences": true, + "additionalProperties": false, + "definitions": { + "ClinicalDemandForecastConfiguration": { + "type": "object", + "title": "Clinical Demand Forecast Configuration", + "description": "All algorithm inputs that affect forecast calculation. Any change to this object should invalidate the calculated forecast.", + "properties": { + "studyOverview": { + "$ref": "#/definitions/StudyOverview", + "title": "Study Overview", + "description": "Basic information about the clinical trial study." + }, + "enrollment": { + "$ref": "#/definitions/Enrollment", + "title": "Enrollment", + "description": "Patient enrollment targets and rates." + }, + "kitConfiguration": { + "type": "object", + "title": "Kit Configuration", + "description": "Configuration of kits with containers and items, keyed by kit ID.", + "patternProperties": { + ".*": { + "$ref": "#/definitions/Kit" + } + }, + "additionalProperties": false + }, + "dosingRegimens": { + "type": "object", + "title": "Dosing Regimens", + "description": "Available dosing regimens for the study, keyed by ID.", + "patternProperties": { + ".*": { + "$ref": "#/definitions/DosingRegimen" + } + }, + "additionalProperties": false + }, + "treatmentArms": { + "type": "object", + "title": "Treatment Arms", + "description": "Treatment arms in the clinical trial, keyed by ID.", + "patternProperties": { + ".*": { + "$ref": "#/definitions/TreatmentArm" + } + }, + "additionalProperties": false + }, + "subjectFlow": { + "$ref": "#/definitions/SubjectFlow", + "title": "Subject Flow", + "description": "Site supply and region configuration." + }, + "actuals": { + "$ref": "#/definitions/ClinicalDemandActuals", + "title": "Clinical Demand Actuals", + "description": "Actual enrollment data entered by users to reconcile with forecast." + }, + "trialDesign": { + "type": "string", + "title": "Trial Design", + "description": "Trial design type: parallel (single regimen per arm) or crossOver (sequence of treatments with washout periods).", + "enum": ["parallel", "crossOver"], + "default": "parallel" + } + }, + "required": [ + "studyOverview", + "enrollment", + "kitConfiguration", + "dosingRegimens", + "treatmentArms", + "subjectFlow", + "actuals", + "trialDesign" + ], + "additionalProperties": false + }, + "StudyOverview": { + "type": "object", + "title": "Study Overview", + "description": "Basic study information from wizard step 1.", + "properties": { + "studyName": { + "type": "string", + "title": "Study Name", + "description": "Name of the clinical trial study." + }, + "studyPhase": { + "type": "string", + "title": "Study Phase", + "description": "Phase of the clinical trial.", + "enum": ["phase_i", "phase_ii", "phase_iii", "phase_iv"] + }, + "protocolSummary": { + "type": "string", + "title": "Protocol Summary", + "description": "Summary of the trial protocol. Optional field with recommended default: empty string." + }, + "therapeuticArea": { + "type": "string", + "title": "Therapeutic Area", + "description": "Disease category being treated (e.g., Oncology, CNS, Rare Disease). Will be normalized by LLM for benchmarking. Required field with recommended default: empty string." + } + }, + "required": [ + "studyName", + "studyPhase", + "protocolSummary", + "therapeuticArea" + ], + "additionalProperties": false + }, + "Enrollment": { + "type": "object", + "title": "Enrollment", + "description": "Enrollment configuration from wizard step 2.", + "properties": { + "targetCompleted": { + "type": "integer", + "title": "Target Completed", + "description": "Target number of subjects to complete the trial.", + "minimum": 0 + }, + "screenFailureRate": { + "type": "number", + "title": "Screen Failure Rate", + "description": "Percentage of subjects who fail screening (0-100).", + "minimum": 0, + "maximum": 100 + }, + "attritionRate": { + "type": "number", + "title": "Attrition Rate", + "description": "Percentage of enrolled subjects who drop out of the study entirely and stop receiving all doses (0-100). NOTE: This is different from adverse events in dose escalation regimens. Attrition causes subjects to exit the study early, while adverse events only prevent dose escalation but subjects continue receiving doses at their maximum tolerated level.", + "minimum": 0, + "maximum": 100 + } + }, + "required": ["targetCompleted", "screenFailureRate", "attritionRate"], + "additionalProperties": false + }, + "Kit": { + "type": "object", + "title": "Kit", + "description": "A kit containing items and containers. The kit ID is the key in the kitConfiguration map.", + "properties": { + "name": { + "type": "string", + "title": "Kit Name", + "description": "Display name of the kit." + }, + "items": { + "type": "object", + "title": "Items", + "description": "Items and containers within this kit, keyed by node ID.", + "patternProperties": { + ".*": { + "$ref": "#/definitions/KitNode" + } + }, + "additionalProperties": false + } + }, + "required": ["name", "items"], + "additionalProperties": false + }, + "KitNode": { + "oneOf": [ + { + "$ref": "#/definitions/KitContainer" + }, + { + "$ref": "#/definitions/KitItem" + } + ] + }, + "KitContainer": { + "type": "object", + "title": "Kit Container", + "description": "A structural container (bottle, blister pack) that holds other nodes. The container ID is the key in the parent items/children map.", + "properties": { + "nodeType": { + "type": "string", + "const": "container", + "title": "Node Type", + "description": "Discriminator identifying this as a container node." + }, + "name": { + "type": "string", + "title": "Name", + "description": "Name of the container." + }, + "deliveryForm": { + "type": "string", + "title": "Delivery Form", + "description": "Type of container.", + "enum": ["bottle", "carton", "dose_pack", "blister_pack"] + }, + "children": { + "type": "object", + "title": "Children", + "description": "Items contained within this container, keyed by node ID.", + "patternProperties": { + ".*": { + "$ref": "#/definitions/KitNode" + } + }, + "additionalProperties": false + }, + "ordering": { + "type": "integer", + "title": "Ordering", + "description": "Display order of this node within its parent. Lower numbers appear first." + } + }, + "required": ["nodeType", "name", "deliveryForm", "children", "ordering"], + "additionalProperties": false + }, + "KitItem": { + "type": "object", + "title": "Kit Item", + "description": "An actual pharmaceutical item (tablet, capsule, vial) with dosage. The item ID is the key in the parent items/children map.", + "properties": { + "nodeType": { + "type": "string", + "const": "item", + "title": "Node Type", + "description": "Discriminator identifying this as an item node." + }, + "name": { + "type": "string", + "title": "Name", + "description": "Name of the item." + }, + "deliveryForm": { + "type": "string", + "title": "Delivery Form", + "description": "Type of pharmaceutical item.", + "enum": ["tablet", "capsule", "vial", "syringe", "sachet"] + }, + "quantity": { + "type": "integer", + "title": "Quantity", + "description": "Number of units contained in this item within one kit. For example, if this is a bottle containing tablets, quantity=10 means the bottle contains 10 tablets. This is NOT the same as dosingRegimen.kitItemQuantities, which specifies consumption per dose. This quantity field defines the container/packaging size.", + "minimum": 0 + }, + "dosage": { + "type": "number", + "title": "Dosage", + "description": "Dosage amount.", + "minimum": 0 + }, + "units": { + "type": "string", + "title": "Units", + "description": "Units of dosage (e.g., mg, ml)." + }, + "ordering": { + "type": "integer", + "title": "Ordering", + "description": "Display order of this node within its parent. Lower numbers appear first." + } + }, + "required": [ + "nodeType", + "name", + "deliveryForm", + "quantity", + "dosage", + "units", + "ordering" + ], + "additionalProperties": false + }, + "DosingRegimen": { + "type": "object", + "title": "Dosing Regimen", + "description": "Dosing regimen configuration with nested dosingRegimenDefinition discriminated union.", + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "Name of the dosing regimen." + }, + "description": { + "type": "string", + "title": "Description", + "description": "Description of the dosing regimen." + }, + "dosingRegimenDefinition": { + "oneOf": [ + { + "$ref": "#/definitions/ActiveRegimenDefinition" + }, + { + "$ref": "#/definitions/PlaceboRegimenDefinition" + } + ], + "title": "Dosing Regimen Definition", + "description": "Discriminated union: either an active regimen definition (isPlacebo: false) or a placebo definition (isPlacebo: true)." + } + }, + "required": ["name", "description", "dosingRegimenDefinition"], + "additionalProperties": false + }, + "ActiveRegimenDefinition": { + "type": "object", + "title": "Active Regimen Definition", + "description": "Definition for an active (non-placebo) dosing regimen.", + "properties": { + "isPlacebo": { + "type": "boolean", + "const": false, + "title": "Is Placebo", + "description": "Discriminator: false for active regimens." + }, + "treatmentDuration": { + "type": "number", + "title": "Treatment Duration", + "description": "Total duration of treatment for all subjects.", + "minimum": 0 + }, + "treatmentDurationUnit": { + "type": "string", + "title": "Treatment Duration Unit", + "description": "Unit of treatment duration.", + "enum": ["days", "weeks", "months", "years"] + }, + "dosingFrequency": { + "type": "string", + "title": "Dosing Frequency", + "description": "Frequency of dose administration.", + "enum": [ + "once_daily_qd", + "twice_daily_bid", + "three_times_daily_tid", + "four_times_daily_qid", + "once_weekly_qw", + "every_2_weeks_q2w", + "every_3_weeks_q3w", + "every_4_weeks_q4w", + "monthly", + "every_6_weeks_q6w", + "every_8_weeks_q8w", + "every_12_weeks_q12w", + "every_other_day" + ] + }, + "fixedConfig": { + "anyOf": [ + { + "$ref": "#/definitions/FixedDoseConfig" + }, + { + "type": "null" + } + ], + "title": "Fixed Dose Configuration", + "description": "Configuration for fixed dose regimens. Null if using another regimen type." + }, + "doseEscalationConfig": { + "anyOf": [ + { + "$ref": "#/definitions/DoseEscalationConfig" + }, + { + "type": "null" + } + ], + "title": "Dose Escalation Configuration", + "description": "Configuration for dose escalation regimens. Null if using another regimen type." + }, + "weightBasedConfig": { + "anyOf": [ + { + "$ref": "#/definitions/WeightBasedConfig" + }, + { + "type": "null" + } + ], + "title": "Weight-Based Configuration", + "description": "Configuration for weight-based dosing regimens. Null if using another regimen type." + }, + "ageBasedConfig": { + "anyOf": [ + { + "$ref": "#/definitions/AgeBasedConfig" + }, + { + "type": "null" + } + ], + "title": "Age-Based Configuration", + "description": "Configuration for age-based dosing regimens. Null if using another regimen type." + }, + "dispensationIntervals": { + "type": "array", + "items": { + "type": "number", + "minimum": 1 + }, + "minItems": 1, + "title": "Dispensation Intervals", + "description": "Non-empty array of intervals between consecutive dispensation events, in dispensationIntervalUnit." + }, + "dispensationIntervalUnit": { + "type": "string", + "title": "Dispensation Interval Unit", + "description": "Unit for dispensationIntervals. Independent of treatmentDurationUnit.", + "enum": ["days", "weeks", "months", "years"] + } + }, + "required": [ + "isPlacebo", + "treatmentDuration", + "treatmentDurationUnit", + "dosingFrequency", + "fixedConfig", + "doseEscalationConfig", + "weightBasedConfig", + "ageBasedConfig", + "dispensationIntervals", + "dispensationIntervalUnit" + ], + "additionalProperties": false + }, + "PlaceboRegimenDefinition": { + "type": "object", + "title": "Placebo Regimen Definition", + "description": "Definition for a placebo regimen that references an active regimen.", + "properties": { + "isPlacebo": { + "type": "boolean", + "const": true, + "title": "Is Placebo", + "description": "Discriminator: true for placebo regimens." + }, + "placeboOf": { + "type": "string", + "title": "Placebo Of", + "description": "ID of the active dosing regimen this placebo references. Placebo inherits all dosing configuration from the active regimen." + } + }, + "required": ["isPlacebo", "placeboOf"], + "additionalProperties": false + }, + "FixedDoseConfig": { + "type": "object", + "title": "Fixed Dose Configuration", + "description": "Configuration for fixed dose regimens.", + "properties": { + "kitItemQuantities": { + "type": "object", + "title": "Kit Item Quantities", + "description": "Mapping of kit item IDs to quantities consumed per dose.", + "patternProperties": { + ".*": { + "type": "number", + "minimum": 0 + } + }, + "additionalProperties": false + } + }, + "required": ["kitItemQuantities"], + "additionalProperties": false + }, + "DoseEscalationConfig": { + "type": "object", + "title": "Dose Escalation Configuration", + "description": "Configuration for dose escalation levels.", + "properties": { + "levels": { + "type": "object", + "title": "Escalation Levels", + "description": "Map of level IDs to dose escalation levels. Use ordering property to maintain display order.", + "patternProperties": { + ".*": { + "$ref": "#/definitions/DoseEscalationLevel" + } + }, + "additionalProperties": false + } + }, + "required": ["levels"], + "additionalProperties": false + }, + "DoseEscalationLevel": { + "title": "Dose Escalation Level", + "description": "Individual dose escalation level configuration. This is a discriminated union with three variants: First level (isFirstLevel=true), Intermediate level (isFinalLevel=false, has adverseEventRate), and Final level (isFinalLevel=true, no duration).", + "oneOf": [ + { + "$ref": "#/definitions/DoseEscalationLevelFirst" + }, + { + "$ref": "#/definitions/DoseEscalationLevelIntermediate" + }, + { + "$ref": "#/definitions/DoseEscalationLevelFinal" + } + ] + }, + "DoseEscalationLevelBase": { + "$comment": "additionalProperties is true because this object acts as a base schema and is composed with variant-specific definitions via allOf.", + "type": "object", + "title": "Dose Escalation Level Base", + "description": "Base properties shared by all dose escalation level variants.", + "properties": { + "kitItemQuantities": { + "type": "object", + "title": "Kit Item Quantities", + "description": "Mapping of kit item IDs to quantities consumed per dose at this escalation level.", + "patternProperties": { + ".*": { + "type": "number", + "minimum": 0 + } + }, + "additionalProperties": false + }, + "ordering": { + "type": "number", + "title": "Ordering", + "description": "Display order of this level within the escalation sequence. Lower numbers appear first." + } + }, + "required": ["kitItemQuantities", "ordering"], + "additionalProperties": true + }, + "DoseEscalationLevelFirst": { + "title": "First Dose Escalation Level", + "description": "First escalation level. Has isFirstLevel=true, isFinalLevel=false, duration and durationUnit. Does NOT have adverseEventRate because adverse events occur when trialing a level, and there is no previous level to trial from.", + "allOf": [ + { + "$ref": "#/definitions/DoseEscalationLevelBase" + }, + { + "type": "object", + "properties": { + "kitItemQuantities": {}, + "isFirstLevel": { + "type": "boolean", + "const": true, + "title": "Is First Level", + "description": "Must be true for first level." + }, + "isFinalLevel": { + "type": "boolean", + "const": false, + "title": "Is Final Level", + "description": "Must be false for first level (first level cannot also be final)." + }, + "duration": { + "type": "number", + "title": "Duration", + "description": "Duration of this escalation level.", + "exclusiveMinimum": 0 + }, + "durationUnit": { + "type": "string", + "title": "Duration Unit", + "description": "Unit of duration for this level.", + "enum": ["days", "weeks", "months", "years"] + }, + "ordering": {} + }, + "required": [ + "isFirstLevel", + "isFinalLevel", + "duration", + "durationUnit" + ], + "additionalProperties": false + } + ] + }, + "DoseEscalationLevelIntermediate": { + "title": "Intermediate Dose Escalation Level", + "description": "Intermediate escalation level (not first, not final). Has isFinalLevel=false, duration, durationUnit, and adverseEventRate. Does NOT have isFirstLevel property.", + "allOf": [ + { + "$ref": "#/definitions/DoseEscalationLevelBase" + }, + { + "type": "object", + "properties": { + "kitItemQuantities": {}, + "isFinalLevel": { + "type": "boolean", + "const": false, + "title": "Is Final Level", + "description": "Must be false for intermediate level." + }, + "duration": { + "type": "number", + "title": "Duration", + "description": "Duration of this escalation level.", + "exclusiveMinimum": 0 + }, + "durationUnit": { + "type": "string", + "title": "Duration Unit", + "description": "Unit of duration for this level.", + "enum": ["days", "weeks", "months", "years"] + }, + "adverseEventRate": { + "type": "number", + "title": "Adverse Event Rate", + "description": "Percentage of subjects who experience adverse events when TRIALING THIS level from the previous level.", + "minimum": 0, + "maximum": 100 + }, + "ordering": {} + }, + "required": [ + "isFinalLevel", + "duration", + "durationUnit", + "adverseEventRate" + ], + "additionalProperties": false + } + ] + }, + "DoseEscalationLevelFinal": { + "title": "Final Dose Escalation Level", + "description": "Final escalation level. Has isFinalLevel=true and adverseEventRate. Does NOT have duration or durationUnit (subjects continue at final level until treatment ends).", + "allOf": [ + { + "$ref": "#/definitions/DoseEscalationLevelBase" + }, + { + "type": "object", + "properties": { + "kitItemQuantities": {}, + "isFinalLevel": { + "type": "boolean", + "const": true, + "title": "Is Final Level", + "description": "Must be true for final level." + }, + "adverseEventRate": { + "type": "number", + "title": "Adverse Event Rate", + "description": "Percentage of subjects who experience adverse events when TRIALING THIS level from the previous level.", + "minimum": 0, + "maximum": 100 + }, + "ordering": {} + }, + "required": ["isFinalLevel", "adverseEventRate"], + "additionalProperties": false + } + ] + }, + "WeightBasedConfig": { + "type": "object", + "title": "Weight-Based Configuration", + "description": "Configuration for weight-based dosing.", + "properties": { + "weightUnit": { + "type": "string", + "title": "Weight Unit", + "description": "Unit of weight measurement.", + "enum": ["lbs", "kg"] + }, + "ranges": { + "type": "object", + "title": "Weight Ranges", + "description": "Map of range IDs to weight ranges for kit assignment. Use ordering property to maintain display order.", + "patternProperties": { + ".*": { + "$ref": "#/definitions/WeightRange" + } + }, + "additionalProperties": false + } + }, + "required": ["weightUnit", "ranges"], + "additionalProperties": false + }, + "WeightRange": { + "type": "object", + "title": "Weight Range", + "description": "Weight range configuration for dosing.", + "properties": { + "kitItemQuantities": { + "type": "object", + "title": "Kit Item Quantities", + "description": "Mapping of kit item IDs to quantities consumed per dose for subjects in THIS weight range. Each value represents the number of units (e.g., tablets) of that item required for a single dose. Different weight ranges can specify different quantities for the same item.", + "patternProperties": { + ".*": { + "type": "number", + "minimum": 0 + } + }, + "additionalProperties": false + }, + "fromWeight": { + "type": ["number", "null"], + "title": "From Weight", + "description": "Minimum weight for this range (null for no lower bound).", + "minimum": 0 + }, + "toWeight": { + "type": ["number", "null"], + "title": "To Weight", + "description": "Maximum weight for this range (null for no upper bound).", + "minimum": 0 + }, + "expectedPercentage": { + "type": "number", + "title": "Expected Percentage", + "description": "Expected percentage of subjects in this weight range.", + "minimum": 0, + "maximum": 100 + }, + "ordering": { + "type": "number", + "title": "Ordering", + "description": "Display order of this range. Lower numbers appear first." + } + }, + "required": [ + "kitItemQuantities", + "fromWeight", + "toWeight", + "expectedPercentage", + "ordering" + ], + "additionalProperties": false + }, + "AgeBasedConfig": { + "type": "object", + "title": "Age-Based Configuration", + "description": "Configuration for age-based dosing.", + "properties": { + "ageUnit": { + "type": "string", + "title": "Age Unit", + "description": "Unit of age measurement.", + "enum": ["years", "months"] + }, + "ranges": { + "type": "object", + "title": "Age Ranges", + "description": "Map of range IDs to age ranges for kit assignment. Use ordering property to maintain display order.", + "patternProperties": { + ".*": { + "$ref": "#/definitions/AgeRange" + } + }, + "additionalProperties": false + } + }, + "required": ["ageUnit", "ranges"], + "additionalProperties": false + }, + "AgeRange": { + "type": "object", + "title": "Age Range", + "description": "Age range configuration for dosing.", + "properties": { + "kitItemQuantities": { + "type": "object", + "title": "Kit Item Quantities", + "description": "Mapping of kit item IDs to quantities consumed per dose for subjects in THIS age range. Each value represents the number of units (e.g., tablets) of that item required for a single dose. Different age ranges can specify different quantities for the same item.", + "patternProperties": { + ".*": { + "type": "number", + "minimum": 0 + } + }, + "additionalProperties": false + }, + "fromAge": { + "type": ["number", "null"], + "title": "From Age", + "description": "Minimum age for this range (null for no lower bound).", + "minimum": 0 + }, + "toAge": { + "type": ["number", "null"], + "title": "To Age", + "description": "Maximum age for this range (null for no upper bound).", + "minimum": 0 + }, + "expectedPercentage": { + "type": "number", + "title": "Expected Percentage", + "description": "Expected percentage of subjects in this age range.", + "minimum": 0, + "maximum": 100 + }, + "ordering": { + "type": "number", + "title": "Ordering", + "description": "Display order of this range. Lower numbers appear first." + } + }, + "required": [ + "kitItemQuantities", + "fromAge", + "toAge", + "expectedPercentage", + "ordering" + ], + "additionalProperties": false + }, + "TreatmentPeriod": { + "type": "object", + "title": "Treatment Period", + "description": "Treatment period in a dosing regimen sequence.", + "properties": { + "periodType": { + "type": "string", + "const": "treatment", + "title": "Period Type", + "description": "Discriminator identifying this as a treatment period." + }, + "dosingRegimenId": { + "type": "string", + "title": "Dosing Regimen ID", + "description": "ID of the dosing regimen used during this treatment period." + }, + "ordering": { + "type": "number", + "title": "Ordering", + "description": "Order position of this period in the sequence." + } + }, + "required": ["periodType", "dosingRegimenId", "ordering"], + "additionalProperties": false + }, + "WashoutPeriod": { + "type": "object", + "title": "Washout Period", + "description": "Washout period (no dosing) in a dosing regimen sequence.", + "properties": { + "periodType": { + "type": "string", + "const": "washout", + "title": "Period Type", + "description": "Discriminator identifying this as a washout period." + }, + "duration": { + "type": "number", + "title": "Duration", + "description": "Duration of the washout period.", + "exclusiveMinimum": 0 + }, + "durationUnit": { + "type": "string", + "title": "Duration Unit", + "description": "Unit of washout duration.", + "enum": ["days", "weeks", "months", "years"] + }, + "ordering": { + "type": "number", + "title": "Ordering", + "description": "Order position of this period in the sequence." + } + }, + "required": ["periodType", "duration", "durationUnit", "ordering"], + "additionalProperties": false + }, + "SequencePeriod": { + "oneOf": [ + { + "$ref": "#/definitions/TreatmentPeriod" + }, + { + "$ref": "#/definitions/WashoutPeriod" + } + ] + }, + "SequencePeriodMap": { + "type": "object", + "title": "Sequence Periods", + "description": "Ordered sequence of treatment and washout periods, keyed by unique period ID.", + "patternProperties": { + ".*": { + "$ref": "#/definitions/SequencePeriod" + } + }, + "additionalProperties": false, + "minProperties": 1 + }, + "TreatmentArm": { + "type": "object", + "title": "Treatment Arm", + "description": "Treatment arm configuration from wizard step 5.", + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "Name of the treatment arm." + }, + "allocationPercentage": { + "type": "number", + "title": "Allocation Percentage", + "description": "Percentage of subjects allocated to this arm.", + "minimum": 0, + "maximum": 100 + }, + "dosingRegimenSequence": { + "$ref": "#/definitions/SequencePeriodMap", + "title": "Dosing Regimen Sequence", + "description": "Sequence of treatment and washout periods for this treatment arm." + } + }, + "required": ["name", "allocationPercentage", "dosingRegimenSequence"], + "additionalProperties": false + }, + "SubjectFlow": { + "type": "object", + "title": "Subject Flow", + "description": "Site supply and region configuration from wizard step 6.", + "properties": { + "regions": { + "type": "object", + "title": "Countries", + "description": "Region-specific site configuration, keyed by ID.", + "patternProperties": { + ".*": { + "$ref": "#/definitions/RegionConfig" + } + }, + "additionalProperties": false + } + }, + "required": ["regions"], + "additionalProperties": false + }, + "RegionConfig": { + "oneOf": [ + { + "$ref": "#/definitions/RegionContainer" + }, + { + "$ref": "#/definitions/RegionLeaf" + } + ] + }, + "RegionContainer": { + "type": "object", + "title": "Region Container", + "description": "Container node for grouping regions. Has subregions but no sites.", + "properties": { + "nodeType": { + "type": "string", + "const": "container", + "title": "Node Type", + "description": "Discriminator identifying this as a container node." + }, + "regionName": { + "type": "string", + "title": "Region Name", + "description": "Name of the region container." + }, + "subregions": { + "type": "object", + "title": "Subregions", + "description": "Child regions within this container.", + "patternProperties": { + ".*": { + "$ref": "#/definitions/RegionConfig" + } + }, + "additionalProperties": false + }, + "activationStartDate": { + "type": ["string", "null"], + "format": "date", + "title": "Activation Start Date", + "description": "Default activation start date for child regions. Null means no default." + }, + "screenRate": { + "type": ["number", "null"], + "title": "Screen Rate", + "description": "Default screen rate for child regions. Null means no default.", + "exclusiveMinimum": 0 + }, + "siteActivationRate": { + "type": ["number", "null"], + "title": "Site Activation Rate", + "description": "Default site activation rate for child regions. Null means no default.", + "exclusiveMinimum": 0 + }, + "maxEnrollmentPerSite": { + "type": ["number", "null"], + "title": "Maximum Enrollment Per Site", + "description": "Default max enrollment per site for child regions. Null means no default.", + "minimum": 0 + }, + "siteSeeding": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "object", + "additionalProperties": { + "type": "number", + "minimum": 0 + } + } + ], + "title": "Site Seeding", + "description": "Default site seeding quantity per kit for child regions. Map of kitId to quantity, or null to inherit." + } + }, + "required": ["nodeType", "regionName", "subregions"], + "additionalProperties": false + }, + "RegionLeaf": { + "type": "object", + "title": "Region Leaf", + "description": "Leaf node representing an operational region with sites.", + "properties": { + "nodeType": { + "type": "string", + "const": "region", + "title": "Node Type", + "description": "Discriminator identifying this as a leaf region node." + }, + "regionName": { + "type": "string", + "title": "Region Name", + "description": "Name of the region." + }, + "numberOfSites": { + "type": "integer", + "title": "Number of Sites", + "description": "Total number of sites in this region.", + "minimum": 1 + }, + "activationStartDate": { + "type": ["string", "null"], + "format": "date", + "title": "Activation Start Date", + "description": "Date when site activation begins. Null inherits from parent container." + }, + "screenRate": { + "type": ["number", "null"], + "title": "Screen Rate", + "description": "Number of subjects screened per site per month. Null inherits from parent container.", + "exclusiveMinimum": 0 + }, + "siteActivationRate": { + "type": ["number", "null"], + "title": "Site Activation Rate", + "description": "Number of sites activated per month. Null inherits from parent container.", + "exclusiveMinimum": 0 + }, + "maxEnrollmentPerSite": { + "type": ["number", "null"], + "title": "Maximum Enrollment Per Site", + "description": "Maximum number of patients that can be enrolled per site. Null for unlimited or inherited.", + "minimum": 0 + }, + "siteSeeding": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "object", + "additionalProperties": { + "type": "number", + "minimum": 0 + } + } + ], + "title": "Site Seeding", + "description": "Site seeding quantity per kit. Map of kitId to quantity, or null to inherit from parent." + } + }, + "required": ["nodeType", "regionName", "numberOfSites"], + "additionalProperties": false + }, + "RegionForecast": { + "type": "object", + "title": "Region Forecast", + "description": "Atomic monthly forecast data for a single region. All values are monthly (not cumulative). UI computes cumulative views.", + "properties": { + "sites": { + "$ref": "#/definitions/PositiveDateMap", + "title": "Sites", + "description": "Monthly count of sites opened this month." + }, + "subjectsScreened": { + "$ref": "#/definitions/PositiveDateMap", + "title": "Subjects Screened", + "description": "Monthly count of subjects screened this month." + }, + "subjectsScreenedOut": { + "$ref": "#/definitions/PositiveDateMap", + "title": "Subjects Screened Out", + "description": "Monthly count of subjects who failed screening this month." + }, + "subjectsEnrolled": { + "$ref": "#/definitions/ByArmDateMap", + "title": "Subjects Enrolled", + "description": "Monthly subjects enrolled, broken down by treatment arm." + }, + "subjectsActive": { + "$ref": "#/definitions/ByArmDateMap", + "title": "Subjects Active", + "description": "Monthly change in active subjects (new active this month), broken down by treatment arm." + }, + "subjectsCompleted": { + "$ref": "#/definitions/ByArmDateMap", + "title": "Subjects Completed", + "description": "Monthly subjects who completed treatment, broken down by treatment arm." + }, + "subjectsDroppedOut": { + "$ref": "#/definitions/ByArmDateMap", + "title": "Subjects Dropped Out", + "description": "Monthly subjects who dropped out, broken down by treatment arm." + }, + "kitDemand": { + "$ref": "#/definitions/ByArmSequenceKitDateMap", + "title": "Kit Demand", + "description": "Monthly kit demand (whole kits rounded up), broken down by treatment arm and kit ID. Use this for supply planning - it accounts for buffer/waste in partially-used bottles." + }, + "kitItemDemand": { + "$ref": "#/definitions/ByArmSequenceKitItemDateMap", + "title": "Kit Item Demand", + "description": "Monthly kit item consumption (what patients actually receive), broken down by treatment arm and item ID. Displayed as 'Dosing' in the UI. Note: This is consumption, not supply demand - it does not include buffer/waste from partially-used bottles. For supply planning, use kitDemand." + }, + "siteSeedingKitDemand": { + "$ref": "#/definitions/ByKitDateMap", + "title": "Site Seeding Kit Demand", + "description": "Site seeding kit demand - kits shipped when sites activate (not treatment-driven). Keyed by kit ID." + } + }, + "required": [ + "sites", + "subjectsScreened", + "subjectsScreenedOut", + "subjectsEnrolled", + "subjectsActive", + "subjectsCompleted", + "subjectsDroppedOut", + "kitDemand", + "kitItemDemand", + "siteSeedingKitDemand" + ], + "additionalProperties": false + }, + "ClinicalDemandCalculatedForecast": { + "type": "object", + "title": "Clinical Demand Calculated Forecast", + "description": "Atomic monthly forecast data. Contains per-region data only. UI computes global totals and cumulative views.", + "properties": { + "byRegion": { + "type": "object", + "title": "By Region", + "description": "Region-specific forecast data keyed by region ID.", + "additionalProperties": { + "$ref": "#/definitions/RegionForecast" + } + } + }, + "required": ["byRegion"], + "additionalProperties": false + }, + "PositiveDateMap": { + "type": "object", + "title": "Positive Date Map", + "description": "Mapping of dates to positive numerical values.", + "patternProperties": { + "^\\d{4}-(0[1-9]|1[0-2])-01$": { + "type": "number", + "minimum": 0 + } + }, + "additionalProperties": false + }, + "ByArmDateMap": { + "type": "object", + "title": "By Arm Date Map", + "description": "Mapping of treatment arm IDs to date-value maps. Each arm ID maps to a PositiveDateMap.", + "patternProperties": { + ".*": { + "$ref": "#/definitions/PositiveDateMap" + } + }, + "additionalProperties": false + }, + "KitDemandEntry": { + "type": "object", + "title": "Kit Demand Entry", + "description": "Kit demand with placeboOf tagging for active/placebo separation.", + "properties": { + "demand": { + "$ref": "#/definitions/PositiveDateMap", + "description": "Monthly kit demand values keyed by YYYY-MM-DD date." + }, + "placeboOf": { + "type": ["string", "null"], + "description": "Regimen ID this is placebo of, or null if active." + } + }, + "required": ["demand", "placeboOf"], + "additionalProperties": false + }, + "KitItemDemandEntry": { + "type": "object", + "title": "Kit Item Demand Entry", + "description": "Kit item consumption entry with placeboOf tagging for active/placebo separation. Represents what patients actually receive, not supply demand.", + "properties": { + "demand": { + "$ref": "#/definitions/PositiveDateMap", + "description": "Monthly kit item demand values keyed by YYYY-MM-DD date." + }, + "placeboOf": { + "type": ["string", "null"], + "description": "Regimen ID this is placebo of, or null if active." + } + }, + "required": ["demand", "placeboOf"], + "additionalProperties": false + }, + "ByArmSequenceKitDateMap": { + "type": "object", + "title": "By Arm Sequence Kit Date Map", + "description": "Mapping of treatment arm IDs to sequence periods to kit demand. Structure: arm → sequenceId → kit → KitDemandEntry. The sequence ID matches keys in dosingRegimenSequence.", + "patternProperties": { + ".*": { + "type": "object", + "description": "Mapping of sequence period IDs to kit demand for this arm.", + "patternProperties": { + ".*": { + "type": "object", + "description": "Mapping of kit IDs to KitDemandEntry for this sequence period.", + "patternProperties": { + ".*": { + "$ref": "#/definitions/KitDemandEntry" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "ByArmSequenceKitItemDateMap": { + "type": "object", + "title": "By Arm Sequence Kit Item Date Map", + "description": "Mapping of treatment arm IDs to sequence periods to kit item consumption. Structure: arm → sequenceId → item → KitItemDemandEntry. The sequence ID matches keys in dosingRegimenSequence. Displayed as 'Dosing' in the UI.", + "patternProperties": { + ".*": { + "type": "object", + "description": "Mapping of sequence period IDs to kit item demand for this arm.", + "patternProperties": { + ".*": { + "type": "object", + "description": "Mapping of item IDs to KitItemDemandEntry for this sequence period.", + "patternProperties": { + ".*": { + "$ref": "#/definitions/KitItemDemandEntry" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "ByKitDateMap": { + "type": "object", + "title": "By Kit Date Map", + "description": "Mapping of kit IDs to date-value maps for site-level kit demand (not per-arm).", + "patternProperties": { + ".*": { + "$ref": "#/definitions/PositiveDateMap" + } + }, + "additionalProperties": false + }, + "ClinicalDemandActuals": { + "type": "object", + "title": "Clinical Demand Actuals", + "description": "Actual enrollment data entered by users to reconcile with forecast.", + "properties": { + "enrollment": { + "type": "object", + "title": "Enrollment Actuals", + "description": "Actual enrollment by region and month.", + "patternProperties": { + ".*": { + "type": "object", + "patternProperties": { + ".*": { + "type": "object", + "properties": { + "total": { + "type": "number", + "title": "Total Enrolled", + "description": "Total subjects enrolled for this region and month.", + "minimum": 0 + }, + "byArm": { + "type": "object", + "title": "Enrollment by Arm", + "description": "Enrollment count per treatment arm.", + "patternProperties": { + ".*": { + "type": "number", + "minimum": 0 + } + }, + "additionalProperties": false + } + }, + "required": ["total", "byArm"], + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "sites": { + "type": "object", + "title": "Sites Actuals", + "description": "Actual site counts by region and month.", + "patternProperties": { + ".*": { + "type": "object", + "patternProperties": { + ".*": { + "type": "number", + "minimum": 0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "notes": { + "type": "object", + "title": "Actuals Notes", + "description": "Explanatory notes for enrollment actuals by region and month.", + "patternProperties": { + ".*": { + "type": "object", + "patternProperties": { + ".*": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "siteNotes": { + "type": "object", + "title": "Site Actuals Notes", + "description": "Explanatory notes for site activation actuals by region and month.", + "patternProperties": { + ".*": { + "type": "object", + "patternProperties": { + ".*": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "required": ["enrollment", "sites", "notes", "siteNotes"], + "additionalProperties": false + } + } +} diff --git a/src/schemas/json/abc-clinical-demand-forecast-3.0.0.json b/src/schemas/json/abc-clinical-demand-forecast-3.0.0.json new file mode 100644 index 00000000000..ce157afe4b7 --- /dev/null +++ b/src/schemas/json/abc-clinical-demand-forecast-3.0.0.json @@ -0,0 +1,1885 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://json.schemastore.org/abc-clinical-demand-forecast-3.0.0.json", + "$comment": "AUTO-GENERATED by scripts/assemble-schemas.js \u2014 DO NOT EDIT BY HAND", + "title": "ABCClinicalDemandForecast JSON Schema", + "description": "Schema defining the structure of ABCClinicalDemandForecast used for clinical trial demand forecasting in ABC-Plan.", + "type": "object", + "properties": { + "$schema": { + "description": "Link to https://json.schemastore.org/abc-clinical-demand-forecast-3.0.0.json", + "type": "string", + "enum": [ + "https://json.schemastore.org/abc-clinical-demand-forecast-3.0.0.json" + ] + }, + "planNotes": { + "type": "string", + "title": "Plan Notes", + "description": "Optional notes about the clinical demand forecast plan." + }, + "analytics": { + "$comment": "SINGLE SOURCE OF TRUTH for analytics configuration. Used by scripts/assemble-schemas.js to replace __ABC_JSON_SCHEMA_PLACEHOLDER_ANALYTICS__ in each template. DO NOT reference this file directly \u2014 it is inlined into parent schemas at build time.", + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "title": "Analytics Item", + "description": "An analytics item that can be either a time series or an analytics note", + "oneOf": [ + { + "type": "object", + "title": "Time Series", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "type": { + "type": "string", + "enum": ["TIME_SERIES"] + }, + "subtitle": { + "type": "string" + }, + "excludeMonthsFromBeginning": { + "type": "number", + "description": "Number of months to exclude from the beginning of the time series. Positive values trim, negative values add months before the plan start date." + }, + "excludeMonthsFromEnd": { + "type": "number", + "description": "Number of months to exclude from the end of the time series. Positive values trim, negative values add months after the plan end date." + }, + "timeAggregateType": { + "type": "string", + "enum": ["Annual", "Quarterly", "Monthly"], + "title": "Time Aggregate Type", + "description": "The aggregation level for time series data display" + }, + "metrics": { + "type": "array", + "items": { + "type": "object", + "properties": { + "metricType": { + "type": "string", + "enum": [ + "enrolled", + "active", + "completed", + "kitDemand", + "sites", + "screened", + "screenedOut", + "droppedOut", + "siteSeedingKitDemand", + "kitItemDemand" + ] + }, + "abcMaterialIDs": { + "type": "array", + "items": { + "type": "string" + } + }, + "visualization": { + "type": "object", + "oneOf": [ + { + "properties": { + "type": { + "type": "string", + "enum": ["line"] + }, + "strokeWidth": { + "type": "number" + }, + "strokeDasharray": { + "type": "string" + }, + "dotSize": { + "type": "number", + "minimum": 0 + }, + "dotFill": { + "type": "string" + }, + "showDataPointValues": { + "type": "boolean", + "description": "Display data point values directly on the chart" + } + }, + "required": [ + "type", + "strokeWidth", + "strokeDasharray", + "dotSize", + "dotFill", + "showDataPointValues" + ], + "additionalProperties": false + }, + { + "properties": { + "type": { + "type": "string", + "enum": ["bar"] + }, + "barWidth": { + "type": "number" + }, + "radius": { + "type": "number" + }, + "stackId": { + "type": "string" + }, + "showAsPercent": { + "type": "boolean" + }, + "showDataPointValues": { + "type": "boolean", + "description": "Display data point values directly on the chart" + } + }, + "required": [ + "type", + "barWidth", + "radius", + "stackId", + "showAsPercent", + "showDataPointValues" + ], + "additionalProperties": false + }, + { + "properties": { + "type": { + "type": "string", + "enum": ["area"] + }, + "fillOpacity": { + "type": "number" + }, + "strokeWidth": { + "type": "number" + }, + "stackId": { + "type": "string" + }, + "showAsPercent": { + "type": "boolean" + }, + "showDataPointValues": { + "type": "boolean", + "description": "Display data point values directly on the chart" + } + }, + "required": [ + "type", + "fillOpacity", + "strokeWidth", + "stackId", + "showAsPercent", + "showDataPointValues" + ], + "additionalProperties": false + } + ] + }, + "yAxisIndex": { + "type": "number" + }, + "color": { + "type": "string" + }, + "zIndex": { + "type": "number" + }, + "label": { + "type": "string" + }, + "showQuantitiesAs": { + "type": ["string", "null"], + "oneOf": [ + { + "type": "string", + "enum": ["Units", "Lots", "Monetary"] + }, + { + "type": "null" + } + ], + "title": "Show Quantities As", + "description": "How to display the quantities for this metric" + }, + "comparisonPlanID": { + "type": ["string", "null"], + "title": "Comparison Plan ID", + "description": "The ID of the comparison plan to use for this metric or null if no comparison plan is used" + }, + "regionID": { + "type": "string", + "title": "Region ID", + "description": "Optional region filter for Clinical Demand Forecast metrics" + }, + "armID": { + "type": "string", + "title": "Arm ID", + "description": "Optional treatment arm filter for Clinical Demand Forecast metrics" + }, + "kitID": { + "type": "string", + "title": "Kit ID", + "description": "Optional kit filter for Clinical Demand Forecast metrics" + }, + "itemID": { + "type": "string", + "title": "Item ID", + "description": "Optional item filter for Clinical Demand Forecast metrics" + }, + "isCumulative": { + "type": "boolean", + "title": "Is Cumulative", + "description": "Whether to display cumulative values" + } + }, + "required": [ + "metricType", + "abcMaterialIDs", + "visualization", + "yAxisIndex", + "color", + "zIndex", + "label", + "showQuantitiesAs", + "comparisonPlanID" + ], + "additionalProperties": false + } + } + }, + "required": [ + "id", + "name", + "type", + "subtitle", + "excludeMonthsFromBeginning", + "excludeMonthsFromEnd", + "timeAggregateType", + "metrics" + ], + "additionalProperties": false + }, + { + "type": "object", + "title": "Analytics Note Item", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "type": { + "type": "string", + "enum": ["ANALYTICS_NOTE"] + }, + "subtitle": { + "type": "string" + }, + "backgroundColor": { + "type": "string" + }, + "imageUrl": { + "type": "string" + }, + "text": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "fontFamily": { + "type": "string" + }, + "fontSize": { + "type": "number" + }, + "color": { + "type": "string" + }, + "horizontalAlignment": { + "type": "string", + "enum": ["left", "center", "right"] + }, + "verticalAlignment": { + "type": "string", + "enum": ["top", "center", "bottom"] + } + }, + "required": [ + "content", + "fontFamily", + "fontSize", + "color", + "horizontalAlignment", + "verticalAlignment" + ], + "additionalProperties": false + } + }, + "required": ["id", "name", "type", "subtitle"], + "additionalProperties": false + } + ] + } + }, + "layouts": { + "type": "array", + "items": { + "type": "object", + "properties": { + "contentId": { + "type": "string" + }, + "x": { + "type": "number", + "minimum": 0, + "maximum": 11, + "description": "Grid column position (0-11 for 12-column grid)" + }, + "y": { + "type": "number", + "minimum": 0, + "description": "Grid row position" + }, + "tabID": { + "type": "string", + "description": "ID of the tab this layout item belongs to" + }, + "w": { + "type": "number", + "minimum": 1, + "maximum": 12, + "description": "Width in grid units (1-12)" + }, + "h": { + "type": "number", + "minimum": 1, + "description": "Height in grid units" + }, + "minH": { + "type": "number", + "minimum": 1, + "description": "Minimum height in grid units" + }, + "maxH": { + "type": "number", + "minimum": 1, + "description": "Maximum height in grid units" + }, + "minW": { + "type": "number", + "minimum": 1, + "description": "Minimum width in grid units" + }, + "maxW": { + "type": "number", + "minimum": 1, + "description": "Maximum width in grid units" + }, + "isDraggable": { + "type": "boolean", + "description": "Whether the item can be dragged" + }, + "isResizable": { + "type": "boolean", + "description": "Whether the item can be resized" + } + }, + "required": ["contentId", "x", "y", "tabID", "w", "h"], + "additionalProperties": false + } + }, + "tabs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "ordering": { + "type": "number" + } + }, + "required": ["id", "name", "ordering"], + "additionalProperties": false + } + } + }, + "required": ["items", "layouts", "tabs"], + "additionalProperties": false + }, + "clinicalDemandForecastConfiguration": { + "$ref": "#/definitions/ClinicalDemandForecastConfiguration", + "title": "Clinical Demand Forecast Configuration", + "description": "All algorithm inputs that affect forecast calculation. Any change to this object invalidates clinicalDemandCalculatedForecast." + }, + "clinicalDemandCalculatedForecast": { + "$ref": "#/definitions/ClinicalDemandCalculatedForecast", + "title": "Clinical Demand Calculated Forecast", + "description": "Calculated demand forecast with atomic monthly data per-region, per-arm." + } + }, + "required": [ + "planNotes", + "analytics", + "clinicalDemandForecastConfiguration", + "clinicalDemandCalculatedForecast" + ], + "abcHasValidKitItemReferences": true, + "additionalProperties": false, + "definitions": { + "ClinicalDemandForecastConfiguration": { + "type": "object", + "title": "Clinical Demand Forecast Configuration", + "description": "All algorithm inputs that affect forecast calculation. Any change to this object should invalidate the calculated forecast.", + "properties": { + "studyOverview": { + "$ref": "#/definitions/StudyOverview", + "title": "Study Overview", + "description": "Basic information about the clinical trial study." + }, + "enrollment": { + "$ref": "#/definitions/Enrollment", + "title": "Enrollment", + "description": "Patient enrollment targets and rates." + }, + "kitConfiguration": { + "type": "object", + "title": "Kit Configuration", + "description": "Configuration of kits with containers and items, keyed by kit ID.", + "patternProperties": { + ".*": { + "$ref": "#/definitions/Kit" + } + }, + "additionalProperties": false + }, + "dosingRegimens": { + "type": "object", + "title": "Dosing Regimens", + "description": "Available dosing regimens for the study, keyed by ID.", + "patternProperties": { + ".*": { + "$ref": "#/definitions/DosingRegimen" + } + }, + "additionalProperties": false + }, + "treatmentArms": { + "type": "object", + "title": "Treatment Arms", + "description": "Treatment arms in the clinical trial, keyed by ID.", + "patternProperties": { + ".*": { + "$ref": "#/definitions/TreatmentArm" + } + }, + "additionalProperties": false + }, + "subjectFlow": { + "$ref": "#/definitions/SubjectFlow", + "title": "Subject Flow", + "description": "Site supply and region configuration." + }, + "actuals": { + "$ref": "#/definitions/ClinicalDemandActuals", + "title": "Clinical Demand Actuals", + "description": "Actual enrollment data entered by users to reconcile with forecast." + }, + "trialDesign": { + "type": "string", + "title": "Trial Design", + "description": "Trial design type: parallel (single regimen per arm) or crossOver (sequence of treatments with washout periods).", + "enum": ["parallel", "crossOver"], + "default": "parallel" + } + }, + "required": [ + "studyOverview", + "enrollment", + "kitConfiguration", + "dosingRegimens", + "treatmentArms", + "subjectFlow", + "actuals", + "trialDesign" + ], + "additionalProperties": false + }, + "StudyOverview": { + "type": "object", + "title": "Study Overview", + "description": "Basic study information from wizard step 1.", + "properties": { + "studyName": { + "type": "string", + "title": "Study Name", + "description": "Name of the clinical trial study." + }, + "studyPhase": { + "type": "string", + "title": "Study Phase", + "description": "Phase of the clinical trial.", + "enum": ["phase_i", "phase_ii", "phase_iii", "phase_iv"] + }, + "protocolSummary": { + "type": "string", + "title": "Protocol Summary", + "description": "Summary of the trial protocol. Optional field with recommended default: empty string." + }, + "therapeuticArea": { + "type": "string", + "title": "Therapeutic Area", + "description": "Disease category being treated (e.g., Oncology, CNS, Rare Disease). Will be normalized by LLM for benchmarking. Required field with recommended default: empty string." + } + }, + "required": [ + "studyName", + "studyPhase", + "protocolSummary", + "therapeuticArea" + ], + "additionalProperties": false + }, + "Enrollment": { + "type": "object", + "title": "Enrollment", + "description": "Enrollment configuration from wizard step 2.", + "properties": { + "targetCompleted": { + "type": "integer", + "title": "Target Completed", + "description": "Target number of subjects to complete the trial.", + "minimum": 0 + }, + "screenFailureRate": { + "type": "number", + "title": "Screen Failure Rate", + "description": "Percentage of subjects who fail screening (0-100).", + "minimum": 0, + "maximum": 100 + }, + "attritionRate": { + "type": "number", + "title": "Attrition Rate", + "description": "Percentage of enrolled subjects who drop out of the study entirely and stop receiving all doses (0-100). NOTE: This is different from adverse events in dose escalation regimens. Attrition causes subjects to exit the study early, while adverse events only prevent dose escalation but subjects continue receiving doses at their maximum tolerated level.", + "minimum": 0, + "maximum": 100 + } + }, + "required": ["targetCompleted", "screenFailureRate", "attritionRate"], + "additionalProperties": false + }, + "Kit": { + "type": "object", + "title": "Kit", + "description": "A kit containing items and containers. The kit ID is the key in the kitConfiguration map.", + "properties": { + "name": { + "type": "string", + "title": "Kit Name", + "description": "Display name of the kit." + }, + "items": { + "type": "object", + "title": "Items", + "description": "Items and containers within this kit, keyed by node ID.", + "patternProperties": { + ".*": { + "$ref": "#/definitions/KitNode" + } + }, + "additionalProperties": false + } + }, + "required": ["name", "items"], + "additionalProperties": false + }, + "KitNode": { + "oneOf": [ + { + "$ref": "#/definitions/KitContainer" + }, + { + "$ref": "#/definitions/KitItem" + } + ] + }, + "KitContainer": { + "type": "object", + "title": "Kit Container", + "description": "A structural container (bottle, blister pack) that holds other nodes. The container ID is the key in the parent items/children map.", + "properties": { + "nodeType": { + "type": "string", + "const": "container", + "title": "Node Type", + "description": "Discriminator identifying this as a container node." + }, + "name": { + "type": "string", + "title": "Name", + "description": "Name of the container." + }, + "deliveryForm": { + "type": "string", + "title": "Delivery Form", + "description": "Type of container.", + "enum": ["bottle", "carton", "dose_pack", "blister_pack"] + }, + "children": { + "type": "object", + "title": "Children", + "description": "Items contained within this container, keyed by node ID.", + "patternProperties": { + ".*": { + "$ref": "#/definitions/KitNode" + } + }, + "additionalProperties": false + }, + "ordering": { + "type": "integer", + "title": "Ordering", + "description": "Display order of this node within its parent. Lower numbers appear first." + } + }, + "required": ["nodeType", "name", "deliveryForm", "children", "ordering"], + "additionalProperties": false + }, + "KitItem": { + "type": "object", + "title": "Kit Item", + "description": "An actual pharmaceutical item (tablet, capsule, vial) with dosage. The item ID is the key in the parent items/children map.", + "properties": { + "nodeType": { + "type": "string", + "const": "item", + "title": "Node Type", + "description": "Discriminator identifying this as an item node." + }, + "name": { + "type": "string", + "title": "Name", + "description": "Name of the item." + }, + "deliveryForm": { + "type": "string", + "title": "Delivery Form", + "description": "Type of pharmaceutical item.", + "enum": ["tablet", "capsule", "vial", "syringe", "sachet"] + }, + "quantity": { + "type": "integer", + "title": "Quantity", + "description": "Number of units contained in this item within one kit. For example, if this is a bottle containing tablets, quantity=10 means the bottle contains 10 tablets. This is NOT the same as dosingRegimen.kitItemQuantities, which specifies consumption per dose. This quantity field defines the container/packaging size.", + "minimum": 0 + }, + "dosage": { + "type": "number", + "title": "Dosage", + "description": "Dosage amount.", + "minimum": 0 + }, + "units": { + "type": "string", + "title": "Units", + "description": "Units of dosage (e.g., mg, ml)." + }, + "ordering": { + "type": "integer", + "title": "Ordering", + "description": "Display order of this node within its parent. Lower numbers appear first." + } + }, + "required": [ + "nodeType", + "name", + "deliveryForm", + "quantity", + "dosage", + "units", + "ordering" + ], + "additionalProperties": false + }, + "DosingFrequency": { + "type": "object", + "title": "Dosing Frequency", + "description": "Phased dosing frequency. Each phase defines a repeating cycle pattern. The last phase runs for the remainder of the treatment.", + "properties": { + "description": { + "type": ["string", "null"], + "title": "Description", + "description": "Human-readable description of the dosing frequency (e.g., 'BID', 'capecitabine 14/7', 'weekly for 6 weeks then monthly'). Doubles as the AI generation prompt. null when created manually without AI." + }, + "phases": { + "type": "array", + "items": { + "$ref": "#/definitions/DosingPhase" + }, + "minItems": 1, + "maxItems": 10, + "title": "Dosing Phases", + "description": "Ordered array of dosing phases. Single-phase regimens have one element. Multi-phase regimens list phases in chronological order; the last phase has repeatFor: 0 (fills remainder of treatment)." + } + }, + "required": ["description", "phases"], + "additionalProperties": false + }, + "DosingPhase": { + "type": "object", + "title": "Dosing Phase", + "description": "A single phase within a dosing frequency. Defines a repeating cycle pattern and a repeat duration. repeatFor: 0 means terminal (remainder of treatment).", + "properties": { + "cycleDoses": { + "type": "array", + "items": { + "type": "integer", + "minimum": 0 + }, + "minItems": 1, + "maxItems": 365, + "title": "Cycle Doses", + "description": "Element i = doses in unit i of the repeating cycle. For cycleUnit 'days', element = doses on that day. For 'months', element = total doses in that calendar month." + }, + "cycleUnit": { + "type": "string", + "enum": ["days", "months"], + "title": "Cycle Unit", + "description": "Time granularity. 'days' = fixed-length day cycle. 'months' = calendar month cycle." + }, + "repeatFor": { + "type": "integer", + "minimum": 0, + "title": "Repeat For", + "description": "How many units of repeatForUnit the cycle repeats for. 0 = terminal phase (remainder of treatment)." + }, + "repeatForUnit": { + "type": "string", + "enum": ["days", "weeks", "months", "years"], + "title": "Repeat For Unit", + "description": "Unit for repeatFor. Ignored when repeatFor is 0 (convention: match cycleUnit)." + } + }, + "required": ["cycleDoses", "cycleUnit", "repeatFor", "repeatForUnit"], + "additionalProperties": false + }, + "DosingRegimen": { + "type": "object", + "title": "Dosing Regimen", + "description": "Dosing regimen configuration with nested dosingRegimenDefinition discriminated union.", + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "Name of the dosing regimen." + }, + "description": { + "type": "string", + "title": "Description", + "description": "Description of the dosing regimen." + }, + "dosingRegimenDefinition": { + "oneOf": [ + { + "$ref": "#/definitions/ActiveRegimenDefinition" + }, + { + "$ref": "#/definitions/PlaceboRegimenDefinition" + } + ], + "title": "Dosing Regimen Definition", + "description": "Discriminated union: either an active regimen definition (isPlacebo: false) or a placebo definition (isPlacebo: true)." + } + }, + "required": ["name", "description", "dosingRegimenDefinition"], + "additionalProperties": false + }, + "ActiveRegimenDefinition": { + "type": "object", + "title": "Active Regimen Definition", + "description": "Definition for an active (non-placebo) dosing regimen.", + "properties": { + "isPlacebo": { + "type": "boolean", + "const": false, + "title": "Is Placebo", + "description": "Discriminator: false for active regimens." + }, + "treatmentDuration": { + "type": "number", + "title": "Treatment Duration", + "description": "Total duration of treatment for all subjects.", + "minimum": 0 + }, + "treatmentDurationUnit": { + "type": "string", + "title": "Treatment Duration Unit", + "description": "Unit of treatment duration.", + "enum": ["days", "weeks", "months", "years"] + }, + "dosingFrequency": { + "$ref": "#/definitions/DosingFrequency" + }, + "fixedConfig": { + "anyOf": [ + { + "$ref": "#/definitions/FixedDoseConfig" + }, + { + "type": "null" + } + ], + "title": "Fixed Dose Configuration", + "description": "Configuration for fixed dose regimens. Null if using another regimen type." + }, + "doseEscalationConfig": { + "anyOf": [ + { + "$ref": "#/definitions/DoseEscalationConfig" + }, + { + "type": "null" + } + ], + "title": "Dose Escalation Configuration", + "description": "Configuration for dose escalation regimens. Null if using another regimen type." + }, + "weightBasedConfig": { + "anyOf": [ + { + "$ref": "#/definitions/WeightBasedConfig" + }, + { + "type": "null" + } + ], + "title": "Weight-Based Configuration", + "description": "Configuration for weight-based dosing regimens. Null if using another regimen type." + }, + "ageBasedConfig": { + "anyOf": [ + { + "$ref": "#/definitions/AgeBasedConfig" + }, + { + "type": "null" + } + ], + "title": "Age-Based Configuration", + "description": "Configuration for age-based dosing regimens. Null if using another regimen type." + }, + "dispensationIntervals": { + "type": "array", + "items": { + "type": "number", + "minimum": 1 + }, + "minItems": 1, + "title": "Dispensation Intervals", + "description": "Non-empty array of intervals between consecutive dispensation events, in dispensationIntervalUnit." + }, + "dispensationIntervalUnit": { + "type": "string", + "title": "Dispensation Interval Unit", + "description": "Unit for dispensationIntervals. Independent of treatmentDurationUnit.", + "enum": ["days", "weeks", "months", "years"] + } + }, + "required": [ + "isPlacebo", + "treatmentDuration", + "treatmentDurationUnit", + "dosingFrequency", + "fixedConfig", + "doseEscalationConfig", + "weightBasedConfig", + "ageBasedConfig", + "dispensationIntervals", + "dispensationIntervalUnit" + ], + "additionalProperties": false + }, + "PlaceboRegimenDefinition": { + "type": "object", + "title": "Placebo Regimen Definition", + "description": "Definition for a placebo regimen that references an active regimen.", + "properties": { + "isPlacebo": { + "type": "boolean", + "const": true, + "title": "Is Placebo", + "description": "Discriminator: true for placebo regimens." + }, + "placeboOf": { + "type": "string", + "title": "Placebo Of", + "description": "ID of the active dosing regimen this placebo references. Placebo inherits all dosing configuration from the active regimen." + } + }, + "required": ["isPlacebo", "placeboOf"], + "additionalProperties": false + }, + "FixedDoseConfig": { + "type": "object", + "title": "Fixed Dose Configuration", + "description": "Configuration for fixed dose regimens.", + "properties": { + "kitItemQuantities": { + "type": "object", + "title": "Kit Item Quantities", + "description": "Mapping of kit item IDs to quantities consumed per dose.", + "patternProperties": { + ".*": { + "type": "number", + "minimum": 0 + } + }, + "additionalProperties": false + } + }, + "required": ["kitItemQuantities"], + "additionalProperties": false + }, + "DoseEscalationConfig": { + "type": "object", + "title": "Dose Escalation Configuration", + "description": "Configuration for dose escalation levels.", + "properties": { + "levels": { + "type": "object", + "title": "Escalation Levels", + "description": "Map of level IDs to dose escalation levels. Use ordering property to maintain display order.", + "patternProperties": { + ".*": { + "$ref": "#/definitions/DoseEscalationLevel" + } + }, + "additionalProperties": false + } + }, + "required": ["levels"], + "additionalProperties": false + }, + "DoseEscalationLevel": { + "title": "Dose Escalation Level", + "description": "Individual dose escalation level configuration. This is a discriminated union with three variants: First level (isFirstLevel=true), Intermediate level (isFinalLevel=false, has adverseEventRate), and Final level (isFinalLevel=true, no duration).", + "oneOf": [ + { + "$ref": "#/definitions/DoseEscalationLevelFirst" + }, + { + "$ref": "#/definitions/DoseEscalationLevelIntermediate" + }, + { + "$ref": "#/definitions/DoseEscalationLevelFinal" + } + ] + }, + "DoseEscalationLevelBase": { + "$comment": "additionalProperties is true because this object acts as a base schema and is composed with variant-specific definitions via allOf.", + "type": "object", + "title": "Dose Escalation Level Base", + "description": "Base properties shared by all dose escalation level variants.", + "properties": { + "kitItemQuantities": { + "type": "object", + "title": "Kit Item Quantities", + "description": "Mapping of kit item IDs to quantities consumed per dose at this escalation level.", + "patternProperties": { + ".*": { + "type": "number", + "minimum": 0 + } + }, + "additionalProperties": false + }, + "ordering": { + "type": "number", + "title": "Ordering", + "description": "Display order of this level within the escalation sequence. Lower numbers appear first." + } + }, + "required": ["kitItemQuantities", "ordering"], + "additionalProperties": true + }, + "DoseEscalationLevelFirst": { + "title": "First Dose Escalation Level", + "description": "First escalation level. Has isFirstLevel=true, isFinalLevel=false, duration and durationUnit. Does NOT have adverseEventRate because adverse events occur when trialing a level, and there is no previous level to trial from.", + "allOf": [ + { + "$ref": "#/definitions/DoseEscalationLevelBase" + }, + { + "type": "object", + "properties": { + "kitItemQuantities": {}, + "isFirstLevel": { + "type": "boolean", + "const": true, + "title": "Is First Level", + "description": "Must be true for first level." + }, + "isFinalLevel": { + "type": "boolean", + "const": false, + "title": "Is Final Level", + "description": "Must be false for first level (first level cannot also be final)." + }, + "duration": { + "type": "number", + "title": "Duration", + "description": "Duration of this escalation level.", + "exclusiveMinimum": 0 + }, + "durationUnit": { + "type": "string", + "title": "Duration Unit", + "description": "Unit of duration for this level.", + "enum": ["days", "weeks", "months", "years"] + }, + "ordering": {} + }, + "required": [ + "isFirstLevel", + "isFinalLevel", + "duration", + "durationUnit" + ], + "additionalProperties": false + } + ] + }, + "DoseEscalationLevelIntermediate": { + "title": "Intermediate Dose Escalation Level", + "description": "Intermediate escalation level (not first, not final). Has isFinalLevel=false, duration, durationUnit, and adverseEventRate. Does NOT have isFirstLevel property.", + "allOf": [ + { + "$ref": "#/definitions/DoseEscalationLevelBase" + }, + { + "type": "object", + "properties": { + "kitItemQuantities": {}, + "isFinalLevel": { + "type": "boolean", + "const": false, + "title": "Is Final Level", + "description": "Must be false for intermediate level." + }, + "duration": { + "type": "number", + "title": "Duration", + "description": "Duration of this escalation level.", + "exclusiveMinimum": 0 + }, + "durationUnit": { + "type": "string", + "title": "Duration Unit", + "description": "Unit of duration for this level.", + "enum": ["days", "weeks", "months", "years"] + }, + "adverseEventRate": { + "type": "number", + "title": "Adverse Event Rate", + "description": "Percentage of subjects who experience adverse events when TRIALING THIS level from the previous level.", + "minimum": 0, + "maximum": 100 + }, + "ordering": {} + }, + "required": [ + "isFinalLevel", + "duration", + "durationUnit", + "adverseEventRate" + ], + "additionalProperties": false + } + ] + }, + "DoseEscalationLevelFinal": { + "title": "Final Dose Escalation Level", + "description": "Final escalation level. Has isFinalLevel=true and adverseEventRate. Does NOT have duration or durationUnit (subjects continue at final level until treatment ends).", + "allOf": [ + { + "$ref": "#/definitions/DoseEscalationLevelBase" + }, + { + "type": "object", + "properties": { + "kitItemQuantities": {}, + "isFinalLevel": { + "type": "boolean", + "const": true, + "title": "Is Final Level", + "description": "Must be true for final level." + }, + "adverseEventRate": { + "type": "number", + "title": "Adverse Event Rate", + "description": "Percentage of subjects who experience adverse events when TRIALING THIS level from the previous level.", + "minimum": 0, + "maximum": 100 + }, + "ordering": {} + }, + "required": ["isFinalLevel", "adverseEventRate"], + "additionalProperties": false + } + ] + }, + "WeightBasedConfig": { + "type": "object", + "title": "Weight-Based Configuration", + "description": "Configuration for weight-based dosing.", + "properties": { + "weightUnit": { + "type": "string", + "title": "Weight Unit", + "description": "Unit of weight measurement.", + "enum": ["lbs", "kg"] + }, + "ranges": { + "type": "object", + "title": "Weight Ranges", + "description": "Map of range IDs to weight ranges for kit assignment. Use ordering property to maintain display order.", + "patternProperties": { + ".*": { + "$ref": "#/definitions/WeightRange" + } + }, + "additionalProperties": false + } + }, + "required": ["weightUnit", "ranges"], + "additionalProperties": false + }, + "WeightRange": { + "type": "object", + "title": "Weight Range", + "description": "Weight range configuration for dosing.", + "properties": { + "kitItemQuantities": { + "type": "object", + "title": "Kit Item Quantities", + "description": "Mapping of kit item IDs to quantities consumed per dose for subjects in THIS weight range. Each value represents the number of units (e.g., tablets) of that item required for a single dose. Different weight ranges can specify different quantities for the same item.", + "patternProperties": { + ".*": { + "type": "number", + "minimum": 0 + } + }, + "additionalProperties": false + }, + "fromWeight": { + "type": ["number", "null"], + "title": "From Weight", + "description": "Minimum weight for this range (null for no lower bound).", + "minimum": 0 + }, + "toWeight": { + "type": ["number", "null"], + "title": "To Weight", + "description": "Maximum weight for this range (null for no upper bound).", + "minimum": 0 + }, + "expectedPercentage": { + "type": "number", + "title": "Expected Percentage", + "description": "Expected percentage of subjects in this weight range.", + "minimum": 0, + "maximum": 100 + }, + "ordering": { + "type": "number", + "title": "Ordering", + "description": "Display order of this range. Lower numbers appear first." + } + }, + "required": [ + "kitItemQuantities", + "fromWeight", + "toWeight", + "expectedPercentage", + "ordering" + ], + "additionalProperties": false + }, + "AgeBasedConfig": { + "type": "object", + "title": "Age-Based Configuration", + "description": "Configuration for age-based dosing.", + "properties": { + "ageUnit": { + "type": "string", + "title": "Age Unit", + "description": "Unit of age measurement.", + "enum": ["years", "months"] + }, + "ranges": { + "type": "object", + "title": "Age Ranges", + "description": "Map of range IDs to age ranges for kit assignment. Use ordering property to maintain display order.", + "patternProperties": { + ".*": { + "$ref": "#/definitions/AgeRange" + } + }, + "additionalProperties": false + } + }, + "required": ["ageUnit", "ranges"], + "additionalProperties": false + }, + "AgeRange": { + "type": "object", + "title": "Age Range", + "description": "Age range configuration for dosing.", + "properties": { + "kitItemQuantities": { + "type": "object", + "title": "Kit Item Quantities", + "description": "Mapping of kit item IDs to quantities consumed per dose for subjects in THIS age range. Each value represents the number of units (e.g., tablets) of that item required for a single dose. Different age ranges can specify different quantities for the same item.", + "patternProperties": { + ".*": { + "type": "number", + "minimum": 0 + } + }, + "additionalProperties": false + }, + "fromAge": { + "type": ["number", "null"], + "title": "From Age", + "description": "Minimum age for this range (null for no lower bound).", + "minimum": 0 + }, + "toAge": { + "type": ["number", "null"], + "title": "To Age", + "description": "Maximum age for this range (null for no upper bound).", + "minimum": 0 + }, + "expectedPercentage": { + "type": "number", + "title": "Expected Percentage", + "description": "Expected percentage of subjects in this age range.", + "minimum": 0, + "maximum": 100 + }, + "ordering": { + "type": "number", + "title": "Ordering", + "description": "Display order of this range. Lower numbers appear first." + } + }, + "required": [ + "kitItemQuantities", + "fromAge", + "toAge", + "expectedPercentage", + "ordering" + ], + "additionalProperties": false + }, + "TreatmentPeriod": { + "type": "object", + "title": "Treatment Period", + "description": "Treatment period in a dosing regimen sequence.", + "properties": { + "periodType": { + "type": "string", + "const": "treatment", + "title": "Period Type", + "description": "Discriminator identifying this as a treatment period." + }, + "dosingRegimenId": { + "type": "string", + "title": "Dosing Regimen ID", + "description": "ID of the dosing regimen used during this treatment period." + }, + "ordering": { + "type": "number", + "title": "Ordering", + "description": "Order position of this period in the sequence." + } + }, + "required": ["periodType", "dosingRegimenId", "ordering"], + "additionalProperties": false + }, + "WashoutPeriod": { + "type": "object", + "title": "Washout Period", + "description": "Washout period (no dosing) in a dosing regimen sequence.", + "properties": { + "periodType": { + "type": "string", + "const": "washout", + "title": "Period Type", + "description": "Discriminator identifying this as a washout period." + }, + "duration": { + "type": "number", + "title": "Duration", + "description": "Duration of the washout period.", + "exclusiveMinimum": 0 + }, + "durationUnit": { + "type": "string", + "title": "Duration Unit", + "description": "Unit of washout duration.", + "enum": ["days", "weeks", "months", "years"] + }, + "ordering": { + "type": "number", + "title": "Ordering", + "description": "Order position of this period in the sequence." + } + }, + "required": ["periodType", "duration", "durationUnit", "ordering"], + "additionalProperties": false + }, + "SequencePeriod": { + "oneOf": [ + { + "$ref": "#/definitions/TreatmentPeriod" + }, + { + "$ref": "#/definitions/WashoutPeriod" + } + ] + }, + "SequencePeriodMap": { + "type": "object", + "title": "Sequence Periods", + "description": "Ordered sequence of treatment and washout periods, keyed by unique period ID.", + "patternProperties": { + ".*": { + "$ref": "#/definitions/SequencePeriod" + } + }, + "additionalProperties": false, + "minProperties": 1 + }, + "TreatmentArm": { + "type": "object", + "title": "Treatment Arm", + "description": "Treatment arm configuration from wizard step 5.", + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "Name of the treatment arm." + }, + "allocationPercentage": { + "type": "number", + "title": "Allocation Percentage", + "description": "Percentage of subjects allocated to this arm.", + "minimum": 0, + "maximum": 100 + }, + "dosingRegimenSequence": { + "$ref": "#/definitions/SequencePeriodMap", + "title": "Dosing Regimen Sequence", + "description": "Sequence of treatment and washout periods for this treatment arm." + } + }, + "required": ["name", "allocationPercentage", "dosingRegimenSequence"], + "additionalProperties": false + }, + "SubjectFlow": { + "type": "object", + "title": "Subject Flow", + "description": "Site supply and region configuration from wizard step 6.", + "properties": { + "regions": { + "type": "object", + "title": "Countries", + "description": "Region-specific site configuration, keyed by ID.", + "patternProperties": { + ".*": { + "$ref": "#/definitions/RegionConfig" + } + }, + "additionalProperties": false + } + }, + "required": ["regions"], + "additionalProperties": false + }, + "RegionConfig": { + "oneOf": [ + { + "$ref": "#/definitions/RegionContainer" + }, + { + "$ref": "#/definitions/RegionLeaf" + } + ] + }, + "RegionContainer": { + "type": "object", + "title": "Region Container", + "description": "Container node for grouping regions. Has subregions but no sites.", + "properties": { + "nodeType": { + "type": "string", + "const": "container", + "title": "Node Type", + "description": "Discriminator identifying this as a container node." + }, + "regionName": { + "type": "string", + "title": "Region Name", + "description": "Name of the region container." + }, + "subregions": { + "type": "object", + "title": "Subregions", + "description": "Child regions within this container.", + "patternProperties": { + ".*": { + "$ref": "#/definitions/RegionConfig" + } + }, + "additionalProperties": false + }, + "activationStartDate": { + "type": ["string", "null"], + "format": "date", + "title": "Activation Start Date", + "description": "Default activation start date for child regions. Null means no default." + }, + "screenRate": { + "type": ["number", "null"], + "title": "Screen Rate", + "description": "Default screen rate for child regions. Null means no default.", + "exclusiveMinimum": 0 + }, + "siteActivationRate": { + "type": ["number", "null"], + "title": "Site Activation Rate", + "description": "Default site activation rate for child regions. Null means no default.", + "exclusiveMinimum": 0 + }, + "maxEnrollmentPerSite": { + "type": ["number", "null"], + "title": "Maximum Enrollment Per Site", + "description": "Default max enrollment per site for child regions. Null means no default.", + "minimum": 0 + }, + "siteSeeding": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "object", + "additionalProperties": { + "type": "number", + "minimum": 0 + } + } + ], + "title": "Site Seeding", + "description": "Default site seeding quantity per kit for child regions. Map of kitId to quantity, or null to inherit." + } + }, + "required": ["nodeType", "regionName", "subregions"], + "additionalProperties": false + }, + "RegionLeaf": { + "type": "object", + "title": "Region Leaf", + "description": "Leaf node representing an operational region with sites.", + "properties": { + "nodeType": { + "type": "string", + "const": "region", + "title": "Node Type", + "description": "Discriminator identifying this as a leaf region node." + }, + "regionName": { + "type": "string", + "title": "Region Name", + "description": "Name of the region." + }, + "numberOfSites": { + "type": "integer", + "title": "Number of Sites", + "description": "Total number of sites in this region.", + "minimum": 1 + }, + "activationStartDate": { + "type": ["string", "null"], + "format": "date", + "title": "Activation Start Date", + "description": "Date when site activation begins. Null inherits from parent container." + }, + "screenRate": { + "type": ["number", "null"], + "title": "Screen Rate", + "description": "Number of subjects screened per site per month. Null inherits from parent container.", + "exclusiveMinimum": 0 + }, + "siteActivationRate": { + "type": ["number", "null"], + "title": "Site Activation Rate", + "description": "Number of sites activated per month. Null inherits from parent container.", + "exclusiveMinimum": 0 + }, + "maxEnrollmentPerSite": { + "type": ["number", "null"], + "title": "Maximum Enrollment Per Site", + "description": "Maximum number of patients that can be enrolled per site. Null for unlimited or inherited.", + "minimum": 0 + }, + "siteSeeding": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "object", + "additionalProperties": { + "type": "number", + "minimum": 0 + } + } + ], + "title": "Site Seeding", + "description": "Site seeding quantity per kit. Map of kitId to quantity, or null to inherit from parent." + } + }, + "required": ["nodeType", "regionName", "numberOfSites"], + "additionalProperties": false + }, + "RegionForecast": { + "type": "object", + "title": "Region Forecast", + "description": "Atomic monthly forecast data for a single region. All values are monthly (not cumulative). UI computes cumulative views.", + "properties": { + "sites": { + "$ref": "#/definitions/PositiveDateMap", + "title": "Sites", + "description": "Monthly count of sites opened this month." + }, + "subjectsScreened": { + "$ref": "#/definitions/PositiveDateMap", + "title": "Subjects Screened", + "description": "Monthly count of subjects screened this month." + }, + "subjectsScreenedOut": { + "$ref": "#/definitions/PositiveDateMap", + "title": "Subjects Screened Out", + "description": "Monthly count of subjects who failed screening this month." + }, + "subjectsEnrolled": { + "$ref": "#/definitions/ByArmDateMap", + "title": "Subjects Enrolled", + "description": "Monthly subjects enrolled, broken down by treatment arm." + }, + "subjectsActive": { + "$ref": "#/definitions/ByArmDateMap", + "title": "Subjects Active", + "description": "Monthly change in active subjects (new active this month), broken down by treatment arm." + }, + "subjectsCompleted": { + "$ref": "#/definitions/ByArmDateMap", + "title": "Subjects Completed", + "description": "Monthly subjects who completed treatment, broken down by treatment arm." + }, + "subjectsDroppedOut": { + "$ref": "#/definitions/ByArmDateMap", + "title": "Subjects Dropped Out", + "description": "Monthly subjects who dropped out, broken down by treatment arm." + }, + "kitDemand": { + "$ref": "#/definitions/ByArmSequenceKitDateMap", + "title": "Kit Demand", + "description": "Monthly kit demand (whole kits rounded up), broken down by treatment arm and kit ID. Use this for supply planning - it accounts for buffer/waste in partially-used bottles." + }, + "kitItemDemand": { + "$ref": "#/definitions/ByArmSequenceKitItemDateMap", + "title": "Kit Item Demand", + "description": "Monthly kit item consumption (what patients actually receive), broken down by treatment arm and item ID. Displayed as 'Dosing' in the UI. Note: This is consumption, not supply demand - it does not include buffer/waste from partially-used bottles. For supply planning, use kitDemand." + }, + "siteSeedingKitDemand": { + "$ref": "#/definitions/ByKitDateMap", + "title": "Site Seeding Kit Demand", + "description": "Site seeding kit demand - kits shipped when sites activate (not treatment-driven). Keyed by kit ID." + } + }, + "required": [ + "sites", + "subjectsScreened", + "subjectsScreenedOut", + "subjectsEnrolled", + "subjectsActive", + "subjectsCompleted", + "subjectsDroppedOut", + "kitDemand", + "kitItemDemand", + "siteSeedingKitDemand" + ], + "additionalProperties": false + }, + "ClinicalDemandCalculatedForecast": { + "type": "object", + "title": "Clinical Demand Calculated Forecast", + "description": "Atomic monthly forecast data. Contains per-region data only. UI computes global totals and cumulative views.", + "properties": { + "byRegion": { + "type": "object", + "title": "By Region", + "description": "Region-specific forecast data keyed by region ID.", + "additionalProperties": { + "$ref": "#/definitions/RegionForecast" + } + } + }, + "required": ["byRegion"], + "additionalProperties": false + }, + "PositiveDateMap": { + "type": "object", + "title": "Positive Date Map", + "description": "Mapping of dates to positive numerical values.", + "patternProperties": { + "^\\d{4}-(0[1-9]|1[0-2])-01$": { + "type": "number", + "minimum": 0 + } + }, + "additionalProperties": false + }, + "ByArmDateMap": { + "type": "object", + "title": "By Arm Date Map", + "description": "Mapping of treatment arm IDs to date-value maps. Each arm ID maps to a PositiveDateMap.", + "patternProperties": { + ".*": { + "$ref": "#/definitions/PositiveDateMap" + } + }, + "additionalProperties": false + }, + "KitDemandEntry": { + "type": "object", + "title": "Kit Demand Entry", + "description": "Kit demand with placeboOf tagging for active/placebo separation.", + "properties": { + "demand": { + "$ref": "#/definitions/PositiveDateMap", + "description": "Monthly kit demand values keyed by YYYY-MM-DD date." + }, + "placeboOf": { + "type": ["string", "null"], + "description": "Regimen ID this is placebo of, or null if active." + } + }, + "required": ["demand", "placeboOf"], + "additionalProperties": false + }, + "KitItemDemandEntry": { + "type": "object", + "title": "Kit Item Demand Entry", + "description": "Kit item consumption entry with placeboOf tagging for active/placebo separation. Represents what patients actually receive, not supply demand.", + "properties": { + "demand": { + "$ref": "#/definitions/PositiveDateMap", + "description": "Monthly kit item demand values keyed by YYYY-MM-DD date." + }, + "placeboOf": { + "type": ["string", "null"], + "description": "Regimen ID this is placebo of, or null if active." + } + }, + "required": ["demand", "placeboOf"], + "additionalProperties": false + }, + "ByArmSequenceKitDateMap": { + "type": "object", + "title": "By Arm Sequence Kit Date Map", + "description": "Mapping of treatment arm IDs to sequence periods to kit demand. Structure: arm \u2192 sequenceId \u2192 kit \u2192 KitDemandEntry. The sequence ID matches keys in dosingRegimenSequence.", + "patternProperties": { + ".*": { + "type": "object", + "description": "Mapping of sequence period IDs to kit demand for this arm.", + "patternProperties": { + ".*": { + "type": "object", + "description": "Mapping of kit IDs to KitDemandEntry for this sequence period.", + "patternProperties": { + ".*": { + "$ref": "#/definitions/KitDemandEntry" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "ByArmSequenceKitItemDateMap": { + "type": "object", + "title": "By Arm Sequence Kit Item Date Map", + "description": "Mapping of treatment arm IDs to sequence periods to kit item consumption. Structure: arm \u2192 sequenceId \u2192 item \u2192 KitItemDemandEntry. The sequence ID matches keys in dosingRegimenSequence. Displayed as 'Dosing' in the UI.", + "patternProperties": { + ".*": { + "type": "object", + "description": "Mapping of sequence period IDs to kit item demand for this arm.", + "patternProperties": { + ".*": { + "type": "object", + "description": "Mapping of item IDs to KitItemDemandEntry for this sequence period.", + "patternProperties": { + ".*": { + "$ref": "#/definitions/KitItemDemandEntry" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "ByKitDateMap": { + "type": "object", + "title": "By Kit Date Map", + "description": "Mapping of kit IDs to date-value maps for site-level kit demand (not per-arm).", + "patternProperties": { + ".*": { + "$ref": "#/definitions/PositiveDateMap" + } + }, + "additionalProperties": false + }, + "ClinicalDemandActuals": { + "type": "object", + "title": "Clinical Demand Actuals", + "description": "Actual enrollment data entered by users to reconcile with forecast.", + "properties": { + "enrollment": { + "type": "object", + "title": "Enrollment Actuals", + "description": "Actual enrollment by region and month.", + "patternProperties": { + ".*": { + "type": "object", + "patternProperties": { + ".*": { + "type": "object", + "properties": { + "total": { + "type": "number", + "title": "Total Enrolled", + "description": "Total subjects enrolled for this region and month.", + "minimum": 0 + }, + "byArm": { + "type": "object", + "title": "Enrollment by Arm", + "description": "Enrollment count per treatment arm.", + "patternProperties": { + ".*": { + "type": "number", + "minimum": 0 + } + }, + "additionalProperties": false + } + }, + "required": ["total", "byArm"], + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "sites": { + "type": "object", + "title": "Sites Actuals", + "description": "Actual site counts by region and month.", + "patternProperties": { + ".*": { + "type": "object", + "patternProperties": { + ".*": { + "type": "number", + "minimum": 0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "notes": { + "type": "object", + "title": "Actuals Notes", + "description": "Explanatory notes for enrollment actuals by region and month.", + "patternProperties": { + ".*": { + "type": "object", + "patternProperties": { + ".*": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "siteNotes": { + "type": "object", + "title": "Site Actuals Notes", + "description": "Explanatory notes for site activation actuals by region and month.", + "patternProperties": { + ".*": { + "type": "object", + "patternProperties": { + ".*": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "required": ["enrollment", "sites", "notes", "siteNotes"], + "additionalProperties": false + } + } +} diff --git a/src/test/abc-clinical-demand-forecast-2.0.0/abc-clinical-demand-forecast.json b/src/test/abc-clinical-demand-forecast-2.0.0/abc-clinical-demand-forecast.json new file mode 100644 index 00000000000..6fe4a6098e9 --- /dev/null +++ b/src/test/abc-clinical-demand-forecast-2.0.0/abc-clinical-demand-forecast.json @@ -0,0 +1,239 @@ +{ + "$schema": "https://json.schemastore.org/abc-clinical-demand-forecast-2.0.0.json", + "analytics": { + "items": [], + "layouts": [], + "tabs": [ + { + "id": "1780000000001", + "name": "Overview", + "ordering": 0 + } + ] + }, + "clinicalDemandCalculatedForecast": { + "byRegion": { + "region-1": { + "kitDemand": { + "arm-1780000000030": { + "period-1768534785069-6trwchxnu": { + "1780000001000": { + "demand": { + "2026-01-01": 1, + "2026-02-01": 0 + }, + "placeboOf": null + } + } + }, + "arm-1780000000040": { + "period-1768534785069-1np60aauq": { + "1780000001000": { + "demand": { + "2026-01-01": 1, + "2026-02-01": 0 + }, + "placeboOf": "regimen-1780000000010" + } + } + } + }, + "kitItemDemand": { + "arm-1780000000030": { + "period-1768534785069-6trwchxnu": { + "1780000000100": { + "demand": { + "2026-01-01": 10, + "2026-02-01": 0 + }, + "placeboOf": null + } + } + }, + "arm-1780000000040": { + "period-1768534785069-1np60aauq": { + "1780000000100": { + "demand": { + "2026-01-01": 10, + "2026-02-01": 0 + }, + "placeboOf": "regimen-1780000000010" + } + } + } + }, + "siteSeedingKitDemand": { + "1780000001000": { + "2026-01-01": 0, + "2026-02-01": 0 + } + }, + "sites": { + "2026-01-01": 1, + "2026-02-01": 0 + }, + "subjectsActive": { + "arm-1780000000030": { + "2026-01-01": 1, + "2026-02-01": 0 + }, + "arm-1780000000040": { + "2026-01-01": 1, + "2026-02-01": 0 + } + }, + "subjectsCompleted": { + "arm-1780000000030": { + "2026-01-01": 0, + "2026-02-01": 1 + }, + "arm-1780000000040": { + "2026-01-01": 0, + "2026-02-01": 1 + } + }, + "subjectsDroppedOut": { + "arm-1780000000030": { + "2026-01-01": 0, + "2026-02-01": 0 + }, + "arm-1780000000040": { + "2026-01-01": 0, + "2026-02-01": 0 + } + }, + "subjectsEnrolled": { + "arm-1780000000030": { + "2026-01-01": 1, + "2026-02-01": 0 + }, + "arm-1780000000040": { + "2026-01-01": 1, + "2026-02-01": 0 + } + }, + "subjectsScreened": { + "2026-01-01": 2, + "2026-02-01": 0 + }, + "subjectsScreenedOut": { + "2026-01-01": 0, + "2026-02-01": 0 + } + } + } + }, + "clinicalDemandForecastConfiguration": { + "actuals": { + "enrollment": {}, + "notes": {}, + "siteNotes": {}, + "sites": {} + }, + "dosingRegimens": { + "regimen-1780000000010": { + "description": "Active treatment with 10 mg tablets", + "dosingRegimenDefinition": { + "ageBasedConfig": null, + "dispensationIntervalUnit": "days", + "dispensationIntervals": [10], + "doseEscalationConfig": null, + "dosingFrequency": "once_daily_qd", + "fixedConfig": { + "kitItemQuantities": { + "1780000000100": 1 + } + }, + "isPlacebo": false, + "treatmentDuration": 10, + "treatmentDurationUnit": "days", + "weightBasedConfig": null + }, + "name": "Fixed 10 mg" + }, + "regimen-1780000000020": { + "description": "Placebo treatment", + "dosingRegimenDefinition": { + "isPlacebo": true, + "placeboOf": "regimen-1780000000010" + }, + "name": "Placebo" + } + }, + "enrollment": { + "attritionRate": 0, + "screenFailureRate": 0, + "targetCompleted": 2 + }, + "kitConfiguration": { + "1780000001000": { + "items": { + "1780000000101": { + "children": { + "1780000000100": { + "deliveryForm": "tablet", + "dosage": 10, + "name": "Tablet", + "nodeType": "item", + "ordering": 0, + "quantity": 10, + "units": "mg" + } + }, + "deliveryForm": "bottle", + "name": "Bottle", + "nodeType": "container", + "ordering": 0 + } + }, + "name": "Memorax Kit" + } + }, + "studyOverview": { + "protocolSummary": "Simplest blinded actuals test: 2 subjects, 2 arms at 50/50 split", + "studyName": "Clinical Demand Forecast Blinded Actuals Entry Simplest Test", + "studyPhase": "phase_i", + "therapeuticArea": "CNS/Neurology" + }, + "subjectFlow": { + "regions": { + "region-1": { + "activationStartDate": "2026-01-01", + "maxEnrollmentPerSite": null, + "nodeType": "region", + "numberOfSites": 1, + "regionName": "USA", + "screenRate": 2, + "siteActivationRate": 1, + "siteSeeding": null + } + } + }, + "treatmentArms": { + "arm-1780000000030": { + "allocationPercentage": 0.5, + "dosingRegimenSequence": { + "period-1768534785069-6trwchxnu": { + "dosingRegimenId": "regimen-1780000000010", + "ordering": 0, + "periodType": "treatment" + } + }, + "name": "Memorax" + }, + "arm-1780000000040": { + "allocationPercentage": 0.5, + "dosingRegimenSequence": { + "period-1768534785069-1np60aauq": { + "dosingRegimenId": "regimen-1780000000020", + "ordering": 0, + "periodType": "treatment" + } + }, + "name": "Placebo" + } + }, + "trialDesign": "parallel" + }, + "planNotes": "{\"blocks\":[{\"key\":\"a1\",\"text\":\"TEST PURPOSE: Validates blinded actuals entry with end date change.\",\"type\":\"header-three\",\"depth\":0,\"inlineStyleRanges\":[{\"offset\":0,\"length\":13,\"style\":\"BOLD\"}],\"entityRanges\":[],\"data\":{}},{\"key\":\"a2\",\"text\":\"\",\"type\":\"unstyled\",\"depth\":0,\"inlineStyleRanges\":[],\"entityRanges\":[],\"data\":{}},{\"key\":\"a3\",\"text\":\"CONFIGURATION:\",\"type\":\"unstyled\",\"depth\":0,\"inlineStyleRanges\":[{\"offset\":0,\"length\":14,\"style\":\"BOLD\"}],\"entityRanges\":[],\"data\":{}},{\"key\":\"a4\",\"text\":\"Two treatment arms: 50% Memorax (active), 50% Placebo\",\"type\":\"unordered-list-item\",\"depth\":0,\"inlineStyleRanges\":[],\"entityRanges\":[],\"data\":{}},{\"key\":\"a5\",\"text\":\"Target: 2 subjects, enrollment rate: 2/month\",\"type\":\"unordered-list-item\",\"depth\":0,\"inlineStyleRanges\":[],\"entityRanges\":[],\"data\":{}},{\"key\":\"a6\",\"text\":\"Initial forecast: Completes in January 2026\",\"type\":\"unordered-list-item\",\"depth\":0,\"inlineStyleRanges\":[],\"entityRanges\":[],\"data\":{}},{\"key\":\"a7\",\"text\":\"\",\"type\":\"unstyled\",\"depth\":0,\"inlineStyleRanges\":[],\"entityRanges\":[],\"data\":{}},{\"key\":\"a8\",\"text\":\"BLINDED ACTUALS TEST:\",\"type\":\"unstyled\",\"depth\":0,\"inlineStyleRanges\":[{\"offset\":0,\"length\":21,\"style\":\"BOLD\"}],\"entityRanges\":[],\"data\":{}},{\"key\":\"a9\",\"text\":\"Enter 1 subject actual for January (blinded entry)\",\"type\":\"unordered-list-item\",\"depth\":0,\"inlineStyleRanges\":[],\"entityRanges\":[],\"data\":{}},{\"key\":\"a10\",\"text\":\"Allocated to Memorax (first treatment arm in sorted order)\",\"type\":\"unordered-list-item\",\"depth\":0,\"inlineStyleRanges\":[],\"entityRanges\":[],\"data\":{}},{\"key\":\"a11\",\"text\":\"After re-forecast: End date extends to February 2026\",\"type\":\"unordered-list-item\",\"depth\":0,\"inlineStyleRanges\":[],\"entityRanges\":[],\"data\":{}}],\"entityMap\":{}}" +} diff --git a/src/test/abc-clinical-demand-forecast-3.0.0/abc-clinical-demand-forecast.json b/src/test/abc-clinical-demand-forecast-3.0.0/abc-clinical-demand-forecast.json new file mode 100644 index 00000000000..b6c43ae6bf3 --- /dev/null +++ b/src/test/abc-clinical-demand-forecast-3.0.0/abc-clinical-demand-forecast.json @@ -0,0 +1,249 @@ +{ + "$schema": "https://json.schemastore.org/abc-clinical-demand-forecast-3.0.0.json", + "analytics": { + "items": [], + "layouts": [], + "tabs": [ + { + "id": "1780000000001", + "name": "Overview", + "ordering": 0 + } + ] + }, + "clinicalDemandCalculatedForecast": { + "byRegion": { + "region-1": { + "kitDemand": { + "arm-1780000000030": { + "period-1768534785069-6trwchxnu": { + "1780000001000": { + "demand": { + "2026-01-01": 1, + "2026-02-01": 0 + }, + "placeboOf": null + } + } + }, + "arm-1780000000040": { + "period-1768534785069-1np60aauq": { + "1780000001000": { + "demand": { + "2026-01-01": 1, + "2026-02-01": 0 + }, + "placeboOf": "regimen-1780000000010" + } + } + } + }, + "kitItemDemand": { + "arm-1780000000030": { + "period-1768534785069-6trwchxnu": { + "1780000000100": { + "demand": { + "2026-01-01": 10, + "2026-02-01": 0 + }, + "placeboOf": null + } + } + }, + "arm-1780000000040": { + "period-1768534785069-1np60aauq": { + "1780000000100": { + "demand": { + "2026-01-01": 10, + "2026-02-01": 0 + }, + "placeboOf": "regimen-1780000000010" + } + } + } + }, + "siteSeedingKitDemand": { + "1780000001000": { + "2026-01-01": 0, + "2026-02-01": 0 + } + }, + "sites": { + "2026-01-01": 1, + "2026-02-01": 0 + }, + "subjectsActive": { + "arm-1780000000030": { + "2026-01-01": 1, + "2026-02-01": 0 + }, + "arm-1780000000040": { + "2026-01-01": 1, + "2026-02-01": 0 + } + }, + "subjectsCompleted": { + "arm-1780000000030": { + "2026-01-01": 0, + "2026-02-01": 1 + }, + "arm-1780000000040": { + "2026-01-01": 0, + "2026-02-01": 1 + } + }, + "subjectsDroppedOut": { + "arm-1780000000030": { + "2026-01-01": 0, + "2026-02-01": 0 + }, + "arm-1780000000040": { + "2026-01-01": 0, + "2026-02-01": 0 + } + }, + "subjectsEnrolled": { + "arm-1780000000030": { + "2026-01-01": 1, + "2026-02-01": 0 + }, + "arm-1780000000040": { + "2026-01-01": 1, + "2026-02-01": 0 + } + }, + "subjectsScreened": { + "2026-01-01": 2, + "2026-02-01": 0 + }, + "subjectsScreenedOut": { + "2026-01-01": 0, + "2026-02-01": 0 + } + } + } + }, + "clinicalDemandForecastConfiguration": { + "actuals": { + "enrollment": {}, + "notes": {}, + "siteNotes": {}, + "sites": {} + }, + "dosingRegimens": { + "regimen-1780000000010": { + "description": "Active treatment with 10 mg tablets", + "dosingRegimenDefinition": { + "ageBasedConfig": null, + "dispensationIntervalUnit": "days", + "dispensationIntervals": [10], + "doseEscalationConfig": null, + "dosingFrequency": { + "description": "once daily for the full treatment duration", + "phases": [ + { + "cycleDoses": [1], + "cycleUnit": "days", + "repeatFor": 0, + "repeatForUnit": "days" + } + ] + }, + "fixedConfig": { + "kitItemQuantities": { + "1780000000100": 1 + } + }, + "isPlacebo": false, + "treatmentDuration": 10, + "treatmentDurationUnit": "days", + "weightBasedConfig": null + }, + "name": "Fixed 10 mg" + }, + "regimen-1780000000020": { + "description": "Placebo treatment", + "dosingRegimenDefinition": { + "isPlacebo": true, + "placeboOf": "regimen-1780000000010" + }, + "name": "Placebo" + } + }, + "enrollment": { + "attritionRate": 0, + "screenFailureRate": 0, + "targetCompleted": 2 + }, + "kitConfiguration": { + "1780000001000": { + "items": { + "1780000000101": { + "children": { + "1780000000100": { + "deliveryForm": "tablet", + "dosage": 10, + "name": "Tablet", + "nodeType": "item", + "ordering": 0, + "quantity": 10, + "units": "mg" + } + }, + "deliveryForm": "bottle", + "name": "Bottle", + "nodeType": "container", + "ordering": 0 + } + }, + "name": "Memorax Kit" + } + }, + "studyOverview": { + "protocolSummary": "Simplest blinded actuals test: 2 subjects, 2 arms at 50/50 split", + "studyName": "Clinical Demand Forecast Blinded Actuals Entry Simplest Test", + "studyPhase": "phase_i", + "therapeuticArea": "CNS/Neurology" + }, + "subjectFlow": { + "regions": { + "region-1": { + "activationStartDate": "2026-01-01", + "maxEnrollmentPerSite": null, + "nodeType": "region", + "numberOfSites": 1, + "regionName": "USA", + "screenRate": 2, + "siteActivationRate": 1, + "siteSeeding": null + } + } + }, + "treatmentArms": { + "arm-1780000000030": { + "allocationPercentage": 0.5, + "dosingRegimenSequence": { + "period-1768534785069-6trwchxnu": { + "dosingRegimenId": "regimen-1780000000010", + "ordering": 0, + "periodType": "treatment" + } + }, + "name": "Memorax" + }, + "arm-1780000000040": { + "allocationPercentage": 0.5, + "dosingRegimenSequence": { + "period-1768534785069-1np60aauq": { + "dosingRegimenId": "regimen-1780000000020", + "ordering": 0, + "periodType": "treatment" + } + }, + "name": "Placebo" + } + }, + "trialDesign": "parallel" + }, + "planNotes": "{\"blocks\":[{\"key\":\"a1\",\"text\":\"TEST PURPOSE: Validates blinded actuals entry with end date change.\",\"type\":\"header-three\",\"depth\":0,\"inlineStyleRanges\":[{\"offset\":0,\"length\":13,\"style\":\"BOLD\"}],\"entityRanges\":[],\"data\":{}},{\"key\":\"a2\",\"text\":\"\",\"type\":\"unstyled\",\"depth\":0,\"inlineStyleRanges\":[],\"entityRanges\":[],\"data\":{}},{\"key\":\"a3\",\"text\":\"CONFIGURATION:\",\"type\":\"unstyled\",\"depth\":0,\"inlineStyleRanges\":[{\"offset\":0,\"length\":14,\"style\":\"BOLD\"}],\"entityRanges\":[],\"data\":{}},{\"key\":\"a4\",\"text\":\"Two treatment arms: 50% Memorax (active), 50% Placebo\",\"type\":\"unordered-list-item\",\"depth\":0,\"inlineStyleRanges\":[],\"entityRanges\":[],\"data\":{}},{\"key\":\"a5\",\"text\":\"Target: 2 subjects, enrollment rate: 2/month\",\"type\":\"unordered-list-item\",\"depth\":0,\"inlineStyleRanges\":[],\"entityRanges\":[],\"data\":{}},{\"key\":\"a6\",\"text\":\"Initial forecast: Completes in January 2026\",\"type\":\"unordered-list-item\",\"depth\":0,\"inlineStyleRanges\":[],\"entityRanges\":[],\"data\":{}},{\"key\":\"a7\",\"text\":\"\",\"type\":\"unstyled\",\"depth\":0,\"inlineStyleRanges\":[],\"entityRanges\":[],\"data\":{}},{\"key\":\"a8\",\"text\":\"BLINDED ACTUALS TEST:\",\"type\":\"unstyled\",\"depth\":0,\"inlineStyleRanges\":[{\"offset\":0,\"length\":21,\"style\":\"BOLD\"}],\"entityRanges\":[],\"data\":{}},{\"key\":\"a9\",\"text\":\"Enter 1 subject actual for January (blinded entry)\",\"type\":\"unordered-list-item\",\"depth\":0,\"inlineStyleRanges\":[],\"entityRanges\":[],\"data\":{}},{\"key\":\"a10\",\"text\":\"Allocated to Memorax (first treatment arm in sorted order)\",\"type\":\"unordered-list-item\",\"depth\":0,\"inlineStyleRanges\":[],\"entityRanges\":[],\"data\":{}},{\"key\":\"a11\",\"text\":\"After re-forecast: End date extends to February 2026\",\"type\":\"unordered-list-item\",\"depth\":0,\"inlineStyleRanges\":[],\"entityRanges\":[],\"data\":{}}],\"entityMap\":{}}" +} From 07d763be04906a35ed3307dd37269014f04ce296 Mon Sep 17 00:00:00 2001 From: James D Bloom <733179+jamesdbloom@users.noreply.github.com> Date: Mon, 11 May 2026 17:47:43 +0100 Subject: [PATCH 5/8] Add MockServer expectations schema (external) (#5672) --- src/api/json/catalog.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/api/json/catalog.json b/src/api/json/catalog.json index 411b7933080..cb56e90c24d 100644 --- a/src/api/json/catalog.json +++ b/src/api/json/catalog.json @@ -4451,6 +4451,16 @@ ], "url": "https://raw.githubusercontent.com/getmockd/mockd/main/schema/mockd.schema.json" }, + { + "name": "MockServer Expectations", + "description": "MockServer expectations configuration for defining request matchers and response actions. See https://www.mock-server.com", + "fileMatch": [ + "mockserverInitialization.json", + "*mockserver*Initialization.json", + "mockserver*.json" + ], + "url": "https://www.mock-server.com/schema/expectations.json" + }, { "name": "monospace.yml", "description": "MonoSpace configuration file", From 8c3d9e72b3cb50f507d4582c90260b2cf71db3db Mon Sep 17 00:00:00 2001 From: falsify Date: Mon, 11 May 2026 19:48:07 +0300 Subject: [PATCH 6/8] Add PRML manifest schema (#5673) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add PRML manifest schema PRML — Pre-Registered ML Manifest — is an open CC BY 4.0 specification for committing machine-learning evaluation claims to a SHA-256 hash before the experiment runs. The schema validates the eight required PRML v0.1 fields plus optional v0.2 RFC additions (streaming variant, runner attestation, revocation). File patterns target *.prml.yaml, manifest.prml.yaml, and the prml-manifest.{yml,yaml} convention. Both schema versions are JSON Schema 2020-12 conformant and validated against 32 locked conformance vectors (12 v0.1 + 8 v0.2 + 12 v0.1 inputs against v0.2 schema for backwards-compat). Detail at https://spec.falsify.dev/schema/. Spec author: Studio 11 Turkey Ltd. Şti. (hello@studio-11.co). v0.1 stable; v0.2 RFC freeze 2026-05-22 23:59 UTC. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: Studio 11 Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- src/api/json/catalog.json | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/api/json/catalog.json b/src/api/json/catalog.json index cb56e90c24d..c4a69f0a632 100644 --- a/src/api/json/catalog.json +++ b/src/api/json/catalog.json @@ -5269,6 +5269,22 @@ "fileMatch": [".pre-commit-hooks.yml", ".pre-commit-hooks.yaml"], "url": "https://www.schemastore.org/pre-commit-hooks.json" }, + { + "name": "PRML manifest", + "description": "Pre-Registered ML Manifest — open spec for committing ML evaluation claims to a SHA-256 hash before the run (CC BY 4.0). https://spec.falsify.dev/v0.1", + "fileMatch": [ + "*.prml.yaml", + "*.prml.yml", + "manifest.prml.yaml", + "prml-manifest.yaml", + "prml-manifest.yml" + ], + "url": "https://spec.falsify.dev/schema/prml-v0.1.schema.json", + "versions": { + "v0.1": "https://spec.falsify.dev/schema/prml-v0.1.schema.json", + "v0.2-rfc": "https://spec.falsify.dev/schema/prml-v0.2.schema.json" + } + }, { "name": ".phrase.yml", "description": "Phrase configuration file", From 07b12eb5fd81733f9f91c0f56af1eb277d7a82ae Mon Sep 17 00:00:00 2001 From: Adil Hanney Date: Mon, 11 May 2026 17:48:53 +0100 Subject: [PATCH 7/8] fix: add ".rustfmt.toml" to file matches (#5675) --- src/api/json/catalog.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/json/catalog.json b/src/api/json/catalog.json index c4a69f0a632..d3de9285ba8 100644 --- a/src/api/json/catalog.json +++ b/src/api/json/catalog.json @@ -5889,8 +5889,8 @@ }, { "name": "rustfmt", - "description": "rustfmt, a tool to format Rust code", - "fileMatch": ["rustfmt.toml"], + "description": "rustfmt, a tool to format Rust code. Documentation: https://rust-lang.github.io/rustfmt", + "fileMatch": ["rustfmt.toml", ".rustfmt.toml"], "url": "https://www.schemastore.org/rustfmt.json" }, { From a93943861dfde560581e8f11c88fd7828afa8320 Mon Sep 17 00:00:00 2001 From: Uladzimir Khadakouski Date: Mon, 11 May 2026 18:49:13 +0200 Subject: [PATCH 8/8] Add memnex schema (#5676) * Add memnex schema memnex is an open specification for portable meeting outputs (transcripts, summaries, action items, decisions) from local-first processing tools. Spec: https://github.com/UladzKha/memnex Reference implementation: https://www.npmjs.com/package/memnex-spec * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- src/api/json/catalog.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/api/json/catalog.json b/src/api/json/catalog.json index d3de9285ba8..ea070c330b3 100644 --- a/src/api/json/catalog.json +++ b/src/api/json/catalog.json @@ -9823,6 +9823,12 @@ "description": "Configuration file for Acton projects", "fileMatch": ["Acton.toml"], "url": "https://raw.githubusercontent.com/ton-blockchain/acton/master/crates/acton-config/schemas/acton.schema.json" + }, + { + "name": "memnex", + "description": "Open specification for portable meeting outputs — transcripts, summaries, action items, and decisions. https://github.com/UladzKha/memnex", + "fileMatch": ["*.memnex.json", "meeting-output.json"], + "url": "https://raw.githubusercontent.com/UladzKha/memnex/main/schema/v0.1/meeting-output.schema.json" } ] }