chore(core): stage 1 contracts — dialog, API schemas + fixtures, integrations, monitoring, modules
CI / ruff · pytest (pull_request) Successful in 22s

Freeze interfaces between lanes A (bot), B (backend), C (dashboard) so three developers work in
parallel without touching each other's files: dialog Event/Reply + engine registry with echo stub,
integration protocols with in-memory defaults, MonitoringService with noop stub, /api/v1 schemas
with fixture-backed routes, frozen main.py wiring, config/modules.yaml + due_scales(), DAR-5,
tzdata, Andrey trajectory, lanes in WORKFLOW, ADR-010.

Refs #34

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 15:22:25 +03:00
co-authored by Claude Opus 5.5
parent a86008f248
commit b4731fb1d6
30 changed files with 1126 additions and 89 deletions
+5 -15
View File
@@ -60,9 +60,7 @@ def test_yaml_loads_and_every_rule_has_evaluator() -> None:
def test_unknown_rule_id_rejected() -> None:
with pytest.raises(ValidationError):
RulesConfig.model_validate(
{"rules": [{"id": "nope", "actions": [{"type": "alert", "level": "info"}]}]}
)
RulesConfig.model_validate({"rules": [{"id": "nope", "actions": [{"type": "alert", "level": "info"}]}]})
def test_alert_without_level_rejected() -> None:
@@ -134,9 +132,7 @@ def test_cssrs_levels() -> None:
(None, "moderate", None, False),
],
)
def test_weapon_access_with_risk(
weapon: bool | None, cssrs: str | None, item9: int | None, fires: bool
) -> None:
def test_weapon_access_with_risk(weapon: bool | None, cssrs: str | None, item9: int | None, fires: bool) -> None:
scales = [] if item9 is None else [ScalePoint(scale="phq9", ts=at(5, 20), total=10, item9=item9)]
s = snap(5, weapon_access=weapon, cssrs_risk=cssrs, scale_results=scales)
assert ("weapon_access_risk" in codes(evaluate(s), "crisis")) is fires
@@ -188,9 +184,7 @@ def test_future_scale_results_ignored() -> None:
assert evaluate(s) == []
@pytest.mark.parametrize(
("sex", "total", "fires"), [("m", 4, True), ("m", 3, False), ("f", 3, True), ("f", 2, False)]
)
@pytest.mark.parametrize(("sex", "total", "fires"), [("m", 4, True), ("m", 3, False), ("f", 3, True), ("f", 2, False)])
def test_auditc_by_sex(sex: str, total: int, fires: bool) -> None:
s = snap(1, sex=sex, scale_results=[ScalePoint(scale="auditc", ts=at(0), total=total)])
acts = evaluate(s)
@@ -212,9 +206,7 @@ def test_quit_intent_voiced() -> None:
def test_quit_intent_followed_by_skipped_dose() -> None:
s = snap(
7, checkins=[ci(5, med_taken=True, quit_intent=True), ci(6, med_taken=True), ci(7, med_taken=False)]
)
s = snap(7, checkins=[ci(5, med_taken=True, quit_intent=True), ci(6, med_taken=True), ci(7, med_taken=False)])
assert "quit_intent" in codes(evaluate(s), "alert")
no_intent = snap(7, checkins=[ci(6, med_taken=True), ci(7, med_taken=False)])
assert "quit_intent" not in codes(evaluate(no_intent))
@@ -303,9 +295,7 @@ def test_silence_over_3_days() -> None:
assert evaluate(snap(10, last_patient_message_at=at(7, 21))) == [] # exactly 3 days
@pytest.mark.parametrize(
("age", "day", "fires"), [(23, 8, True), (24, 28, True), (25, 8, False), (23, 29, False)]
)
@pytest.mark.parametrize(("age", "day", "fires"), [(23, 8, True), (24, 28, True), (25, 8, False), (23, 29, False)])
def test_young_adult_enhanced_monitoring(age: int, day: int, fires: bool) -> None:
acts = evaluate(snap(day, age=age))
assert (acts == [a for a in acts if a.type == "enhanced_monitoring"] and bool(acts)) is fires