web: Update nullable number handling

web
Alinson S. Xavier 3 months ago
parent fff70cce67
commit 53489c1638

@ -13,7 +13,6 @@ import "tabulator-tables/dist/css/tabulator.min.css";
import "../Common/Forms/Tables.css";
import { useState } from "react";
import Footer from "./Footer";
import { validate } from "../../core/Data/validate";
import { offerDownload } from "../Common/io";
import { preprocess } from "../../core/Operations/preprocessing";
import Toast from "../Common/Forms/Toast";
@ -68,19 +67,14 @@ const CaseBuilder = () => {
setAndSaveScenario(newScenario);
};
const onLoad = (scenario: UnitCommitmentScenario) => {
const preprocessed = preprocess(
scenario,
) as unknown as UnitCommitmentScenario;
// Validate and assign default values
if (!validate(preprocessed)) {
setToastMessage("Error loading JSON file");
console.error(validate.errors);
const onLoad = (data: any) => {
const json = JSON.parse(data);
const [scenario, err] = preprocess(json);
if (err) {
setToastMessage(err.message);
return;
}
setAndSaveScenario(preprocessed);
setAndSaveScenario(scenario!);
setToastMessage("Data loaded successfully");
};

@ -22,8 +22,7 @@ function Header(props: HeaderProps) {
function onLoad() {
fileElem.current!.showFilePicker((data: any) => {
const scenario = JSON.parse(data) as UnitCommitmentScenario;
props.onLoad(scenario);
props.onLoad(data);
});
}

@ -51,13 +51,13 @@ export const ThermalUnitsColumnSpec: ColumnSpec[] = [
title: "Production cost curve (MW)",
type: "number[N]",
length: 10,
width: 75,
width: 80,
},
{
title: "Production cost curve ($)",
type: "number[N]",
length: 10,
width: 75,
width: 80,
},
{
title: "Startup costs ($)",
@ -83,22 +83,22 @@ export const ThermalUnitsColumnSpec: ColumnSpec[] = [
},
{
title: "Ramp up limit (MW)",
type: "number",
type: "number?",
width: 100,
},
{
title: "Ramp down limit (MW)",
type: "number",
type: "number?",
width: 100,
},
{
title: "Startup limit (MW)",
type: "number",
type: "number?",
width: 80,
},
{
title: "Shutdown limit (MW)",
type: "number",
type: "number?",
width: 100,
},
{

@ -55,12 +55,12 @@ export const TransmissionLinesColumnSpec: ColumnSpec[] = [
},
{
title: "Normal flow limit (MW)",
type: "number",
type: "number?",
width: 60,
},
{
title: "Emergency flow limit (MW)",
type: "number",
type: "number?",
width: 60,
},
{

@ -17,7 +17,14 @@ import { UnitCommitmentScenario } from "../../../core/Data/types";
export interface ColumnSpec {
title: string;
type: "string" | "number" | "number[N]" | "number[T]" | "busRef" | "boolean";
type:
| "string"
| "number"
| "number?"
| "number[N]"
| "number[T]"
| "busRef"
| "boolean";
length?: number;
width: number;
}
@ -53,6 +60,7 @@ export const generateTableColumns = (
});
break;
case "number":
case "number?":
columns.push({
...columnsCommonAttrs,
title: spec.title,
@ -118,6 +126,7 @@ export const generateTableData = (
switch (spec.type) {
case "string":
case "number":
case "number?":
case "boolean":
case "busRef":
entry[spec.title] = entryData[spec.title];
@ -285,7 +294,7 @@ export const parseCsv = (
export const floatFormatter = (cell: CellComponent) => {
const v = cell.getValue();
if (v === "") {
if (v === "" || v === null) {
return "—";
} else {
return parseFloat(cell.getValue()).toLocaleString("en-US", {

@ -120,8 +120,8 @@ export const TEST_SCENARIO: UnitCommitmentScenario = {
"Source bus": "b1",
"Target bus": "b2",
"Susceptance (S)": 29.49686,
"Normal flow limit (MW)": 15000.0,
"Emergency flow limit (MW)": 20000.0,
"Normal flow limit (MW)": null,
"Emergency flow limit (MW)": null,
"Flow limit penalty ($/MW)": 5000.0,
},
},

@ -97,15 +97,18 @@ export const schema = {
},
"Susceptance (S)": {
type: "number",
minimum: 0,
},
"Normal flow limit (MW)": {
type: "number",
minimum: 0,
nullable: true,
default: null,
},
"Emergency flow limit (MW)": {
type: "number",
minimum: 0,
nullable: true,
default: null,
},
"Flow limit penalty ($/MW)": {
type: "number",
@ -254,18 +257,26 @@ export const schema = {
"Ramp up limit (MW)": {
type: "number",
minimum: 0,
nullable: true,
default: null,
},
"Ramp down limit (MW)": {
type: "number",
minimum: 0,
nullable: true,
default: null,
},
"Startup limit (MW)": {
type: "number",
minimum: 0,
nullable: true,
default: null,
},
"Shutdown limit (MW)": {
type: "number",
minimum: 0,
nullable: true,
default: null,
},
"Initial status (h)": {
type: "integer",

@ -25,10 +25,10 @@ export interface ThermalUnit {
"Production cost curve ($)": number[];
"Startup costs ($)": number[];
"Startup delays (h)": number[];
"Ramp up limit (MW)": number | "";
"Ramp down limit (MW)": number | "";
"Startup limit (MW)": number | "";
"Shutdown limit (MW)": number | "";
"Ramp up limit (MW)": number | null;
"Ramp down limit (MW)": number | null;
"Startup limit (MW)": number | null;
"Shutdown limit (MW)": number | null;
"Minimum downtime (h)": number;
"Minimum uptime (h)": number;
"Initial status (h)": number;
@ -40,8 +40,8 @@ export interface TransmissionLine {
"Source bus": string;
"Target bus": string;
"Susceptance (S)": number;
"Normal flow limit (MW)": number;
"Emergency flow limit (MW)": number;
"Normal flow limit (MW)": number | null;
"Emergency flow limit (MW)": number | null;
"Flow limit penalty ($/MW)": number;
}

@ -43,6 +43,9 @@ export const generateUniqueName = (container: any, prefix: string): string => {
export const parseNumber = (
valueStr: string,
): [number, ValidationError | null] => {
if (valueStr === "") {
return [0, { message: "Field must not be blank" }];
}
const valueFloat = parseFloat(valueStr);
if (isNaN(valueFloat)) {
return [0, { message: `"${valueStr}" is not a valid number` }];
@ -51,6 +54,13 @@ export const parseNumber = (
}
};
export const parseNullableNumber = (
valueStr: string,
): [number | null, ValidationError | null] => {
if (valueStr === "") return [null, null];
return parseNumber(valueStr);
};
export const parseBool = (
valueStr: string,
): [boolean, ValidationError | null] => {
@ -93,9 +103,12 @@ export const changeNumberData = (
field: string,
newValueStr: string,
container: { [key: string]: any },
nullable: boolean = false,
): [{ [key: string]: any }, ValidationError | null] => {
// Parse value
const [newValueFloat, err] = parseNumber(newValueStr);
const [newValueFloat, err] = nullable
? parseNullableNumber(newValueStr)
: parseNumber(newValueStr);
if (err) return [container, err];
// Build the new object
@ -211,6 +224,8 @@ export const changeData = (
return changeBusRefData(fieldName, newValueStr, container, scenario);
case "number":
return changeNumberData(fieldName, newValueStr, container);
case "number?":
return changeNumberData(fieldName, newValueStr, container, true);
case "number[T]":
return changeNumberVecTData(
fieldName,

@ -61,10 +61,10 @@ export const createThermalUnit = (
"Production cost curve ($)": [0, 10],
"Startup costs ($)": [0],
"Startup delays (h)": [1],
"Ramp up limit (MW)": "",
"Ramp down limit (MW)": "",
"Startup limit (MW)": "",
"Shutdown limit (MW)": "",
"Ramp up limit (MW)": null,
"Ramp down limit (MW)": null,
"Startup limit (MW)": null,
"Shutdown limit (MW)": null,
"Minimum downtime (h)": 1,
"Minimum uptime (h)": 1,
"Initial status (h)": -24,

@ -1,25 +1,35 @@
// @ts-nocheck
/*
* UnitCommitment.jl: Optimization Package for Security-Constrained Unit Commitment
* Copyright (C) 2020-2025, UChicago Argonne, LLC. All rights reserved.
* Released under the modified BSD license. See COPYING.md for more details.
*/
import { validate } from "../Data/validate";
import { validate, ValidationError } from "../Data/validate";
import { UnitCommitmentScenario } from "../Data/types";
import { migrate } from "../Data/migrate";
export const preprocess = (data) => {
export const preprocess = (
data: any,
): [UnitCommitmentScenario | null, ValidationError | null] => {
// Make a copy of the original data
let result = JSON.parse(JSON.stringify(data));
// Run migration
migrate(result);
// Run JSON validation and assign default values
if (!validate(result)) {
console.error(validate.errors);
throw Error("Invalid JSON");
return [
null,
{ message: "Invalid JSON file. See console for more details." },
];
}
// Expand scalars into arrays
// @ts-ignore
const timeHorizon = result["Parameters"]["Time horizon (h)"];
// @ts-ignore
const timeStep = result["Parameters"]["Time step (min)"];
const T = (timeHorizon * 60) / timeStep;
for (const busName in result["Buses"]) {
@ -30,5 +40,7 @@ export const preprocess = (data) => {
busData["Load (MW)"] = Array(T).fill(busLoad);
}
}
return result;
const scenario = result as unknown as UnitCommitmentScenario;
return [scenario, null];
};

Loading…
Cancel
Save