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
@@ -82,9 +82,7 @@ async def patients_of_doctor(session: AsyncSession, doctor_id: str) -> Sequence[
# --- daily check-ins and scales ---------------------------------------------------------------------
async def upsert_checkin(
session: AsyncSession, *, token: str, day: date, now: datetime, **fields: Any
) -> DailyCheckin:
async def upsert_checkin(session: AsyncSession, *, token: str, day: date, now: datetime, **fields: Any) -> DailyCheckin:
"""Merge answers into the (token, day) check-in.
The daily dialog fills the row step by step (mood, then medication, then the module question), so
@@ -93,9 +91,7 @@ async def upsert_checkin(
unknown = set(fields) - _CHECKIN_FIELDS
if unknown:
raise ValueError(f"unknown check-in fields: {sorted(unknown)}")
row = await session.scalar(
select(DailyCheckin).where(DailyCheckin.token == token, DailyCheckin.day == day)
)
row = await session.scalar(select(DailyCheckin).where(DailyCheckin.token == token, DailyCheckin.day == day))
if row is None:
row = DailyCheckin(token=token, day=day, side_effects=[], updated_at=now)
session.add(row)
@@ -107,9 +103,7 @@ async def upsert_checkin(
return row
async def list_checkins(
session: AsyncSession, token: str, since: date | None = None
) -> Sequence[DailyCheckin]:
async def list_checkins(session: AsyncSession, token: str, since: date | None = None) -> Sequence[DailyCheckin]:
stmt = select(DailyCheckin).where(DailyCheckin.token == token)
if since is not None:
stmt = stmt.where(DailyCheckin.day >= since)
@@ -176,9 +170,7 @@ async def add_scale_result(
return row
async def list_scale_results(
session: AsyncSession, token: str, scale: str | None = None
) -> Sequence[ScaleResult]:
async def list_scale_results(session: AsyncSession, token: str, scale: str | None = None) -> Sequence[ScaleResult]:
stmt = select(ScaleResult).where(ScaleResult.token == token)
if scale is not None:
stmt = stmt.where(ScaleResult.scale == scale)
@@ -210,9 +202,7 @@ async def open_alert_codes(session: AsyncSession, token: str) -> set[str]:
return set(rows.all())
async def list_alerts(
session: AsyncSession, *, token: str | None = None, open_only: bool = False
) -> Sequence[Alert]:
async def list_alerts(session: AsyncSession, *, token: str | None = None, open_only: bool = False) -> Sequence[Alert]:
stmt = select(Alert)
if token is not None:
stmt = stmt.where(Alert.token == token)