refactor(backend)!: separate algorithm and data layers

Reorganize algorithm packages by business responsibility, move orchestration into services, and keep database access behind pooled repositories.

Harden analysis API validation, remove unsafe legacy simulation endpoints, and add regression and architecture boundary coverage.

BREAKING CHANGE: legacy algorithm module paths and obsolete simulation endpoints are removed.
This commit is contained in:
2026-09-04 17:30:55 +08:00
parent 9b095c7439
commit 5966d039de
91 changed files with 1418 additions and 4020 deletions
+24 -24
View File
@@ -19,38 +19,38 @@ def _empty_scada_mappings(simulation):
)
def test_run_simulation_exposes_explicit_valve_control():
def test_run_simulation_accepts_explicit_valve_control():
from app.services import simulation
assert "valve_control" in inspect.signature(simulation.run_simulation).parameters
def test_extended_runner_cleans_temporary_database_after_failure(monkeypatch):
from app.algorithms.simulation import runner
def test_valve_close_analysis_uses_normalized_scheme_type(monkeypatch):
from app.services import simulation_scenarios
lifecycle: list[tuple[str, str]] = []
captured = {}
monkeypatch.setattr(simulation_scenarios, "get_option", lambda _name: {})
monkeypatch.setattr(
simulation_scenarios,
"set_option",
lambda _name, _changes: None,
)
monkeypatch.setattr(
simulation_scenarios.simulation,
"run_simulation",
lambda **kwargs: captured.update(kwargs),
)
@contextmanager
def temporary_project(project: str, purpose: str):
lifecycle.append(("create", project))
try:
yield "isolated_project"
finally:
lifecycle.append(("delete", project))
simulation_scenarios.valve_close_analysis.__wrapped__(
name="demo",
modify_pattern_start_time="2026-01-01T00:00:00+08:00",
modify_valve_opening={"V1": 0.0},
scheme_name="valve_case",
_temporary_project="temporary_demo",
)
monkeypatch.setattr(runner, "temporary_project_database", temporary_project)
@runner._clean_extended_simulation
def fail(name, simulation_type, *, _temporary_project=None):
assert name == "demo"
assert simulation_type == "extended"
assert _temporary_project == "isolated_project"
raise RuntimeError("simulation failed")
with pytest.raises(RuntimeError, match="simulation failed"):
fail("demo", "extended")
assert lifecycle == [("create", "demo"), ("delete", "demo")]
assert captured["scheme_type"] == "valve_close_analysis"
assert captured["result_db_name"] == "demo"
def test_apply_valve_control_matches_runner_semantics(monkeypatch):