diff --git a/backend/src/acidwatch_api/models/arcs.py b/backend/src/acidwatch_api/models/arcs.py index abdbf289..a298d35f 100644 --- a/backend/src/acidwatch_api/models/arcs.py +++ b/backend/src/acidwatch_api/models/arcs.py @@ -19,7 +19,7 @@ class ArcsAdapter(BaseAdapter): model_id = "arcs" display_name = "ARCS" description = DESCRIPTION - category = "Reactive" + category = "ChemicalEquilibrium" valid_substances = [ "CH2O2", diff --git a/backend/src/acidwatch_api/models/arcs_exp.py b/backend/src/acidwatch_api/models/arcs_exp.py index 4e5a896c..75ba4407 100644 --- a/backend/src/acidwatch_api/models/arcs_exp.py +++ b/backend/src/acidwatch_api/models/arcs_exp.py @@ -18,7 +18,7 @@ class ArcsExpAdapter(BaseAdapter): model_id = "arcs_exp" display_name = "ARCS experimental" description = DESCRIPTION - category = "Reactive" + category = "ChemicalEquilibrium" valid_substances = [ "CH2O2", diff --git a/backend/src/acidwatch_api/models/base.py b/backend/src/acidwatch_api/models/base.py index ba793e11..0c9c88d9 100644 --- a/backend/src/acidwatch_api/models/base.py +++ b/backend/src/acidwatch_api/models/base.py @@ -261,9 +261,9 @@ def __init_subclass__(cls) -> None: ] category: Annotated[ - Literal["Reactive", "PhaseTransition"], + Literal["ChemicalEquilibrium", "PhaseEquilibrium"], Doc( - "Category of the model: Reactive (chemical reactions) or PhaseTransition (phase changes)" + "Category of the model: ChemicalEquilibrium (chemical reactions) or PhaseEquilibrium (phase changes)" ), ] diff --git a/backend/src/acidwatch_api/models/gibbs_minimization_model.py b/backend/src/acidwatch_api/models/gibbs_minimization_model.py index f1d44459..9ab4b76b 100644 --- a/backend/src/acidwatch_api/models/gibbs_minimization_model.py +++ b/backend/src/acidwatch_api/models/gibbs_minimization_model.py @@ -142,7 +142,7 @@ class GibbsMinimizationModelAdapter(BaseAdapter): display_name = "Gibbs Minimization Model" parameters: GibbsMinimizationModelParameters description = DESCRIPTION - category = "Reactive" + category = "ChemicalEquilibrium" async def run(self) -> RunResult: eos = self.parameters.equation_of_state diff --git a/backend/src/acidwatch_api/models/phpitz_reactive.py b/backend/src/acidwatch_api/models/phpitz_reactive.py index 4cb1a9bc..8bc44bd1 100644 --- a/backend/src/acidwatch_api/models/phpitz_reactive.py +++ b/backend/src/acidwatch_api/models/phpitz_reactive.py @@ -35,7 +35,7 @@ class PhpitzReactiveAdapter(BaseAdapter): ] description = "Computational model developed by Baard Kaasa as part of our CCS research on CO2 Impurities." - category = "Reactive" + category = "ChemicalEquilibrium" base_url = SETTINGS.phpitz_api_base_uri async def run(self) -> RunResult: diff --git a/backend/src/acidwatch_api/models/phpitz_solubility.py b/backend/src/acidwatch_api/models/phpitz_solubility.py index d5ad3a86..3047444a 100644 --- a/backend/src/acidwatch_api/models/phpitz_solubility.py +++ b/backend/src/acidwatch_api/models/phpitz_solubility.py @@ -37,7 +37,7 @@ class PhpitzSolubilityAdapter(BaseAdapter): ] description = "Computational model developed by Baard Kaasa as part of our CCS research on CO2 Impurities. Solubility part." - category = "PhaseTransition" + category = "PhaseEquilibrium" base_url = SETTINGS.phpitz_api_base_uri async def run(self) -> RunResult: @@ -59,7 +59,7 @@ async def run(self) -> RunResult: phases = self._extract_phases(data) - return phases, TextResult(data=data["raw"], label="pHPitz Solubility Output") + return phases, TextResult(data=data["raw"], label="Text") def _extract_phases(self, data: dict[str, Any]) -> list[Phase]: solubility = data.get("solubility") or {} diff --git a/backend/src/acidwatch_api/models/solubilityccs.py b/backend/src/acidwatch_api/models/solubilityccs.py index 545bbf57..9aa5ec4d 100644 --- a/backend/src/acidwatch_api/models/solubilityccs.py +++ b/backend/src/acidwatch_api/models/solubilityccs.py @@ -36,7 +36,7 @@ class SolubilityCCSAdapter(BaseAdapter): description = DESCRIPTION valid_substances = ["H2O", "H2SO4", "HNO3"] parameters: SolubilityCCSParameters - category = "PhaseTransition" + category = "PhaseEquilibrium" async def run(self) -> RunResult: # Get concentrations (mole fractions) diff --git a/backend/src/acidwatch_api/models/tocomo.py b/backend/src/acidwatch_api/models/tocomo.py index 9ea8e229..e5290c65 100644 --- a/backend/src/acidwatch_api/models/tocomo.py +++ b/backend/src/acidwatch_api/models/tocomo.py @@ -30,7 +30,7 @@ class TocomoAdapter(BaseAdapter): model_id = "tocomo" display_name = "ToCoMo" description = DESCRIPTION - category = "Reactive" + category = "ChemicalEquilibrium" valid_substances = [ "O2", diff --git a/backend/tests/test_grid_simulations_endpoint.py b/backend/tests/test_grid_simulations_endpoint.py index c45603b5..28fe03e4 100644 --- a/backend/tests/test_grid_simulations_endpoint.py +++ b/backend/tests/test_grid_simulations_endpoint.py @@ -35,7 +35,7 @@ class HalvingAdapter(base.BaseAdapter): model_id = "halving" display_name = "Halving Model" description = "" - category = "Reactive" + category = "ChemicalEquilibrium" valid_substances = ["H2O"] async def run(self): @@ -54,7 +54,7 @@ class QuadruplingAdapter(base.BaseAdapter): model_id = "quadrupling" display_name = "Quadrupling Model" description = "" - category = "PhaseTransition" + category = "PhaseEquilibrium" valid_substances = ["H2O"] async def run(self): @@ -73,7 +73,7 @@ class FailingAdapter(base.BaseAdapter): model_id = "failing" display_name = "Failing Model" description = "" - category = "Reactive" + category = "ChemicalEquilibrium" valid_substances = ["H2O"] async def run(self): @@ -84,7 +84,7 @@ class TwoSubstanceAdapter(base.BaseAdapter): model_id = "two-substance" display_name = "Two Substance Model" description = "" - category = "Reactive" + category = "ChemicalEquilibrium" valid_substances = ["H2O", "NaCl"] async def run(self): diff --git a/backend/tests/test_models_endpoints.py b/backend/tests/test_models_endpoints.py index 53ff7ddd..470da259 100644 --- a/backend/tests/test_models_endpoints.py +++ b/backend/tests/test_models_endpoints.py @@ -50,7 +50,7 @@ class DummyAdapter(base.BaseAdapter): model_id = "dummy" display_name = "Dummy Model" description = "" - category = "Reactive" + category = "ChemicalEquilibrium" valid_substances = ["H2O"] async def run(self): @@ -69,7 +69,7 @@ class SecondDummyAdapter(base.BaseAdapter): model_id = "dummy_2" display_name = "Dummy Model" description = "" - category = "PhaseTransition" + category = "PhaseEquilibrium" valid_substances = ["H2O"] async def run(self): @@ -88,7 +88,7 @@ class FailingDummyAdapter(base.BaseAdapter): model_id = "failing_dummy" display_name = "Failing Dummy Model" description = "" - category = "Reactive" + category = "ChemicalEquilibrium" valid_substances = ["H2O"] async def run(self): @@ -209,7 +209,7 @@ async def run(self): "accessError": None, "displayName": "Dummy Model", "description": "", - "category": "Reactive", + "category": "ChemicalEquilibrium", "modelId": "dummy", "parameters": { # Notice that it's "someField" and not "some_field" diff --git a/frontend/src/components/Simulation/ModelSelect.tsx b/frontend/src/components/Simulation/ModelSelect.tsx index dbe8d08b..f89e7c18 100644 --- a/frontend/src/components/Simulation/ModelSelect.tsx +++ b/frontend/src/components/Simulation/ModelSelect.tsx @@ -76,12 +76,12 @@ const ModelSelect: React.FC<{ return ( <> - Reactive Models + Chemical Equilibrium
{models - .filter((model) => model.category === "Reactive") + .filter((model) => model.category === "ChemicalEquilibrium") .map((model, index) => { const isActive = selectedModels.some((m) => m.modelId === model.modelId); return ( @@ -95,11 +95,11 @@ const ModelSelect: React.FC<{ })}
- Phase Transition Models + Phase Equilibrium
{models - .filter((model) => model.category === "PhaseTransition") + .filter((model) => model.category === "PhaseEquilibrium") .map((model, index) => { const isActive = selectedModels.some((m) => m.modelId === model.modelId); return ( diff --git a/frontend/src/components/Simulation/Results.tsx b/frontend/src/components/Simulation/Results.tsx index ed1a9281..f2cb3887 100644 --- a/frontend/src/components/Simulation/Results.tsx +++ b/frontend/src/components/Simulation/Results.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { Tabs, Typography } from "@equinor/eds-core-react"; +import { Accordion, Tabs, Typography } from "@equinor/eds-core-react"; import { useState } from "react"; import { Panel, SimulationResults } from "@/dto/SimulationResults"; import PhaseResultTable from "@/components/Simulation/PhaseResultTable"; @@ -8,6 +8,7 @@ import { MassBalanceError } from "@/components/Simulation/MassBalanceError"; import { extractPlotData } from "@/functions/Formatting"; import BarChart from "@/components/BarChart"; import GenericTable from "@/components/GenericTable"; +import { useAvailableModels } from "@/contexts/ModelContext"; interface ResultsProps { simulationResults?: SimulationResults; @@ -53,21 +54,21 @@ function getPanelContent(panel: Panel): React.ReactElement { } } -const Results: React.FC = ({ simulationResults }) => { - const [activeTab, setActiveTab] = useState(0); - - const handleChange = (index: string | number) => { - setActiveTab(index); - }; +interface ModelResultTabsProps { + simulationResults: SimulationResults; + modelIndices: number[]; +} - if (!simulationResults) return No simulation results found; +const ModelResultTabs: React.FC = ({ simulationResults, modelIndices }) => { + const [activeTab, setActiveTab] = useState(0); const panelTabs: string[] = []; const panelContents: React.ReactElement[] = []; - simulationResults.results.forEach((result, modelIndex) => { + for (const modelIndex of modelIndices) { + const result = simulationResults.results[modelIndex]; const modelId = simulationResults.input.models[modelIndex]?.modelId || `Model ${modelIndex + 1}`; - const modelPrefix = simulationResults.results.length > 1 ? `${modelId}: ` : ""; + const modelPrefix = modelIndices.length > 1 ? `${modelId}: ` : ""; const initialConcentrations = simulationResults.input.concentrations; const phasesWithConcentrations = result.phases.filter((phase) => Object.keys(phase.concentrations).length > 0); @@ -100,29 +101,70 @@ const Results: React.FC = ({ simulationResults }) => { {getPanelContent(panel)} ); } + } + + if (panelTabs.length === 0) return null; + + return ( + setActiveTab(index)}> + + {panelTabs.map((label, index) => ( + {label} + ))} + + + {panelContents.map((content, index) => ( + {content} + ))} + + + ); +}; + +const Results: React.FC = ({ simulationResults }) => { + const { models } = useAvailableModels(); + + if (!simulationResults) return No simulation results found; + + const sections: { category: string; modelNames: string[]; indices: number[] }[] = []; + + simulationResults.input.models.forEach((inputModel, index) => { + const modelConfig = models.find((m) => m.modelId === inputModel.modelId); + const category = modelConfig?.category ?? "Results"; + const displayName = modelConfig?.displayName ?? inputModel.modelId; + const existing = sections.find((s) => s.category === category); + if (existing) { + existing.indices.push(index); + existing.modelNames.push(displayName); + } else { + sections.push({ category, modelNames: [displayName], indices: [index] }); + } }); return ( - <> - - - {panelTabs.map((label, index) => ( - {label} - ))} - Raw JSON - - - {panelContents.map((content, index) => ( - {content} - ))} - -
-
{JSON.stringify(simulationResults, null, 2)}
-
-
-
-
- +
+ {sections.map((section, i) => ( + + + {`${section.category}: ${section.modelNames.join(", ")}`} + + + + + + ))} + + + + Raw JSON + +
+                            {JSON.stringify(simulationResults, null, 2)}
+                        
+
+
+
+
); }; diff --git a/frontend/src/dto/FormConfig.ts b/frontend/src/dto/FormConfig.ts index 1b61c84b..12258f2c 100644 --- a/frontend/src/dto/FormConfig.ts +++ b/frontend/src/dto/FormConfig.ts @@ -19,6 +19,6 @@ export const ModelConfig = z.object({ validSubstances: z.array(z.string()), parameters: z.record(z.string(), ParameterConfig), description: z.string(), - category: z.enum(["Reactive", "PhaseTransition"]), + category: z.enum(["ChemicalEquilibrium", "PhaseEquilibrium"]), }); export type ModelConfig = z.infer; diff --git a/frontend/src/functions/Filtering.ts b/frontend/src/functions/Filtering.ts index 71bbc558..ceabde8a 100644 --- a/frontend/src/functions/Filtering.ts +++ b/frontend/src/functions/Filtering.ts @@ -6,7 +6,7 @@ export const filterValidModels = (experiment: ExperimentResult, models: ModelCon Object.entries(experiment.initialConcentrations).filter(([, value]) => Number(value) !== 0) ); return models - .filter((model) => model.category === "Reactive") + .filter((model) => model.category === "ChemicalEquilibrium") .filter((model) => Object.entries(filteredConcs).every(([key]) => model.validSubstances.includes(key))); }; diff --git a/frontend/src/pages/LabResults.tsx b/frontend/src/pages/LabResults.tsx index f9fa2f37..f47b948c 100644 --- a/frontend/src/pages/LabResults.tsx +++ b/frontend/src/pages/LabResults.tsx @@ -21,7 +21,7 @@ import { SimulationResults } from "@/dto/SimulationResults"; import { MainContainer } from "@/components/styles"; const defaultModels = (models: ModelConfig[]) => - new Set(models.filter((m) => m.category === "Reactive").map((m) => m.modelId)); + new Set(models.filter((m) => m.category === "ChemicalEquilibrium").map((m) => m.modelId)); const LabResults: React.FC = () => { const [selectedExperiments, setSelectedExperiments] = useState([]); @@ -133,7 +133,7 @@ const LabResults: React.FC = () => {
{models - .filter((model) => model.category === "Reactive") + .filter((model) => model.category === "ChemicalEquilibrium") .map((model) => ( { - if (a.category === "Reactive" && b.category === "PhaseTransition") return -1; - if (a.category === "PhaseTransition" && b.category === "Reactive") return 1; + if (a.category === "ChemicalEquilibrium" && b.category === "PhaseEquilibrium") return -1; + if (a.category === "PhaseEquilibrium" && b.category === "ChemicalEquilibrium") return 1; return 0; }); } diff --git a/frontend/tests/functions/Filtering.test.tsx b/frontend/tests/functions/Filtering.test.tsx index a0e503f4..c507a8d1 100644 --- a/frontend/tests/functions/Filtering.test.tsx +++ b/frontend/tests/functions/Filtering.test.tsx @@ -11,7 +11,7 @@ describe("filterValidModels", () => { validSubstances: ["CO", "H2O"], parameters: {}, description: "Test model", - category: "Reactive", + category: "ChemicalEquilibrium", }, { modelId: "model2", @@ -19,7 +19,7 @@ describe("filterValidModels", () => { validSubstances: ["COS", "H2O", "CH4", "N2"], parameters: {}, description: "Test model", - category: "Reactive", + category: "ChemicalEquilibrium", }, { modelId: "model3", @@ -27,7 +27,7 @@ describe("filterValidModels", () => { validSubstances: ["CO", "H2O"], parameters: {}, description: "Test model", - category: "PhaseTransition", + category: "PhaseEquilibrium", }, { modelId: "model4", @@ -35,7 +35,7 @@ describe("filterValidModels", () => { validSubstances: ["COS", "H2O"], parameters: {}, description: "Test model", - category: "Reactive", + category: "ChemicalEquilibrium", }, { modelId: "model5", @@ -43,7 +43,7 @@ describe("filterValidModels", () => { validSubstances: ["O2", "N2", "H2O"], parameters: {}, description: "Test model", - category: "Reactive", + category: "ChemicalEquilibrium", }, ]; @@ -85,7 +85,7 @@ describe("filterValidModels", () => { it("filters models by Reaction category", () => { const result = filterValidModels(mockExperiment[0], mockModels); console.log(result); - expect(result.every((model) => model.category === "Reactive")).toBe(true); + expect(result.every((model) => model.category === "ChemicalEquilibrium")).toBe(true); }); it("filters models by valid substances", () => { @@ -112,6 +112,6 @@ describe("filterValidModels", () => { it("handles experiment with all zero concentrations", () => { const result = filterValidModels(mockExperiment[3], mockModels); - expect(result.every((model) => model.category === "Reactive")).toBe(true); + expect(result.every((model) => model.category === "ChemicalEquilibrium")).toBe(true); }); }); diff --git a/frontend/tests/hooks/useSimulationQueriesResult.test.tsx b/frontend/tests/hooks/useSimulationQueriesResult.test.tsx index 4bb73859..d9eb6bb4 100644 --- a/frontend/tests/hooks/useSimulationQueriesResult.test.tsx +++ b/frontend/tests/hooks/useSimulationQueriesResult.test.tsx @@ -31,7 +31,7 @@ const mockModels: ModelConfig[] = [ validSubstances: ["CO2", "H2O"], parameters: { Temperature: { default: 300 }, Pressure: { default: 1 } }, description: "Test model", - category: "Reactive", + category: "ChemicalEquilibrium", }, { modelId: "model2", @@ -39,7 +39,7 @@ const mockModels: ModelConfig[] = [ validSubstances: ["CO2", "H2O", "CH4"], parameters: {}, description: "Test model", - category: "Reactive", + category: "ChemicalEquilibrium", }, ];