mirror of
https://github.com/ANL-CEEESA/UnitCommitment.jl.git
synced 2025-12-06 08:18:51 -06:00
web: Improve CSV validation
This commit is contained in:
@@ -97,6 +97,10 @@ const ProfiledUnitsComponent = (props: ProfiledUnitsProps) => {
|
|||||||
props.onError(err.message);
|
props.onError(err.message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
for (const gen in newGenerators) {
|
||||||
|
newGenerators[gen]["Type"] = "Profiled";
|
||||||
|
}
|
||||||
|
|
||||||
const newScenario = {
|
const newScenario = {
|
||||||
...props.scenario,
|
...props.scenario,
|
||||||
Generators: newGenerators,
|
Generators: newGenerators,
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
} from "../../CaseBuilder/Buses/Buses";
|
} from "../../CaseBuilder/Buses/Buses";
|
||||||
import { generateCsv, parseCsv } from "./DataTable";
|
import { generateCsv, parseCsv } from "./DataTable";
|
||||||
import { TEST_DATA_1 } from "../../../core/fixtures.test";
|
import { TEST_DATA_1 } from "../../../core/fixtures.test";
|
||||||
|
import { ProfiledUnitsColumnSpec } from "../../CaseBuilder/ProfiledUnits/ProfiledUnits";
|
||||||
|
|
||||||
test("generate CSV", () => {
|
test("generate CSV", () => {
|
||||||
const [data, columns] = generateBusesData(TEST_DATA_1);
|
const [data, columns] = generateBusesData(TEST_DATA_1);
|
||||||
@@ -23,7 +24,7 @@ test("generate CSV", () => {
|
|||||||
assert.strictEqual(actualCsv, expectedCsv);
|
assert.strictEqual(actualCsv, expectedCsv);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("parse CSV", () => {
|
test("parse CSV (Buses)", () => {
|
||||||
const csvContents =
|
const csvContents =
|
||||||
"Name,Load (MW) 00:00,Load (MW) 01:00,Load (MW) 02:00,Load (MW) 03:00,Load (MW) 04:00\n" +
|
"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" +
|
"b1,0,1,2,3,4\n" +
|
||||||
@@ -39,3 +40,64 @@ test("parse CSV", () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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)`);
|
||||||
|
});
|
||||||
|
|
||||||
|
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)');
|
||||||
|
});
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
import { ValidationError } from "../../../core/Validation/validate";
|
import { ValidationError } from "../../../core/Validation/validate";
|
||||||
import { UnitCommitmentScenario } from "../../../core/fixtures";
|
import { UnitCommitmentScenario } from "../../../core/fixtures";
|
||||||
import Papa from "papaparse";
|
import Papa from "papaparse";
|
||||||
|
import { parseNumber } from "../../../core/Operations/commonOps";
|
||||||
|
|
||||||
export interface ColumnSpec {
|
export interface ColumnSpec {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -178,14 +179,32 @@ export const parseCsv = (
|
|||||||
const data: { [key: string]: any } = {};
|
const data: { [key: string]: any } = {};
|
||||||
for (let i = 0; i < csv.data.length; i++) {
|
for (let i = 0; i < csv.data.length; i++) {
|
||||||
const row = csv.data[i] as { [key: string]: any };
|
const row = csv.data[i] as { [key: string]: any };
|
||||||
|
const rowRef = ` (row ${i + 1})`;
|
||||||
const name = row["Name"] as string;
|
const name = row["Name"] as string;
|
||||||
|
if (name in data) {
|
||||||
|
return [null, { message: `Name "${name}" is duplicated` + rowRef }];
|
||||||
|
}
|
||||||
data[name] = {};
|
data[name] = {};
|
||||||
|
|
||||||
for (const spec of colSpecs) {
|
for (const spec of colSpecs) {
|
||||||
if (spec.title === "Name") continue;
|
if (spec.title === "Name") continue;
|
||||||
switch (spec.type) {
|
switch (spec.type) {
|
||||||
case "string":
|
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)) {
|
||||||
|
return [
|
||||||
|
null,
|
||||||
|
{ message: `Bus "${busName}" does not exist` + rowRef },
|
||||||
|
];
|
||||||
|
}
|
||||||
data[name][spec.title] = row[spec.title];
|
data[name][spec.title] = row[spec.title];
|
||||||
break;
|
break;
|
||||||
case "number[]":
|
case "number[]":
|
||||||
@@ -198,7 +217,7 @@ export const parseCsv = (
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
console.error(`Unknown type: ${spec.type}`);
|
throw Error(`Unknown type: ${spec.type}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,9 @@ export const generateUniqueName = (container: any, prefix: string): string => {
|
|||||||
return name;
|
return name;
|
||||||
};
|
};
|
||||||
|
|
||||||
const parseNumber = (valueStr: string): [number, ValidationError | null] => {
|
export const parseNumber = (
|
||||||
|
valueStr: string,
|
||||||
|
): [number, ValidationError | null] => {
|
||||||
const valueFloat = parseFloat(valueStr);
|
const valueFloat = parseFloat(valueStr);
|
||||||
if (isNaN(valueFloat)) {
|
if (isNaN(valueFloat)) {
|
||||||
return [0, { message: `"${valueStr}" is not a valid number` }];
|
return [0, { message: `"${valueStr}" is not a valid number` }];
|
||||||
|
|||||||
Reference in New Issue
Block a user