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
+69
View File
@@ -0,0 +1,69 @@
import importlib.util
from datetime import date, datetime, timedelta, timezone
from pathlib import Path
import pytest
def _load_time_module():
module_path = Path(__file__).resolve().parents[2] / "app" / "domain" / "time.py"
spec = importlib.util.spec_from_file_location("tests_domain_time", module_path)
module = importlib.util.module_from_spec(spec)
assert spec and spec.loader
spec.loader.exec_module(module)
return module
def test_parse_utc_time_rejects_naive_datetimes():
module = _load_time_module()
with pytest.raises(ValueError, match="timezone information"):
module.parse_utc_time("2025-01-01T08:00:00")
def test_parse_utc_time_normalizes_offset_datetime_to_utc():
module = _load_time_module()
result = module.parse_utc_time("2025-01-01T08:00:00+08:00")
assert result == datetime(2025, 1, 1, 0, 0, tzinfo=timezone.utc)
def test_extract_date_keeps_original_offset_calendar_day():
module = _load_time_module()
result = module.extract_date("2025-01-01T00:30:00+08:00")
assert result == date(2025, 1, 1)
def test_utc_now_returns_timezone_aware_utc_datetime():
module = _load_time_module()
result = module.utc_now()
assert result.tzinfo == timezone.utc
assert result.utcoffset() == timedelta(0)
@pytest.mark.parametrize(
("clock", "expected_seconds"),
[
("1:00", 3600),
("01:00", 3600),
("0:05", 300),
("0:05:00", 300),
("24:00", 86400),
],
)
def test_parse_clock_duration_seconds_accepts_epanet_clock_formats(
clock, expected_seconds
):
module = _load_time_module()
assert module.parse_clock_duration_seconds(clock) == expected_seconds
@pytest.mark.parametrize("clock", ["bad", "1:60", "1:00:60", "-1:00"])
def test_parse_clock_duration_seconds_rejects_invalid_clock_formats(clock):
module = _load_time_module()
with pytest.raises(ValueError):
module.parse_clock_duration_seconds(clock)