mirror of
https://github.com/ANL-CEEESA/RELOG.git
synced 2025-12-06 15:48:51 -06:00
casebuilder: Validate JSON schema on load/save
This commit is contained in:
@@ -13,6 +13,7 @@ import { defaultData, defaultPlant, defaultProduct } from "./defaults";
|
||||
import { randomPosition } from "./PipelineBlock";
|
||||
import { exportData, importData } from "./export";
|
||||
import { generateFile } from "./csv";
|
||||
import { validate } from "./validate";
|
||||
|
||||
const setDefaults = (actualDict, defaultDict) => {
|
||||
for (const [key, defaultValue] of Object.entries(defaultDict)) {
|
||||
@@ -69,6 +70,7 @@ const openRelogDB = async () => {
|
||||
const InputPage = () => {
|
||||
const fileElem = useRef();
|
||||
let [data, setData] = useState(defaultData);
|
||||
let [messages, setMessages] = useState([]);
|
||||
|
||||
const save = async (data) => {
|
||||
const db = await openRelogDB();
|
||||
@@ -238,7 +240,18 @@ const InputPage = () => {
|
||||
};
|
||||
|
||||
const onSave = () => {
|
||||
generateFile("case.json", JSON.stringify(exportData(data), null, 2));
|
||||
const exported = exportData(data);
|
||||
const valid = validate(exported);
|
||||
console.log(exported);
|
||||
console.log(validate.errors);
|
||||
if (valid) {
|
||||
generateFile("case.json", JSON.stringify(exported, null, 2));
|
||||
} else {
|
||||
setMessages([
|
||||
...messages,
|
||||
"Data has validation errors and could not be saved.",
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
const onClear = () => {
|
||||
@@ -248,9 +261,20 @@ const InputPage = () => {
|
||||
};
|
||||
|
||||
const onLoad = (contents) => {
|
||||
const newData = importData(JSON.parse(contents));
|
||||
setData(newData);
|
||||
save(newData);
|
||||
const parsed = JSON.parse(contents);
|
||||
const valid = validate(parsed);
|
||||
if (valid) {
|
||||
const newData = importData(parsed);
|
||||
setData(newData);
|
||||
save(newData);
|
||||
} else {
|
||||
console.log(validate.errors);
|
||||
setMessages([...messages, "File is corrupted and could not be loaded."]);
|
||||
}
|
||||
};
|
||||
|
||||
const onDismissMessage = (idx) => {
|
||||
setMessages([...messages.slice(0, idx), ...messages.slice(idx + 1)]);
|
||||
};
|
||||
|
||||
const onChange = (val, field1, field2) => {
|
||||
@@ -289,6 +313,16 @@ const InputPage = () => {
|
||||
);
|
||||
}
|
||||
|
||||
let messageComps = [];
|
||||
for (let i = 0; i < messages.length; i++) {
|
||||
messageComps.push(
|
||||
<div className="message error" key={i}>
|
||||
<p>{messages[i]}</p>
|
||||
<Button label="Dismiss" onClick={() => onDismissMessage(i)} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const onFileSelected = () => {
|
||||
const file = fileElem.current.files[0];
|
||||
if (file) {
|
||||
@@ -337,6 +371,7 @@ const InputPage = () => {
|
||||
{productComps}
|
||||
{plantComps}
|
||||
</div>
|
||||
<div id="messageTray">{messageComps}</div>
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -197,7 +197,7 @@ const PlantBlock = (props) => {
|
||||
/>
|
||||
<TextInputRow
|
||||
label="Variable operating cost"
|
||||
unit="$"
|
||||
unit="$/tonne"
|
||||
tooltip="The cost that the plant incurs to process each tonne of input."
|
||||
value={props.value["variable operating cost ($/tonne)"]}
|
||||
onChange={(v) => onChange(v, "variable operating cost ($/tonne)")}
|
||||
|
||||
@@ -169,7 +169,7 @@ const ProductBlock = (props) => {
|
||||
/>
|
||||
<DictInputRow
|
||||
label="Transportation emissions"
|
||||
unit="J/km/tonne"
|
||||
unit="tonne/km/tonne"
|
||||
tooltip="A dictionary mapping the name of each greenhouse gas, produced to transport one tonne of this product along one kilometer, to the amount of gas produced."
|
||||
keyPlaceholder="Emission name"
|
||||
value={props.value["transportation emissions (tonne/km/tonne)"]}
|
||||
|
||||
@@ -305,6 +305,7 @@ const compressDisposalLimits = (original, result) => {
|
||||
return;
|
||||
}
|
||||
const total = computeTotalInitialAmount(original);
|
||||
if (!total) return;
|
||||
const limit = original["disposal limit (tonne)"];
|
||||
let perc = Math.round((limit[0] / total[0]) * 1e6) / 1e6;
|
||||
for (let i = 1; i < limit.length; i++) {
|
||||
@@ -369,6 +370,12 @@ export const importPlant = (original) => {
|
||||
const plant = {};
|
||||
const parameters = {};
|
||||
|
||||
plant["storage"] = {};
|
||||
plant["storage"]["cost ($/tonne)"] = 0;
|
||||
plant["storage"]["limit (tonne)"] = 0;
|
||||
plant["disposal cost ($/tonne)"] = 0;
|
||||
plant["disposal limit (tonne)"] = 0;
|
||||
|
||||
// Initialize null values
|
||||
["x", "y"].forEach((key) => {
|
||||
plant[key] = null;
|
||||
@@ -428,9 +435,9 @@ export const importPlant = (original) => {
|
||||
// Compute area cost factor
|
||||
let acf = 1;
|
||||
if (costsInitialized) {
|
||||
acf = plant["opening cost (min capacity) ($)"];
|
||||
acf = plant["opening cost (max capacity) ($)"];
|
||||
if (Array.isArray(acf)) acf = acf[0];
|
||||
acf = minCapDict["opening cost ($)"][0] / acf;
|
||||
acf = maxCapDict["opening cost ($)"][0] / acf;
|
||||
}
|
||||
resLocDict[locName]["area cost factor"] = acf;
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
@@ -50,3 +51,47 @@ body {
|
||||
.react-flow__handle-left {
|
||||
left: -4px !important;
|
||||
}
|
||||
|
||||
#messageTray {
|
||||
max-width: var(--site-width);
|
||||
margin: 0 auto;
|
||||
position: fixed;
|
||||
bottom: 12px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
#messageTray .message {
|
||||
background-color: rgb(221, 69, 69);
|
||||
color: #eee;
|
||||
padding: 12px;
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.4);
|
||||
display: flex;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
#messageTray .message p {
|
||||
flex: 1;
|
||||
margin: 0;
|
||||
padding: 12px 0;
|
||||
}
|
||||
|
||||
#messageTray .message button {
|
||||
margin: 0;
|
||||
background: transparent;
|
||||
border: 1px solid #eee;
|
||||
color: #eee;
|
||||
float: right;
|
||||
padding: 0 24px;
|
||||
line-height: 6px;
|
||||
}
|
||||
|
||||
#messageTray .message button:hover {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
#messageTray .message button:active {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
182
relog-web/src/validate.js
Normal file
182
relog-web/src/validate.js
Normal file
@@ -0,0 +1,182 @@
|
||||
const Ajv = require("ajv");
|
||||
const ajv = new Ajv();
|
||||
|
||||
const schema = {
|
||||
$schema: "http://json-schema.org/draft-07/schema#",
|
||||
$id: "https://anl-ceeesa.github.io/RELOG/input",
|
||||
title: "Schema for RELOG Input File",
|
||||
definitions: {
|
||||
TimeSeries: {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "number",
|
||||
},
|
||||
},
|
||||
Parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
"time horizon (years)": {
|
||||
type: "number",
|
||||
},
|
||||
},
|
||||
required: ["time horizon (years)"],
|
||||
},
|
||||
Plant: {
|
||||
type: "object",
|
||||
additionalProperties: {
|
||||
type: "object",
|
||||
properties: {
|
||||
input: {
|
||||
type: "string",
|
||||
},
|
||||
"outputs (tonne/tonne)": {
|
||||
type: "object",
|
||||
additionalProperties: {
|
||||
type: "number",
|
||||
},
|
||||
},
|
||||
"energy (GJ/tonne)": {
|
||||
$ref: "#/definitions/TimeSeries",
|
||||
},
|
||||
"emissions (tonne/tonne)": {
|
||||
type: "object",
|
||||
additionalProperties: {
|
||||
$ref: "#/definitions/TimeSeries",
|
||||
},
|
||||
},
|
||||
locations: {
|
||||
$ref: "#/definitions/PlantLocation",
|
||||
},
|
||||
},
|
||||
required: ["input", "locations"],
|
||||
},
|
||||
},
|
||||
PlantLocation: {
|
||||
type: "object",
|
||||
additionalProperties: {
|
||||
type: "object",
|
||||
properties: {
|
||||
location: {
|
||||
type: "string",
|
||||
},
|
||||
"latitude (deg)": {
|
||||
type: "number",
|
||||
},
|
||||
"longitude (deg)": {
|
||||
type: "number",
|
||||
},
|
||||
disposal: {
|
||||
type: "object",
|
||||
additionalProperties: {
|
||||
type: "object",
|
||||
properties: {
|
||||
"cost ($/tonne)": {
|
||||
$ref: "#/definitions/TimeSeries",
|
||||
},
|
||||
"limit (tonne)": {
|
||||
$ref: "#/definitions/TimeSeries",
|
||||
},
|
||||
},
|
||||
required: ["cost ($/tonne)"],
|
||||
},
|
||||
},
|
||||
storage: {
|
||||
type: "object",
|
||||
properties: {
|
||||
"cost ($/tonne)": {
|
||||
$ref: "#/definitions/TimeSeries",
|
||||
},
|
||||
"limit (tonne)": {
|
||||
type: "number",
|
||||
},
|
||||
},
|
||||
required: ["cost ($/tonne)", "limit (tonne)"],
|
||||
},
|
||||
"capacities (tonne)": {
|
||||
type: "object",
|
||||
additionalProperties: {
|
||||
type: "object",
|
||||
properties: {
|
||||
"variable operating cost ($/tonne)": {
|
||||
$ref: "#/definitions/TimeSeries",
|
||||
},
|
||||
"fixed operating cost ($)": {
|
||||
$ref: "#/definitions/TimeSeries",
|
||||
},
|
||||
"opening cost ($)": {
|
||||
$ref: "#/definitions/TimeSeries",
|
||||
},
|
||||
},
|
||||
required: [
|
||||
"variable operating cost ($/tonne)",
|
||||
"fixed operating cost ($)",
|
||||
"opening cost ($)",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
required: ["capacities (tonne)"],
|
||||
},
|
||||
},
|
||||
InitialAmount: {
|
||||
type: "object",
|
||||
additionalProperties: {
|
||||
type: "object",
|
||||
properties: {
|
||||
location: {
|
||||
type: "string",
|
||||
},
|
||||
"latitude (deg)": {
|
||||
type: "number",
|
||||
},
|
||||
"longitude (deg)": {
|
||||
type: "number",
|
||||
},
|
||||
"amount (tonne)": {
|
||||
$ref: "#/definitions/TimeSeries",
|
||||
},
|
||||
},
|
||||
required: ["amount (tonne)"],
|
||||
},
|
||||
},
|
||||
Product: {
|
||||
type: "object",
|
||||
additionalProperties: {
|
||||
type: "object",
|
||||
properties: {
|
||||
"transportation cost ($/km/tonne)": {
|
||||
$ref: "#/definitions/TimeSeries",
|
||||
},
|
||||
"transportation energy (J/km/tonne)": {
|
||||
$ref: "#/definitions/TimeSeries",
|
||||
},
|
||||
"transportation emissions (tonne/km/tonne)": {
|
||||
type: "object",
|
||||
additionalProperties: {
|
||||
$ref: "#/definitions/TimeSeries",
|
||||
},
|
||||
},
|
||||
"initial amounts": {
|
||||
$ref: "#/definitions/InitialAmount",
|
||||
},
|
||||
},
|
||||
required: ["transportation cost ($/km/tonne)"],
|
||||
},
|
||||
},
|
||||
},
|
||||
type: "object",
|
||||
properties: {
|
||||
parameters: {
|
||||
$ref: "#/definitions/Parameters",
|
||||
},
|
||||
plants: {
|
||||
$ref: "#/definitions/Plant",
|
||||
},
|
||||
products: {
|
||||
$ref: "#/definitions/Product",
|
||||
},
|
||||
},
|
||||
required: ["parameters", "plants", "products"],
|
||||
};
|
||||
|
||||
export const validate = ajv.compile(schema);
|
||||
Reference in New Issue
Block a user