feat(integrations): demo adapters for МИС, connect-code identity, ГРЛС excerpts, doctor notifier
CI / ruff · pytest (pull_request) Successful in 1m26s
CI / ruff · pytest (pull_request) Successful in 1m26s
- setup() at package import (idempotent, replaces only base.py defaults): MisMock, ConnectCodeIdentity, GrlsExcerpts, CompositeNotifier; outbox stays MemoryOutbox - MisMock: clinical DB prescriptions, fallback data/meds/mis_prescriptions.json (synthetic, by token) - ConnectCodeIdentity: one-time connect code via identity_repo, 15 min grace for the double check in one connect flow (Telegram /start + engine `new` state) - GrlsExcerpts: synthetic paraphrased instruction excerpts for sertraline, paroxetine, escitalopram, fluoxetine, mirtazapine, prazosin - CompositeNotifier: in-memory feed (recent(since_id)) + Telegram doctor chat, PII-free, never raises - test_llm_med_qa: default-registry test now patches NoDrugInfo explicitly Refs #14 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
"""Demo adapters behind the frozen integration Protocols (SPEC §9, #14).
|
||||
|
||||
`setup()` runs when the package is first imported. Every consumer imports `app.integrations.base`, which
|
||||
initialises this package first, and `app/main.py` is a frozen contract — so the registry is wired before
|
||||
any request or job without a startup hook. Setup only constructs objects: data files and DB sessions are
|
||||
opened lazily on the first call, so importing the package never fails on I/O.
|
||||
|
||||
Registry after setup:
|
||||
- `prescriptions` — `MisMock`: clinical DB `prescriptions`, then `data/meds/mis_prescriptions.json`;
|
||||
- `identity` — `ConnectCodeIdentity`: one-time connect code from the doctor (ЕСИА stand-in);
|
||||
- `drug_info` — `GrlsExcerpts`: `data/meds/grls/*.yaml`;
|
||||
- `notifier` — `CompositeNotifier`: in-memory dashboard feed + optional Telegram doctor chat;
|
||||
- `outbox` — unchanged `MemoryOutbox` (the web console drains it; the Telegram channel wraps it at start).
|
||||
"""
|
||||
|
||||
from app.integrations.base import (
|
||||
EmptyPrescriptions,
|
||||
MemoryNotifier,
|
||||
NoDrugInfo,
|
||||
RejectIdentity,
|
||||
integrations,
|
||||
)
|
||||
from app.integrations.connect_code import ConnectCodeIdentity
|
||||
from app.integrations.grls import GrlsExcerpts
|
||||
from app.integrations.mis_mock import MisMock
|
||||
from app.integrations.notify import CompositeNotifier, NotificationFeed
|
||||
|
||||
__all__ = ["CompositeNotifier", "ConnectCodeIdentity", "GrlsExcerpts", "MisMock", "NotificationFeed", "setup"]
|
||||
|
||||
_done = False
|
||||
|
||||
|
||||
def setup(*, force: bool = False) -> None:
|
||||
"""Register the demo adapters. Idempotent; never replaces an adapter someone already swapped in
|
||||
(a test fake, the Telegram outbox), only the in-memory defaults from `base.py` — unless `force`."""
|
||||
global _done
|
||||
if _done and not force:
|
||||
return
|
||||
if force or isinstance(integrations.prescriptions, EmptyPrescriptions):
|
||||
integrations.prescriptions = MisMock()
|
||||
if force or isinstance(integrations.identity, RejectIdentity):
|
||||
integrations.identity = ConnectCodeIdentity()
|
||||
if force or isinstance(integrations.drug_info, NoDrugInfo):
|
||||
integrations.drug_info = GrlsExcerpts()
|
||||
# Exact type check: MemoryNotifier subclasses used as test sinks must stay in place.
|
||||
if force or type(integrations.notifier) is MemoryNotifier:
|
||||
integrations.notifier = CompositeNotifier()
|
||||
_done = True
|
||||
|
||||
|
||||
setup()
|
||||
|
||||
Reference in New Issue
Block a user