feat(investigations): add event impact and work-order feedback flows
Add isolated burst and pollution impact evidence, parallel work-order workflows, persisted notifications and graph updates. Checkpoint current code. Known follow-ups remain for large pollution result previews and graceful geometry failure.
This commit is contained in:
@@ -10,7 +10,8 @@ SCENARIO_TOOLS = frozenset({
|
||||
})
|
||||
|
||||
|
||||
def calculate_scenario(name: str, parameters: dict[str, Any], *, network: str, username: str) -> dict[str, Any]:
|
||||
def calculate_scenario(name: str, parameters: dict[str, Any], *, network: str, username: str,
|
||||
model_only: bool = False) -> dict[str, Any]:
|
||||
from app.services import simulation_scenarios as scenarios
|
||||
|
||||
parameters = dict(parameters)
|
||||
@@ -18,6 +19,8 @@ def calculate_scenario(name: str, parameters: dict[str, Any], *, network: str, u
|
||||
links = parameters.pop("output_link_ids")
|
||||
start = parameters.pop("start_time")
|
||||
duration = parameters.pop("duration")
|
||||
include_impact = parameters.pop("include_impact_coverage", False)
|
||||
impact_threshold = parameters.pop("impact_threshold_mg_l", 0)
|
||||
common = dict(name=network, modify_pattern_start_time=start.isoformat(),
|
||||
modify_total_duration=duration, persist_results=False, scheme_name=None)
|
||||
if name == "run_simulation":
|
||||
@@ -27,11 +30,27 @@ def calculate_scenario(name: str, parameters: dict[str, Any], *, network: str, u
|
||||
network, start, duration, output_node_ids=nodes, output_link_ids=links,
|
||||
)
|
||||
elif name == "run_burst_simulation":
|
||||
boundary = {}
|
||||
if model_only:
|
||||
from app.infra.db.postgresql.scada import ScadaElementMappings
|
||||
# Replay scenarios use current model assumptions, never mutable live
|
||||
# SCADA readings in place of the investigation's frozen observations.
|
||||
boundary["scada_mappings"] = ScadaElementMappings(
|
||||
reservoirs={}, tanks={}, fixed_pumps={}, variable_pumps={},
|
||||
pressure={}, demand={}, quality={},
|
||||
)
|
||||
result = scenarios.burst_analysis(**common, username=username,
|
||||
burst_ID=parameters["burst_pipe_ids"], burst_size=parameters["orifice_areas_cm2"])
|
||||
burst_ID=parameters["burst_pipe_ids"], burst_size=parameters["orifice_areas_cm2"], **boundary)
|
||||
elif name == "run_contaminant_simulation":
|
||||
boundary = {}
|
||||
if model_only:
|
||||
from app.infra.db.postgresql.scada import ScadaElementMappings
|
||||
boundary["scada_mappings"] = ScadaElementMappings(
|
||||
reservoirs={}, tanks={}, fixed_pumps={}, variable_pumps={},
|
||||
pressure={}, demand={}, quality={},
|
||||
)
|
||||
result = scenarios.contaminant_simulation(**common, username=username,
|
||||
source=parameters["source"], concentration=parameters["concentration"], source_pattern=parameters.get("pattern"))
|
||||
source=parameters["source"], concentration=parameters["concentration"], source_pattern=parameters.get("pattern"), **boundary)
|
||||
elif name == "analyze_valve_closure":
|
||||
result = scenarios.valve_close_analysis(**common,
|
||||
modify_valve_opening={valve: 0.0 for valve in parameters["valves"]})
|
||||
@@ -62,7 +81,7 @@ def calculate_scenario(name: str, parameters: dict[str, Any], *, network: str, u
|
||||
raise ValueError("Scenario did not return calculation results")
|
||||
selected_nodes = [row for row in result.get("node_results", []) if str(row.get("node")) in nodes]
|
||||
selected_links = [row for row in result.get("link_results", []) if str(row.get("link")) in links]
|
||||
return {
|
||||
output = {
|
||||
"input_source": "online_model_simulation" if name == "run_simulation" else "isolated_model_simulation",
|
||||
"persisted_baseline": name == "run_simulation", "persisted_scheme": False,
|
||||
"start_time": start.isoformat(), "duration_seconds": duration,
|
||||
@@ -72,3 +91,15 @@ def calculate_scenario(name: str, parameters: dict[str, Any], *, network: str, u
|
||||
"missing_nodes": sorted(set(nodes) - {str(row["node"]) for row in selected_nodes}),
|
||||
"missing_links": sorted(set(links) - {str(row["link"]) for row in selected_links})},
|
||||
}
|
||||
if name == "run_burst_simulation" and include_impact:
|
||||
from app.services.burst_impact import estimate_burst_impact
|
||||
impact = estimate_burst_impact(network, start, result, model_only=model_only)
|
||||
impact["coverage"] = {**output["coverage"], **impact.get("coverage", {})}
|
||||
output.update(impact)
|
||||
if name == "run_contaminant_simulation" and include_impact:
|
||||
from app.services.contaminant_impact import estimate_contaminant_impact
|
||||
impact = estimate_contaminant_impact(network, start, duration, result,
|
||||
threshold_mg_l=impact_threshold, model_only=model_only)
|
||||
impact["coverage"] = {**output["coverage"], **impact.get("coverage", {})}
|
||||
output.update(impact)
|
||||
return output
|
||||
|
||||
Reference in New Issue
Block a user