mirror of
https://github.com/ANL-CEEESA/UnitCommitment.jl.git
synced 2025-12-06 08:18:51 -06:00
web: ThermalUnits: CSV upload
This commit is contained in:
48
web/src/components/CaseBuilder/Buses.test.ts
Normal file
48
web/src/components/CaseBuilder/Buses.test.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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 assert from "node:assert";
|
||||
import { BusesColumnSpec, generateBusesData } from "./Buses";
|
||||
import { generateCsv, parseCsv } from "../Common/Forms/DataTable";
|
||||
import { TEST_DATA_1 } from "../../core/fixtures.test";
|
||||
|
||||
test("generate CSV", () => {
|
||||
const [data, columns] = generateBusesData(TEST_DATA_1);
|
||||
const actualCsv = generateCsv(data, columns);
|
||||
const expectedCsv =
|
||||
"Name,Load (MW) 00:00,Load (MW) 01:00,Load (MW) 02:00,Load (MW) 03:00,Load (MW) 04:00\n" +
|
||||
"b1,35.79534,34.38835,33.45083,32.89729,33.25044\n" +
|
||||
"b2,14.03739,13.48563,13.11797,12.9009,13.03939\n" +
|
||||
"b3,27.3729,26.29698,25.58005,25.15675,25.4268";
|
||||
assert.strictEqual(actualCsv, expectedCsv);
|
||||
});
|
||||
|
||||
test("parse CSV", () => {
|
||||
const csvContents =
|
||||
"Name,Load (MW) 00:00,Load (MW) 01:00,Load (MW) 02:00,Load (MW) 03:00,Load (MW) 04:00\n" +
|
||||
"b1,0,1,2,3,4\n" +
|
||||
"b3,27.3729,26.29698,25.58005,25.15675,25.4268";
|
||||
const [newBuses, err] = parseCsv(csvContents, BusesColumnSpec, TEST_DATA_1);
|
||||
assert(err === null);
|
||||
assert.deepEqual(newBuses, {
|
||||
b1: {
|
||||
"Load (MW)": [0, 1, 2, 3, 4],
|
||||
},
|
||||
b3: {
|
||||
"Load (MW)": [27.3729, 26.29698, 25.58005, 25.15675, 25.4268],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("parse CSV with duplicated names", () => {
|
||||
const csvContents =
|
||||
"Name,Load (MW) 00:00,Load (MW) 01:00,Load (MW) 02:00,Load (MW) 03:00,Load (MW) 04:00\n" +
|
||||
"b1,0,0,0,0,0\n" +
|
||||
"b1,0,0,0,0,0";
|
||||
const [, err] = parseCsv(csvContents, BusesColumnSpec, TEST_DATA_1);
|
||||
assert(err !== null);
|
||||
assert.equal(err.message, `Name "b1" is duplicated (row 2)`);
|
||||
});
|
||||
209
web/src/components/CaseBuilder/ThermalUnits.test.ts
Normal file
209
web/src/components/CaseBuilder/ThermalUnits.test.ts
Normal file
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
* 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 {
|
||||
floatFormatter,
|
||||
generateCsv,
|
||||
generateTableColumns,
|
||||
generateTableData,
|
||||
} from "../Common/Forms/DataTable";
|
||||
import { TEST_DATA_1 } from "../../core/fixtures.test";
|
||||
import {
|
||||
generateThermalUnitsData,
|
||||
parseThermalUnitsCsv,
|
||||
ThermalUnitsColumnSpec,
|
||||
} from "./ThermalUnits";
|
||||
import assert from "node:assert";
|
||||
import {
|
||||
getProfiledGenerators,
|
||||
getThermalGenerators,
|
||||
} from "../../core/fixtures";
|
||||
|
||||
test("generateTableColumns", () => {
|
||||
const columns = generateTableColumns(TEST_DATA_1, ThermalUnitsColumnSpec);
|
||||
assert.equal(columns[2]!["columns"]!.length, 10);
|
||||
assert.deepEqual(columns[2]!["columns"]![0], {
|
||||
editor: "input",
|
||||
editorParams: {
|
||||
selectContents: true,
|
||||
},
|
||||
field: "Production cost curve (MW) 1",
|
||||
formatter: floatFormatter,
|
||||
headerHozAlign: "left",
|
||||
headerSort: false,
|
||||
headerWordWrap: true,
|
||||
hozAlign: "left",
|
||||
minWidth: 60,
|
||||
resizable: false,
|
||||
title: "1",
|
||||
});
|
||||
});
|
||||
|
||||
test("generateTableData", () => {
|
||||
const data = generateTableData(
|
||||
getThermalGenerators(TEST_DATA_1),
|
||||
ThermalUnitsColumnSpec,
|
||||
TEST_DATA_1,
|
||||
);
|
||||
assert.deepEqual(data[0], {
|
||||
Name: "g1",
|
||||
Bus: "b1",
|
||||
"Initial power (MW)": 115,
|
||||
"Initial status (h)": 12,
|
||||
"Minimum downtime (h)": 4,
|
||||
"Minimum uptime (h)": 4,
|
||||
"Ramp down limit (MW)": 232.68,
|
||||
"Ramp up limit (MW)": 232.68,
|
||||
"Shutdown limit (MW)": 232.68,
|
||||
"Startup limit (MW)": 232.68,
|
||||
"Production cost curve ($) 1": 1400,
|
||||
"Production cost curve ($) 2": 1600,
|
||||
"Production cost curve ($) 3": 2200,
|
||||
"Production cost curve ($) 4": 2400,
|
||||
"Production cost curve ($) 5": "",
|
||||
"Production cost curve ($) 6": "",
|
||||
"Production cost curve ($) 7": "",
|
||||
"Production cost curve ($) 8": "",
|
||||
"Production cost curve ($) 9": "",
|
||||
"Production cost curve ($) 10": "",
|
||||
"Production cost curve (MW) 1": 100,
|
||||
"Production cost curve (MW) 2": 110,
|
||||
"Production cost curve (MW) 3": 130,
|
||||
"Production cost curve (MW) 4": 135,
|
||||
"Production cost curve (MW) 5": "",
|
||||
"Production cost curve (MW) 6": "",
|
||||
"Production cost curve (MW) 7": "",
|
||||
"Production cost curve (MW) 8": "",
|
||||
"Production cost curve (MW) 9": "",
|
||||
"Production cost curve (MW) 10": "",
|
||||
"Startup costs ($) 1": 300,
|
||||
"Startup costs ($) 2": 400,
|
||||
"Startup costs ($) 3": "",
|
||||
"Startup costs ($) 4": "",
|
||||
"Startup costs ($) 5": "",
|
||||
"Startup delays (h) 1": 1,
|
||||
"Startup delays (h) 2": 4,
|
||||
"Startup delays (h) 3": "",
|
||||
"Startup delays (h) 4": "",
|
||||
"Startup delays (h) 5": "",
|
||||
"Must run?": false,
|
||||
});
|
||||
});
|
||||
|
||||
const expectedCsvContents =
|
||||
"Name,Bus," +
|
||||
"Production cost curve (MW) 1," +
|
||||
"Production cost curve (MW) 2," +
|
||||
"Production cost curve (MW) 3," +
|
||||
"Production cost curve (MW) 4," +
|
||||
"Production cost curve (MW) 5," +
|
||||
"Production cost curve (MW) 6," +
|
||||
"Production cost curve (MW) 7," +
|
||||
"Production cost curve (MW) 8," +
|
||||
"Production cost curve (MW) 9," +
|
||||
"Production cost curve (MW) 10," +
|
||||
"Production cost curve ($) 1," +
|
||||
"Production cost curve ($) 2," +
|
||||
"Production cost curve ($) 3," +
|
||||
"Production cost curve ($) 4," +
|
||||
"Production cost curve ($) 5," +
|
||||
"Production cost curve ($) 6," +
|
||||
"Production cost curve ($) 7," +
|
||||
"Production cost curve ($) 8," +
|
||||
"Production cost curve ($) 9," +
|
||||
"Production cost curve ($) 10," +
|
||||
"Startup costs ($) 1," +
|
||||
"Startup costs ($) 2," +
|
||||
"Startup costs ($) 3," +
|
||||
"Startup costs ($) 4," +
|
||||
"Startup costs ($) 5," +
|
||||
"Startup delays (h) 1," +
|
||||
"Startup delays (h) 2," +
|
||||
"Startup delays (h) 3," +
|
||||
"Startup delays (h) 4," +
|
||||
"Startup delays (h) 5," +
|
||||
"Minimum uptime (h),Minimum downtime (h),Ramp up limit (MW)," +
|
||||
"Ramp down limit (MW),Startup limit (MW),Shutdown limit (MW)," +
|
||||
"Initial status (h),Initial power (MW),Must run?\n" +
|
||||
"g1,b1,100,110,130,135,,,,,,,1400,1600,2200,2400,,,,,,,300,400,,,,1,4,,,,4,4,232.68,232.68,232.68,232.68,12,115,false";
|
||||
|
||||
const invalidCsv =
|
||||
"Name,Bus," +
|
||||
"Production cost curve (MW) 1," +
|
||||
"Production cost curve (MW) 2," +
|
||||
"Production cost curve (MW) 3," +
|
||||
"Production cost curve (MW) 4," +
|
||||
"Production cost curve (MW) 5," +
|
||||
"Production cost curve (MW) 6," +
|
||||
"Production cost curve (MW) 7," +
|
||||
"Production cost curve (MW) 8," +
|
||||
"Production cost curve (MW) 9," +
|
||||
"Production cost curve (MW) 10," +
|
||||
"Production cost curve ($) 1," +
|
||||
"Production cost curve ($) 2," +
|
||||
"Production cost curve ($) 3," +
|
||||
"Production cost curve ($) 4," +
|
||||
"Production cost curve ($) 5," +
|
||||
"Production cost curve ($) 6," +
|
||||
"Production cost curve ($) 7," +
|
||||
"Production cost curve ($) 8," +
|
||||
"Production cost curve ($) 9," +
|
||||
"Production cost curve ($) 10," +
|
||||
"Startup costs ($) 1," +
|
||||
"Startup costs ($) 2," +
|
||||
"Startup costs ($) 3," +
|
||||
"Startup costs ($) 4," +
|
||||
"Startup costs ($) 5," +
|
||||
"Startup delays (h) 1," +
|
||||
"Startup delays (h) 2," +
|
||||
"Startup delays (h) 3," +
|
||||
"Startup delays (h) 4," +
|
||||
"Startup delays (h) 5," +
|
||||
"Minimum uptime (h),Minimum downtime (h),Ramp up limit (MW)," +
|
||||
"Ramp down limit (MW),Startup limit (MW),Shutdown limit (MW)," +
|
||||
"Initial status (h),Initial power (MW),Must run?\n" +
|
||||
"g1,b1,100,110,130,x,,,,,,,1400,1600,2200,2400,,,,,,,300,400,,,,1,4,,,,4,4,232.68,232.68,232.68,232.68,12,115,false";
|
||||
|
||||
test("generateCSV", () => {
|
||||
const [data, columns] = generateThermalUnitsData(TEST_DATA_1);
|
||||
const actualCsvContents = generateCsv(data, columns);
|
||||
assert.equal(actualCsvContents, expectedCsvContents);
|
||||
});
|
||||
|
||||
test("parseCSV", () => {
|
||||
const [scenario, err] = parseThermalUnitsCsv(
|
||||
expectedCsvContents,
|
||||
TEST_DATA_1,
|
||||
);
|
||||
assert(!err);
|
||||
const thermalGens = getThermalGenerators(scenario);
|
||||
const profGens = getProfiledGenerators(scenario);
|
||||
assert.equal(Object.keys(thermalGens).length, 1);
|
||||
assert.equal(Object.keys(profGens).length, 2);
|
||||
assert.deepEqual(thermalGens["g1"], {
|
||||
Bus: "b1",
|
||||
Type: "Thermal",
|
||||
"Production cost curve (MW)": [100.0, 110.0, 130.0, 135.0],
|
||||
"Production cost curve ($)": [1400.0, 1600.0, 2200.0, 2400.0],
|
||||
"Startup costs ($)": [300.0, 400.0],
|
||||
"Startup delays (h)": [1, 4],
|
||||
"Ramp up limit (MW)": 232.68,
|
||||
"Ramp down limit (MW)": 232.68,
|
||||
"Startup limit (MW)": 232.68,
|
||||
"Shutdown limit (MW)": 232.68,
|
||||
"Minimum downtime (h)": 4,
|
||||
"Minimum uptime (h)": 4,
|
||||
"Initial status (h)": 12,
|
||||
"Initial power (MW)": 115,
|
||||
"Must run?": false,
|
||||
});
|
||||
});
|
||||
|
||||
test("parseCSV with invalid number[T]", () => {
|
||||
const [, err] = parseThermalUnitsCsv(invalidCsv, TEST_DATA_1);
|
||||
assert(err);
|
||||
assert.equal(err.message, '"x" is not a valid number (row 1)');
|
||||
});
|
||||
@@ -9,6 +9,7 @@ import DataTable, {
|
||||
generateCsv,
|
||||
generateTableColumns,
|
||||
generateTableData,
|
||||
parseCsv,
|
||||
} from "../Common/Forms/DataTable";
|
||||
import { CaseBuilderSectionProps } from "./CaseBuilder";
|
||||
import { useRef } from "react";
|
||||
@@ -22,6 +23,7 @@ import {
|
||||
faUpload,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import {
|
||||
getProfiledGenerators,
|
||||
getThermalGenerators,
|
||||
UnitCommitmentScenario,
|
||||
} from "../../core/fixtures";
|
||||
@@ -115,7 +117,7 @@ export const ThermalUnitsColumnSpec: ColumnSpec[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const generateThermalUnitsData = (
|
||||
export const generateThermalUnitsData = (
|
||||
scenario: UnitCommitmentScenario,
|
||||
): [any[], ColumnDefinition[]] => {
|
||||
const columns = generateTableColumns(scenario, ThermalUnitsColumnSpec);
|
||||
@@ -127,6 +129,31 @@ const generateThermalUnitsData = (
|
||||
return [data, columns];
|
||||
};
|
||||
|
||||
export const parseThermalUnitsCsv = (
|
||||
csvContents: string,
|
||||
scenario: UnitCommitmentScenario,
|
||||
): [UnitCommitmentScenario, ValidationError | null] => {
|
||||
const [thermalGens, err] = parseCsv(
|
||||
csvContents,
|
||||
ThermalUnitsColumnSpec,
|
||||
scenario,
|
||||
);
|
||||
if (err) return [scenario, err];
|
||||
|
||||
// Process imported generators
|
||||
for (const gen in thermalGens) {
|
||||
thermalGens[gen]["Type"] = "Thermal";
|
||||
}
|
||||
|
||||
// Merge with existing data
|
||||
const profGens = getProfiledGenerators(scenario);
|
||||
const newScenario = {
|
||||
...scenario,
|
||||
Generators: { ...thermalGens, ...profGens },
|
||||
};
|
||||
return [newScenario, null];
|
||||
};
|
||||
|
||||
const ThermalUnitsComponent = (props: CaseBuilderSectionProps) => {
|
||||
const fileUploadElem = useRef<FileUploadElement>(null);
|
||||
|
||||
@@ -137,26 +164,14 @@ const ThermalUnitsComponent = (props: CaseBuilderSectionProps) => {
|
||||
};
|
||||
|
||||
const onUpload = () => {
|
||||
// fileUploadElem.current!.showFilePicker((csvContents: any) => {
|
||||
// const [newGenerators, err] = parseCsv(
|
||||
// csvContents,
|
||||
// ThermalUnitsColumnSpec,
|
||||
// props.scenario,
|
||||
// );
|
||||
// if (err) {
|
||||
// props.onError(err.message);
|
||||
// return;
|
||||
// }
|
||||
// for (const gen in newGenerators) {
|
||||
// newGenerators[gen]["Type"] = "Thermal";
|
||||
// }
|
||||
//
|
||||
// const newScenario = {
|
||||
// ...props.scenario,
|
||||
// Generators: newGenerators,
|
||||
// };
|
||||
// props.onDataChanged(newScenario);
|
||||
// });
|
||||
fileUploadElem.current!.showFilePicker((csv: any) => {
|
||||
const [newScenario, err] = parseThermalUnitsCsv(csv, props.scenario);
|
||||
if (err) {
|
||||
props.onError(err.message);
|
||||
return;
|
||||
}
|
||||
props.onDataChanged(newScenario);
|
||||
});
|
||||
};
|
||||
|
||||
const onAdd = () => {
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
/*
|
||||
* 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 assert from "node:assert";
|
||||
import { BusesColumnSpec, generateBusesData } from "../../CaseBuilder/Buses";
|
||||
import {
|
||||
floatFormatter,
|
||||
generateCsv,
|
||||
generateTableColumns,
|
||||
generateTableData,
|
||||
parseCsv,
|
||||
} from "./DataTable";
|
||||
import { TEST_DATA_1 } from "../../../core/fixtures.test";
|
||||
import { ThermalUnitsColumnSpec } from "../../CaseBuilder/ThermalUnits";
|
||||
import { getThermalGenerators } from "../../../core/fixtures";
|
||||
|
||||
test("generateTableColumns (ThermalUnits)", () => {
|
||||
const columns = generateTableColumns(TEST_DATA_1, ThermalUnitsColumnSpec);
|
||||
assert.equal(columns[2]!["columns"]!.length, 10);
|
||||
assert.deepEqual(columns[2]!["columns"]![0], {
|
||||
editor: "input",
|
||||
editorParams: {
|
||||
selectContents: true,
|
||||
},
|
||||
field: "Production cost curve (MW) 1",
|
||||
formatter: floatFormatter,
|
||||
headerHozAlign: "left",
|
||||
headerSort: false,
|
||||
headerWordWrap: true,
|
||||
hozAlign: "left",
|
||||
minWidth: 60,
|
||||
resizable: false,
|
||||
title: "1",
|
||||
});
|
||||
});
|
||||
|
||||
test("generateTableData (ThermalUnits)", () => {
|
||||
const data = generateTableData(
|
||||
getThermalGenerators(TEST_DATA_1),
|
||||
ThermalUnitsColumnSpec,
|
||||
TEST_DATA_1,
|
||||
);
|
||||
assert.deepEqual(data[0], {
|
||||
Name: "g1",
|
||||
Bus: "b1",
|
||||
"Initial power (MW)": 115,
|
||||
"Initial status (h)": 12,
|
||||
"Minimum downtime (h)": 4,
|
||||
"Minimum uptime (h)": 4,
|
||||
"Ramp down limit (MW)": 232.68,
|
||||
"Ramp up limit (MW)": 232.68,
|
||||
"Shutdown limit (MW)": 232.68,
|
||||
"Startup limit (MW)": 232.68,
|
||||
"Production cost curve ($) 1": 1400,
|
||||
"Production cost curve ($) 2": 1600,
|
||||
"Production cost curve ($) 3": 2200,
|
||||
"Production cost curve ($) 4": 2400,
|
||||
"Production cost curve ($) 5": "",
|
||||
"Production cost curve ($) 6": "",
|
||||
"Production cost curve ($) 7": "",
|
||||
"Production cost curve ($) 8": "",
|
||||
"Production cost curve ($) 9": "",
|
||||
"Production cost curve ($) 10": "",
|
||||
"Production cost curve (MW) 1": 100,
|
||||
"Production cost curve (MW) 2": 110,
|
||||
"Production cost curve (MW) 3": 130,
|
||||
"Production cost curve (MW) 4": 135,
|
||||
"Production cost curve (MW) 5": "",
|
||||
"Production cost curve (MW) 6": "",
|
||||
"Production cost curve (MW) 7": "",
|
||||
"Production cost curve (MW) 8": "",
|
||||
"Production cost curve (MW) 9": "",
|
||||
"Production cost curve (MW) 10": "",
|
||||
"Startup costs ($) 1": 300,
|
||||
"Startup costs ($) 2": 400,
|
||||
"Startup costs ($) 3": "",
|
||||
"Startup costs ($) 4": "",
|
||||
"Startup costs ($) 5": "",
|
||||
"Startup delays (h) 1": 1,
|
||||
"Startup delays (h) 2": 4,
|
||||
"Startup delays (h) 3": "",
|
||||
"Startup delays (h) 4": "",
|
||||
"Startup delays (h) 5": "",
|
||||
"Must run?": false,
|
||||
});
|
||||
});
|
||||
|
||||
test("generate CSV", () => {
|
||||
const [data, columns] = generateBusesData(TEST_DATA_1);
|
||||
const actualCsv = generateCsv(data, columns);
|
||||
const expectedCsv =
|
||||
"Name,Load (MW) 00:00,Load (MW) 01:00,Load (MW) 02:00,Load (MW) 03:00,Load (MW) 04:00\n" +
|
||||
"b1,35.79534,34.38835,33.45083,32.89729,33.25044\n" +
|
||||
"b2,14.03739,13.48563,13.11797,12.9009,13.03939\n" +
|
||||
"b3,27.3729,26.29698,25.58005,25.15675,25.4268";
|
||||
assert.strictEqual(actualCsv, expectedCsv);
|
||||
});
|
||||
|
||||
test("parse CSV (Buses)", () => {
|
||||
const csvContents =
|
||||
"Name,Load (MW) 00:00,Load (MW) 01:00,Load (MW) 02:00,Load (MW) 03:00,Load (MW) 04:00\n" +
|
||||
"b1,0,1,2,3,4\n" +
|
||||
"b3,27.3729,26.29698,25.58005,25.15675,25.4268";
|
||||
const [newBuses, err] = parseCsv(csvContents, BusesColumnSpec, TEST_DATA_1);
|
||||
assert(err === null);
|
||||
assert.deepEqual(newBuses, {
|
||||
b1: {
|
||||
"Load (MW)": [0, 1, 2, 3, 4],
|
||||
},
|
||||
b3: {
|
||||
"Load (MW)": [27.3729, 26.29698, 25.58005, 25.15675, 25.4268],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("parse CSV with duplicated names", () => {
|
||||
const csvContents =
|
||||
"Name,Load (MW) 00:00,Load (MW) 01:00,Load (MW) 02:00,Load (MW) 03:00,Load (MW) 04:00\n" +
|
||||
"b1,0,0,0,0,0\n" +
|
||||
"b1,0,0,0,0,0";
|
||||
const [, err] = parseCsv(csvContents, BusesColumnSpec, TEST_DATA_1);
|
||||
assert(err !== null);
|
||||
assert.equal(err.message, `Name "b1" is duplicated (row 2)`);
|
||||
});
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
import { ValidationError } from "../../../core/Validation/validate";
|
||||
import { UnitCommitmentScenario } from "../../../core/fixtures";
|
||||
import Papa from "papaparse";
|
||||
import { parseNumber } from "../../../core/Operations/commonOps";
|
||||
import { parseBool, parseNumber } from "../../../core/Operations/commonOps";
|
||||
|
||||
export interface ColumnSpec {
|
||||
title: string;
|
||||
@@ -228,11 +228,12 @@ export const parseCsv = (
|
||||
case "string":
|
||||
data[name][spec.title] = row[spec.title];
|
||||
break;
|
||||
case "number":
|
||||
case "number": {
|
||||
const [val, err] = parseNumber(row[spec.title]);
|
||||
if (err) return [null, { message: err.message + rowRef }];
|
||||
data[name][spec.title] = val;
|
||||
break;
|
||||
}
|
||||
case "busRef":
|
||||
const busName = row[spec.title];
|
||||
if (!(busName in scenario.Buses)) {
|
||||
@@ -243,15 +244,36 @@ export const parseCsv = (
|
||||
}
|
||||
data[name][spec.title] = row[spec.title];
|
||||
break;
|
||||
case "number[T]":
|
||||
case "number[T]": {
|
||||
data[name][spec.title] = Array(timeslots.length);
|
||||
|
||||
for (let i = 0; i < timeslots.length; i++) {
|
||||
data[name][spec.title][i] = parseFloat(
|
||||
row[`${spec.title} ${timeslots[i]}`],
|
||||
);
|
||||
const [vf, err] = parseNumber(row[`${spec.title} ${timeslots[i]}`]);
|
||||
if (err) return [data, { message: err.message + rowRef }];
|
||||
data[name][spec.title][i] = vf;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "number[N]": {
|
||||
data[name][spec.title] = Array(spec.length).fill(0);
|
||||
for (let i = 0; i < spec.length!; i++) {
|
||||
let v = row[`${spec.title} ${i + 1}`];
|
||||
if (v.trim() === "") {
|
||||
data[name][spec.title].splice(i, spec.length! - i);
|
||||
break;
|
||||
} else {
|
||||
const [vf, err] = parseNumber(row[`${spec.title} ${i + 1}`]);
|
||||
if (err) return [data, { message: err.message + rowRef }];
|
||||
data[name][spec.title][i] = vf;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "boolean": {
|
||||
const [val, err] = parseBool(row[spec.title]);
|
||||
if (err) return [data, { message: err.message + rowRef }];
|
||||
data[name][spec.title] = val;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw Error(`Unknown type: ${spec.type}`);
|
||||
}
|
||||
|
||||
30
web/src/core/Operations/commonOps.test.ts
Normal file
30
web/src/core/Operations/commonOps.test.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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 { parseBool } from "./commonOps";
|
||||
import assert from "node:assert";
|
||||
|
||||
test("parseBool", () => {
|
||||
// True values
|
||||
for (const str of ["true", "TRUE", "1"]) {
|
||||
let [v, err] = parseBool(str);
|
||||
assert(!err);
|
||||
assert.equal(v, true);
|
||||
}
|
||||
|
||||
// False values
|
||||
for (const str of ["false", "FALSE", "0"]) {
|
||||
let [v, err] = parseBool(str);
|
||||
assert(!err);
|
||||
assert.equal(v, false);
|
||||
}
|
||||
|
||||
// Invalid values
|
||||
for (const str of ["qwe", ""]) {
|
||||
let [, err] = parseBool(str);
|
||||
assert(err);
|
||||
}
|
||||
});
|
||||
@@ -51,6 +51,18 @@ export const parseNumber = (
|
||||
}
|
||||
};
|
||||
|
||||
export const parseBool = (
|
||||
valueStr: string,
|
||||
): [boolean, ValidationError | null] => {
|
||||
if (["true", "1"].includes(valueStr.toLowerCase())) {
|
||||
return [true, null];
|
||||
}
|
||||
if (["false", "0"].includes(valueStr.toLowerCase())) {
|
||||
return [false, null];
|
||||
}
|
||||
return [true, { message: `"${valueStr}" is not a valid boolean value` }];
|
||||
};
|
||||
|
||||
export const changeStringData = (
|
||||
field: string,
|
||||
newValue: string,
|
||||
|
||||
@@ -63,8 +63,8 @@ export const createThermalUnit = (
|
||||
[name]: {
|
||||
Bus: busName,
|
||||
Type: "Thermal",
|
||||
"Production cost curve (MW)": [0],
|
||||
"Production cost curve ($)": [0],
|
||||
"Production cost curve (MW)": [0, 100],
|
||||
"Production cost curve ($)": [0, 10],
|
||||
"Startup costs ($)": [0],
|
||||
"Startup delays (h)": [1],
|
||||
"Ramp up limit (MW)": "",
|
||||
|
||||
Reference in New Issue
Block a user