mirror of
https://github.com/ANL-CEEESA/UnitCommitment.jl.git
synced 2025-12-06 08:18:51 -06:00
web: ProfiledUnits: Revise CSV upload
This commit is contained in:
111
web/src/components/CaseBuilder/ProfiledUnits.test.ts
Normal file
111
web/src/components/CaseBuilder/ProfiledUnits.test.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* 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,
|
||||
generateTableColumns,
|
||||
parseCsv,
|
||||
} from "../Common/Forms/DataTable";
|
||||
import {
|
||||
parseProfiledUnitsCsv,
|
||||
ProfiledUnitsColumnSpec,
|
||||
} from "./ProfiledUnits";
|
||||
import { TEST_DATA_1 } from "../../core/fixtures.test";
|
||||
import assert from "node:assert";
|
||||
import {
|
||||
getProfiledGenerators,
|
||||
getThermalGenerators,
|
||||
} from "../../core/fixtures";
|
||||
|
||||
test("parse CSV", () => {
|
||||
const csvContents =
|
||||
"Name,Bus,Cost ($/MW),Maximum power (MW) 00:00,Maximum power (MW) 01:00," +
|
||||
"Maximum power (MW) 02:00,Maximum power (MW) 03:00," +
|
||||
"Maximum power (MW) 04:00,Minimum power (MW) 00:00," +
|
||||
"Minimum power (MW) 01:00,Minimum power (MW) 02:00," +
|
||||
"Minimum power (MW) 03:00,Minimum power (MW) 04:00\n" +
|
||||
"pu1,b1,50,260.25384545,72.89148068,377.17886108,336.66732361," +
|
||||
"376.82781758,52.05076909,14.57829614,75.43577222,67.33346472,75.36556352\n" +
|
||||
"pu2,b1,0,0,0,0,0,0,0,0,0,0,0";
|
||||
const [scenario, err] = parseProfiledUnitsCsv(csvContents, TEST_DATA_1);
|
||||
assert(err === null);
|
||||
const thermalGens = getThermalGenerators(scenario);
|
||||
const profGens = getProfiledGenerators(scenario);
|
||||
assert.equal(Object.keys(thermalGens).length, 1);
|
||||
assert.equal(Object.keys(profGens).length, 2);
|
||||
|
||||
assert.deepEqual(profGens, {
|
||||
pu1: {
|
||||
Bus: "b1",
|
||||
"Minimum power (MW)": [
|
||||
52.05076909, 14.57829614, 75.43577222, 67.33346472, 75.36556352,
|
||||
],
|
||||
"Maximum power (MW)": [
|
||||
260.25384545, 72.89148068, 377.17886108, 336.66732361, 376.82781758,
|
||||
],
|
||||
"Cost ($/MW)": 50.0,
|
||||
Type: "Profiled",
|
||||
},
|
||||
pu2: {
|
||||
Bus: "b1",
|
||||
"Minimum power (MW)": [0, 0, 0, 0, 0],
|
||||
"Maximum power (MW)": [0, 0, 0, 0, 0],
|
||||
"Cost ($/MW)": 0.0,
|
||||
Type: "Profiled",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("parse CSV with invalid bus", () => {
|
||||
const csvContents =
|
||||
"Name,Bus,Cost ($/MW),Maximum power (MW) 00:00,Maximum power (MW) 01:00," +
|
||||
"Maximum power (MW) 02:00,Maximum power (MW) 03:00," +
|
||||
"Maximum power (MW) 04:00,Minimum power (MW) 00:00," +
|
||||
"Minimum power (MW) 01:00,Minimum power (MW) 02:00," +
|
||||
"Minimum power (MW) 03:00,Minimum power (MW) 04:00\n" +
|
||||
"pu1,b99,50,260.25384545,72.89148068,377.17886108,336.66732361," +
|
||||
"376.82781758,52.05076909,14.57829614,75.43577222,67.33346472,75.36556352\n" +
|
||||
"pu2,b1,0,0,0,0,0,0,0,0,0,0,0";
|
||||
const [, err] = parseCsv(csvContents, ProfiledUnitsColumnSpec, TEST_DATA_1);
|
||||
assert(err !== null);
|
||||
assert.equal(err.message, 'Bus "b99" does not exist (row 1)');
|
||||
});
|
||||
|
||||
test("generateTableColumns", () => {
|
||||
const columns = generateTableColumns(TEST_DATA_1, ProfiledUnitsColumnSpec);
|
||||
assert.equal(columns.length, 5);
|
||||
assert.deepEqual(columns[0], {
|
||||
editor: "input",
|
||||
editorParams: {
|
||||
selectContents: true,
|
||||
},
|
||||
field: "Name",
|
||||
formatter: "plaintext",
|
||||
headerHozAlign: "left",
|
||||
headerSort: false,
|
||||
headerWordWrap: true,
|
||||
hozAlign: "left",
|
||||
minWidth: 100,
|
||||
resizable: false,
|
||||
title: "Name",
|
||||
});
|
||||
assert.equal(columns[3]!["columns"]!.length, 5);
|
||||
assert.deepEqual(columns[3]!["columns"]![0], {
|
||||
editor: "input",
|
||||
editorParams: {
|
||||
selectContents: true,
|
||||
},
|
||||
field: "Maximum power (MW) 00:00",
|
||||
formatter: floatFormatter,
|
||||
headerHozAlign: "left",
|
||||
headerSort: false,
|
||||
headerWordWrap: true,
|
||||
hozAlign: "left",
|
||||
minWidth: 60,
|
||||
resizable: false,
|
||||
title: "00:00",
|
||||
});
|
||||
});
|
||||
@@ -20,6 +20,7 @@ import DataTable, {
|
||||
} from "../Common/Forms/DataTable";
|
||||
import {
|
||||
getProfiledGenerators,
|
||||
getThermalGenerators,
|
||||
UnitCommitmentScenario,
|
||||
} from "../../core/fixtures";
|
||||
import { ColumnDefinition } from "tabulator-tables";
|
||||
@@ -75,6 +76,31 @@ const generateProfiledUnitsData = (
|
||||
return [data, columns];
|
||||
};
|
||||
|
||||
export const parseProfiledUnitsCsv = (
|
||||
csvContents: string,
|
||||
scenario: UnitCommitmentScenario,
|
||||
): [UnitCommitmentScenario, ValidationError | null] => {
|
||||
const [profGens, err] = parseCsv(
|
||||
csvContents,
|
||||
ProfiledUnitsColumnSpec,
|
||||
scenario,
|
||||
);
|
||||
if (err) return [scenario, err];
|
||||
|
||||
// Process imported generators
|
||||
for (const gen in profGens) {
|
||||
profGens[gen]["Type"] = "Profiled";
|
||||
}
|
||||
|
||||
// Merge with existing data
|
||||
const thermalGens = getThermalGenerators(scenario);
|
||||
const newScenario = {
|
||||
...scenario,
|
||||
Generators: { ...thermalGens, ...profGens },
|
||||
};
|
||||
return [newScenario, null];
|
||||
};
|
||||
|
||||
const ProfiledUnitsComponent = (props: CaseBuilderSectionProps) => {
|
||||
const fileUploadElem = useRef<FileUploadElement>(null);
|
||||
|
||||
@@ -85,24 +111,12 @@ const ProfiledUnitsComponent = (props: CaseBuilderSectionProps) => {
|
||||
};
|
||||
|
||||
const onUpload = () => {
|
||||
fileUploadElem.current!.showFilePicker((csvContents: any) => {
|
||||
const [newGenerators, err] = parseCsv(
|
||||
csvContents,
|
||||
ProfiledUnitsColumnSpec,
|
||||
props.scenario,
|
||||
);
|
||||
fileUploadElem.current!.showFilePicker((csv: any) => {
|
||||
const [newScenario, err] = parseProfiledUnitsCsv(csv, props.scenario);
|
||||
if (err) {
|
||||
props.onError(err.message);
|
||||
return;
|
||||
}
|
||||
for (const gen in newGenerators) {
|
||||
newGenerators[gen]["Type"] = "Profiled";
|
||||
}
|
||||
|
||||
const newScenario = {
|
||||
...props.scenario,
|
||||
Generators: newGenerators,
|
||||
};
|
||||
props.onDataChanged(newScenario);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -14,46 +14,9 @@ import {
|
||||
parseCsv,
|
||||
} from "./DataTable";
|
||||
import { TEST_DATA_1 } from "../../../core/fixtures.test";
|
||||
import { ProfiledUnitsColumnSpec } from "../../CaseBuilder/ProfiledUnits";
|
||||
import { ThermalUnitsColumnSpec } from "../../CaseBuilder/ThermalUnits";
|
||||
import { getThermalGenerators } from "../../../core/fixtures";
|
||||
|
||||
test("generateTableColumns (ProfiledUnits)", () => {
|
||||
const columns = generateTableColumns(TEST_DATA_1, ProfiledUnitsColumnSpec);
|
||||
assert.equal(columns.length, 5);
|
||||
assert.deepEqual(columns[0], {
|
||||
editor: "input",
|
||||
editorParams: {
|
||||
selectContents: true,
|
||||
},
|
||||
field: "Name",
|
||||
formatter: "plaintext",
|
||||
headerHozAlign: "left",
|
||||
headerSort: false,
|
||||
headerWordWrap: true,
|
||||
hozAlign: "left",
|
||||
minWidth: 100,
|
||||
resizable: false,
|
||||
title: "Name",
|
||||
});
|
||||
assert.equal(columns[3]!["columns"]!.length, 5);
|
||||
assert.deepEqual(columns[3]!["columns"]![0], {
|
||||
editor: "input",
|
||||
editorParams: {
|
||||
selectContents: true,
|
||||
},
|
||||
field: "Maximum power (MW) 00:00",
|
||||
formatter: floatFormatter,
|
||||
headerHozAlign: "left",
|
||||
headerSort: false,
|
||||
headerWordWrap: true,
|
||||
hozAlign: "left",
|
||||
minWidth: 60,
|
||||
resizable: false,
|
||||
title: "00:00",
|
||||
});
|
||||
});
|
||||
|
||||
test("generateTableColumns (ThermalUnits)", () => {
|
||||
const columns = generateTableColumns(TEST_DATA_1, ThermalUnitsColumnSpec);
|
||||
assert.equal(columns[2]!["columns"]!.length, 10);
|
||||
@@ -81,7 +44,7 @@ test("generateTableData (ThermalUnits)", () => {
|
||||
TEST_DATA_1,
|
||||
);
|
||||
assert.deepEqual(data[0], {
|
||||
Name: "gen1",
|
||||
Name: "g1",
|
||||
Bus: "b1",
|
||||
"Initial power (MW)": 115,
|
||||
"Initial status (h)": 12,
|
||||
@@ -121,6 +84,7 @@ test("generateTableData (ThermalUnits)", () => {
|
||||
"Startup delays (h) 3": "",
|
||||
"Startup delays (h) 4": "",
|
||||
"Startup delays (h) 5": "",
|
||||
"Must run?": false,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -161,54 +125,3 @@ test("parse CSV with duplicated names", () => {
|
||||
assert(err !== null);
|
||||
assert.equal(err.message, `Name "b1" is duplicated (row 2)`);
|
||||
});
|
||||
|
||||
test("parse CSV (Profiled Units)", () => {
|
||||
const csvContents =
|
||||
"Name,Bus,Cost ($/MW),Maximum power (MW) 00:00,Maximum power (MW) 01:00," +
|
||||
"Maximum power (MW) 02:00,Maximum power (MW) 03:00," +
|
||||
"Maximum power (MW) 04:00,Minimum power (MW) 00:00," +
|
||||
"Minimum power (MW) 01:00,Minimum power (MW) 02:00," +
|
||||
"Minimum power (MW) 03:00,Minimum power (MW) 04:00\n" +
|
||||
"pu1,b1,50,260.25384545,72.89148068,377.17886108,336.66732361," +
|
||||
"376.82781758,52.05076909,14.57829614,75.43577222,67.33346472,75.36556352\n" +
|
||||
"pu2,b1,0,0,0,0,0,0,0,0,0,0,0";
|
||||
const [newGenerators, err] = parseCsv(
|
||||
csvContents,
|
||||
ProfiledUnitsColumnSpec,
|
||||
TEST_DATA_1,
|
||||
);
|
||||
assert(err === null);
|
||||
assert.deepEqual(newGenerators, {
|
||||
pu1: {
|
||||
Bus: "b1",
|
||||
"Minimum power (MW)": [
|
||||
52.05076909, 14.57829614, 75.43577222, 67.33346472, 75.36556352,
|
||||
],
|
||||
"Maximum power (MW)": [
|
||||
260.25384545, 72.89148068, 377.17886108, 336.66732361, 376.82781758,
|
||||
],
|
||||
"Cost ($/MW)": 50.0,
|
||||
},
|
||||
pu2: {
|
||||
Bus: "b1",
|
||||
"Minimum power (MW)": [0, 0, 0, 0, 0],
|
||||
"Maximum power (MW)": [0, 0, 0, 0, 0],
|
||||
"Cost ($/MW)": 0.0,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("parse CSV with invalid bus", () => {
|
||||
const csvContents =
|
||||
"Name,Bus,Cost ($/MW),Maximum power (MW) 00:00,Maximum power (MW) 01:00," +
|
||||
"Maximum power (MW) 02:00,Maximum power (MW) 03:00," +
|
||||
"Maximum power (MW) 04:00,Minimum power (MW) 00:00," +
|
||||
"Minimum power (MW) 01:00,Minimum power (MW) 02:00," +
|
||||
"Minimum power (MW) 03:00,Minimum power (MW) 04:00\n" +
|
||||
"pu1,b99,50,260.25384545,72.89148068,377.17886108,336.66732361," +
|
||||
"376.82781758,52.05076909,14.57829614,75.43577222,67.33346472,75.36556352\n" +
|
||||
"pu2,b1,0,0,0,0,0,0,0,0,0,0,0";
|
||||
const [, err] = parseCsv(csvContents, ProfiledUnitsColumnSpec, TEST_DATA_1);
|
||||
assert(err !== null);
|
||||
assert.equal(err.message, 'Bus "b99" does not exist (row 1)');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user