Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 129 additions & 0 deletions src/main/java/neqsim/process/util/report/RunAndReportFacade.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package neqsim.process.util.report;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import neqsim.process.equipment.ProcessEquipmentInterface;
import neqsim.process.processmodel.ProcessSystem;
import neqsim.process.util.report.RunAndReportRequest.RunMode;
import neqsim.thermo.system.SystemInterface;

/**
* Facade that runs a flowsheet and emits a predictable KPI + provenance payload.
*/
public class RunAndReportFacade {
private static final Logger logger = LogManager.getLogger(RunAndReportFacade.class);

/**
* Execute a steady-state or transient run, capture KPIs and provenance, and serialize a report.
*
* @param system flowsheet to execute
* @param request configuration for the run
* @return standardized result payload
*/
public RunReportResult runAndReport(ProcessSystem system, RunAndReportRequest request) {
if (system == null) {
throw new IllegalArgumentException("Process system cannot be null");
}
RunAndReportRequest effectiveRequest = request == null ? new RunAndReportRequest() : request;

String flowsheetHash = computeFlowsheetHash(system, effectiveRequest.getHashConfig());
UUID runId = effectiveRequest.getRunId();

if (effectiveRequest.getRunMode() == RunMode.TRANSIENT) {
executeTransient(system, effectiveRequest, runId);
} else {
system.run(runId);
}

Map<String, Object> kpis = collectKpis(system);
RunReportResult.Provenance provenance = new RunReportResult.Provenance(flowsheetHash,
effectiveRequest.getTemplateId(), collectThermoModelVersions(system));

String serializedReport = new Report(system).generateJsonReport(effectiveRequest.getReportConfig());

return new RunReportResult(effectiveRequest.getRunMode(), runId, kpis, provenance,
serializedReport);
}

private void executeTransient(ProcessSystem system, RunAndReportRequest request, UUID runId) {
for (int step = 0; step < request.getTransientSteps(); step++) {
system.runTransient(request.getTimeStep(), runId);
}
}

private Map<String, Object> collectKpis(ProcessSystem system) {
Map<String, Object> kpis = new LinkedHashMap<>();

kpis.put("unitOperationCount", system.getUnitOperations().size());
kpis.put("power_kW", system.getPower("kW"));
kpis.put("heaterDuty_kW", system.getHeaterDuty("kW"));
kpis.put("coolerDuty_kW", system.getCoolerDuty("kW"));

double exergyChange = system.getExergyChange("J");
kpis.put("exergyChange_kJ", exergyChange / 1.0e3);
kpis.put("maxMassBalanceError_percent", calculateMaxMassBalanceError(system));

return kpis;
}

private double calculateMaxMassBalanceError(ProcessSystem system) {
double maxError = Double.NaN;
try {
for (ProcessSystem.MassBalanceResult result : system.checkMassBalance().values()) {
double percentError = result.getPercentError();
if (Double.isNaN(percentError)) {
continue;
}
if (Double.isNaN(maxError) || Math.abs(percentError) > maxError) {
maxError = Math.abs(percentError);
}
}
} catch (Exception ex) {
logger.warn("Unable to calculate mass balance KPI", ex);
}
return maxError;
}

private List<String> collectThermoModelVersions(ProcessSystem system) {
Set<String> versions = new LinkedHashSet<>();
for (ProcessEquipmentInterface unit : system.getUnitOperations()) {
try {
SystemInterface thermo = unit.getThermoSystem();
if (thermo != null) {
String modelName = thermo.getModelName();
String implementationVersion = thermo.getClass().getPackage().getImplementationVersion();
if (implementationVersion != null && !implementationVersion.isEmpty()) {
versions.add(modelName + "@" + implementationVersion);
} else {
versions.add(modelName);
}
}
} catch (Exception ex) {
logger.debug("Unable to read thermo model from unit {}", unit.getName(), ex);
}
}
return new ArrayList<>(versions);
}

private String computeFlowsheetHash(ProcessSystem system, ReportConfig hashConfig) {
String report = new Report(system).generateJsonReport(hashConfig);
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(report.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(hash);
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("SHA-256 algorithm not available", e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package neqsim.process.util.report;

import java.util.UUID;

/**
* Configuration for the run-and-report facade.
*/
public class RunAndReportRequest {
/** Supported run modes. */
public enum RunMode {
/** Execute a steady-state calculation. */
STEADY_STATE,
/** Execute a transient calculation for a configured number of steps. */
TRANSIENT
}

private RunMode runMode = RunMode.STEADY_STATE;
private int transientSteps = 1;
private double timeStep = 1.0;
private String templateId = "";
private ReportConfig reportConfig = new ReportConfig(ReportConfig.DetailLevel.SUMMARY);
private ReportConfig hashConfig = new ReportConfig(ReportConfig.DetailLevel.MINIMUM);
private UUID runId = UUID.randomUUID();

public RunMode getRunMode() {
return runMode;
}

public RunAndReportRequest setRunMode(RunMode runMode) {
this.runMode = runMode;
return this;
}

public int getTransientSteps() {
return transientSteps;
}

public RunAndReportRequest setTransientSteps(int transientSteps) {
if (transientSteps < 1) {
throw new IllegalArgumentException("Transient steps must be at least 1");
}
this.transientSteps = transientSteps;
return this;
}

public double getTimeStep() {
return timeStep;
}

public RunAndReportRequest setTimeStep(double timeStep) {
if (timeStep <= 0.0) {
throw new IllegalArgumentException("Time step must be positive");
}
this.timeStep = timeStep;
return this;
}

public String getTemplateId() {
return templateId;
}

public RunAndReportRequest setTemplateId(String templateId) {
this.templateId = templateId == null ? "" : templateId;
return this;
}

public ReportConfig getReportConfig() {
return reportConfig;
}

public RunAndReportRequest setReportConfig(ReportConfig reportConfig) {
if (reportConfig != null) {
this.reportConfig = reportConfig;
}
return this;
}

public ReportConfig getHashConfig() {
return hashConfig;
}

public RunAndReportRequest setHashConfig(ReportConfig hashConfig) {
if (hashConfig != null) {
this.hashConfig = hashConfig;
}
return this;
}

public UUID getRunId() {
return runId;
}

public RunAndReportRequest setRunId(UUID runId) {
if (runId != null) {
this.runId = runId;
}
return this;
}
}
128 changes: 128 additions & 0 deletions src/main/java/neqsim/process/util/report/RunReportResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package neqsim.process.util.report;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

/**
* Standardized payload from the run-and-report facade.
*/
public class RunReportResult {
/**
* Lightweight provenance record that can be persisted together with KPI results.
*/
public static class Provenance {
private final String flowsheetHash;
private final String templateId;
private final List<String> thermoModelVersions;

public Provenance(String flowsheetHash, String templateId, List<String> thermoModelVersions) {
this.flowsheetHash = flowsheetHash;
this.templateId = templateId;
this.thermoModelVersions = thermoModelVersions;
}

public String getFlowsheetHash() {
return flowsheetHash;
}

public String getTemplateId() {
return templateId;
}

public List<String> getThermoModelVersions() {
return thermoModelVersions;
}
}

private final RunAndReportRequest.RunMode runMode;
private final UUID runId;
private final Map<String, Object> kpis;
private final Provenance provenance;
private final String report;

public RunReportResult(RunAndReportRequest.RunMode runMode, UUID runId, Map<String, Object> kpis,
Provenance provenance, String report) {
this.runMode = runMode;
this.runId = runId;
this.kpis = new LinkedHashMap<>(kpis);
this.provenance = provenance;
this.report = report;
}

public RunAndReportRequest.RunMode getRunMode() {
return runMode;
}

public UUID getRunId() {
return runId;
}

public Map<String, Object> getKpis() {
return Collections.unmodifiableMap(kpis);
}

public Provenance getProvenance() {
return provenance;
}

public String getReport() {
return report;
}

/**
* Convert the result into a JSON string using a predictable schema.
*
* @return JSON representation of the run result
*/
public String toJson() {
Gson gson = new GsonBuilder().serializeSpecialFloatingPointValues().setPrettyPrinting().create();
return gson.toJson(toJsonObject());
}

/**
* Convert the result to a JSON object for further processing.
*
* @return structured JSON object
*/
public JsonObject toJsonObject() {
JsonObject obj = new JsonObject();
obj.addProperty("runMode", runMode.name());
obj.addProperty("runId", runId.toString());

JsonObject kpiObject = new JsonObject();
for (Map.Entry<String, Object> entry : kpis.entrySet()) {
Object value = entry.getValue();
if (value instanceof Number) {
kpiObject.addProperty(entry.getKey(), (Number) value);
} else if (value instanceof Boolean) {
kpiObject.addProperty(entry.getKey(), (Boolean) value);
} else {
kpiObject.addProperty(entry.getKey(), value == null ? null : value.toString());
}
}
obj.add("kpis", kpiObject);

if (provenance != null) {
JsonObject prov = new JsonObject();
prov.addProperty("flowsheetHash", provenance.getFlowsheetHash());
prov.addProperty("templateId", provenance.getTemplateId());

JsonObject thermo = new JsonObject();
int counter = 0;
for (String version : provenance.getThermoModelVersions()) {
thermo.addProperty("model" + counter++, version);
}
obj.add("provenance", prov);
prov.add("thermoModels", thermo);
}

obj.addProperty("report", report);
return obj;
}
}