notebookvb

This commit is contained in:
Vladimir Buzalka
2026-06-14 08:22:25 +02:00
parent 2346ad7739
commit 9133fe9497
9 changed files with 355 additions and 295 deletions
+91 -22
View File
@@ -57,32 +57,94 @@ přesuny, odpovědi), žádný `state.json`.
počítače, na Z230 → `reporter:c:\medicus\medicus.fdb`).
- `ANTHROPIC_API_KEY` z `Medevio/.env`.
## Vytvoření požadavku v Medeviu — `medevio_recept.py`
## Vytvoření požadavku v Medeviu — `mcp_medevio.zaloz_pozadavek_recept`
Funkce pro agenta: jakmile je pacient + léky správně identifikován, založí mu
v Medeviu požadavek **„Recept na léky"**, aby ho lékař viděl (Medevio kontrolujeme
průběžně, e-mail zřídka).
Jakmile agent správně identifikuje pacienta + léky, založí mu v Medeviu požadavek
**„Recept na léky"** přesně jako by ho podal pacient v aplikaci — vyplní **oba fieldy
dotazníku** a přidá **štítek CLAUDE**. Vše v jednom volání:
```python
from medevio_recept import vytvor_recept
rid = vytvor_recept(rodne_cislo="730920/8104",
nazev_leku="Euthyrox 100 µg",
poznamka="docházejí mi léky")
import mcp_medevio
mcp_medevio.zaloz_pozadavek_recept(patient_uuid, leky="Euthyrox 100", poznamka="docházejí mi léky")
```
Co se stane (vše odchyceno z webu Medevia 2026-06-13, ověřeno na testovacím pacientovi):
1. **RČ → patient UUID** přes MySQL `medevio.medevio_pacient` (`identification_number``patient_id`).
2. `fillECRFForm` (prázdný) → `createPatientRequestWithoutReservation` → založí „Recept na léky".
3. `createClinicPatientRequestNote` → obě pole do **interní poznámky** (formátováno „Název léků / Poznámka").
4. `assignTagToPatientRequest` → štítek **CLAUDE** (`pridat_stitek=False` vypne).
Mapování (ověřeno naživo na Vladkovi 2026-06-13):
- `leky` → dotazník pole **„Název léků"** (přes ECRF field `nazev-leku`)
- `poznamka` → dotazník pole **„Poznámka"** (jde přes `userNote`**funguje** i z klinické strany!)
- `stitek=True` (default) → přiřadí **štítek CLAUDE** (`assignTagToPatientRequest`)
**Proč interní poznámka, ne dotazník:** lékařský přístup neumí vyplnit pacientský
ECRF dotazník smysluplně (z lékařské strany má jen 1 pole `nazev-leku`), proto obsah
jde do interní poznámky (viditelná jen pro ordinaci).
Postup uvnitř: `fillECRFForm` (oba fieldy, `byDoctor:False`) → `createPatientRequestWithoutReservation`
(`createdByDoctor:False`) → `assignTagToPatientRequest`. Auth: Bearer token z `Medevio/token.txt`
(auto-refresh při 401). Konstanty/mutace viz `Medevio/medevio_api_notes.md`.
Auth: Bearer token z `Medevio/token.txt` (dlouhodobý). Test: `python medevio_recept.py`
(založí testovací Recept na Vladkovi `0210db7b-…`). Pro test bez DB lookup je parametr
`patient_uuid=`. Mutace + konstanty jsou v `Medevio/medevio_api_notes.md`.
Agent (`recepty_agent.py`) volá tuto funkci automaticky po jednoznačné identifikaci
pacienta; `leky_str` z `_format_leky`, `pozn_str` z `_format_poznamka` (hlavička + zkomprimované tělo mailu).
UUID pacienta hledá `_medevio_find_patient` v MySQL `medevio_pacient` (RČ → `patient_id`).
POZN.: požadavky v Medeviu nejdou smazat, jen zavřít („Vyřídit") — proto testovat na
testovacím pacientovi Vladko (`0210db7b-…`).
## Skóre jistoty identifikace pacienta — `skore_jistoty`
Než agent založí požadavek, spočítá **skóre 0100**, jak jistě nalezený pacient
odpovídá pacientovi z mailu. Kombinuje víc nezávislých signálů; **rozpor srazí dolů**
(tím se ošetří díra, kdy shoda na RČ s překlepem trefí jiného pacienta).
| Signál (shoda) | + | Rozpor | |
|---|---|---|---|
| RČ sedí | 55 | jméno úplně jiné | 45 |
| jméno přesně / příjmení / částečně | 30 / 15 / 8 | datum narození nesedí | 35 |
| datum narození sedí | 20 | RČ nesedí na pacienta | 35 |
| e-mail odesílatele v kartotéce | 30 | | |
| telefon z mailu v kartotéce | 20 | | |
| lék v historii receptů | 10 | | |
Rozhodnutí (jediný práh `SCORE_AUTO=85`):
- **≥ 85** → založí požadavek automaticky (štítek CLAUDE).
- **< 85** → **NIC nezaloží** a místo toho se **zeptá člověka přes Telegram**
(viz níže). Důvod: vytvoření požadavku je **nevratné a hned viditelné pacientovi**
— pacienta v něm nejde přepsat ani požadavek smazat. „Založit a pak ověřit"
proto nedává smysl; ověřujeme PŘED založením.
Skóre i důvody jdou do logu. Funkce je čistá (testovatelná stubem), bez zápisů.
## Human-in-the-loop přes Telegram (nejistá identifikace)
Když je jistota < 85, agent jen zapíše dotaz do fronty a jde dál. Vyřízení dělá
samostatný proces. Moduly:
| Modul | Role |
|-------|------|
| `recept_pending.py` | fronta dotazů (`_pending_recepty.json`, atomický zápis), stavy `ceka``zalozeno`/`preskoceno` |
| `recept_dialog.py` | čistě: `format_otazka` (text do Telegramu) + `parse_odpoved` (RČ / číslo kandidáta / „ne") |
| `recept_telegram.py` | přenos přes **user účet agenta** (Telethon, `Knihovny/telegram_user.py`), vlastní session `recepty`, píše Vladovi (`6639316354`) |
| `recept_resolver.py` | proces s vlastní session: pošle otázky, krátce polluje odpovědi (přes `precti_zpravy`, since_id), podle odpovědi založí (správné RČ je definitivní) a označí mail |
Tok: e-mailový agent (vysoká jistota → založí; jinak → `recept_pending.pridej`).
Resolver: otázka z účtu agenta → odpověď jako **reply** (párování přes
`reply_to_msg_id`) → `mcp_medevio.zaloz_pozadavek_recept` správnému pacientovi
→ mail dostane `ClaudeZpracovalRecept`. Bez odpovědi záznam zůstává `ceka`
(čeká se libovolně dlouho, znovu se neptá).
Telegram infrastruktura je popsaná v Trilium „2026-06-14 Telegram — bot, user
účet agenta a MCP server". User účet (na rozdíl od bota) unese víc souběžných
sessions, každá vidí všechny zprávy → odpovědi se rozlišují přes reply.
**Jednorázový krok (uživatel, v terminálu — čeká na SMS kód):**
```
python -m Knihovny.telegram_user login --jako recepty
```
Pak spuštění resolveru: `python recept_resolver.py`
Pozn.: nová session nezná Vladovu „entitu" → posílání by spadlo na *Could not
find the input entity*. Resolver to řeší sám: na startu volá `recept_telegram.priprav()`
(`get_dialogs` → entita se uloží do session). Login lze řídit i na dálku
dvoukrokově: `login_posli_kod('recepty')` → PHONE_CODE_HASH → `login_dokonci(kod, hash, 'recepty')`.
**Ověřeno naživo 2026-06-14:** celý round-trip — agent zapsal nejistý dotaz →
resolver poslal otázku do Telegramu → Vlado odpověděl RČ jako reply → resolver
založil „Recept na léky" správnému pacientovi (dotazník Název léků + Poznámka,
štítek CLAUDE) a poslal potvrzení zpět.
## Známé limity / TODO
@@ -92,9 +154,16 @@ Auth: Bearer token z `Medevio/token.txt` (dlouhodobý). Test: `python medevio_re
do KARKONTAKT doplňovat.
- Párování jménem vyžaduje přesnou shodu množiny slov — překlepy ve jméně
nenajde (kandidát: fuzzy matching / nabídka podobných jmen).
- Zatím bez označování mailů, bez summary e-mailu, bez odpovědi pacientovi —
kandidáti na další krok (vzor: `EmailAgent/faktury_agent.py`).
- Bez idempotence (žádný state) — testovací běhy čtou vždy posledních N mailů.
- Bez summary e-mailu a bez odpovědi pacientovi — kandidáti na další krok
(vzor: `EmailAgent/faktury_agent.py`).
- **Idempotence**: po úspěšném založení požadavku se mail označí kategorií
`ClaudeZpracovalRecept` (`graph_mail.ensure_category` / `add_category`,
vyžaduje Mail.ReadWrite — ověřeno, app ho má). Při dalším běhu se takto
označené maily přeskočí (ještě před AI klasifikací). Maily čekající na
odpověď přes Telegram se přeskočí podle `recept_pending.je_mail_pending`
(znovu se neptá).
- Pozor: agent čte jen `NEWEST_N` (5) nejnovějších mailů — hloub do inboxu
nejde. Když je všech 5 nejnovějších označených, neudělá nic.
## Spuštění