Czego chce zamawiający (z rozmów 7–8 września 2026, w jego kolejności), a niżej jego słowa dosłownie:
„powinienem mieć interfejs, w który mogę sobie wkładać swoje wyniki na przykład z poboru krwi [...] chcę mieć jebutną bazkę danych w którą mogę lądować swoje dowolne kwity medyczne z dowolnych źródeł, podstawowe to takie z ręki, dalej z labów (to trzeba przez RPA) jak na przykład synevo ale też z NFZtu (spięcie z P1) i też z zegarków, apek [...] na pewno na razie z apple watcha bo to mam i apki zdrowie ios”
„powinien być z tym wszystkim jakiś za ZT-cf schowany portalik webowy no i jakieś histogramy, trendy per pomiar/odczyt, docelowo również algorytmy medyczne po interfejsie wystawionym przez [silnik algorytmów Wellysy] [...] powinienem móc udostępniać moje wybrane wyniki wybranym userkom [...] powinienem móc ustalić scope jaki kto ma widzieć [...] apka webowa+telegram, tę telegramową osadź sobie jako swojemu snow-tg-botowi”
„chcę jeszcze móc zdjęcia wgrywać, żeby mi to OCRowało”
„najpierw ojebiemy web, z tego weba apkę TG, a później popakujemy to chujstwo ionikiem w apki mobilne [...] mamy już konto apple, i tak, skoro tylko przez apkę iOS można pobierać zegarek, to robimy apkę iOS”
„synevo to jest must — widzę to tak, że z interfejsu apkowego operator wyraża intent, żeby za niego wklepać i pobrać i dostaje formularz z kodami — mam przed sobą teraz taką karteczkę z kodem qr nawet, i tam na tej stronce się wpisuje PESEL+jednorazowy-kod z tej karteczki”
„i jeszcze diagnostyka też trzeba dodać do rpa, bo z tego też mam kwit”
„integracja P1 jako usługodawca też jest w zakresie, mamy do tego już popisane api w wellysie”
„są już dostępne jakieś algorytmy medyczne prewencyjne — jak jesteś chłopem 40+ i palisz czy cośtam, to powinieneś robić badanie [...] raz na kwartał — to jest bardzo ważna funkcjonalność aplikacji, żeby ciągle aplikacja sprawdzała where im at i jakie badania powinienem robić i faktycznie mnie do tego popychała — powinna aktywnie kombinować co ze mną jest i CO DALEJ”
„na wersję drugą zrobimy — GDZIE TE BADANIA ROBIĆ jakie są procedury itp itd. na wersję trzecią zrobimy — JAK TE BADANIA FINANSOWAĆ (wallet!)”
„po prawej ręce musi być asystent na webowym wireframe”
Uwagi do briefu: mailem do Cze albo w rozmowie — wracają do specyfikacji jako kolejna wersja.
Część A (co budujemy — język wartości) i część B (jak). Wymagania FR mają priorytet MoSCoW i kryterium akceptacji „do wyklikania”.
„pomożesz mi zrobić nową, lepszą weliskę [...] powinienem mieć interfejs, w który mogę sobie wkładać swoje wyniki na przykład z poboru krwi [...] jebutną bazkę danych w którą mogę lądować swoje dowolne dokument medyczne z dowolnych źródeł, podstawowe to takie z ręki, dalej z labów (to trzeba przez RPA) jak na przykład synevo ale też z NFZtu (spięcie z P1) i też z zegarków, apek [...] na pewno na razie z apple watcha bo to mam i apki zdrowie ios [...] za ZT-cf schowany portalik webowy no i jakieś histogramy, trendy per pomiar/odczyt, docelowo również algorytmy medyczne po interfejsie wystawionym przez silnik algorytmów Wellysy [...] udostępniać moje wybrane wyniki wybranym userkom [...] scope jaki kto ma widzieć [...] apka webowa+telegram, tę telegramową osadź sobie jako swojemu Snow-tg-botowi [...] przyjmij zasadę, że 90% rzeczy już jest dostępne [...] chcę jeszcze móc zdjęcia wgrywać, żeby mi to OCRowało"
<b>Zdanie, które wyznacza sukces:</b> <i>„aplikacja ciągle sprawdza where im at i faktycznie mnie popycha"</i>. Bazka jest fundamentem, ale produkt jest udany dopiero wtedy, gdy operator <b>przestaje przepisywać wyniki</b> i <b>nie musi pamiętać, jakie badanie mu się należy</b> — aplikacja wie i mówi pierwsza.
<b>Dlaczego to jest „nowa Wellysa":</b> dzisiejsza <code>wellysa-app</code> zaczyna od ekranów (ankieta → zakup → wizyta). WELISKA zaczyna od <b>danych osoby</b> i od <b>reguł profilaktyki</b>: co mam → co z tego wynika → co dalej (v1) → gdzie (v2) → za co (v3). Pierwszy użytkownik = operator (dogfood, jak Snow z mapą Soho); po dowodzie produkt wchodzi pod P1 PHA dla użytkowników Wellysy.
| pojęcie | znaczenie w WELISCE |
|---|---|
| obserwacja | jeden odczyt: parametr (LOINC) + wartość + jednostka (UCUM) + czas + zakres referencyjny + źródło + proweniencja; kształt FHIR R4 Observation (profil HL7 EU Lab); ontologia Wellysy: pomiar+metryka+norma |
| raport | zbiór obserwacji z jednego zlecenia/wizyty/eksportu (DiagnosticReport) |
| dokument | plik źródłowy (zdjęcie, PDF, XML, CDA) w magazynie z proweniencją |
| zdarzenie zdrowotne | nie-pomiar: wizyta, procedura, szczepienie, recepta, rozpoznanie (z P1 ZM/IKP/ręki) — wejście do CO DALEJ |
| źródło | manual, ocr, lab:synevo, lab:diagnostyka, lab:alab, ikp-pdf, p1-edm, apple-health, health-connect, export-xml; każde ma tryb: ręka / intent (użytkownik wyzwala, np. Synevo z kodem) / cron / push |
| proweniencja | kto/co/kiedy wprowadził + odnośnik do dokumentu; bez niej rekord nie istnieje |
| trend | wykres jednego parametru w czasie, wiele źródeł, z zakresem ref.; wartości sprowadzone do jednostki kanonicznej |
| reguła profilaktyczna | (kryteria wejścia z profilu, badanie/procedura z kodem, interwał, źródło + data pomiaru, siła zalecenia, ważność) — z NFZ/PZH/PTD/PTL/ESC; ontologia Wellysy: wskazanie |
| temat (topic) | jedna rzecz do pilnowania (np. „kolonoskopia przesiewowa"); stan: nie dotyczy / do zrobienia / po terminie / zrobione / odłożone / zrezygnowano |
| CO DALEJ | lista tematów ze stanem + aktywne popychanie (TG/push) — rdzeń v1 |
| zakres udostępnienia (scope) | (odbiorca, parametry, okres, źródła, dokumenty?, ważność) — co druga osoba widzi |
| wskaźnik (algorytm) | wielkość liczona z obserwacji przez silnik algorytmów Wellysy (HOMA-IR, SCORE2…) — informacja z proweniencją, nie wyrok |
| HITL | człowiek zatwierdza: OCR przed zapisem, użytkownik decyzję „zrobione/odkładam", lekarz interpretację |
| rola | kto | co robi |
|---|---|---|
| operator / pierwszy użytkownik | Cze | wkłada wyniki, wyzwala pobrania (Synevo), ogląda trendy i CO DALEJ, udostępnia; akceptuje SPEC i etapy |
| CTO-czak (buduje) | Snow | architektura, kod, testy, ops, zgłoszenie; nie ogląda danych bez potrzeby (audit) |
| odbiorca udostępnienia | osoby wskazane przez operatora (email w CF Access); docelowo lekarz | widzą TYLKO scope |
| silnik algorytmów | silnik algorytmów Wellysy (wsa/silnik) | liczy wskaźniki; kontrakt |
| słowniki | słownik medyczny | LOINC/UCUM/ICD-9-PL przez slownik_lookup |
| podmiot leczniczy dla P1 | Wellysa (Wellysa/p1, wellysa-api) | EDM/ZM pacjenta za zgodą — jako usługodawca (cert PROD) |
| platforma | platforma | tunel, CF Access, bot TG, magazyn plików, spec-sajt (//) |
| compliance | zespół compliance | przegląd Art. 9 przed drugim użytkownikiem |
| orzekanie | lekarz | poza produktem — produkt umożliwia, nie orzeka |
docs WELISKA C4-DEPLOYMENT)czak-wsa), silnik CO DALEJ (reguły YAML), sejf poświadczeń (kredki).| etap | co operator ZOBACZY | moduły / idee |
|---|---|---|
| 1. Bazka + ręka + trend (web) | wpisuję glukozę, widzę trend z zakresem; portal za CF Access | BAZ, REK, POR · |
| 2. Zdjęcie → tabela → zatwierdź | wrzucam zdjęcie wydruku, poprawiam tabelę, klik = w bazce, oryginał w magazynie | OCR, MAG · |
| 3. CO DALEJ (v1 rdzeń) | lista „do zrobienia" z powodem i źródłem reguły; TG mnie popycha; „zrobione/odkładam" | COD · |
| 4. Apka TG | to samo z telefonu: wpis, zdjęcie, trend, CO DALEJ, powiadomienia | TG · |
| 6. P1 przez Wellysę | zgoda → moje dokumenty z EDM (wyniki, wypisy) lądują w bazce; IKP = drop PDF | P1 · |
| 7. Udostępniam zakres | wskazuję osobę i co widzi; test negatywny: reszta niewidoczna | UDO · |
| 8. Apka iOS (Ionic) + Watch w tle | tętno/HRV/sen/kroki/SpO2/waga wpadają same; potem Android (Health Connect) | WATCH, APP · |
| 9. Wskaźniki z silnika | HOMA-IR / SCORE2 z proweniencją, lista brakujących wejść | ALG · |
| 10. Art. 9 domknięte | szyfrowanie, audit, eksport/usuń, 0 findingów PII — brama przed drugim użytkownikiem | ART9 · |
Kolejność 1→2→3→4 jest twarda (operator: web → TG → Ionic); 5–9 równolegle po decyzjach z ; 10 przed multi-user.
wellysa-app/wellysa-api — WELISKA rośnie obok; wellysa-api dostaje TYLKO rozszerzenie P1-EDM (etap 6). Spięcie z produktem = osobna krowa po dogfoodzie.<i>ID stabilne, MoSCoW (M/S/C/W), kryterium akceptacji „do wyklikania" na żywych danych operatora, trace do artefaktów fazy D (<code>[dbml: tabela]</code> <code>[api: ścieżka]</code> <code>[ekran: SCR-x]</code> <code>[test: T-…]</code>).</i>
| ID | Wymaganie | Prio | Kryterium akceptacji | Trace |
|---|---|---|---|---|
| FR-BAZ-1 | Każda obserwacja MUSI mieć kod LOINC, jednostkę UCUM, czas, źródło i proweniencję (kto/co/kiedy + odnośnik do dokumentu); rekord bez tych pól nie istnieje. | M | ✅ SELECT 100% wierszy z 5 polami; wpis bez jednostki = odmowa | [dbml: observation] [api: POST /observations] |
| FR-BAZ-2 | Duplikat (to samo źródło + kod + czas + wartość) NIE tworzy drugiego rekordu — ponowny import tego samego PDF/partii = 0 nowych wierszy. | M | ✅ import tego samego pliku 2× → licznik bez zmian | [dbml: observation.hash_dedup] [test: T-BAZ-2] |
| FR-BAZ-3 | Wartości w różnych jednostkach MUSZĄ być sprowadzane do jednostki kanonicznej parametru (mg/dL↔mmol/L) — trend nie może mieszać jednostek. | M | ✅ glukoza 5.0 mmol/L i 90 mg/dL na jednym wykresie w jednej skali | [dbml: unit_conversion, observation.value_canon] |
| FR-BAZ-4 | Kody i jednostki POWINNY pochodzić ze słownika słownik medyczny (slownik_lookup), nie z własnej kopii LOINC; brak route → lokalny subset TYLKO obserwacji labowych, oznaczony jako tymczasowy. | S | ✅ podpowiedź parametru zwraca kod z proweniencją słownik medyczny:… | [dbml: metric.provenance] [api: GET /metrics/search] |
| FR-BAZ-5 | Bazka MUSI trzymać zdarzenia zdrowotne (wizyta, procedura, szczepienie, recepta, rozpoznanie) obok pomiarów — bo CO DALEJ liczy interwały od nich. | M | ✅ szczepienie Tdap z datą widoczne i użyte przez temat „tężec/krztusiec" | [dbml: health_event] |
| ID | Wymaganie | Prio | Kryterium akceptacji | Trace |
|---|---|---|---|---|
| FR-REK-1 | Użytkownik MUSI móc wpisać wynik w web i w TG (/wynik glukoza 92 mg/dL 2026-09-07) z podpowiedzią parametru i walidacją jednostki. | M | ✅ zła jednostka = odmowa z podpowiedzią; poprawny wpis w trendzie < 5 s | [ekran: SCR-WPIS] [api: POST /observations] |
| FR-REK-2 | Wpis ręczny MUSI móc nieść zakres referencyjny i nazwę labu (jak na wydruku). | S | ✅ flaga poza-zakresem liczona z wpisanego zakresu | [dbml: observation.ref_*] |
| FR-REK-3 | Użytkownik POWINIEN móc poprawić/usunąć własny wpis; poprawka zostawia ślad w audicie. | S | ✅ edycja widoczna w access_log | [dbml: access_log] |
| ID | Wymaganie | Prio | Kryterium akceptacji | Trace |
|---|---|---|---|---|
| FR-OCR-1 | System MUSI przyjąć zdjęcie (JPG/HEIC/PNG) i PDF z web/TG/apki, zapisać oryginał z proweniencją i uruchomić OCR z rozpoznaniem TABELI. | M | ✅ zdjęcie z telefonu → oryginał w magazynie + propozycja tabeli | [api: POST /documents] [ekran: SCR-WGRAJ] |
| FR-OCR-2 | Propozycja MUSI pokazać per wiersz: nazwę z wydruku, dopasowany parametr (LOINC), wartość, jednostkę, zakres i pewność; niepewne wiersze oznaczone. | M | ✅ wiersz z pewnością < 0.8 podświetlony do sprawdzenia | [api: GET /documents/{id}/ocr] |
| FR-OCR-3 | NIC nie trafia do bazki bez zatwierdzenia użytkownika; użytkownik poprawia wiersze, może zapamiętać mapowanie nazwa-lab → LOINC. | M | ✅ 0 rekordów bez kliku; drugi wydruk tego samego labu mapuje się sam | [api: POST /documents/{id}/confirm] [dbml: lab_name_map] |
| FR-OCR-4 | Jakość: na 10–20 żywych wydrukach operatora ≥ 90% komórek poprawnych PRZED poprawką (miara KR1). | M | ✅ macierz w TEST-DESIGN, wynik w kwicie | [test: T-OCR-4] |
| FR-OCR-5 | OCR domyślnie self-host (PaddleOCR PP-StructureV3, CPU, dane nie opuszczają boxa); fallback płatny TYLKO w regionie EU i TYLKO po decyzji . | M | ✅ konfiguracja bez fallbacku działa; wyjście poza EU niemożliwe z konfiguracji | [c4: api.ocr] |
| ID | Wymaganie | Prio | Kryterium akceptacji | Trace |
|---|---|---|---|---|
| FR-COD-1 | System MUSI prowadzić profil (płeć, data ur., palenie z paczkolatami, wzrost/waga, wywiad rodzinny, choroby przewlekłe, ciąża) z historią zmian — wejście reguł. | M | ✅ zmiana „palę → rzuciłem 2024" widoczna w historii i zmienia listę | [dbml: person.profile_json, profile_history] [ekran: SCR-PROFIL] |
| FR-COD-2 | System MUSI trzymać reguły profilaktyczne jako dane (nie kod): kryteria wejścia, badanie/procedura z kodem, interwał, źródło + data pomiaru, siła zalecenia, valid_from/review_by. Zestaw v1 = programy NFZ (Moje Zdrowie, mammografia, szyjka/HPV, kolonoskopia, ChUK, LDCT od 2026-10), szczepienia PSO, PTD (glukoza), PTL (lipidogram), ESC SCORE2. | M | ✅ ≥20 reguł z URL-em źródła i datą; reguła po review_by = flaga „do przeglądu" | [dbml: rule] |
| FR-COD-3 | Dla każdego tematu system MUSI wyliczać stan: nie dotyczy / do zrobienia / po terminie / zrobione / odłożone / zrezygnowano — z dowodem (obserwacja/zdarzenie), datą następnego terminu i powodem („M, 44 lata, 20 paczkolat → LDCT co 12 mies."). | M | ✅ operator widzi swoją listę z powodem i źródłem przy każdej pozycji | [dbml: maintenance_state] [api: GET /codalej] [ekran: SCR-COD] |
| FR-COD-4 | Ewaluacja MUSI odpalać się po każdym imporcie/wpisie i raz dziennie; nowy wynik (np. lipidogram) automatycznie zamyka temat i wylicza następny termin. | M | ✅ import lipidogramu → temat „lipidogram" = zrobione, next_due = +3 lata (50+) | [c4: api.rules] [test: T-COD-4] |
| FR-COD-5 | System MUSI popychać: powiadomienie TG (etap 4) / push (etap 8) gdy temat staje się „do zrobienia"/„po terminie", z rosnącą przerwą (nie spam) i możliwością „odłóż do…". | M | ✅ powiadomienie ≤ 24 h od zmiany stanu; „odłóż" wycisza do daty | [dbml: nudge] [api: POST /codalej/{topic}/action] |
| FR-COD-6 | Użytkownik MUSI móc oznaczyć „zrobione" (z datą, opcjonalnie dowodem), „odkładam" (do kiedy, powód), „nie dotyczy" — decyzja jest jego, z audit-śladem. | M | ✅ „zrobione 2026-08" bez wyniku w bazce = stan done z last_completed_ref=user | [api: POST /codalej/{topic}/action] |
| FR-COD-7 | Wskaźnik ryzyka SCORE2/SCORE2-OP POWINIEN być liczony modelem (region high, PL) z obserwacji (cholesterol, RR, palenie) — i wchodzić do reguł (Moje Zdrowie ≥40). | S | ✅ SCORE2 dla operatora zgodny z kalkulatorem ESC na 10 przypadkach testowych | [api: /algorithms] [test: T-COD-7] |
| FR-COD-8 | Każda pozycja MUSI mieć link do źródła (program NFZ/PZH/PTD…) i zdanie, że to informacja, nie zalecenie lekarskie. | M | ✅ klik w źródło otwiera stronę programu | [ekran: SCR-COD] |
| ID | Wymaganie | Prio | Kryterium akceptacji | Trace |
|---|---|---|---|---|
| FR-POR-1 | Portal MUSI być za Cloudflare Access (email); anonim nie widzi nic (302 na login). | M | ✅ curl bez tokenu → 302; z tokenem → 200 | [c4: cf] |
| FR-POR-2 | Panel MUSI pokazać ostatnie wyniki z flagą poza zakresem, skrót CO DALEJ (ile do zrobienia/po terminie) i stan źródeł. | M | ✅ wynik poza zakresem = wyróżniony; licznik CO DALEJ zgodny z listą | [ekran: SCR-PANEL] |
| FR-POR-3 | Trend parametru MUSI pokazać punkty ze WSZYSTKICH źródeł w jednej skali, zakres ref., histogram, filtr okresu/źródła; ≤ 10 s. | M | ✅ trend glukozy z ręki + OCR + labu na jednym wykresie | [ekran: SCR-TREND] [api: GET /trends/{loinc}] |
| FR-POR-4 | Każde źródło MUSI mieć freshness-badge (ostatni tick, ostatni sukces, błąd) i trzeci stan „nie zmierzono". | M | ✅ padnięty wafel widoczny jako błąd, nie jako „brak nowych" | [dbml: source] [api: GET /sources] |
| FR-POR-5 | Portal POWINIEN być zarejestrowany jako okno środowisko Wellysy (panel) z probe „rows>0 z żywej bazki". | S | ✅ wpis w rejestrze okna + zielony probe | [c4: web] |
| ID | Wymaganie | Prio | Kryterium akceptacji | Trace |
|---|---|---|---|---|
| FR-TG-1 | Bot MUSI obsłużyć: /wynik, zdjęcie → OCR → potwierdzenie inline, /trend <parametr> (obraz), /codalej, /synevo (intent + kod), /zrodla. | M | ✅ każda komenda działa na żywym koncie operatora | [ekran: TG-*] |
| FR-TG-2 | Bot MUSI wysyłać powiadomienia: nowy import, wynik poza zakresem, zmiana stanu CO DALEJ; z przyciskami „zrobione/odłóż". | M | ✅ powiadomienie ≤ 1 min od importu | [dbml: nudge] |
| FR-TG-3 | Bot MUSI rozmawiać TYLKO z tożsamościami dopiętymi do konta (operator); obcy chat = odmowa. | M | ✅ inny użytkownik TG dostaje odmowę | [c4: tg] |
| ID | Wymaganie | Prio | Kryterium akceptacji | Trace |
|---|---|---|---|---|
| FR-LAB-2 | Diagnostyka (must — operator ma dokument zlecenia): ta sama ścieżka na intent co Synevo — użytkownik wpisuje numer zlecenia z dokument zlecenia (albo robi zdjęcie dokument — odczyt numeru), data urodzenia/PESEL z profilu; wafel loguje się na wyniki.diag.pl, pobiera PDF i tabelę; opcjonalnie konto stałe (KSK + hasło w sejfie) → cron. ALAB analogicznie (nr zlecenia / e-mail+hasło). Regulaminowo tylko własne konto, rate-limit. | M | ✅ realny wynik operatora z Diagnostyki w bazce po podaniu nr zlecenia z dokument; ALAB — gdy operator ma zlecenie | [dbml: intent_job] [api: POST /sources/{source}/intent] [ekran: SCR-ZRODLA] |
| FR-LAB-3 | Nazwy badań z PDF MUSZĄ mapować się na LOINC przez lab_name_map; nieznana nazwa = kolejka „do zmapowania", nie cichy odrzut. | M | ✅ kolejka pokazuje nieznaną nazwę z propozycją | [dbml: lab_name_map] |
| FR-LAB-4 | Każdy pobrany PDF MUSI trafić do magazynu z proweniencją (nr zlecenia, data). | M | ✅ art_list pokazuje PDF per zlecenie | [dbml: document] |
| ID | Wymaganie | Prio | Kryterium akceptacji | Trace |
|---|---|---|---|---|
| FR-P1-1 | System MUSI umieć — po zgodzie pacjenta zarejestrowanej w P1 (moduł Zgody) — pobrać indeks dokumentów EDM pacjenta (IHE XDS.b ITI-18) i dokumenty (ITI-43) przez Wellysę jako usługodawcę (cert PROD, mTLS TLS 1.2). | M | ✅ realny dokument operatora z EDM w bazce jako raport + oryginał CDA w magazynie | [c4: wellysa_api→p1] [dbml: document.kind=cda] |
| FR-P1-2 | wellysa-api MUSI dostać rozszerzenie o EDM/Zgody (dziś tylko e-Rejestracja); WELISKA woła API Wellysy, nie P1 wprost. | M | ✅ endpointy EDM w wellysa-api z testami; WELISKA nie ma certu P1 | [api: robot pobierający→wellysa_api] |
| FR-P1-3 | Zdarzenia Medyczne (wizyty, procedury, recepty) POWINNY lądować jako health_event — wejście CO DALEJ (np. kolonoskopia wykonana w szpitalu zamyka temat). | S | ✅ ZM „kolonoskopia" → temat = zrobione | [dbml: health_event] |
| FR-P1-4 | Drop PDF z IKP (ręczny) zostaje jako fallback przez ten sam parser co laby. | S | ✅ PDF z IKP → tabela do zatwierdzenia | [api: POST /documents] |
| ID | Wymaganie | Prio | Kryterium akceptacji | Trace |
|---|---|---|---|---|
| FR-UDO-1 | Operator MUSI móc utworzyć udostępnienie: odbiorca (email), parametry (lista lub wszystkie), okres, źródła, czy z dokumentami, ważność. | M | ✅ grant widoczny na liście z zakresem | [dbml: share_grant] [api: POST /shares] [ekran: SCR-UDO] |
| FR-UDO-2 | Odbiorca po CF Access widzi TYLKO scope; parametr spoza scope → 403/pusto; każdy odczyt w audicie. | M | ✅ test negatywny zielony; access_log ma wpis per odczyt | [c4: api.share] [test: T-UDO-2] |
| FR-UDO-3 | Odwołanie MUSI działać natychmiast; wygaśnięcie automatycznie. | M | ✅ po odwołaniu odbiorca dostaje 403 w ≤ 1 s | [api: DELETE /shares/{id}] |
| FR-UDO-4 | Operator POWINIEN widzieć, kto i kiedy oglądał. | S | ✅ licznik i lista odczytów przy grancie | [dbml: access_log] |
| ID | Wymaganie | Prio | Kryterium akceptacji | Trace |
|---|---|---|---|---|
| FR-APP-1 | Apka mobilna MUSI być skorupą Ionic/Capacitor nad webem (te same ekrany), z natywnym modułem HealthSync (Swift) i HealthWorker (Kotlin). | M | ✅ ten sam build web działa w web i w apce | [c4: app] |
| FR-WATCH-1 | iOS: HealthKit w tle (entitlement background-delivery, observer + anchored query w didFinishLaunching, background URLSession, completion handler) dla: tętno, tętno spoczynkowe, HRV, sen, kroki, SpO2, waga, ciśnienie; upload partiami, idempotentnie. | M | ✅ próbki z Watcha widoczne w portalu bez otwierania apki (po odblokowaniu telefonu) | [api: POST /healthkit/batch] [test: T-WATCH-1] |
| FR-WATCH-2 | Android: Health Connect z READ_HEALTH_DATA_IN_BACKGROUND + WorkManager + Changes API; te same typy. | S | ✅ próbki z telefonu Android w portalu | [c4: app] |
| FR-WATCH-3 | Jednorazowy backfill z export.xml apki Zdrowie (strumieniowo, dedup). | S | ✅ historia z 3 lat w trendzie bez duplikatów | [api: POST /documents kind=xml] |
| FR-APP-2 | Dystrybucja: iOS TestFlight internal (konto Apple Wellysy), Android internal testing; polityka prywatności i zgoda na transmisję (Apple 5.1.3, Play Health declaration przy closed/prod). | M | ✅ build na iPhonie operatora z TestFlight | [test: T-APP-2] |
| ID | Wymaganie | Prio | Kryterium akceptacji | Trace |
|---|---|---|---|---|
| FR-ALG-1 | System MUSI pokazać, które wskaźniki z katalogu silnika da się policzyć z moich danych i czego brakuje (LOINC). | M | ✅ HOMA-IR: „brakuje insulina (LOINC 20448-7)" | [api: GET /algorithms] |
| FR-ALG-2 | Wynik MUSI nieść wartość, jednostkę, wzór, użyte wejścia, proweniencję i ostrzeżenia — i zdanie „to nie jest diagnoza". | M | ✅ HOMA-IR zgodny z ręcznym przeliczeniem | [dbml: algorithm_run] [api: POST /algorithms/{id}/run] |
| FR-ALG-3 | Rozjazd jednostek rozwiązuje WELISKA przed wysłaniem (kanoniczne UCUM) — kontrakt z silnikiem mówi, czy silnik konwertuje sam. | M | ✅ wejście w mmol/L i mg/dL daje ten sam wynik | [dbml: unit_conversion] |
| ID | Wymaganie | Prio | Kryterium akceptacji | Trace |
|---|---|---|---|---|
| FR-MAG-1 | Każdy import MUSI trzymać oryginał (sha256, mime, proweniencja, tagi) w magazyn plików środowisko Wellysy; do fixu — lokalny wolumen z automatyczną dogrywką. | M | ✅ art_list (albo lokalny indeks) pokazuje plik per import | [dbml: document] |
| FR-MAG-2 | Obserwacja bez oryginału MUSI być oznaczona „bez oryginału" (nie ukryta). | S | ✅ flaga w trendzie/tabeli | [ekran: SCR-TREND] |
| ID | Wymaganie | Prio | Kryterium akceptacji | Trace |
|---|---|---|---|---|
| FR-ART9-1 | Dane wyłącznie w środowisko Wellysy (serwer platformy): bazka na szyfrowanym wolumenie, pliki w bucket lore; zero danych w RAG-u snowa. | M | ✅ audit ścieżek + audit_secrets = 0 findingów | [c4: db, store] |
| FR-ART9-2 | Logi bez PII (redact_pii), kredki wyłącznie w sejf poświadczeń, kod jednorazowy Synevo nigdy na dysku. | M | ✅ grep logów po PESEL/kodzie = 0 | [c4: sejf poświadczeń] |
| FR-ART9-3 | Każdy odczyt przez odbiorcę i każda operacja na danych w access_log (traceability). | M | ✅ wpis per odczyt | [dbml: access_log] |
| FR-ART9-4 | Eksport całości (JSON + CSV + oryginały) na żądanie — RODO art. 20. | M | ✅ ZIP odtwarza bazkę na czystej instancji | [api: GET /me/export] |
| FR-ART9-5 | Twarde usunięcie wszystkiego po potwierdzeniu (bazka + magazyn + kopie). | M | ✅ po usunięciu count(*)=0, pliki zniknęły | [api: DELETE /me] |
| FR-ART9-6 | Przed drugim użytkownikiem: przegląd zespół compliance, DPA dla OCR-fallbacku, polityka prywatności apki (Apple 5.1.1/5.1.3, Play). | M | ✅ dokument przeglądu zamknięty | [test: T-ART9-6] |
docs WELISKA MOCKUP, docs WELISKA SEKWENCJE)<b>Ekrany web/apka (8):</b> SCR-PANEL (ostatnie wyniki, flagi, skrót CO DALEJ, źródła) · SCR-TREND (wykres + histogram + tabela punktów ze źródłem) · SCR-WPIS (wpis z ręki) · SCR-WGRAJ (zdjęcie/PDF → tabela do zatwierdzenia) · SCR-COD (lista tematów ze stanem, powodem, źródłem, akcje) · SCR-ZRODLA (stan źródeł, intent Synevo z kodem/QR, kolejka niezmapowanych nazw) · SCR-UDO (udostępnienia + tworzenie) · SCR-PROFIL (profil + historia). <b>TG (6 komend + powiadomienia):</b> <code>/wynik</code>, zdjęcie, <code>/trend</code>, <code>/codalej</code>, <code>/synevo</code>, <code>/zrodla</code>. <b>Apka:</b> ekran zgody HealthKit/Health Connect + wskaźnik synchronizacji.
<b>Przepływy kluczowe (sekwencje):</b> (a) ręka → walidacja → zapis → ewaluacja COD → trend; (b) zdjęcie → OCR → HITL → zapis + magazyn → ewaluacja; (c) Synevo: intent → kod/QR → wafel → PDF → parser → mapowanie → zapis → powiadomienie; (d) HealthKit: wybudzenie w tle → anchored query → batch → dedup → zapis; (e) P1: zgoda → ITI-18 → ITI-43 → CDA parser → raport/zdarzenia; (f) CO DALEJ: zmiana profilu/import → ewaluacja → stan → nudge → decyzja użytkownika; (g) udostępnienie: grant → CF Access (email) → odczyt w scope → audit; (h) wskaźnik: dostępność → wejścia → silnik → wynik z proweniencją.
| co | po co | kanał | termin |
|---|---|---|---|
| ACCEPT SPEC v0.2 (albo odbicie) | zamrożenie CO; flip krowy ; promocja idei do epików | komentarz w | przed etapem 1 |
| Ratyfikacja WELISKA pod P1 PHA w PRODUCT-LINES wsa | linia-gate | komentarz w / dokument zespół strategiczny Wellysy | z akceptem |
| Przejścia artefaktów fazy D one-by-one (DBML → C4 → sekwencje → ekrany → testy) | wzorzec ROD: wartość powstaje w przejściach | komentarze w – | przed etapem 1 |
| Bot TG (BotFather → sejf poświadczeń) | etap 4 | etap 4 | |
| Hostname portalu + spec-sajtu | etap 1, akceptacja w spec-sajcie | etap 1 | |
| Koszty/konta: Azure DI (fallback), konta stałe Diagnostyka/ALAB (opcja), LabExtract (cennik), cert PROD P1 Wellysy (kto trzyma, czy dogfood może użyć), dostęp Snow do konta Apple/TestFlight | etapy 2, 5, 6, 8 | przed tymi etapami | |
| Profil (płeć, ur., palenie, wywiad, choroby) | CO DALEJ | SCR-PROFIL po etapie 1 | etap 3 |
| Zgoda w P1 na dostęp Wellysy do EDM | etap 6 | IKP / placówka | etap 6 |
Jednorazowy export.xml z apki Zdrowie | backfill Watcha | upload | etap 8 |
Każdy etap odbierany na <b>żywych danych operatora</b>: dowód = zapis w bazce + widok w oknie + wpis w audicie + (od etapu 3) zmiana stanu CO DALEJ. Zielone testy ≠ odebrane; odebrane = operator zobaczył swój wynik i swoją listę.
Nowy pomysł w trakcie = zgłoszenie do briefu (nie cicha rozbudowa zakresu); zmiana granic = nowa wersja SPEC + ponowny akcept. Uwagi w spec-sajcie (hejty inline, po) lądują jako komentarze w .
| ID | wymaganie |
|---|---|
| NFR-1 | Prywatność: dane wyłącznie w środowisko Wellysy ; zero danych pacjenta w RAG snowa; logi bez PII |
| NFR-2 | Proweniencja 100% rekordów; brak oryginału = flaga |
| NFR-3 | Wydajność: trend ≤ 10 s przy 100 k obserwacji (Watch: dziesiątki tysięcy/mies.); ewaluacja CO DALEJ ≤ 5 s |
| NFR-4 | Idempotencja: każdy import/webhook/wafel wznawialny bez duplikatów |
| NFR-5 | Pomiar zamiast deklaracji: freshness-badge per źródło; trzeci stan widoczny w oknie |
| NFR-6 | Reguły profilaktyczne wersjonowane z datą pomiaru i review_by; reguła po terminie = flaga, nie cicha prawda |
| NFR-7 | Dostępność „wystarczająco" dla 1 użytkownika; backup dobowy bazki + magazynu poza git-tree |
docs WELISKA DB-SCHEMA17 tabel (DBML, parse round-trip zielony): <code>person</code>, <code>profile_history</code>, <code>metric</code>, <code>unit_conversion</code>, <code>source</code>, <code>document</code>, <code>report</code>, <code>observation</code>, <code>lab_name_map</code>, <code>intent_job</code>, <code>share_grant</code>, <code>access_log</code>, <code>rule</code>, <code>maintenance_state</code>, <code>nudge</code>, <code>algorithm_run</code>, <code>health_event</code>. Silnik: SQLite WAL na szyfrowanym wolumenie. Nazwy semantyczne ↔ <code>wellysa-ontology.dbml</code> (person/metryka/pomiar/norma/wskazanie/source/consent).
| integracja | decyzja v0.2 | odrzucone (powód) | źródło |
|---|---|---|---|
| OCR | PaddleOCR 3.x PP-StructureV3 (latin_PP-OCRv5, CPU) + normalizacja LLM → fallback Azure DI West Europe po | Textract (brak pl), Surya/marker (licencja wag), MinerU (wzmianka w UI), olmOCR (en) | Recon OCR |
| Apple Watch | własna apka iOS (Ionic + Swift HealthSync w tle), @capgo/capacitor-health do uprawnień | Health Auto Export (decyzja operatora), perfood (Cap 4), cordova-plugin-health (bez anchora/entitlementu) | Recon Ionic |
| Android | Health Connect + WorkManager + READ_HEALTH_DATA_IN_BACKGROUND | — | Recon Ionic |
| Diagnostyka/ALAB | intent (nr zlecenia/kod) + opcjonalnie konto stałe (cron) | — | Recon źródła |
| P1 | przez Wellysę jako usługodawcę: Zgody → ITI-18 → ITI-43 (EDM), ZM; wellysa-api + EDM | P1 jako pacjent (nie istnieje) | Wellysa/p1 spec, Recon źródła |
| IKP | drop PDF | RPA z 2FA PZ | Recon źródła |
| reguły | własny silnik YAML (~20 reguł PL) + SCORE2 model; USPSTF API jako strength | CQL/CPG/OpenCDS (ciężar) | Recon algorytmy |
Backfill: <code>export.xml</code> (Apple), istniejące PDF-y operatora (drop), EDM z P1 po zgodzie (historia), ewentualnie RODO art. 20 z labów. Brak systemu poprzedniego.
docs WELISKA C4-DEPLOYMENTdev: serwer platformy, workspace środowisko Wellysy, <code>czak-Snow-weliska.service</code> (API + portal statyczny), robot pobierający jako timery systemd, tunel cloudflared → hostname z, CF Access policy per email; prod: ta sama topologia, osobny wolumen i hostname. Gitflow: branch z tasku → PR → merge (operator) → deploy dev; prod = tag. iOS: TestFlight internal (re-upload ≤ 90 dni); Android: internal testing.
Jak FR-ART9-1…6. Dodatkowo: kod jednorazowy Synevo tylko w pamięci robot pobierający; cert P1 zostaje u Wellysy (WELISKA nie trzyma certu); OCR-fallback poza EU = zakaz z konfiguracji; apka: zgoda przed transmisją, polityka prywatności, brak iCloud dla PHI.
| # | ryzyko / pytanie | plan |
|---|---|---|
| R1 | jakość OCR na polskich wydrukach niezmierzona | macierz 10–20 własnych wydruków (etap 2) = KR1 |
| R2 | HealthKit budzi apkę „at most once per period"; częstość realna niezmierzona | pomiar na urządzeniu w etapie 8; freshness-badge |
| R3 | rejestr artefaktów środowisko Wellysy read-only | lokalny wolumen z dogrywką |
| R4 | słownik medyczny niewidoczny dla ext lore | tymczasowy subset LOINC labowy (oznaczony) |
| R5 | regulamin Diagnostyki (scraping własnych wyników = szara strefa) | tylko intent/konto operatora, rate-limit; decyzja |
| R6 | silnik algorytmów Wellysy nie wystawi kontraktu w czasie | etap 9 na końcu; bez własnych wzorów |
| R7 | reguły NFZ zmieniają się (3 zmiany w 16 mies.) | review_by + flaga; źródła z datą pomiaru |
| R8 | cert PROD P1 Wellysy — kto trzyma, czy dogfood może użyć | pkt 5; do decyzji etap 6 = INT (testy) → PROD |
| R9 | Play Health declaration na internal testing — niezmierzone | Android po iOS; sprawdzić przy publikacji |
| R10 | format QR na karteczce Synevo — niezmierzony | pomiar na żywej karteczce operatora (etap 5); fallback = wpis ręczny kodu |
EKG z Watcha; Medplum jako FHIR-API dla lekarzy; drugi użytkownik (rodzina) i multi-tenant; spięcie z <code>wellysa-app</code> (profil zdrowotny z bazki); LabExtract (Medalion) jako OCR domenowy; import z apki Diagnostyki (mojeID); RODO-eksport z IKP w formacie maszynowym; półautomat IKP (sesja PZ operatora); <b>v2 GDZIE </b> i <b>v3 WALLET </b> — SPEC-rozszerzenia napisane 2026-09-08 (<code>docs WELISKA-GDZIE SPEC</code>, <code>docs WELISKA-WALLET SPEC</code>), wchodzą po dowodzie v1 i po warsztacie Wallet (decyzja A6).
Diagram wyprowadzony wprost z definicji modelu (DBML) — nie rysowany obok. Pod nim mapa: skąd bierze się każda tabela, i pełna definicja do wglądu technicznego.
erDiagram
person ||--o{ profile_history : "person_id"
metric ||--o{ unit_conversion : "metric_id"
source ||--o{ document : "source_id"
person ||--o{ report : "person_id"
source ||--o{ report : "source_id"
document ||--o{ report : "document_id"
person ||--o{ observation : "person_id"
metric ||--o{ observation : "metric_id"
report ||--o{ observation : "report_id"
source ||--o{ observation : "source_id"
metric ||--o{ lab_name_map : "metric_id"
person ||--o{ intent_job : "person_id"
source ||--o{ intent_job : "source_id"
report ||--o{ intent_job : "result_report_id"
person ||--o{ share_grant : "person_id"
share_grant ||--o{ access_log : "grant_id"
person ||--o{ maintenance_state : "person_id"
rule ||--o{ maintenance_state : "rule_id"
person ||--o{ nudge : "person_id"
maintenance_state ||--o{ nudge : "state_id"
person ||--o{ algorithm_run : "person_id"
person ||--o{ health_event : "person_id"
source ||--o{ health_event : "source_id"
document ||--o{ health_event : "document_id"
rule ||--o{ topic_procedure : "rule_id"
procedure ||--o{ topic_procedure : "procedure_id"
provider ||--o{ provider_system : "provider_id"
provider ||--o{ offering : "provider_id"
procedure ||--o{ offering : "procedure_id"
provider ||--o{ queue_measure : "provider_id"
procedure ||--o{ queue_measure : "procedure_id"
offering ||--o{ slot : "offering_id"
person ||--o{ booking : "person_id"
rule ||--o{ booking : "rule_id"
offering ||--o{ booking : "offering_id"
slot ||--o{ booking : "slot_id"
payment_plan ||--o{ booking : "payment_plan_id"
person ||--o{ initiative_log : "person_id"
catalog_copy_version ||--o{ initiative_log : "copy_version_id"
payer ||--o{ financing_model : "payer_id"
financing_model ||--o{ coverage : "financing_model_id"
procedure ||--o{ coverage : "procedure_id"
person ||--o{ instrument : "person_id"
financing_model ||--o{ instrument : "financing_model_id"
instrument ||--o{ usage_counter : "instrument_id"
coverage ||--o{ usage_counter : "coverage_id"
person ||--o{ payment_plan : "person_id"
offering ||--o{ payment_plan : "offering_id"
payment_plan ||--o{ payment_plan_line : "payment_plan_id"
instrument ||--o{ payment_plan_line : "instrument_id"
payment_plan ||--o{ split_payment : "payment_plan_id"
person ||--o{ confirmation : "person_id"
payer ||--o{ confirmation : "payer_id"
share_grant ||--o{ confirmation : "consent_id"
payment_plan ||--o{ external_finance_offer : "payment_plan_id"
payer ||--o{ external_finance_offer : "payer_id"
person ||--o{ points_ledger : "person_id"
person {
integer id
text display_name
text sex
date birth_date
text cf_email
text profile_json
}
profile_history {
integer id
integer person_id
text profile_json
datetime changed_at
text changed_by
}
metric {
integer id
text loinc
text display_pl
text ucum_default
text kind
text provenance
}
unit_conversion {
integer id
integer metric_id
text from_ucum
text to_ucum
real factor
text source
}
source {
text id
text display_pl
text mode
datetime last_tick
datetime last_ok
text last_error
}
document {
integer id
text kind
text sha256
text storage_uri
text mime
integer bytes
}
report {
integer id
integer person_id
text source_id
text source_ref
integer document_id
datetime issued_at
}
observation {
integer id
integer person_id
integer metric_id
integer report_id
real value_num
text value_text
}
lab_name_map {
integer id
text lab
text lab_name
text lab_unit
integer metric_id
real confidence
}
intent_job {
integer id
integer person_id
text source_id
datetime requested_at
text requested_via
text state
}
share_grant {
integer id
integer person_id
text subject_email
text label
text scope_json
datetime valid_to
}
access_log {
integer id
datetime at
text actor_email
integer grant_id
text action
text what
}
rule {
text id
text topic
text rule_yaml
text procedure_code
integer interval_days
text source_org
}
maintenance_state {
integer id
integer person_id
text topic
text rule_id
text status
datetime last_completed_at
}
nudge {
integer id
integer person_id
integer state_id
text channel
datetime sent_at
datetime acked_at
}
algorithm_run {
integer id
integer person_id
text algorithm_id
text inputs_json
real value
text unit
}
health_event {
integer id
integer person_id
text kind
text code_system
text code
text display
}
procedure {
integer id
text code_system
text code
text display_pl
text prep_note
text public_class
}
topic_procedure {
integer id
text rule_id
integer procedure_id
text source_org
date source_measured
date review_by
}
provider {
integer id
text kind
text name
text registry_ref
text address
real lat
}
provider_system {
integer id
integer provider_id
text system
text scope
date as_of
}
offering {
integer id
integer provider_id
integer procedure_id
text channel
real price_pln
integer lead_days
}
queue_measure {
integer id
integer provider_id
integer procedure_id
integer wait_days
datetime measured_at
text source
}
slot {
integer id
integer offering_id
datetime starts_at
text source
text external_ref
datetime fetched_at
}
booking {
integer id
integer person_id
text rule_id
integer offering_id
integer slot_id
text mode
}
initiative_log {
integer id
integer person_id
datetime at
text from_topic
text channel
integer copy_version_id
}
catalog_copy_version {
integer id
text key
text text
datetime valid_from
datetime valid_to
}
payer {
integer id
text kind
text name
text contract_ref
}
financing_model {
integer id
integer payer_id
text name
text currency
text unit
}
coverage {
integer id
integer financing_model_id
integer procedure_id
text kind
real covers_pln
real covers_pct
}
instrument {
integer id
integer person_id
integer financing_model_id
text kind
text insurer_context
datetime mandate_granted_at
}
usage_counter {
integer id
integer instrument_id
integer coverage_id
text period
integer used
}
payment_plan {
integer id
integer person_id
integer offering_id
real total_pln
text order_rule
text user_override
}
payment_plan_line {
integer id
integer payment_plan_id
integer seq
integer instrument_id
text currency
real amount_pln
}
split_payment {
integer id
integer payment_plan_id
text psp_ref
text state
datetime at
}
confirmation {
integer id
integer person_id
integer payer_id
text topic
integer completed_on_time
integer consent_id
}
external_finance_offer {
integer id
integer payment_plan_id
integer payer_id
real apr_pct
real total_pln
text example_text
}
points_ledger {
integer id
integer person_id
text event
integer points
datetime at
text note
}| tabela | z czego derywuje (SPEC) | ontologia Wellysy |
|---|---|---|
person, profile_history | FR-COD-1 profil z historią | person |
metric, unit_conversion | FR-BAZ-3/4 (LOINC/UCUM z słownik medyczny, mnożniki) | metryka |
source | FR-POR-4 (freshness, trzeci stan), tryby ręka/intent/cron/push | source |
document | FR-OCR-1, FR-MAG-1 (oryginał + OCR surowy) | — |
report, observation | FR-BAZ-1/2 (proweniencja, dedup), FR-OCR-3 | pomiar + norma |
lab_name_map | FR-LAB-3, FR-OCR-3 (mapowanie nazw → LOINC, HITL) | — |
intent_job | FR-LAB-1 (Synevo na intent; kod nieprzechowywany) | — |
share_grant, access_log | FR-UDO-1..4, FR-ART9-3 | consent |
rule, maintenance_state, nudge | FR-COD-2..6 | wskazanie |
algorithm_run | FR-ALG-2 | algorytm |
health_event | FR-BAZ-5, FR-P1-3 | procedure |
value_canon + unit_conversion zamiast liczenia w locie — trend ≤ 10 s przy 100 k wierszy (NFR-3); mnożniki mają źródło (problem rozwiązany na wejściu).hash_dedup jako UNIQUE — idempotencja importów/webhooków w bazie, nie w kodzie.code_hint) — FR-ART9-2.rule_yaml + kolumny indeksowe — treść reguły czytelna dla człowieka i audytowalna; ewaluator czyta YAML.maintenance_state UNIQUE(person, topic) — jeden stan na temat (wzór Epic HM), historia decyzji w access_log/nudge.// DB-SCHEMA — WELISKA v0.1 (dokument, SPEC docs WELISKA SPEC)
// Silnik: SQLite WAL (szyfrowany wolumen). Nazwy semantyczne = wellysa-ontology.dbml
// (person/metryka/pomiar/norma/wskazanie/source/consent) — mapowanie w komentarzach [ont:].
// Kształt FHIR R4: observation ≈ Observation (profil HL7 EU Lab), report ≈ DiagnosticReport,
// document ≈ DocumentReference/Binary. Zero PII poza pudełkiem wsa .
Project weliska {
database_type: 'SQLite'
Note: 'Osobista bazka zdrowia operatora. Każdy wiersz danych ma proweniencję (source, source_ref, ingested_at, ingested_by). Duplikaty odcina hash_dedup. Art. 9 RODO: at-rest encryption, audit odczytów w access_log.'
}
Table person { // [ont: person] — w v1 dokładnie 1 wiersz (operator)
id integer [pk]
display_name text [not null]
sex text [not null, note: 'male|female|other — wejście reguł CO DALEJ']
birth_date date [not null]
cf_email text [unique, note: 'tożsamość z CF Access']
profile_json text [note: 'JSON: smoking{status,pack_years,quit_year}, height_cm, family_history[], conditions[] (ICD-10), pregnancy — wejście reguł; wersjonowane w profile_history']
created_at datetime [not null]
}
Table profile_history { // zmiany profilu = ślad (kto/kiedy), bo reguły od nich zależą
id integer [pk]
person_id integer [not null, ref: > person.id]
profile_json text [not null]
changed_at datetime [not null]
changed_by text [not null, note: 'person|import|czak']
}
Table metric { // [ont: metryka] — słownik parametrów, kody z słownik medyczny (slownik_lookup), NIE kopia LOINC
id integer [pk]
loinc text [unique, not null, note: 'kod LOINC (np. 2345-7 glukoza)']
display_pl text [not null]
ucum_default text [not null, note: 'jednostka kanoniczna UCUM (mg/dL, mmol/L, %, /uL …)']
kind text [not null, note: 'lab|vital|activity|sleep|ecg|anthropometric']
provenance text [not null, note: 'słownik medyczny:slownik_lookup@<data> | manual']
}
Table unit_conversion { // mnożniki UCUM→UCUM per metric (problem rozwiązany U ŹRÓDŁA)
id integer [pk]
metric_id integer [not null, ref: > metric.id]
from_ucum text [not null]
to_ucum text [not null]
factor real [not null]
source text [not null]
}
Table source { // [ont: source] — rejestr źródeł + stan (freshness-badge okna)
id text [pk, note: 'manual|ocr|lab:synevo|lab:diagnostyka|lab:alab|ikp-pdf|p1-edm|apple-health|health-connect|export-xml']
display_pl text [not null]
mode text [not null, note: 'manual|intent|cron|push']
last_tick datetime
last_ok datetime
last_error text
last_error_at datetime
measured text [note: 'trzeci stan: ok|error|unmeasured — okno pokazuje wprost']
}
Table document { // plik źródłowy: zdjęcie/PDF/XML/JSON; oryginał = magazyn plików czak-wsa (do fixu lokalnie)
id integer [pk]
kind text [not null, note: 'photo|pdf|xml|json|cda']
sha256 text [unique, not null]
storage_uri text [not null, note: 'r2://czak-wsa/doc/<sha>.<ext> | file:///opt/.../<sha>']
mime text [not null]
bytes integer [not null]
source_id text [not null, ref: > source.id]
source_ref text [note: 'nr zlecenia labu / id dokumentu EDM (XDS DocumentEntry.uniqueId) / nazwa pliku']
ocr_json text [note: 'wynik OCR (tabela + pewności) — surowy, przed HITL']
ocr_engine text [note: 'paddleocr-ppstructurev3@ver | azure-di-layout@ver | none']
uploaded_at datetime [not null]
uploaded_by text [not null]
}
Table report { // [FHIR DiagnosticReport] — jedno zlecenie / wizyta / eksport
id integer [pk]
person_id integer [not null, ref: > person.id]
source_id text [not null, ref: > source.id]
source_ref text
document_id integer [ref: > document.id]
issued_at datetime
lab_name text
status text [not null, note: 'draft(HITL)|final|amended']
meta_json text
ingested_at datetime [not null]
ingested_by text [not null, note: 'person|wafel:<id>|ocr|healthsync']
}
Table observation { // [ont: pomiar] [FHIR Observation] — JEDEN odczyt
id integer [pk]
person_id integer [not null, ref: > person.id]
metric_id integer [not null, ref: > metric.id]
report_id integer [ref: > report.id]
value_num real
value_text text
ucum text [not null, note: 'jednostka jak w źródle']
value_canon real [note: 'wartość w metric.ucum_default (unit_conversion) — do trendu']
ref_low real
ref_high real
ref_text text [note: 'zakres jak wydrukował lab (np. "<5.7")']
flag text [note: 'L|H|N|null — liczone z ref_*; nie z labu']
observed_at datetime [not null]
observed_end datetime [note: 'dla okresów (sen, kroki/dzień)']
source_id text [not null, ref: > source.id]
source_ref text
confidence real [note: 'OCR: pewność komórki 0..1; ręka/lab: null']
hash_dedup text [unique, not null, note: 'sha256(person,metric,observed_at,observed_end,source,value)']
ingested_at datetime [not null]
ingested_by text [not null]
Note: 'Indeks (person_id, metric_id, observed_at) pod trend ≤10 s przy 100k wierszy'
}
Table lab_name_map { // mapa nazw z wydruków/PDF-ów → LOINC (SSIDL niepubliczny); HITL zatwierdza
id integer [pk]
lab text [not null, note: 'synevo|diagnostyka|alab|ikp|other']
lab_name text [not null]
lab_unit text
metric_id integer [ref: > metric.id]
confidence real
verified_by text [note: 'null = do potwierdzenia (kolejka "niezmapowane")']
verified_at datetime
indexes { (lab, lab_name, lab_unit) [unique] }
}
id integer [pk]
person_id integer [not null, ref: > person.id]
source_id text [not null, ref: > source.id]
requested_at datetime [not null]
requested_via text [not null, note: 'web|tg|app']
state text [not null, note: 'queued|running|done|failed|expired']
code_hint text [note: 'ostatnie 2 znaki kodu do rozpoznania w UI — nigdy pełny kod']
result_report_id integer [ref: > report.id]
error text
finished_at datetime
}
Table share_grant { // [ont: consent] — udostępnienie zakresem
id integer [pk]
person_id integer [not null, ref: > person.id]
subject_email text [not null, note: 'tożsamość odbiorcy w CF Access']
label text
scope_json text [not null, note: 'JSON: metrics[loinc]|"*", from, to, sources[], include_documents(bool)']
valid_to datetime
created_at datetime [not null]
revoked_at datetime
}
Table access_log { // audit KAŻDEGO odczytu przez odbiorcę + operacji na danych (Art. 9)
id integer [pk]
at datetime [not null]
actor_email text [not null]
grant_id integer [ref: > share_grant.id]
action text [not null, note: 'read|export|delete|share_create|share_revoke|ocr_confirm|import']
what text [not null, note: 'zakres odczytu (metrics, okres) albo id zasobu']
channel text [not null, note: 'web|tg|app|api']
}
Table rule { // [ont: wskazanie] — reguła profilaktyczna CO DALEJ (YAML w rule_yaml; kolumny = indeks)
id text [pk, note: 'np. pl.nfz.colonoscopy.screening']
topic text [not null]
rule_yaml text [not null]
procedure_code text [note: 'ICD-9-PL / SNOMED / LOINC celu']
interval_days integer
source_org text [not null]
source_url text [not null]
source_measured date [not null]
strength text
valid_from date [not null]
review_by date [not null]
active integer [not null, default: 1]
}
Table maintenance_state { // stan użytkownika per topic (wzór Epic Health Maintenance)
id integer [pk]
person_id integer [not null, ref: > person.id]
topic text [not null]
rule_id text [not null, ref: > rule.id]
status text [not null, note: 'not_applicable|due|overdue|done|postponed|discontinued']
last_completed_at datetime
last_completed_ref text [note: 'observation/report/event id jako dowód']
next_due_at datetime
overdue_after_days integer [default: 90]
user_override_json text [note: 'reason, until — decyzja użytkownika (postpone/discontinue)']
evaluated_at datetime [not null]
indexes { (person_id, topic) [unique] }
}
Table nudge { // popychanie: co, kiedy, którym kanałem, czy zadziałało
id integer [pk]
person_id integer [not null, ref: > person.id]
state_id integer [not null, ref: > maintenance_state.id]
channel text [not null, note: 'tg|push|web']
sent_at datetime [not null]
acked_at datetime
outcome text [note: 'scheduled|done|snoozed|ignored']
}
Table algorithm_run { // wynik z silnik algorytmów Wellysy z proweniencją (nie wyrok)
id integer [pk]
person_id integer [not null, ref: > person.id]
algorithm_id text [not null, note: 'id z katalogu silnika (np. HOMA-IR)']
inputs_json text [not null, note: 'observation ids + wartości + jednostki użyte']
value real
unit text
formula_ref text
provenance_json text [not null, note: 'source, license, engine version']
warnings_json text
computed_at datetime [not null]
}
Table health_event { // zdarzenie inne niż pomiar: wizyta, procedura, szczepienie, recepta (z P1 ZM / IKP / ręka) — wejście do maintenance_state
id integer [pk]
person_id integer [not null, ref: > person.id]
kind text [not null, note: 'procedure|visit|immunization|prescription|diagnosis']
code_system text [note: 'icd9pl|icd10|snomed|atc']
code text
display text [not null]
occurred_at datetime [not null]
source_id text [not null, ref: > source.id]
source_ref text
document_id integer [ref: > document.id]
ingested_at datetime [not null]
}
// ── v2 GDZIE (docs WELISKA-GDZIE SPEC) ────────────────────────────────────────
Table procedure { // [ont: procedure] — badanie/świadczenie z kodem (słownik medyczny: ICD-9-PL, ICHI; lab: LOINC panel)
id integer [pk]
code_system text [not null, note: 'icd9pl|ichi|loinc-panel']
code text [not null]
display_pl text [not null]
prep_note text [note: 'warunki: na czczo, skierowanie…']
public_class text [note: 'moje_zdrowie|screening_no_referral|aos_referral|none — jak jest bezpłatna']
source_org text
source_measured date
review_by date
indexes { (code_system, code) [unique] }
}
Table topic_procedure { // [ont: pathway_step] — temat CO DALEJ → procedury (1..n) z proweniencją
id integer [pk]
rule_id text [not null, ref: > rule.id]
procedure_id integer [not null, ref: > procedure.id]
source_org text [not null]
source_measured date [not null]
review_by date [not null]
}
Table provider { // [ont: provider] — placówka/lab/lekarz z rejestrów (RPWDL, NIL) lub partner
id integer [pk]
kind text [not null, note: 'poz|aos|lab|imaging|telemed|private|hospital']
name text [not null]
registry_ref text [note: 'RPWDL/NIL id']
address text
lat real
lng real
is_partner integer [not null, default: 0]
contract_ref text [note: 'umowa partnera (proweniencja oferty)']
}
Table provider_system { // [ont: provider_system] — umowa NFZ / e-Rejestracja / kalendarz partnera
id integer [pk]
provider_id integer [not null, ref: > provider.id]
system text [not null, note: 'nfz_contract|e_rejestracja|partner_calendar|ikp_link']
scope text [note: 'np. AOS kardiologia']
as_of date
}
Table offering { // [ont: offering] — provider × procedure × kanał; cena i czas; JAWNE wynagrodzenie
id integer [pk]
provider_id integer [not null, ref: > provider.id]
procedure_id integer [not null, ref: > procedure.id]
channel text [not null, note: 'public|private_onsite|remote']
price_pln real [note: 'null dla publicznej']
lead_days integer [note: 'czas do wykonania']
fee_disclosure text [note: 'equal|disclosed:<kwota>|none — zapora: jednakowa stawka ALBO ujawniona']
source_ref text [not null, note: 'cennik partnera + data / umowa NFZ']
valid_to date
}
Table queue_measure { // kolejka publiczna z DATĄ pomiaru (Informator o Terminach / AP-KOLCE) — trzeci stan
id integer [pk]
provider_id integer [not null, ref: > provider.id]
procedure_id integer [ref: > procedure.id]
wait_days integer
measured_at datetime [not null]
source text [not null]
status text [not null, note: 'ok|unmeasured']
}
Table slot { // [ont: slot] — wolny termin z e-Rejestracji / kalendarza partnera
id integer [pk]
offering_id integer [not null, ref: > offering.id]
starts_at datetime [not null]
source text [not null, note: 'e_rejestracja|partner']
external_ref text
fetched_at datetime [not null]
}
Table booking { // umówienie: rezerwacja / voucher / „zamierzam" (link IKP); domyka temat po wykonaniu
id integer [pk]
person_id integer [not null, ref: > person.id]
rule_id text [ref: > rule.id]
offering_id integer [ref: > offering.id]
slot_id integer [ref: > slot.id]
mode text [not null, note: 'e_rejestracja_via_wellysa|voucher|partner_calendar|ikp_intent']
state text [not null, note: 'planned|booked|cancelled|attended|no_show|unknown']
attended_answer text [note: 'byłeś?: yes|no|postponed|null']
external_ref text
created_at datetime [not null]
payment_plan_id integer [ref: > payment_plan.id]
}
Table initiative_log { // PULL: akt inicjatywy pacjenta otwierający warstwę handlową (dowód procesowy)
id integer [pk]
person_id integer [not null, ref: > person.id]
at datetime [not null]
from_topic text
channel text [not null]
copy_version_id integer [ref: > catalog_copy_version.id]
}
Table catalog_copy_version { // wersjonowanie każdego wariantu komunikatu handlowego (2 lata)
id integer [pk]
key text [not null]
text text [not null]
valid_from datetime [not null]
valid_to datetime
}
// ── v3 WALLET (docs WELISKA-WALLET SPEC) ──────────────────────────────────────
Table payer { // [ont: payer] — NFZ | ubezpieczyciel (profil A/B) | pracodawca | dostawca finansowania
id integer [pk]
kind text [not null, note: 'nfz|insurer_financial|insurer_network|employer|finance_provider']
name text [not null]
contract_ref text
}
Table financing_model { // [ont: financing_model] — pakiet/rider/budżet u płatnika
id integer [pk]
payer_id integer [not null, ref: > payer.id]
name text [not null]
currency text [not null, note: 'public|employer|policy|own|external']
unit text [note: 'per_insured_month|per_used_limit|budget']
}
Table coverage { // [ont: coverage] — financing_model × procedura → ile pokrywa; typ zwrot|ryczałt; limit
id integer [pk]
financing_model_id integer [not null, ref: > financing_model.id]
procedure_id integer [ref: > procedure.id]
kind text [not null, note: 'reimbursement|lump_sum — zakaz wzbogacenia dotyczy reimbursement']
covers_pln real
covers_pct real
limit_count integer
limit_period text [note: 'year']
alarm_exempt integer [not null, default: 1, note: 'objawy alarmowe ZAWSZE poza limitem — niekonfigurowalne']
}
Table instrument { // instrument użytkownika w portfelu (polisa, voucher, budżet, deklaracja POZ, własne)
id integer [pk]
person_id integer [not null, ref: > person.id]
financing_model_id integer [ref: > financing_model.id]
kind text [not null, note: 'policy|voucher|employer_budget|poz_declaration|own_funds']
insurer_context text [note: 'izolacja per towarzystwo — kontekst nigdy nie miesza się z innym']
mandate_granted_at datetime [note: 'pełnomocnictwo selektywne do zapytań w imieniu użytkownika']
valid_to date
provenance text [not null]
state text [not null, note: 'active|expired|archived']
}
Table usage_counter { // licznik limitu per instrument × coverage × okres
id integer [pk]
instrument_id integer [not null, ref: > instrument.id]
coverage_id integer [not null, ref: > coverage.id]
period text [not null]
used integer [not null, default: 0]
}
Table payment_plan { // plan pokrycia jednej usługi: pozycje per płatnik w KOLEJNOŚCI + wybór użytkownika
id integer [pk]
person_id integer [not null, ref: > person.id]
offering_id integer [not null, ref: > offering.id]
total_pln real [not null]
order_rule text [not null, note: 'public>employer>policy>own>external']
user_override text [note: 'use_policy_now|pay_own|null']
copay_pln real
fee_disclosed text [note: 'co Wellysa dostaje — pokazane użytkownikowi w miejscu wyboru']
created_at datetime [not null]
}
Table payment_plan_line {
id integer [pk]
payment_plan_id integer [not null, ref: > payment_plan.id]
seq integer [not null]
instrument_id integer [ref: > instrument.id]
currency text [not null]
amount_pln real [not null]
reason text [not null, note: 'dlaczego ta pozycja — komunikat korzyści']
}
Table split_payment { // rozliczenie przez szyny (konta powiązane, split) — Wallet NIE trzyma środków
id integer [pk]
payment_plan_id integer [not null, ref: > payment_plan.id]
psp_ref text
state text [not null, note: 'created|captured|failed|refunded']
at datetime [not null]
}
Table confirmation { // potwierdzenie prewencji dla płatnika — FAKT bez wyniku i bez danych medycznych
id integer [pk]
person_id integer [not null, ref: > person.id]
payer_id integer [not null, ref: > payer.id]
topic text [not null]
completed_on_time integer [not null]
consent_id integer [not null, ref: > share_grant.id, note: 'odrębna, odwoływalna zgoda per płatnik']
sent_at datetime
payload_schema text [not null, default: 'confirmation/v1 — bez pól medycznych (test schematu)']
}
Table external_finance_offer { // oferta rat/BNPL od dostawcy zewnętrznego — Wallet = pośrednik
id integer [pk]
payment_plan_id integer [not null, ref: > payment_plan.id]
payer_id integer [not null, ref: > payer.id]
apr_pct real [not null, note: 'RRSO']
total_pln real [not null]
example_text text [not null, note: 'reprezentatywny przykład (obowiązek)']
withdrawal_until date [not null, note: '14 dni odstąpienia']
state text [not null, note: 'offered|accepted|withdrawn']
}
Table points_ledger { // v3 PKT — nagroda za MĄDRE korzystanie; nie-pieniądz (poza AML)
id integer [pk]
person_id integer [not null, ref: > person.id]
event text [not null]
points integer [not null]
at datetime [not null]
note text
}
Kto i co rozmawia z systemem: użytkownik i osoby, którym udostępnia wyniki, oraz systemy zewnętrzne — laboratoria, konto pacjenta (P1), zegarek, słowniki medyczne, silnik algorytmów, Cloudflare, Telegram.
flowchart LR cze([Operator Cze]) --> W[WELISKA] odb([Odbiorca udostępnienia]) --> W W --> slownik[(słownik medyczny — słowniki)] W --> silnik[silnik algorytmów Wellysy] W --> sejf[sejf poświadczeń] W --> hk[Apple HealthKit / Health Connect] W --> syn[wyniki.synevo.pl] W --> diag[Diagnostyka / ALAB] W --> wapi[wellysa-api] --> p1[P1 e-Zdrowie CeZ] W -.po.-> az[Azure DI West Europe] cf[Cloudflare Access + Tunnel] --> W tg[Telegram] --> W
// C4-DEPLOYMENT — WELISKA v0.1 (dokument). L1 kontekst + L2 kontenery + deployment.
// Aktorzy i systemy zewnętrzne ze SPEC .1; kontenery UJAWNIONE przez sekwencje (docs WELISKA SEKWENCJE).
// ZERO sekretów. Hosty: resource.reg platforma (box Hetzner) — do ugruntowania arch_ground po deployu.
specification {
element actor { style { shape person } }
element system
element container
element database
element component
deploymentNode environment
deploymentNode host
deploymentNode unit
tag external
tag art9
tag planned
}
model {
actor cze 'Operator (Cze)' { description 'pierwszy użytkownik, dogfood; web/TG/iOS; akceptuje SPEC i etapy' }
actor odbiorca 'Odbiorca udostępnienia' { description 'osoba wskazana przez operatora (email w CF Access); docelowo lekarz' }
actor Snow 'Snow' { description 'buduje, utrzymuje; nie widzi danych bez potrzeby (audit)' }
weliska = system 'WELISKA' {
#art9
description 'osobista bazka zdrowia: ingest z wielu źródeł → obserwacje LOINC/UCUM → trendy, CO DALEJ, udostępnianie'
api = container 'API WELISKI' {
technology 'Python 3.12 / FastAPI, systemd unit czak-Snow-weliska'
description 'REST (openapi docs WELISKA API-CONTRACT): obserwacje, raporty, dokumenty+OCR HITL, webhook HealthSync, intent-joby, granty, CO DALEJ, proxy silnika'
ocr = component 'OCR pipeline' { technology 'PaddleOCR PP-StructureV3 (CPU) + normalizacja LLM structured-output; fallback Azure DI ' }
ingest = component 'Ingest + normalizacja' { technology 'parsery PDF (pdfplumber), Apple export.xml, HealthKit JSON, CDA/EDM; lab_name_map → LOINC; unit_conversion → value_canon; hash_dedup' }
rules = component 'Silnik CO DALEJ' { technology 'reguły YAML (rule) → maintenance_state; ewaluacja przy imporcie + tick dzienny; SCORE2 model' }
share = component 'Udostępnianie + audit' { technology 'share_grant scope → filtr zapytań; access_log per odczyt; tożsamość z nagłówka CF Access (JWT)' }
}
db = database 'bazka' {
#art9
technology 'SQLite WAL na szyfrowanym wolumenie; backup dobowy poza git-tree'
description 'docs WELISKA DB-SCHEMA (17 tabel)'
}
web = container 'Portal web (panel)' {
technology 'React/TS (Vite), statyczny, za CF Access; tokeny brandu wsa'
description 'panel, trend, wgraj+zatwierdź OCR, CO DALEJ, udostępnianie, źródła (freshness)'
}
tg = container 'Bot TG (Snow)' {
technology 'bramka komunikatorów + RichResponse; token z sejf poświadczeń '
description '/wynik, zdjęcie→OCR→potwierdź, /trend, /codalej, /synevo (intent+kod), powiadomienia'
}
app = container 'Apka mobilna (Ionic/Capacitor)' {
#planned
technology 'skorupa nad web + natywny HealthSync (Swift: HKObserverQuery+background delivery, anchored query, background URLSession) + Kotlin WorkManager (Health Connect)'
description 'iOS pierwsze (TestFlight internal), Android przez Health Connect'
}
robot pobierający = container 'Wafle-pobieracze' {
technology 'systemd timers + Playwright (wafel.prot); kredki z sejf poświadczeń'
description 'Synevo na intent (PESEL+kod jednorazowy), Diagnostyka/ALAB (intent lub konto stałe), P1-EDM przez Wellysę'
}
store = container 'Magazyn plików' {
technology 'magazyn plików środowisko Wellysy (R2 czak-wsa) — do fixu : lokalny wolumen z dogrywką'
description 'oryginały: zdjęcia, PDF, XML; proweniencja + tagi'
}
}
słownik medyczny = system 'słownik medyczny — słowniki' { #external description 'slownik_lookup: LOINC/UCUM/ICD (route po)' }
silnik = system 'silnik algorytmów Wellysy' { #external description 'katalog 222 wskaźników; kontrakt ' }
sejf poświadczeń = system 'sejf poświadczeń' { #external description 'kredki: portale labów, token TG, cert P1' }
cf = system 'Cloudflare' { #external description 'Tunnel + Access (policy per email) + Pages (spec-sajt)' }
telegram = system 'Telegram Bot API' { #external }
healthkit = system 'Apple HealthKit / Health Connect' { #external description 'na urządzeniu użytkownika; background delivery → HealthSync' }
diag = system 'Diagnostyka / ALAB portale' { #external description 'konto stałe albo nr zlecenia; PDF' }
p1 = system 'P1 e-Zdrowie (CeZ)' { #external description 'EDM IHE XDS.b ITI-18/43, Zgody, ZM — przez Wellysę jako usługodawcę (mTLS, cert PROD)' }
wellysa_api = system 'wellysa-api (Wellysa)' { #external description 'dziś: e-Rejestracja /v1/p1/*; do rozszerzenia o EDM' }
azure = system 'Azure Document Intelligence (West Europe)' { #external #planned description 'fallback OCR — tylko po decyzji ' }
cze -> web 'wpisuje, ogląda, zatwierdza OCR, udostępnia' 'HTTPS za CF Access'
cze -> tg 'komendy, zdjęcia, powiadomienia'
cze -> app 'nosi Watcha; apka w tle wysyła próbki'
odbiorca -> web 'ogląda TYLKO scope' 'CF Access, email'
web -> api 'REST JSON (JWT CF Access w nagłówku)'
tg -> api 'REST JSON (tożsamość czaka)'
app -> api 'POST /healthkit/batch (background URLSession)'
app -> healthkit 'HKObserverQuery + anchored query / Health Connect Changes API'
api -> db 'SQL'
api -> store 'art_put oryginału'
api -> słownik medyczny 'slownik_lookup(LOINC/UCUM)'
api -> silnik 'oblicz wskaźnik (kontrakt)'
api -> robot pobierający 'zleca intent_job'
robot pobierający -> synevo 'Playwright: login kodem, pobierz PDF'
robot pobierający -> diag 'Playwright: login, nowe PDF'
robot pobierający -> wellysa_api 'EDM pacjenta (po rozszerzeniu API)'
wellysa_api -> p1 'SOAP/XDS.b mTLS'
robot pobierający -> sejf poświadczeń 'kredki (NAME-ONLY w logach)'
api -> azure 'fallback OCR (tylko po)'
tg -> telegram 'Bot API'
cf -> web 'Access gate → tunel → statyczny front'
cf -> api 'Access gate → tunel cloudflared'
}
deployment {
environment dev 'dev (serwer platformy, środowisko Wellysy)' {
host box 'Hetzner serwer platformy' {
unit u_api 'czak-Snow-weliska.service' { instanceOf weliska.api }
unit u_db 'wolumen /opt/ext-workspaces/wellysa/weliska (szyfrowany)' { instanceOf weliska.db }
unit u_web 'static /opt/.../weliska-web (serwowane przez api lub cloudflared)' { instanceOf weliska.web }
unit u_tg 'bramka komunikatorów (Snow)' { instanceOf weliska.tg }
unit u_wafle 'czak-Snow-weliska-*.timer' { instanceOf weliska.robot pobierający }
unit u_store 'magazyn plików środowisko Wellysy / lokalny wolumen' { instanceOf weliska.store }
}
host phone 'iPhone operatora' { unit u_app 'WELISKA.app (TestFlight internal)' { instanceOf weliska.app } }
host edge 'Cloudflare' { unit u_cf 'weliska.itm8.pl (Access) + tunel' { instanceOf cf } }
}
environment prod 'prod (po dogfoodzie; ta sama topologia, osobny wolumen i hostname)' {
host box2 'Hetzner serwer platformy' {
unit p_api 'czak-Snow-weliska.service (prod)' { instanceOf weliska.api }
unit p_db 'wolumen prod' { instanceOf weliska.db }
}
}
}
views {
view index of weliska {
title 'WELISKA — L1 kontekst'
include *, cze, odbiorca, słownik medyczny, silnik, sejf poświadczeń, cf, telegram, healthkit, synevo, diag, p1, wellysa_api, azure
autoLayout LeftRight
}
view kontenery of weliska {
title 'WELISKA — L2 kontenery'
include weliska.*, cze, odbiorca, healthkit, synevo, diag, wellysa_api, słownik medyczny, silnik, cf
autoLayout TopBottom
}
view api_komponenty of weliska.api {
title 'WELISKA — L3 komponenty API'
include weliska.api.*, weliska.db, weliska.store, słownik medyczny, silnik, azure
autoLayout LeftRight
}
deployment view dev_deploy {
title 'WELISKA — deployment dev'
include dev.**
autoLayout TopBottom
}
}
wpis z ręki (FR-REK-1, FR-BAZ-1/2, FR-COD-4)
sequenceDiagram
actor U as Operator
participant W as Portal/TG
participant A as API
participant M as słownik medyczny
participant D as bazka
participant R as Silnik CO DALEJ
U->>W: /wynik glukoza 92 mg/dL 2026-09-07
W->>A: GET /metrics/search?q=glukoza
A->>M: slownik_lookup(LOINC)
M-->>A: 2345-7, UCUM mg/dL, proweniencja
W->>A: POST /observations
A->>A: walidacja UCUM · value_canon · hash_dedup
alt zła jednostka
A-->>W: 422 + podpowiedź (odmowa)
else duplikat
A-->>W: 409 + istniejący rekord
else OK
A->>D: INSERT observation (source=manual, ingested_by=person)
A->>R: ewaluuj(person)
R->>D: UPDATE maintenance_state
A-->>W: 201 + flaga L/H/N
end
zdjęcie → tabela → zatwierdź (FR-OCR-1..3, FR-MAG-1)
sequenceDiagram
actor U as Operator
participant W as Portal/TG/Apka
participant A as API
participant O as OCR (PaddleOCR)
participant S as magazyn
participant D as bazka
U->>W: zdjęcie wydruku
W->>A: POST /documents (multipart, source=ocr)
A->>S: art_put(sha256, proweniencja)
A->>D: INSERT document
A-->>W: 202 (OCR w toku)
A->>O: tabela z obrazu
O-->>A: wiersze + pewności
A->>A: lab_name_map → LOINC (pewność), ucum_guess
W->>A: GET /documents/{id}/ocr
A-->>W: propozycja (wiersze <0.8 podświetlone)
U->>W: poprawki + zatwierdź (+ zapamiętaj mapowanie)
W->>A: POST /documents/{id}/confirm
alt brak zatwierdzenia
Note over A,D: NIC nie zapisane (FR-OCR-3)
else zatwierdzone
A->>D: INSERT report + observations (source=ocr, document_id)
A->>D: UPSERT lab_name_map (verified_by=person)
end
Synevo na intent z kodem jednorazowym (FR-LAB-1, FR-ART9-2)
sequenceDiagram
actor U as Operator
participant W as Portal/TG
participant A as API
participant J as intent_job
participant F as Robot pobierający (Playwright)
participant V as sejf poświadczeń
participant SY as wyniki.synevo.pl
participant D as bazka
U->>W: „pobierz z Synevo"
W-->>U: formularz: kod z karteczki (albo skan QR)
U->>W: kod
W->>A: POST /sources/lab:synevo/intent {code}
A->>J: queued (code_hint = 2 ostatnie znaki · kod tylko w pamięci)
A-->>W: 202 job
F->>J: pobierz zlecenie
F->>V: PESEL operatora (NAME-ONLY w logach)
F->>SY: login PESEL + kod
alt kod błędny / wygasł (60 dni)
SY-->>F: odmowa
F->>J: failed/expired + komunikat
A-->>W: powiadomienie „kod nieprawidłowy"
else OK
SY-->>F: PDF + tabela
F->>A: POST /documents (source=lab:synevo, source_ref=nr zlecenia)
A->>D: parser → report/observations · nieznane nazwy → kolejka lab_name_map
F->>J: done + result_report_id
A-->>W: powiadomienie „wynik z Synevo w bazce"
end
HealthKit w tle (FR-WATCH-1, FR-BAZ-2)
sequenceDiagram
participant HK as HealthKit (iOS)
participant HS as HealthSync (Swift, AppDelegate)
participant A as API
participant D as bazka
HK->>HS: wybudzenie (background delivery, typ X)
HS->>HK: HKAnchoredObjectQuery(anchor)
HK-->>HS: nowe próbki + nowy anchor
HS->>A: POST /healthkit/batch (background URLSession, deviceToken)
A->>A: dedup (hash), UCUM → value_canon
A->>D: INSERT observations (source=apple-health)
A-->>HS: 200 {accepted, duplicates, anchor}
HS->>HS: zapisz anchor · completionHandler()
Note over HS: brak completionHandler 3× → HealthKit przestaje budzić
dokumenty pacjenta z EDM przez Wellysę (FR-P1-1..3)
sequenceDiagram
actor U as Operator (pacjent)
participant A as API WELISKI
participant F as Robot pobierający
participant WA as wellysa-api (+EDM)
participant P1 as P1 CeZ (XDS.b)
participant D as bazka
U->>A: „pobierz z P1" (po zgodzie w P1 na dostęp Wellysy)
A->>F: intent_job(source=p1-edm)
F->>WA: GET /p1/edm/index?pesel (cert Wellysy, mTLS)
WA->>P1: WS-Trust SAML → ITI-18 (zgody sprawdzone)
alt brak zgody
P1-->>WA: odmowa (XACML)
WA-->>F: 403 „brak zgody pacjenta"
else OK
P1-->>WA: indeks dokumentów
loop per nowy dokument
WA->>P1: ITI-43 pobierz
P1-->>WA: CDA
F->>A: POST /documents (kind=cda, source=p1-edm, source_ref=uniqueId)
A->>D: parser CDA → report/observations/health_event
end
end
CO DALEJ: ewaluacja i popychanie (FR-COD-3..6)
sequenceDiagram
participant T as trigger (import / zmiana profilu / tick dzienny)
participant R as Silnik reguł
participant D as bazka
participant N as nudge (TG/push)
actor U as Operator
T->>R: ewaluuj(person)
R->>D: profil + observations + health_event + reguły aktywne
loop per reguła
R->>R: applies_if? → not_applicable | last_completed z dowodu | next_due = last + interval | status
R->>D: UPSERT maintenance_state (evaluated_at, powód)
end
alt stan zmienił się na due/overdue
R->>N: wyślij (rosnąca przerwa, „zrobione/odłóż")
N->>U: „LDCT płuc: do zrobienia — M, 44 l., 20 paczkolat · źródło NFZ"
U->>N: odłóż do 2026-12-01 / zrobione 2026-08-15
N->>D: user_override / last_completed_ref=user · access_log
end
udostępnienie i odczyt w scope (FR-UDO-1..3, FR-ART9-3)
sequenceDiagram
actor O as Operator
actor R as Odbiorca
participant CF as CF Access
participant A as API
participant D as bazka
O->>A: POST /shares {email, scope, valid_to}
A->>D: INSERT share_grant
A->>CF: (Lee) policy: dodaj email
R->>CF: wejście → OTP
CF->>A: JWT (email)
R->>A: GET /observations?loinc=X
A->>D: grant aktywny? X w scope? okres?
alt poza scope / odwołany / wygasł
A-->>R: 403
else w scope
A->>D: INSERT access_log(read)
A-->>R: dane przycięte do scope
end
wskaźnik z silnik algorytmów Wellysy (FR-ALG-1..3)
sequenceDiagram
actor U as Operator
participant A as API
participant D as bazka
participant S as silnik silnik algorytmów Wellysy
U->>A: GET /algorithms
A->>D: ostatnie obserwacje per LOINC
A-->>U: HOMA-IR: brakuje insulina (20448-7) · SCORE2: dostępny
U->>A: POST /algorithms/score2/run
A->>A: wejścia w UCUM kanonicznym (unit_conversion)
A->>S: {inputs LOINC/UCUM, sex, age} (kontrakt )
S-->>A: value, unit, formula_ref, provenance, warnings
A->>D: INSERT algorithm_run
A-->>U: wynik + „to nie jest diagnoza"
Ten sam ekran w dwóch szerokościach: komputer (1180 px) i telefon (390 px) — układ zwija menu i tabele do kart. Asystent na prawej szynie na każdym ekranie. Otwórz ekran w nowej karcie · spis 34 ekranów
POST /observations; klik w parametr → SCR-TREND.Ten sam ekran w dwóch szerokościach: komputer (1180 px) i telefon (390 px) — układ zwija menu i tabele do kart. Asystent na prawej szynie na każdym ekranie. Otwórz ekran w nowej karcie · spis 34 ekranów
Ten sam ekran w dwóch szerokościach: komputer (1180 px) i telefon (390 px) — układ zwija menu i tabele do kart. Asystent na prawej szynie na każdym ekranie. Otwórz ekran w nowej karcie · spis 34 ekranów
Ten sam ekran w dwóch szerokościach: komputer (1180 px) i telefon (390 px) — układ zwija menu i tabele do kart. Asystent na prawej szynie na każdym ekranie. Otwórz ekran w nowej karcie · spis 34 ekranów
Ten sam ekran w dwóch szerokościach: komputer (1180 px) i telefon (390 px) — układ zwija menu i tabele do kart. Asystent na prawej szynie na każdym ekranie. Otwórz ekran w nowej karcie · spis 34 ekranów
Ten sam ekran w dwóch szerokościach: komputer (1180 px) i telefon (390 px) — układ zwija menu i tabele do kart. Asystent na prawej szynie na każdym ekranie. Otwórz ekran w nowej karcie · spis 34 ekranów
profile_history → ewaluacja CO DALEJ (lista zmienia się od razu).Ten sam ekran w dwóch szerokościach: komputer (1180 px) i telefon (390 px) — układ zwija menu i tabele do kart. Asystent na prawej szynie na każdym ekranie. Otwórz ekran w nowej karcie · spis 34 ekranów
POST /sources/{source}/intent (kod tylko w locie); potwierdzenie mapowania → lab_name_map.verified_by.Ten sam ekran w dwóch szerokościach: komputer (1180 px) i telefon (390 px) — układ zwija menu i tabele do kart. Asystent na prawej szynie na każdym ekranie. Otwórz ekran w nowej karcie · spis 34 ekranów
Ten sam ekran w dwóch szerokościach: komputer (1180 px) i telefon (390 px) — układ zwija menu i tabele do kart. Asystent na prawej szynie na każdym ekranie. Otwórz ekran w nowej karcie · spis 34 ekranów
Ten sam ekran w dwóch szerokościach: komputer (1180 px) i telefon (390 px) — układ zwija menu i tabele do kart. Asystent na prawej szynie na każdym ekranie. Otwórz ekran w nowej karcie · spis 34 ekranów
Makieta 0.2: ekrany wersji 1 oraz wersji 2/3 (gdzie, portfel) złożone z klocków kitu w kolorach Wellysy — każda encja ma listę, kartę szczegółu i dodawanie; Panel; dwa ekrany z rozwiniętym asystentem. Dane przykładowe (syntetyczne). Każdy ekran przeszedł bramę kanonu dashboardu i audyt brandu.
Aplikacja w Telegramie — te same funkcje jako rozmowa: wpis z ręki, zdjęcie wydruku → odczyt → zatwierdzenie, trend jako obraz, Co dalej, pobranie z Synevo na kod z karteczki, świeżość źródeł, powiadomienia z przyciskami.
Aplikacja mobilna (iOS pierwsza, potem Android): skorupa nad tym samym portalem + natywny most do zegarka (dane z aplikacji Zdrowie w tle). Ekran zgody i stan synchronizacji.
Wersja 2 — GDZIE. Po v1 użytkownik wie CO ma zrobić; v2 odpowiada gdzie, jak szybko, za ile i pod jakim warunkiem — i pozwala to umówić.
„na wersję drugą zrobimy — GDZIE TE BADANIA ROBIĆ jakie są procedury itp itd”
Do tego ustalenia właściciela Wellysy i zespołu strategicznego, które wiążą tę wersję: pacjent widzi komplet opcji i wybiera; w pierwszej fazie ścieżka publiczna to tablica informacyjna, nie rezerwacja; państwo ma już centralną e-Rejestrację, więc jesteśmy nakładką przed nią i po niej; warstwa handlowa tylko na życzenie użytkownika (katalog, bez rankingów).
Zależy od: wersja 1 osiągnięta (bazka + Co dalej). Otwarte decyzje właściciela: informacja czy rezerwacja (A7), narracja wobec IKP (C1), dostęp podmiotu trzeciego do e-Rejestracji (pomiar), partnerzy katalogu i model stawki.
| pole | wartość |
|---|---|
| Produkt / wersja | WELISKA v2 — „GDZIE TE BADANIA ROBIĆ, jakie są procedury" (Cze 2026-09-07) |
| Krowa | (draft) · zależy od (v1: bazka + CO DALEJ) |
| Linia | P1 PHA + P2 B2P Board („tablica i rozwiązania w systemie publicznym") — dokument wsa PRODUCT LINES v2.0 |
| Źródła (dokument zespół strategiczny Wellysy, materiały Wellysy) | PRODUCT LINES v2.0 (P2 faza początkowa vs docelowa; „Wallet poszerza opcje"; kruche założenie o rezerwacji) · research PRODUCT LINES REFRAME v3.0 (dostęp zarządzany; zapora konfliktu interesu; dwa profile płatnika) · research INSURER LANDSCAPE v2.0 (centralna e-Rejestracja NFZ; Moje Zdrowie; „nakładka nad IKP"; kontra właściciela 2026-08-12) · WALLET v0.4 (waluta publiczna warunkowa: deklaracja POZ, trzy stany) · research PRICING SKELETON (pull, nie push; katalog z art. 59) · ROADMAP v1.0 (faza 1: wskazujemy opcje, nie kierujemy; kardiologia + interna) · DECYZJE (A7, C1, A2) |
| Autor | Snow · akceptujący: Cze (operator) → Rafał (właściciel: decyzje A7/C1 z rejestru DECYZJE) |
| Rejestr zmian | v0.1 2026-09-08 Snow — pierwszy draft po przeglądzie 20 dokument zespół strategiczny Wellysy |
Wersja 1 mówi użytkownikowi <b>CO</b> ma zrobić (lista CO DALEJ z powodem i źródłem). Wersja 2 odpowiada na pytanie, które pada zaraz potem: <b>gdzie, jak szybko, za ile i z czyich pieniędzy</b> — i pozwala to umówić.
Cztery ustalenia z dokument, które wyznaczają kształt v2 (nie do dyskusji w tej specce, bo są decyzjami właściciela lub prawem):
REFRAME v3.0 dostęp; właściciel 2026-07-29: „powinny być opcje i flexy Wallet, a nie sztywno ustalona ścieżka"). Zasobność portfela nie wyznacza ścieżki medycznej.PRODUCT LINES początkowa; ROADMAP faza 1: „wskazujemy opcje, nie kierujemy"). Rezerwacja w systemie publicznym = stan docelowy, warunkowany narzędziami od płatnika publicznego — najkruchsze założenie całej architektury (PRODUCT LINES kwestie). Decyzja A7 (informacja vs rezerwacja automatyczna) — otwarta.INSURER LANDSCAPE od tego): centralna e-Rejestracja NFZ od 2026-01-01 (mammografia 45–74, HPV+LBC 25–64, pierwsza wizyta kardiologiczna), od 2026-07-01 wyłączna droga zapisu na te świadczenia; kolejne dziedziny w H2 2026; cała AOS do 2029-12-31; Informator o Terminach Leczenia (~14 tys. placówek, kolejki raportowane co tydzień). Wniosek ratyfikowany po kontrze właściciela (2026-08-12): nie konkurujemy z IKP w pokazywaniu terminów — jesteśmy nakładką: przed (profil + CO DALEJ) i po (panel opcji łączący publiczne z prywatnym i z finansowaniem). Decyzja C1 (narracja) — otwarta.PRICING SKELETON): inicjatywa pacjenta (logowana), cennik jako katalog z art. 59 ustawy o wyrobach (nazwa, cena, specyfikacja — zero odznak „polecane", rankingów, liczników pilności), rozdzielenie warstwy klinicznej od handlowej w architekturze, jawne ujawnienie wynagrodzenia przy wyborze kanału. Zapora konfliktu interesu (REFRAME): wynagrodzenie nigdy od treści zalecenia; jednakowa stawka w zestawie opcji ALBO opłata pokazana pacjentowi — trzeciego nie ma.<b>Zdanie sukcesu v2:</b> <i>przy każdej pozycji CO DALEJ użytkownik widzi trzy realne drogi (publiczna / prywatna / zdalna) z czasem, ceną dla siebie i źródłem finansowania — i jedną z nich umawia bez wychodzenia z aplikacji.</i>
| pojęcie | znaczenie |
|---|---|
| procedura | badanie/świadczenie z kodem (ICD-9-PL / ICHI / LOINC dla labu) — ontologia Wellysy procedure; słownik ze słownika medycznego (ICD-9-PL 5.64, ICHI) |
| temat → procedury | mapa: temat CO DALEJ (np. „lipidogram", „mammografia") → 1..n procedur wykonalnych (np. mammografia = ICD-9 87.37) |
| miejsce (provider) | placówka/lab/lekarz z rejestrów (RPWDL, NIL, umowy NFZ) — ontologia provider, provider_system |
| oferta (offering) | miejsce × procedura × kanał (publiczny / prywatny stacjonarny / zdalny) z ceną i czasem — ontologia offering, price |
| termin (slot) | wolny termin z e-Rejestracji / kalendarza partnera — ontologia slot |
| kolejka | czas oczekiwania publiczny (Informator o Terminach / AP-KOLCE) — informacja, nie rezerwacja |
| panel opcji | dla jednej pozycji CO DALEJ: ≥1 opcja publiczna, ≥1 prywatna, ≥1 zdalna (gdy ma sens medyczny), każda z: czas, koszt dla mnie, źródło finansowania, warunek (np. skierowanie od POZ z deklaracją) |
| ścieżka publiczna | NFZ: Moje Zdrowie przez POZ z deklaracją; programy przesiewowe bez skierowania; AOS przez e-Rejestrację (IKP) |
| czas bezpieczeństwa | okno, w którym opcja publiczna „mieści się w czasie" (ROADMAP faza 1); poza nim panel pokazuje też komercyjne |
| log inicjatywy | zapis kliknięcia „pokaż ceny/opcje" — dowód, że warstwę handlową otworzył pacjent (PRICING) |
| rola | kto | co |
|---|---|---|
| użytkownik | operator (dogfood), potem użytkownicy P1/P2 | ogląda panel opcji, wybiera, umawia |
| właściciel | Rafał | decyzje A7, C1, A2 (P1–P4 produkty czy kanały), profil płatnika A4 |
| CTO-czak | Snow | buduje; utrzymuje mapę temat→procedury i katalog źródeł miejsc |
| słownik medyczny | słownik medyczny (slownik_lookup: ICD-9-PL, ICHI; RPWDL, NIL, AP-KOLCE w korpusie) | procedury, placówki, kolejki |
| Wellysa jako podmiot leczniczy | Wellysa/p1, wellysa-api /v1/p1/ (e-Rejestracja: visits/slots/reserve/booked/cancel), /v1/słownik medyczny/ | terminy publiczne i rezerwacja tam, gdzie płatnik daje narzędzia |
| silnik algorytmów | silnik algorytmów Wellysy (scrape_providers = TARGET) | providerzy z proweniencją |
| partnerzy prywatni | laboratoria (hurt), ZnanyLekarz, telemedycyna, sieć | oferty prywatne (katalog) |
| lekarz POZ z deklaracją | poza produktem | zleca badania publiczne (Moje Zdrowie) |
Wejścia: lista CO DALEJ (v1), profil (deklaracja POZ: gdzie), słownik medyczny (procedury, placówki, kolejki), e-Rejestracja/IKP przez Wellysę (terminy, rezerwacja), katalogi partnerów prywatnych (cennik hurtowy labów, kalendarze). Wyjścia: panel opcji, umówienie (rezerwacja lub voucher), zdarzenie w bazce („umówione", „wykonane"), wejście do v3 (koszt + finansowanie).
| etap | co użytkownik zobaczy | moduły |
|---|---|---|
| 1. Temat → procedura | przy każdym temacie CO DALEJ: co dokładnie trzeba wykonać (kod, nazwa, warunki: skierowanie? na czczo?) | PROC |
| 2. Tablica publiczna | dla procedury: czy i jak jest bezpłatna (Moje Zdrowie przez POZ z deklaracją / program przesiewowy bez skierowania / AOS przez e-Rejestrację), w jakim czasie (kolejka), gdzie najbliżej | PUB, KOL |
| 3. Katalog prywatny (pull) | po kliknięciu „pokaż opcje prywatne": lab (cena), telemedycyna, wizyta prywatna, sieć partnera — jako katalog, bez rankingu; log inicjatywy | PRYW, ZAP |
| 4. Panel opcji | jedno miejsce: publiczna / prywatna / zdalna z czasem, kosztem dla mnie i źródłem (na razie: NFZ vs własne; v3 dołoży polisę/pracodawcę) | GDZ |
| 5. Umówienie | rezerwacja w e-Rejestracji przez Wellysę tam, gdzie dostępna (kardiologia, mammo, HPV od 2026-07); voucher do labu; kalendarz partnera; wpis „umówione" → po wykonaniu wynik ląduje w bazce i zamyka temat | REZ |
| 6. Domknięcie pętli | „byłem?" (informacja zwrotna, PRODUCT LINES domknięcia) → zdarzenie → CO DALEJ przelicza | REZ |
docs WELISKA-WALLET SPEC; w v2 panel pokazuje tylko „NFZ (0 zł, warunek)" vs „cena prywatna".| ID | Wymaganie | Prio | Kryterium akceptacji | Trace |
|---|---|---|---|---|
| FR-PROC-1 | Każdy temat CO DALEJ MUSI mapować się na ≥1 procedurę z kodem (ICD-9-PL / ICHI; lab: LOINC panel) ze słownika medycznego, z warunkami wykonania (skierowanie, przygotowanie). | M | ✅ 100% tematów z v1 (≥20 reguł) ma procedurę z kodem i źródłem | [dbml: rule.procedure_code → procedure] |
| FR-PROC-2 | Mapa temat→procedura MUSI mieć proweniencję (program NFZ / towarzystwo) i review_by, jak reguły v1. | M | ✅ pole źródła i daty przy każdej mapie | [dbml: procedure] |
| ID | Wymaganie | Prio | Kryterium akceptacji | Trace |
|---|---|---|---|---|
| FR-PUB-1 | Dla procedury system MUSI powiedzieć, czy i jak jest bezpłatna: (a) Moje Zdrowie przez POZ z deklaracją (20–49 co 5 lat, 50+ co 3 lata), (b) program przesiewowy bez skierowania (mammografia 45–74, HPV/cytologia 25–64, kolonoskopia 50–65, LDCT od 2026-10), (c) AOS na skierowanie przez e-Rejestrację. | M | ✅ 3 klasy widoczne z warunkiem i źródłem (pacjent.gov.pl / NFZ) | [ekran: panel opcji] |
| FR-PUB-2 | Deklaracja POZ jako warunek waluty publicznej (WALLET dostępu): profil MUSI trzymać, czy i gdzie użytkownik ma deklarację; system rozróżnia 3 stany: w sieci / poza siecią (zlecenie u obcego lekarza — kierujemy tam lub proponujemy przeniesienie, z informacją, że zmiana po limicie jest odpłatna) / brak (waluta niedostępna do złożenia). | M | ✅ zmiana stanu deklaracji zmienia panel opcji | [dbml: person.profile_json.poz_declaration] |
| FR-PUB-3 | System MUSI pokazać najbliższe miejsca z umową NFZ na daną procedurę (RPWDL + umowy przez słownik medyczny) i czas oczekiwania z Informatora o Terminach / AP-KOLCE, z datą pomiaru; brak danych = „nie zmierzono", nie „brak kolejki". | M | ✅ lista miejsc z kolejką i datą; trzeci stan widoczny | [dbml: provider, provider_system, offering] |
| FR-PUB-4 | Dla dziedzin objętych centralną e-Rejestracją system POWINIEN pokazać wolne terminy przez Wellysę jako podmiot leczniczy (/v1/p1/slots), z zastrzeżeniem, że zapis = IKP/e-Rejestracja lub rezerwacja przez Wellysę tylko gdy płatnik daje narzędzie (decyzja A7). | S | ✅ terminy kardiologii/mammo/HPV widoczne; link do IKP zawsze | [api: wellysa-api /v1/p1/*] |
| FR-PUB-5 | Język: „niedublowanie badań i korzystanie z uprawnień, które i tak przysługują" — NIGDY „spychanie na budżet państwa" (WALLET językowe). | M | ✅ lint treści UI na frazy zakazane | [test] |
| ID | Wymaganie | Prio | Kryterium akceptacji | Trace |
|---|---|---|---|---|
| FR-PRYW-1 | Warstwa prywatna otwiera się WYŁĄCZNIE po działaniu użytkownika („pokaż opcje prywatne / ceny"); akt inicjatywy jest logowany (kto, kiedy, z którego tematu). | M | ✅ bez kliknięcia brak cen w UI; access_log ma wpis inicjatywa_handlowa | [dbml: access_log] |
| FR-PRYW-2 | Katalog = nazwa, cena, specyfikacja, miejsce, czas — bez odznak, rankingów, liczników pilności, wizerunku medycznego; kolejność neutralna (alfabetyczna / odległość / cena — wybór użytkownika, nie nasz). | M | ✅ audyt UI: zero elementów spoza art. 59 | [ekran] |
| FR-PRYW-3 | Jeśli Wellysa pobiera opłatę za skierowanie ruchu do partnera, w zestawie opcji stawka jest jednakowa ALBO pokazana pacjentowi przy opcji (zapora REFRAME). | M | ✅ konfiguracja partnera bez pola „stawka" nie wchodzi do panelu bez flagi „ujawnij" | [dbml: offering.fee_disclosure] |
| FR-PRYW-4 | Laboratoria: cena z cennika hurtowego partnera + marża jako jedna cena dla pacjenta; pakiety „reszta poza NFZ" (np. ApoB, hs-CRP, Lp(a)) jako osobne pozycje, nie „pakiet zamiast Moje Zdrowie" (INSURER właściciela). | S | ✅ pozycja katalogu pokazuje, co NFZ pokrywa, a co dokupujesz | [dbml: price] |
| FR-PRYW-5 | Telemedycyna i wizyta prywatna: partnerzy z proweniencją (skąd, umowa, data); brak umowy = pozycja niewidoczna, nie „cena orientacyjna". | M | ✅ każda oferta ma source_ref | [dbml: offering] |
| ID | Wymaganie | Prio | Kryterium akceptacji | Trace |
|---|---|---|---|---|
| FR-GDZ-1 | Dla pozycji CO DALEJ panel MUSI pokazać do trzech dróg: publiczna, prywatna (po pull), zdalna — każda z: czas do wykonania, koszt dla mnie, źródło finansowania, warunek. Brak drogi = jawnie „brak opcji publicznej dla tej procedury", nie pusta kolumna. | M | ✅ operator widzi panel dla ≥5 tematów ze swojej listy | [ekran: SCR-GDZIE] |
| FR-GDZ-2 | Czas bezpieczeństwa: gdy opcja publiczna mieści się w oknie tematu (np. lipidogram „w ciągu 3 miesięcy") — jest pierwsza w kolejności czasowej; panel nie ukrywa prywatnej i nie ukrywa publicznej (obie zawsze). | M | ✅ zmiana kolejki NFZ zmienia kolejność, nie zestaw | [c4: api.rules] |
| FR-GDZ-3 | Ocena medyczna (v1 CO DALEJ) MUSI być liczona przed i niezależnie od panelu opcji — architektonicznie osobny komponent bez dostępu do cen (REFRAME zapora 2). | M | ✅ moduł reguł nie importuje modułu cen (test statyczny) | [c4] |
| FR-GDZ-4 | Panel MUSI działać też przy pustym portfelu (v3): wtedy opcje = NFZ + cena prywatna; brak polisy nie zwęża listy (właściciel 2026-07-29). | M | ✅ test: profil bez polisy → 3 drogi | [test] |
| ID | Wymaganie | Prio | Kryterium akceptacji | Trace |
|---|---|---|---|---|
| FR-REZ-1 | Umówienie publiczne: dla dziedzin w centralnej e-Rejestracji rezerwacja przez Wellysę (/v1/p1/reserve) gdy dostępna dla podmiotu trzeciego; inaczej „umów w IKP" z linkiem + zapis „zamierzam" w bazce. | M | ✅ realna rezerwacja testowa na INT P1 albo jawny komunikat o braku narzędzia | [api: wellysa-api] |
| FR-REZ-2 | Umówienie prywatne: voucher/zlecenie do laboratorium (kod na pobranie), rezerwacja u partnera; kwota i finansowanie = v3 (w v2: własne). | M | ✅ voucher wygenerowany i widoczny w bazce jako zdarzenie „umówione" | [dbml: health_event.kind=planned] |
| FR-REZ-3 | Po terminie system pyta „byłeś?" i domyka: wynik z labu/EDM zamyka temat automatycznie; brak wyniku → pytanie, nie założenie. | M | ✅ wykonana mammografia (EDM) zamyka temat bez kliku | [c4: silnik CO DALEJ] |
| FR-REZ-4 | Odwołanie/zmiana terminu (/v1/p1/cancel) i informacja o niestawiennictwie (v3: blokada kwoty). | S | ✅ odwołanie widoczne w bazce | [api] |
| ID | Wymaganie | Prio | Kryterium akceptacji | Trace |
|---|---|---|---|---|
| FR-ZAP-1 | Log inicjatywy pacjenta i wersjonowanie każdego wariantu komunikatu handlowego przez 2 lata (PRICING pkt 6). | M | ✅ archiwum wersji tekstów katalogu z datą | [dbml: nowa tabela catalog_copy_version] |
| FR-ZAP-2 | Zgoda na kanał push/SMS/e-mail osobna od zgody na dane o zdrowiu; nudge CO DALEJ (temat + źródło) ≠ komunikat handlowy — dwa typy wiadomości, dwie zgody. | M | ✅ bez zgody kanałowej zero wiadomości z ofertą; nudge o temacie dalej działa | [dbml: consent] |
| FR-ZAP-3 | Zero opłat od aptek / za e-receptę; zero wynagrodzenia zależnego od liczby lub wartości sprzedanych świadczeń w warstwie klinicznej — egzekwowane brakiem takich pól w modelu. | M | ✅ schema review: brak pola prowizji per zalecenie | [dbml] |
SCR-GDZIE (panel opcji dla tematu: 3 kolumny publiczna/prywatna/zdalna, każda: miejsce, czas, koszt dla mnie, warunek, przycisk „umów" / „pokaż w IKP") · SCR-KATALOG (katalog prywatny po pull — lista jak art. 59: nazwa, cena, miejsce, czas; sort użytkownika) · SCR-MIEJSCA (mapa + lista placówek z kolejką i datą pomiaru — <code>panel</code> geo-iron: atrybut lokalizacji ⇒ mapa) · SCR-UMOWIONE (moje terminy i vouchery; „byłeś?") · TG: <code>/gdzie <temat></code> → 3 opcje jako karty; <code>/umow</code> → przebieg.
| co | decyzja | od kogo |
|---|---|---|
| A7 | P2: informacja także po udostępnieniu narzędzi rezerwacji czy rezerwacja automatyczna? | Rafał |
| C1 | narracja P2: „nakładka nad IKP/e-Rejestracją" (rekomendacja zespół strategiczny Wellysy + kontra właściciela = tak) | Rafał |
| nowa | dostęp podmiotu trzeciego do centralnej e-Rejestracji — czy Wellysa jako podmiot leczniczy może rezerwować w imieniu pacjenta (najkruchsze założenie) — pomiar u CeZ/NFZ, nie domysł | Snow (pomiar) → Rafał |
| nowa | partnerzy katalogu prywatnego na start (które laby hurtowo, ZnanyLekarz, telemedycyna) i model stawki (jednakowa vs ujawniona) | Rafał |
| nowa | dziedziny na start v2: kardiologia + interna + programy przesiewowe (za ROADMAP faza 1) — potwierdzić | Rafał |
Akcept v2 = po dowodzie v1 (KR1–KR3 krowy) i po warsztacie z właścicielem (Wallet A6 ma ten sam warsztat). Odbiór na żywych danych operatora: ≥5 tematów z panelem 3 dróg, ≥1 realne umówienie (INT P1 albo voucher).
Neutralność szeregowania <b>audytowalna</b> (log kolejności + reguła), separacja komponentów klinicznego i handlowego (import-gate w CI), dane kolejek z datą pomiaru (freshness), zero danych o zdrowiu do partnerów katalogu (tylko procedura + termin), proweniencja każdej oferty.
Z <code>wellysa-ontology.dbml</code>: <code>procedure</code> (kod, system, nazwa, warunki), <code>provider</code> (RPWDL/NIL id, adres, geo), <code>provider_system</code> (umowa NFZ / partner / e-Rejestracja), <code>offering</code> (provider × procedure × kanał, cena, czas, <code>source_ref</code>, <code>fee_disclosure</code>), <code>slot</code> (termin, źródło: e-Rejestracja / kalendarz partnera), <code>pathway</code>/<code>pathway_step</code> (temat → procedury), + <code>queue_measure</code> (kolejka, as_of, źródło), <code>initiative_log</code> (pull), <code>catalog_copy_version</code>. Rozszerzenia <code>person.profile_json</code>: <code>poz_declaration {status: w_sieci|poza|brak, provider_id}</code>.
| co | droga | stan | ||||
|---|---|---|---|---|---|---|
| procedury/placówki/kolejki | słownik medyczny slownik_lookup (ICD-9-PL, ICHI; RPWDL, NIL, AP-KOLCE w korpusie słownik medyczny — do potwierdzenia, które read-API) | tool na powierzchni | ||||
| terminy + rezerwacja publiczna | `wellysa-api /v1/p1/visits | slots | reserve | booked | cancel` (e-Rejestracja, own-pool) — INT → PROD | istnieje (pomiar 2026-09-03) |
| Informator o Terminach Leczenia | źródło publiczne NFZ — pobieracz jako wafel przez słownik medyczny (nie własny) | do zmierzenia (API vs scraping) | ||||
| katalog prywatny | cenniki hurtowe labów (umowy Wellysy), ZnanyLekarz, telemedycyna — przez offering z umową | brak umów = brak pozycji | ||||
| providerzy z dowodem | silnik algorytmów Wellysy scrape_providers (TARGET) | kontrakt |
Wszystko z A5 ZAP + <code>PRODUCT LINES</code> : konto pacjenta nie pozwala na zgodę tematyczną — samoograniczenie zakresu danych jest naszym zobowiązaniem, mówimy to wprost. Ustawa o systemach AI (w mocy od 2026-08-11, kontrole od 2026-10-28) — decyzja C4 (prawnik) przed rekomendacjami kanału.
| # | ryzyko | plan |
|---|---|---|
| R1 | brak narzędzi płatnika do rezerwacji przez podmiot trzeci | faza 1 = informacja + link IKP; pomiar u CeZ |
| R2 | katalog bez umów = pusty | start od labów z umową Wellysy (hurt) |
| R3 | kolejki NFZ niemierzalne dla części dziedzin | trzeci stan „nie zmierzono" |
| R4 | UI wpada w reklamę (art. 14, 55, 59) | lint fraz + audyt elementów przed wydaniem; prawnik (C4/C5) |
| R5 | e-Rejestracja rozszerza się wolniej niż harmonogram | ROADMAP : zostajemy przy wskazywaniu |
Arbitraż kolejkowy (ROADMAP faza 4), rezerwacja w sieciach partnerskich przez API (blokada po stronie sieci — REFRAME warunek e), P2c „zaciągnięcie danych z konta pacjenta" (to v1 etap 6), sieć POZ z deklaracjami jako kanał wejścia (WALLET: „do waluty publicznej wchodzi się przez placówkę").
Podzbiór modelu dla tej wersji; tabele wspólne z v1 (osoba, reguły, udostępnienia) pokazane dla relacji. Pełna definicja w kroku „Model danych” wersji 1.
erDiagram
rule ||--o{ topic_procedure : "rule_id"
procedure ||--o{ topic_procedure : "procedure_id"
provider ||--o{ provider_system : "provider_id"
provider ||--o{ offering : "provider_id"
procedure ||--o{ offering : "procedure_id"
provider ||--o{ queue_measure : "provider_id"
procedure ||--o{ queue_measure : "procedure_id"
offering ||--o{ slot : "offering_id"
person ||--o{ booking : "person_id"
rule ||--o{ booking : "rule_id"
offering ||--o{ booking : "offering_id"
slot ||--o{ booking : "slot_id"
person ||--o{ initiative_log : "person_id"
catalog_copy_version ||--o{ initiative_log : "copy_version_id"
person {
integer id
text display_name
text sex
date birth_date
text cf_email
text profile_json
}
rule {
text id
text topic
text rule_yaml
text procedure_code
integer interval_days
text source_org
}
procedure {
integer id
text code_system
text code
text display_pl
text prep_note
text public_class
}
topic_procedure {
integer id
text rule_id
integer procedure_id
text source_org
date source_measured
date review_by
}
provider {
integer id
text kind
text name
text registry_ref
text address
real lat
}
provider_system {
integer id
integer provider_id
text system
text scope
date as_of
}
offering {
integer id
integer provider_id
integer procedure_id
text channel
real price_pln
integer lead_days
}
queue_measure {
integer id
integer provider_id
integer procedure_id
integer wait_days
datetime measured_at
text source
}
slot {
integer id
integer offering_id
datetime starts_at
text source
text external_ref
datetime fetched_at
}
booking {
integer id
integer person_id
text rule_id
integer offering_id
integer slot_id
text mode
}
initiative_log {
integer id
integer person_id
datetime at
text from_topic
text channel
integer copy_version_id
}
catalog_copy_version {
integer id
text key
text text
datetime valid_from
datetime valid_to
}Ta sama architektura co v1; API dostaje komponenty: procedury i tablica publiczna (słownik medyczny: ICD-9-PL/ICHI, placówki, kolejki z datą pomiaru), katalog prywatny (oferty partnerów z umową, otwierany tylko po akcie inicjatywy użytkownika — logowanym), panel opcji (trzy drogi, komponent kliniczny bez dostępu do cen — separacja w kodzie), umówienie (e-Rejestracja przez Wellysę tam, gdzie płatnik daje narzędzie; voucher; link do IKP) i domknięcie pętli („byłeś?”, dokument zamyka temat). Deployment bez zmian.
flowchart LR COD[Silnik Co dalej v1] --> PROC[Procedury: temat → kod] PROC --> PUB[Tablica publiczna: Moje Zdrowie / przesiewy / AOS] PROC --> PRYW[Katalog prywatny — po inicjatywie] PUB --> GDZ[Panel opcji: 3 drogi] PRYW --> GDZ GDZ --> REZ[Umówienie: e-Rejestracja przez Wellysę / voucher / IKP] REZ --> COD SL[(słownik medyczny: procedury, placówki, kolejki)] --> PUB WA[wellysa-api: terminy P1] --> REZ ZAP[Zapora: log inicjatywy, wersje treści, zgody kanałowe] -.-> PRYW
Komputer (1180 px) i telefon (390 px). Otwórz w nowej karcie
Komputer (1180 px) i telefon (390 px). Otwórz w nowej karcie
Komputer (1180 px) i telefon (390 px). Otwórz w nowej karcie
Komputer (1180 px) i telefon (390 px). Otwórz w nowej karcie
Rozmowy tej wersji: /gdzie, „byłeś?”.
Wiersze macierzy dla tej wersji (wyprowadzone z kryteriów akceptacji specyfikacji V2).
| Test | FR | Prio | Co sprawdzamy | Wyrocznia | Poziom | Technika | Na czym |
|---|---|---|---|---|---|---|---|
| T-PROC-1 | FR-PROC-1 | M | każdy temat v1 ma ≥1 procedurę z kodem i źródłem | inwariant (SQL) | small | EP | topic_procedure |
| T-PUB-1 | FR-PUB-1 | M | 3 klasy bezpłatności z warunkiem i źródłem | spec | medium | decision-table (wiek × płeć × program) | panel |
| T-PUB-2 | FR-PUB-2 | M | 3 stany deklaracji POZ zmieniają panel; „poza siecią" pokazuje warunek przeniesienia (odpłatność po limicie) | spec | medium | state-transition | panel |
| T-PUB-3 | FR-PUB-3 | M | miejsce z kolejką ma datę pomiaru; brak = „nie zmierzono" | spec (trzeci stan) | medium | EP | miejsca |
| T-PUB-5 | FR-PUB-5 | M | lint UI: 0 fraz zakazanych („spychanie", „budżet państwa") | inwariant | small | negatywny | teksty |
| T-PRYW-1 | FR-PRYW-1 | M | bez POST /gdzie/inicjatywa include_private=true → 403; z — 200 i wpis w initiative_log | negatywny + inwariant | medium | state-transition | API |
| T-PRYW-2 | FR-PRYW-2 | M | katalog: 0 odznak/rankingów/liczników/wizerunku; sort tylko użytkownika | audyt UI (lista elementów spoza art. 59) | large | negatywny | SCR-KATALOG |
| T-PRYW-3 | FR-PRYW-3 | M | oferta bez fee_disclosure nie wchodzi do panelu; disclosed → tekst przy opcji | inwariant + spec | medium | EP | offering |
| T-GDZ-1 | FR-GDZ-1 | M | ≥5 tematów operatora z panelem 3 dróg; brak drogi = jawnie | spec | large | E2E | SCR-GDZIE |
| T-GDZ-2 | FR-GDZ-2 | M | zmiana kolejki zmienia kolejność, nigdy zestaw | spec | medium | BVA (okno bezpieczeństwa ±1 dzień) | silnik |
| T-GDZ-3 | FR-GDZ-3, FR-LIM-3 | M | moduł reguł nie importuje modułu cen/limitów | inwariant (import-gate w CI) | small | statyczny | CI |
| T-GDZ-4 | FR-GDZ-4 | M | profil bez polisy → 3 drogi | spec | medium | EP | panel |
| T-REZ-1 | FR-REZ-1 | M | rezerwacja INT P1 przez Wellysę albo jawny ikp_intent (409) | żywy system | large | E2E | wellysa-api |
| T-REZ-3 | FR-REZ-3 | M | mammografia z EDM zamyka temat bez kliku; brak wyniku → pytanie „byłeś?" | spec | medium | state-transition | silnik |
| T-ZAP-1 | FR-ZAP-1 | M | archiwum wersji tekstów katalogu z datą; log inicjatywy per klik | inwariant | medium | — | bazka |
| T-ZAP-2 | FR-ZAP-2 | M | bez zgody kanałowej 0 wiadomości z ofertą; nudge tematu dalej działa | negatywny | large | decision-table (zgoda × typ wiadomości) | TG |
| T-ZAP-3 | FR-ZAP-3 | M | schema review: brak pola prowizji per zalecenie / od apteki | inwariant | small | — | DBML |
| T-PRYW-5 | FR-PRYW-5 | M | oferta bez umowy/source_ref = niewidoczna (nie „cena orientacyjna") | inwariant | small | negatywny | offering |
| T-REZ-4 | FR-REZ-4 | S | odwołanie/zmiana terminu przez /v1/p1/cancel widoczne w bazce; niestawiennictwo oznaczone | żywy system (INT) | large | state-transition | wellysa-api |
Kierunek tej wersji zamawiający przyjął 2026-09-08. Szczegóły (decyzje właściciela wymienione w briefie) rozstrzyga warsztat; budowa rusza po osiągnięciu wersji poprzedniej. Uwagi: mailem do Cze albo w rozmowie.
Wersja 3 — PORTFEL. Po v2 użytkownik wie gdzie i za ile; v3 odpowiada z czego to zapłacić, żeby nie zapłacić dwa razy i nie wyczyścić polisy na drobiazgi — tak, żeby ubezpieczyciel, pracodawca i bank chcieli w tym być.
„na wersję trzecią zrobimy — JAK TE BADANIA FINANSOWAĆ (wallet!)”
Mechanika z materiałów właściciela Wellysy: pięć walut z kolejnością wydawania (publiczna warunkowa → pracownicza → polisowa → własna → zewnętrzna ostatnia), podział jednej usługi między płatników, limity z bezwarunkowym wycięciem objawów alarmowych, potwierdzenie prewencji bez danych medycznych, wiele polis w izolowanych kontekstach, finansowanie zewnętrzne jako pośrednictwo. Liczby z materiałów — do potwierdzenia, nie w produkcie.
Zależy od: wersja 2 (miejsce i cena) i warsztat Wallet z właścicielem (decyzja A6, otwarta od 29.07).
| pole | wartość |
|---|---|
| Produkt / wersja | WELISKA v3 — „JAK TE BADANIA FINANSOWAĆ (wallet)" (Cze 2026-09-07) |
| Krowa | (draft) · zależy od (v2: miejsce i cena) |
| Linia | L3 Wallet = warunek wykonalności P2/P3/P4, nie produkt (BUSINESS LINES v1.0, PRODUCT LINES nadrzędna) |
| Źródła | WALLET v0.4 (pięć walut; waluta publiczna warunkowa; finansowanie zewnętrzne jako pośrednictwo; szyny płatnicze PL; multi-insurance jako pełnomocnik; pięć mechanizmów dla ubezpieczyciela; kierowanie do tańszej opcji jawnie; potwierdzenie prewencji jako produkt) · PRODUCT LINES v2.0 (Wallet statyczny → uzupełniający; Wallet poszerza opcje; trzy poziomy pakietów; limity) · REFRAME v3.0 (cztery pozycje cennika; zapora konfliktu interesu; dwa profile płatnika; wycięcie objawów alarmowych; punkty zdrowotne = nierozwiązane) · PRICING SKELETON (jednostki rozliczeniowe P1–P4; brak kosztu jednostkowego) · SPLIT BENCHMARK (procent od przekierowania wymagał uchylenia przepisów w USA) · INSURER LANDSCAPE v2.0 (przekierowanie do NFZ nie zawsze jest oszczędnością; potrzebny rachunek jednostkowy) · DECYZJE A5, A6, B2, B3, B6 |
| Autor | Snow · akceptujący: Cze → Rafał (warsztat A6) |
| Rejestr zmian | v0.1 2026-09-08 Snow — pierwszy draft |
Po v2 użytkownik wie, gdzie i za ile. v3 odpowiada: <b>z czego to zapłacić, żeby nie zapłacić dwa razy i nie wyczyścić polisy na drobiazgi</b> — i robi to tak, żeby ubezpieczyciel, pracodawca i bank chcieli w tym być. Właściciel (2026-08-05): <i>„konstrukt produktu do VHI/DTP w dużej mierze zależeć może od konstrukcji Wallet-a"</i>; rdzeń: <b>neutralny orkiestrator, nie kolejny ubezpieczyciel</b> — polisa i abonament przestają być PDF-em, stają się walutą.
<b>Zdanie sukcesu v3:</b> <i>przy każdym badaniu użytkownik widzi, kto za nie zapłaci (NFZ / pracodawca / polisa / ja / raty), w jakiej kolejności i dlaczego — wybiera jednym kliknięciem, a każdy z płatników dostaje tylko to, co mu wolno zobaczyć.</i>
| pojęcie | znaczenie (źródło) |
|---|---|
| waluta | jedno z pięciu źródeł pokrycia (WALLET walut): publiczna (programy NFZ — portfel nie pobiera stąd pieniędzy, zaciąga usługę jako bezpłatną), pracownicza (budżet kafeteryjny/abonament pracodawcy), polisowa (budżet ubezpieczyciela pod konkretne procedury), własna (nie tworzy zobowiązania), zewnętrzna (kredyt/raty/BNPL — tworzy zobowiązanie, osobny reżim) |
| kolejność wydawania | publiczna (warunkowa: deklaracja POZ) → pracownicza → polisowa → własna → zewnętrzna ostatnia (WALLET zewnętrzne, mechanizmów pkt 1) |
| podział kosztu | jedna usługa, kilka płatników w czasie rzeczywistym (WALLET) |
| limit | roczny limit per pakiet (np. 3× triage, 1× wizyta) — z bezwarunkowym wycięciem ścieżki objawów alarmowych (REFRAME wiersz „porządkowanie ścieżki") |
| współpłacenie | część z polisy, dopłata użytkownika przy opcji droższej |
| potwierdzenie prewencji | cyfrowy fakt „przeszedł ścieżkę w terminie" — bez wyników i danych medycznych; produkt dla ubezpieczyciela |
| punkty zdrowotne | nagroda za mądre, nie częste korzystanie — nierozwiązane (REFRAME warunek d) |
| pełnomocnictwo selektywne | użytkownik upoważnia Wallet do reprezentowania go przed każdym ubezpieczycielem osobno; izolacja kontekstów: żadne towarzystwo nie widzi cudzej polisy |
| zakaz wzbogacenia | przy zwrocie kosztów suma zwrotów ≤ faktura; ryczałty kumulowalne |
| rola | kto | co widzi / co robi |
|---|---|---|
| użytkownik | operator → użytkownicy P1/P3/P4 | portfel, kolejność, decyzja, jawna informacja o wynagrodzeniu Wellysy |
| właściciel | Rafał | decyzje A5/A6/B2/B3/B6 + pytania otwarte z WALLET (partner-kotwica, rozliczenie, rider wygasa, dopłata do klienta czy platformy) |
| ubezpieczyciel (profil A finansowy / B z siecią) | partnerzy VHI | tylko rozliczenie i agregaty; potwierdzenie prewencji; konfiguracja limitów/ridera; nigdy dane medyczne (BUSINESS LINES danych) |
| pracodawca | P4 | budżet, agregaty; nigdy dane medyczne |
| podmiot finansujący | bank/BNPL | finansuje transakcję; Wallet = pośrednik, nie pożyczkodawca |
| POZ z deklaracją | poza produktem | brama do waluty publicznej |
| CTO-czak | Snow | buduje; zapora konfliktu interesu w architekturze |
| Wellysa | podmiot leczniczy | jedyny dotykający dokumentacji |
Wejścia: panel opcji z ceną (v2), instrumenty użytkownika (polisa, voucher, budżet pracodawcy, deklaracja POZ), umowy z płatnikami (limity, pokrycie per procedura), szyny płatnicze (konta powiązane, split, harmonogram obciążeń, karty, płatność natychmiastowa — WALLET da się zbudować), dostawcy finansowania zewnętrznego (przez API, z obowiązkami informacyjnymi). Wyjścia: plan pokrycia per usługa, rozliczenia z płatnikami (agregaty), potwierdzenia prewencji, zdarzenia finansowe w bazce.
| etap | co użytkownik zobaczy | moduły |
|---|---|---|
| 1. Portfel statyczny (PRODUCT LINES faza początkowa) | wpisuję, co mam: polisa (towarzystwo, pakiet, limity), voucher, budżet pracodawcy, deklaracja POZ (gdzie); przy każdym badaniu: dwie waluty na start — polisa i własne (WALLET) | POR |
| 2. Kolejność i podział | plan pokrycia: NFZ (jeśli deklaracja) → pracodawca → polisa → ja; komunikat korzyści: „wybierając NFZ, zachowujesz limit polisy na trudniejszy moment" — opcja polisy nigdy ukryta ani zablokowana | KOL, KOM |
| 3. Limity i wycięcie alarmów | licznik limitu per pakiet; objawy alarmowe poza limitem zawsze | LIM |
| 4. Potwierdzenie prewencji | „przeszedłem ścieżkę w terminie" → cyfrowe potwierdzenie dla płatnika (bez danych) | POT |
| 5. Multi-insurance | dwie polisy u różnych towarzystw w jednym widoku; izolacja kontekstów; zakaz wzbogacenia | MULTI |
| 6. Finansowanie zewnętrzne (Wallet uzupełniający) | gdy chcę opcję droższą niż portfel: oferta rat od partnera z RRSO, całkowitą kwotą, przykładem; 14 dni odstąpienia; nie dla nagłych | ZEW |
| 7. Punkty zdrowotne (najsłabsze ogniwo — osobna praca projektowa) | nagroda za mądre korzystanie | PKT |
| ID | Wymaganie | Prio | Kryterium akceptacji | Trace |
|---|---|---|---|---|
| FR-POR-1 | Użytkownik MUSI móc zapisać instrumenty: polisa (towarzystwo, pakiet, limity, pokrycie per procedura), voucher, budżet pracodawcy (kwota/okres), deklaracja POZ (gdzie), środki własne (metoda płatności). | M | ✅ portfel z ≥2 instrumentami widoczny; bez instrumentów panel pokazuje NFZ + własne | [dbml: financing_model, payer, coverage, voucher, subscription] |
| FR-POR-2 | Instrument ma proweniencję i ważność (skąd: import z partnera / ręka; do kiedy) i wygasa jawnie. | M | ✅ wygasła polisa = „wygasła", nie „brak" | [dbml] |
| FR-POR-3 | Ścieżka wyjścia: zmiana pracodawcy / rezygnacja z polisy → instrument archiwizowany, historia rozliczeń zostaje (WALLET nie ma — domykamy). | S | ✅ archiwizacja bez utraty historii | [dbml] |
| ID | Wymaganie | Prio | Kryterium akceptacji | Trace |
|---|---|---|---|---|
| FR-KOL-1 | Dla usługi z ceną (v2) silnik MUSI wyliczyć plan pokrycia w kolejności: publiczna (jeśli deklaracja POZ w stanie „dostępna") → pracownicza → polisowa → własna → zewnętrzna, z kwotą per płatnik i resztą dla użytkownika. | M | ✅ 5 przypadków testowych (każda kombinacja instrumentów) daje plan zgodny z tabelą | [c4: nowy komponent wallet] |
| FR-KOL-2 | Plan MUSI pokazywać dlaczego taka kolejność (komunikat korzyści, nie ograniczenia) i pozwalać użytkownikowi zmienić kolejność (np. użyć polisy od razu) — opcja polisy nigdy niedostępna. | M | ✅ przycisk „użyj polisy teraz" zawsze aktywny, gdy pokrycie istnieje | [ekran] |
| FR-KOL-3 | Waluta publiczna MUSI respektować 3 stany deklaracji (v2 FR-PUB-2); w stanie „poza siecią" plan pokazuje koszt/warunek przeniesienia (odpłatne po limicie zmian), nigdy „za darmo". | M | ✅ stan „poza" → komunikat z warunkiem | [dbml: person.profile_json.poz_declaration] |
| FR-KOL-4 | Podział jednej płatności między płatników = przez szyny płatnicze (konta powiązane, split) — Wallet nie przetrzymuje środków. | M | ✅ transakcja testowa z podziałem 2 płatników na sandboxie dostawcy płatności | [c4: system zewn. PSP] |
| FR-KOL-5 | Współpłacenie: przy opcji droższej niż pokrycie plan pokazuje dopłatę użytkownika przed decyzją. | M | ✅ dopłata widoczna przed „zapłać" | [ekran] |
| ID | Wymaganie | Prio | Kryterium akceptacji | Trace |
|---|---|---|---|---|
| FR-LIM-1 | Limity roczne per pakiet (np. N× triage, M× wizyta) konfigurowane per płatnik; licznik widoczny użytkownikowi; po wyczerpaniu ten sam panel opcji (publiczne, prywatne) — nie „koniec pakietu". | M | ✅ wyczerpany limit → panel z pozostałymi drogami | [dbml: coverage.limit] |
| FR-LIM-2 | Ścieżka objawów alarmowych nigdy nie podlega limitowi ani portfelowi — niekonfigurowalne (REFRAME). | M | ✅ test: alarm przy limicie 0 → droga natychmiast | [c4: separacja] |
| FR-LIM-3 | Ocena medyczna liczona bez dostępu do limitów i cen (zapora 2); limit wchodzi dopiero na etapie realizacji. | M | ✅ import-gate w CI | [c4] |
| ID | Wymaganie | Prio | Kryterium akceptacji | Trace |
|---|---|---|---|---|
| FR-KOM-1 | Przy wyborze kanału użytkownik MUSI widzieć, że Wellysa otrzymuje wynagrodzenie (jeśli otrzymuje) — w miejscu wyboru (zapora 3). | M | ✅ tekst przy opcji | [ekran] |
| FR-KOM-2 | Język: „niedublowanie badań i korzystanie z uprawnień"; zero „spychania na państwo"; korzyść, nie ograniczenie. | M | ✅ lint fraz | [test] |
| FR-KOM-3 | Regulamin nazywa optymalizację kosztów usługą zamawianą świadomie (WALLET wolno kierować) — zgoda odrębna od zgód medycznych i kanałowych. | M | ✅ osobna zgoda w bazce | [dbml: consent] |
| ID | Wymaganie | Prio | Kryterium akceptacji | Trace |
|---|---|---|---|---|
| FR-POT-1 | Gdy temat CO DALEJ przechodzi na „zrobione w terminie" z dowodem, system MUSI móc wystawić płatnikowi potwierdzenie faktu (kto, co, kiedy, termin dotrzymany) — bez wyniku i bez danych medycznych. | M | ✅ payload potwierdzenia nie zawiera wartości ani kodów rozpoznań (test schematu) | [api: POST /potwierdzenia] |
| FR-POT-2 | Potwierdzenie wymaga odrębnej zgody użytkownika per płatnik; odwoływalne. | M | ✅ bez zgody brak potwierdzeń | [dbml: consent] |
| FR-POT-3 | Rozliczenie za potwierdzenia = stała opłata za usługę pomiaru, niezależna od tego, dokąd skierowała ścieżka (REFRAME cennik pkt 3) — model nie ma pola „za przekierowanie". | M | ✅ schema review | [dbml] |
| ID | Wymaganie | Prio | Kryterium akceptacji | Trace |
|---|---|---|---|---|
| FR-MULTI-1 | Instrumenty różnych towarzystw żyją w osobnych kontekstach; zapytania do towarzystwa idą „w imieniu użytkownika" (pełnomocnictwo selektywne, token per konto); konsolidacja tylko w UI użytkownika. | M | ✅ test: dane polisy A nigdy w payloadzie do B | [c4: konteksty] |
| FR-MULTI-2 | Zakaz wzbogacenia: przy zwrocie kosztów suma zwrotów ≤ faktura; ryczałty kumulowalne — rozróżnienie typu świadczenia w modelu. | M | ✅ test na dwóch polisach zwrotowych | [dbml: coverage.kind] |
| FR-MULTI-3 | Płatnik dostaje wyłącznie rozliczenie i agregaty; eksport dla płatnika nie zawiera danych medycznych (test schematu). | M | ✅ | [api] |
| ID | Wymaganie | Prio | Kryterium akceptacji | Trace |
|---|---|---|---|---|
| FR-ZEW-1 | Oferta rat/BNPL pochodzi od zewnętrznego dostawcy przez API; Wellysa nie pożycza; prezentacja zawiera RRSO, całkowitą kwotę, reprezentatywny przykład (obowiązek ustawowy). | M | ✅ elementy obecne; brak = oferta niewidoczna | [ekran] |
| FR-ZEW-2 | Prawo odstąpienia 14 dni: procedura zwrotu między placówką a finansującym zdefiniowana i widoczna. | M | ✅ ścieżka odstąpienia w UI | [c4] |
| FR-ZEW-3 | Finansowanie tylko dla usług planowych/komercyjnych; nigdy przy ścieżce alarmowej; nowy rynek = osobne sprawdzenie prawne. | M | ✅ alarm → brak modułu ZEW | [c4] |
| FR-ZEW-4 | Wchodzi ostatnie w kolejności i tylko na świadomy wybór opcji droższej. | M | ✅ plan bez zewnętrznego, gdy portfel wystarcza | [c4] |
| ID | Wymaganie | Prio | Kryterium akceptacji | Trace |
|---|---|---|---|---|
| FR-PKT-1 | Silnik punktów nagradza mądre korzystanie (badanie w terminie, wybór właściwego poziomu opieki), nie częste; wartość punktu = korzyść spoza polisy zdrowotnej (zniżka na kolejny rok, usługi partnerów) — model do wypracowania na warsztacie (REFRAME: „najsłabsze ogniwo"). | C | ✅ prototyp reguł punktowych + symulacja na danych syntetycznych, zanim wejdzie do produktu | [poczekalnia] |
SCR-PORTFEL (instrumenty: polisa/voucher/pracodawca/deklaracja POZ/środki własne, ważność, proweniencja) · SCR-PLAN (dla usługi: kolejność płatników z kwotami, „dlaczego", przełącznik „użyj polisy teraz", dopłata, jawna informacja o wynagrodzeniu) · SCR-LIMITY (liczniki per pakiet; alarm poza limitem) · SCR-POTWIERDZENIA (co i komu potwierdziłem; zgody) · SCR-RATY (oferta zewnętrzna z RRSO/całkowitą kwotą/przykładem; odstąpienie) · TG: <code>/zaplac</code> → plan jako karta z przyciskami.
| # | decyzja | kto |
|---|---|---|
| A6 | warsztat Wallet — kształt, warianty, granica statyczny/uzupełniający (od 2026-07-29) | Rafał + Cze |
| A5/B6 | limit bezpłatny w VHI: koszt akwizycji czy pozycja fakturowana | Rafał |
| B2 | jednostka w linii ubezpieczeniowej: za ubezpieczonego/mies. czy za wykorzystany limit | Rafał |
| B3 | limity roczne w riderze (ile triage, ile wizyt) per pakiet | Rafał |
| W1 | partner-kotwica pilotażu (duży vs elastyczny; Vienna Life w materiale) | Rafał |
| W2 | pilotaż bezgotówkowo czy zwrot kosztów | Rafał |
| W3 | czy rider wygasa automatycznie, gdy klient pominie badanie | Rafał |
| W4 | dopłata od towarzystwa: zniżka dla klienta czy platforma | Rafał |
| W5 | status prawny: pośrednik kredytowy? pieniądz elektroniczny przy voucherach/tokenach? — prawnik przed bankiem | Rafał (prawnik) |
| A4 | dwa profile płatnika (A finansowy / B z siecią) — inna oferta | Rafał |
v3 wchodzi po v2 i po warsztacie A6. Odbiór: plan pokrycia na żywych instrumentach operatora (≥2 waluty), 1 transakcja z podziałem na sandboxie PSP, 1 potwierdzenie prewencji bez danych medycznych (test schematu), test zakazu wzbogacenia.
Separacja: komponent kliniczny nie widzi cen/limitów (import-gate); konteksty per towarzystwo izolowane na poziomie danych (nie tylko UI); płatnicy widzą agregaty (schemat eksportu bez pól medycznych); audyt każdej decyzji finansowej (kto, kolejność, dlaczego); liczby modelu = z umów/faktur, nie z materiałów AI.
Z <code>wellysa-ontology.dbml</code>: <code>financing_model</code>, <code>payer</code>, <code>coverage</code> (financing_model × procedura → ile pokrywa; <code>kind</code>: zwrot | ryczałt; <code>limit</code>), <code>price</code>, <code>subscription</code>, <code>voucher</code>, <code>claim_refund</code> (rozliczenie z płatnikiem). Nowe: <code>instrument</code> (per użytkownik: typ, płatnik, ważność, proweniencja, stan), <code>payment_plan</code> (usługa → pozycje per płatnik, kolejność, uzasadnienie, wybór użytkownika), <code>confirmation</code> (potwierdzenie prewencji: temat, termin dotrzymany, zgoda, odbiorca), <code>insurer_context</code> (izolacja), <code>points_ledger</code> (PKT, później).
| co | droga | stan |
|---|---|---|
| szyny płatnicze | dostawca z kontami powiązanymi + split + harmonogram obciążeń (Stripe już w wellysa-api — checkout; Connect/split do zmierzenia) | do pomiaru |
| ubezpieczyciele | API w imieniu klienta (pełnomocnictwo) — brak standardu; per towarzystwo | umowy = Rafał |
| pracodawca | import budżetu / kafeteria | umowy |
| finansowanie zewnętrzne | dostawca rat/BNPL przez API | wybór po W5 |
| POZ/deklaracja | z v2 | — |
Pośrednictwo kredytowe (status), pieniądz elektroniczny/AML przy voucherach, obowiązki informacyjne (RRSO), odstąpienie 14 dni, tajemnica ubezpieczeniowa (pełnomocnictwo), zakaz wzbogacenia, art. 14/55/59 (komunikacja), wyrób medyczny (MDR) dla oceny objawów — wszystko z dokument; decyzje C4/C5/W5 z prawnikiem <b>przed</b> pierwszą umową.
| # | ryzyko | plan |
|---|---|---|
| R1 | model stoi na liczbach z rozmów z AI | zero liczb w produkcie do potwierdzenia przez wycenę |
| R2 | ubezpieczyciel widzi utowarowienie („to jest Booking.com") | profil A/B, jego marka w ścieżce, jego sieć w kierowaniu, jego dane u niego (REFRAME) |
| R3 | punkty zdrowotne nie działają / nagradzają nadużycie | PKT = prototyp + symulacja przed produktem |
| R4 | przekierowanie do NFZ nie jest oszczędnością (kapitacja, abonament) | rachunek jednostkowy na jednym produkcie zanim na slajd (INSURER to znaczy) |
| R5 | AML/KYC przy tokenach | brak tokenów jako pieniądza w v3; punkty = nie-pieniądz |
Rider bezpłatny dla klienta finansowany z marży/pracodawcy/funduszu prewencyjnego (WALLET czym zarabiamy); warianty wielkościowe (portfel grupowy); EHDS (warunek brzegowy); rozliczenia z siecią przy niestawiennictwie (blokada kwoty przy rezerwacji — mechanizm 5); neobank jako dawca kapitału (po rozstrzygnięciu W5 i po zmianie adresatów: Aion/VeloBank zmieniły właścicieli).
Podzbiór modelu dla tej wersji; tabele wspólne z v1 (osoba, reguły, udostępnienia) pokazane dla relacji. Pełna definicja w kroku „Model danych” wersji 1.
erDiagram
person ||--o{ share_grant : "person_id"
payer ||--o{ financing_model : "payer_id"
financing_model ||--o{ coverage : "financing_model_id"
person ||--o{ instrument : "person_id"
financing_model ||--o{ instrument : "financing_model_id"
instrument ||--o{ usage_counter : "instrument_id"
coverage ||--o{ usage_counter : "coverage_id"
person ||--o{ payment_plan : "person_id"
offering ||--o{ payment_plan : "offering_id"
payment_plan ||--o{ payment_plan_line : "payment_plan_id"
instrument ||--o{ payment_plan_line : "instrument_id"
payment_plan ||--o{ split_payment : "payment_plan_id"
person ||--o{ confirmation : "person_id"
payer ||--o{ confirmation : "payer_id"
share_grant ||--o{ confirmation : "consent_id"
payment_plan ||--o{ external_finance_offer : "payment_plan_id"
payer ||--o{ external_finance_offer : "payer_id"
person ||--o{ points_ledger : "person_id"
person {
integer id
text display_name
text sex
date birth_date
text cf_email
text profile_json
}
share_grant {
integer id
integer person_id
text subject_email
text label
text scope_json
datetime valid_to
}
offering {
integer id
integer provider_id
integer procedure_id
text channel
real price_pln
integer lead_days
}
payer {
integer id
text kind
text name
text contract_ref
}
financing_model {
integer id
integer payer_id
text name
text currency
text unit
}
coverage {
integer id
integer financing_model_id
integer procedure_id
text kind
real covers_pln
real covers_pct
}
instrument {
integer id
integer person_id
integer financing_model_id
text kind
text insurer_context
datetime mandate_granted_at
}
usage_counter {
integer id
integer instrument_id
integer coverage_id
text period
integer used
}
payment_plan {
integer id
integer person_id
integer offering_id
real total_pln
text order_rule
text user_override
}
payment_plan_line {
integer id
integer payment_plan_id
integer seq
integer instrument_id
text currency
real amount_pln
}
split_payment {
integer id
integer payment_plan_id
text psp_ref
text state
datetime at
}
confirmation {
integer id
integer person_id
integer payer_id
text topic
integer completed_on_time
integer consent_id
}
external_finance_offer {
integer id
integer payment_plan_id
integer payer_id
real apr_pct
real total_pln
text example_text
}
points_ledger {
integer id
integer person_id
text event
integer points
datetime at
text note
}Do API dochodzi komponent Wallet: instrumenty użytkownika (izolowane konteksty per towarzystwo), plan pokrycia (kolejność walut, wybór użytkownika, dopłata), limity (alarm zawsze poza limitem), potwierdzenia prewencji (fakt bez danych medycznych), oferta finansowania zewnętrznego (przez dostawcę, z RRSO i odstąpieniem). Płatność przez szyny z podziałem między płatników — Wallet nie trzyma środków. Komponent kliniczny nie widzi cen ani limitów (brama w CI).
flowchart LR GDZ[Panel opcji v2: usługa z ceną] --> PLAN[Plan pokrycia: kolejność walut] POR[(Instrumenty: POZ / polisa / pracodawca / własne)] --> PLAN LIM[Limity per pakiet · alarm poza limitem] --> PLAN PLAN --> PSP[Szyny płatnicze: split między płatników] PLAN -. dopłata wyższa niż portfel .-> ZEW[Finansowanie zewnętrzne: RRSO, 14 dni] COD[Co dalej: temat zrobiony w terminie] --> POT[Potwierdzenie prewencji — bez danych medycznych] POT --> PL[Płatnik: agregaty i rozliczenie] MULTI[Pełnomocnictwo per towarzystwo · izolacja kontekstów] --> POR
Komputer (1180 px) i telefon (390 px). Otwórz w nowej karcie
Komputer (1180 px) i telefon (390 px). Otwórz w nowej karcie
Komputer (1180 px) i telefon (390 px). Otwórz w nowej karcie
Rozmowy tej wersji: /zaplac.
Wiersze macierzy dla tej wersji (wyprowadzone z kryteriów akceptacji specyfikacji V3).
| Test | FR | Prio | Co sprawdzamy | Wyrocznia | Poziom | Technika | Na czym |
|---|---|---|---|---|---|---|---|
| T-POR-1 | FR-POR-1 | M | portfel z ≥2 instrumentami; bez instrumentów panel = NFZ + własne | spec | medium | EP | SCR-PORTFEL |
| T-POR-2 | FR-POR-2 | M | wygasła polisa = „wygasła", nie „brak" | spec | small | BVA (valid_to ±1) | instrument |
| T-KOL-1 | FR-KOL-1 | M | 5 kombinacji instrumentów → plan zgodny z kolejnością public>employer>policy>own>external | golden (tabela) | small | decision-table | silnik wallet |
| T-KOL-2 | FR-KOL-2 | M | „użyj polisy teraz" zawsze aktywne, gdy pokrycie istnieje | spec | medium | negatywny (próba zablokowania) | SCR-PLAN |
| T-KOL-3 | FR-KOL-3 | M | deklaracja „poza siecią" → komunikat z warunkiem, nigdy „za darmo" | spec | medium | state-transition | plan |
| T-KOL-4 | FR-KOL-4 | M | transakcja z podziałem 2 płatników na sandboxie PSP; Wallet nie trzyma środków | żywy system (sandbox) | large | E2E | PSP |
| T-LIM-1 | FR-LIM-1 | M | wyczerpany limit → ten sam panel opcji | spec | medium | BVA (limit 0/1) | panel |
| T-LIM-2 | FR-LIM-2 | M | alarm przy limicie 0 → droga natychmiast; alarm_exempt niekonfigurowalne | inwariant + spec | small | negatywny | silnik |
| T-KOM-1 | FR-KOM-1 | M | tekst o wynagrodzeniu Wellysy przy opcji, gdy fee ≠ none | spec | large | — | UI |
| T-POT-1 | FR-POT-1, FR-POT-3 | M | payload potwierdzenia: 0 wartości, 0 kodów rozpoznań, 0 pola „za przekierowanie" | inwariant (test schematu) | small | negatywny | API |
| T-POT-2 | FR-POT-2 | M | bez zgody per płatnik → 403 | negatywny | medium | EP | API |
| T-MULTI-1 | FR-MULTI-1 | M | dane polisy A nigdy w payloadzie do B | inwariant | medium | negatywny | konteksty |
| T-MULTI-2 | FR-MULTI-2 | M | dwie polisy zwrotowe: suma zwrotów ≤ faktura; ryczałty kumulowalne | golden | small | decision-table (kind × liczba polis) | coverage |
| T-MULTI-3 | FR-MULTI-3 | M | eksport dla płatnika bez pól medycznych | inwariant (schemat) | small | negatywny | API |
| T-ZEW-1 | FR-ZEW-1 | M | oferta bez RRSO/całkowitej kwoty/przykładu = niewidoczna | inwariant | small | EP | oferty |
| T-ZEW-3 | FR-ZEW-3 | M | ścieżka alarmowa → brak modułu ZEW | negatywny | medium | — | silnik |
| T-ZEW-4 | FR-ZEW-4 | M | portfel wystarcza → plan bez pozycji zewnętrznej | spec | small | BVA (dopłata 0/1 zł) | plan |
| T-PKT-1 | FR-PKT-1 | C | symulacja reguł punktowych na danych syntetycznych: nagroda za „w terminie", brak nagrody za częstość | golden | small | property-based (częstość ↑ ⇒ punkty ↛ ↑) | prototyp |
| T-KOM-3 | FR-KOM-3 | M | bez odrębnej zgody na optymalizację kosztów plan nie proponuje kolejności (pokazuje tylko ceny) | negatywny | medium | EP | zgody |
Kierunek tej wersji zamawiający przyjął 2026-09-08. Szczegóły (decyzje właściciela wymienione w briefie) rozstrzyga warsztat; budowa rusza po osiągnięciu wersji poprzedniej. Uwagi: mailem do Cze albo w rozmowie.
Jeden rysunek wszystkich ekranów i przejść między nimi. Strzałka = odnośnik w ekranie.
flowchart LR PANEL[SCR-PANEL] --> TREND[SCR-TREND] PANEL --> COD[SCR-COD] PANEL --> WPIS[SCR-WPIS] PANEL --> WGRAJ[SCR-WGRAJ] PANEL --> ZRODLA[SCR-ZRODLA] PANEL --> UDO[SCR-UDO] PANEL --> PROFIL[SCR-PROFIL] WGRAJ -->|zatwierdź| TREND WPIS -->|zapis| TREND ZRODLA -->|intent Synevo: kod/QR| ZRODLA ZRODLA -->|wynik| TREND COD -->|źródło reguły| EXT[(strona programu NFZ/PZH)] COD -->|zrobione/odłóż| COD PROFIL -->|zmiana| COD TREND -->|wskaźnik| ALG[SCR-ALG] UDO -->|link dla odbiorcy| PANELR[SCR-PANEL odbiorcy — scope]
Z czego system składa się w środku: portal, bot Telegram, aplikacja mobilna z natywnym mostem do zegarka, API (OCR, normalizacja, silnik CO DALEJ, udostępnianie), roboty pobierające z laboratoriów i z P1, bazka, magazyn plików.
flowchart TB
subgraph WELISKA [WELISKA — środowisko Wellysy, serwer platformy]
web[Portal web — panel danych]
tg[Bot TG snow]
app[Apka Ionic — HealthSync Swift / Kotlin]
api[API FastAPI — OCR · ingest · silnik CO DALEJ · udostępnianie]
wafle[Robot pobierający — Playwright na intent/cron]
db[(bazka SQLite WAL, szyfrowany wolumen)]
store[(magazyn plików — magazyn plików R2 czak-wsa)]
end
web --> api
tg --> api
app --> api
api --> db
api --> store
api --> wafle
wafle --> db
wafle --> store
| env | host | jednostki | promocja |
|---|---|---|---|
| dev | serwer platformy (Hetzner), workspace środowisko Wellysy | czak-Snow-weliska.service (API + statyczny web), czak-Snow-weliska-*.timer (robot pobierający), bramka komunikatorów (TG), tunel cloudflared → hostname, CF Access policy per email | merge do main warsztatu (operator) → deploy dev (systemd restart z boxa) |
| prod | ten sam box, osobny wolumen + hostname | j.w. | tag v* → deploy prod; iOS: TestFlight internal build; Android: internal testing |
Branch z tasku (<code>czak/Snow/<N>-slug</code>), PR → review → merge = operator . Sekrety: sejf poświadczeń; CF token na boxie (platforma).
Każde wymaganie ma kryterium akceptacji — zdanie, które da się sprawdzić na gotowym systemie i na prawdziwych danych użytkownika. Tabela jest wyprowadzona ze specyfikacji, nie ułożona obok niej.
| Test | FR | Prio | Co sprawdzamy (kryterium ze SPEC) | Wyrocznia | Poziom | Technika | Na czym |
|---|---|---|---|---|---|---|---|
| T-BAZ-1 | FR-BAZ-1 | M | wpis bez jednostki/kodu/czasu/źródła = odmowa; 100% wierszy z 5 polami | spec + inwariant SQL count(*) where ucum is null = 0 | small | EP (brak każdego z 5 pól = klasa) | API |
| T-BAZ-2 | FR-BAZ-2 | M | ten sam PDF/partia 2× → 0 nowych wierszy | inwariant (hash_dedup UNIQUE) | medium | state-transition (nowy/duplikat) | API + bazka |
| T-BAZ-3 | FR-BAZ-3 | M | 5.0 mmol/L i 90 mg/dL glukozy w jednej skali (value_canon) | golden (5.0 mmol/L = 90.09 mg/dL) | small | BVA (mnożnik, zaokrąglenie) | unit_conversion |
| T-BAZ-4 | FR-BAZ-4 | S | podpowiedź parametru zwraca kod z provenance=słownik medyczny:…; brak route → subset oznaczony manual | spec | medium | EP (słownik medyczny dostępny / niedostępny) | API + słownik medyczny |
| T-BAZ-5 | FR-BAZ-5 | M | szczepienie Tdap z datą → temat „tężec" liczy interwał od niego | spec + SEQ-COD | medium | decision-table (zdarzenie × reguła) | silnik |
| T-REK-1 | FR-REK-1 | M | /wynik glukoza 92 mg/dL (TG) i formularz (web): zła jednostka = odmowa z podpowiedzią; poprawny wpis w trendzie < 5 s | spec; czas mierzony | large | EP + BVA (jednostki, wartości ujemne, przyszła data) | SCR-WPIS, TG |
| T-REK-2 | FR-REK-2 | S | wpis z zakresem 70–99 i wartością 104 → flaga H | spec | small | BVA (99, 100, 70, 69) | API |
| T-REK-3 | FR-REK-3 | S | edycja własnego wpisu → wpis w access_log | inwariant (audit) | medium | state-transition | API |
| T-OCR-1 | FR-OCR-1 | M | zdjęcie HEIC/JPG/PNG i PDF → oryginał w magazynie (sha256) + 202 + propozycja | spec | large | EP (formaty, rozmiar >25 MB = 413) | SCR-WGRAJ |
| T-OCR-2 | FR-OCR-2 | M | wiersz z pewnością < 0.8 oznaczony; każda kolumna obecna | spec | small | BVA (0.79/0.80) | API |
| T-OCR-3 | FR-OCR-3 | M | bez confirm → count(observation where document_id=X)=0; po poprawce i „zapamiętaj" drugi wydruk mapuje się sam | inwariant + spec | medium | state-transition (proposal→confirmed) | API + bazka |
| T-OCR-4 | FR-OCR-4 | M | macierz jakości: 10–20 żywych wydruków operatora (Synevo/Diagnostyka/ALAB/szpital, zdjęcia telefonem + PDF); ≥ 90% komórek poprawnych PRZED poprawką; per komórka: parametr/wartość/jednostka/zakres | golden (ręczna prawda operatora) | large | pairwise (lab × format × jakość zdjęcia) | OCR na boxie |
| T-OCR-5 | FR-OCR-5 | M | konfiguracja bez fallbacku działa; z fallbackiem region ≠ EU = błąd konfiguracji | spec + inwariant konfiguracji | small | decision-table | konfiguracja |
| T-COD-1 | FR-COD-1 | M | zmiana „palę → rzuciłem 2024" w historii; lista tematów przelicza się | spec + SEQ-COD | medium | state-transition (profil) | SCR-PROFIL |
| T-COD-2 | FR-COD-2 | M | ≥ 20 reguł z URL + measured; reguła z review_by < dziś = flaga | inwariant (schema reguły) | small | EP (pola wymagane) | rule.yaml |
| T-COD-3 | FR-COD-3 | M | dla profilu operatora każda pozycja ma stan + powód + źródło + termin | spec | medium | decision-table (płeć × wiek × palenie × wywiad) — tabela w | silnik |
| T-COD-4 | FR-COD-4 | M | import lipidogramu → „lipidogram" = zrobione, next_due = +3 lata (50+) / +5 lat (<50) | spec + golden | medium | BVA (wiek 49/50) | silnik |
| T-COD-5 | FR-COD-5 | M | zmiana stanu → powiadomienie ≤ 24 h; „odłóż do X" wycisza do X; brak spamu (rosnąca przerwa) | spec; czas | large | state-transition (due→nudged→snoozed→due) | TG |
| T-COD-6 | FR-COD-6 | M | „zrobione 2026-08" bez wyniku → done, last_completed_ref=user, audit | spec + inwariant | medium | EP (done/postpone/discontinue/reopen) | API |
| T-COD-7 | FR-COD-7 | S | SCORE2 dla 10 przypadków = kalkulator ESC (±1 p.p.); region high | porównanie z referencją | small | golden | silnik |
| T-COD-8 | FR-COD-8 | M | klik w źródło otwiera stronę programu; zdanie „nie zalecenie lekarskie" obecne | spec | large | — | SCR-COD |
| T-POR-1 | FR-POR-1 | M | curl bez tokenu → 302 cloudflareaccess; z tokenem → 200 | żywy system | large | negatywny | CF Access |
| T-POR-2 | FR-POR-2 | M | wynik poza zakresem wyróżniony; licznik CO DALEJ = liczba pozycji na SCR-COD | spec | large | porównanie dwóch widoków | SCR-PANEL |
| T-POR-3 | FR-POR-3 | M | trend glukozy z 3 źródeł na jednym wykresie, jedna skala, zakres; czas ≤ 10 s przy 100 k wierszy (seed syntetyczny) | spec; czas | medium | wydajność (BVA 100 k) | SCR-TREND |
| T-POR-4 | FR-POR-4 | M | wafel z padniętym logowaniem → badge „błąd", nie „brak nowych"; źródło bez ticku → „nie zmierzono" | spec (trzeci stan) | medium | EP (ok/error/unmeasured) | SCR-ZRODLA |
| T-POR-5 | FR-POR-5 | S | wpis w rejestrze okna + okno_probe rows>0 | żywy system | large | — | panel |
| T-TG-1 | FR-TG-1 | M | 6 komend na żywym koncie operatora (/wynik, zdjęcie, /trend, /codalej, /synevo, /zrodla) | spec | large | scenariusze E2E (SEQ-REK, SEQ-OCR, SEQ-SYN) | bot |
| T-TG-2 | FR-TG-2 | M | import → powiadomienie ≤ 1 min z przyciskami | spec; czas | large | — | bot |
| T-TG-3 | FR-TG-3 | M | obcy chat_id → odmowa | negatywny | medium | EP | bot |
| T-LAB-2 | FR-LAB-2 | M | Diagnostyka na intent: żywy dokument zlecenia operatora → nr zlecenia (+ data ur./PESEL z profilu) → PDF + tabela w bazce; negatywne: zły numer, zlecenie bez wyników; ALAB analogicznie; cron na koncie stałym opcjonalnie | żywy system + negatywny | large | E2E SEQ-SYN (wariant Diagnostyka) | SCR-ZRODLA + wafel |
| T-LAB-3 | FR-LAB-3 | M | nieznana nazwa → kolejka z propozycją; po potwierdzeniu mapuje się sama | spec | medium | state-transition | lab_name_map |
| T-LAB-4 | FR-LAB-4 | M | PDF per zlecenie w magazynie z source_ref | inwariant | medium | — | document |
| T-P1-1 | FR-P1-1 | M | zgoda w P1 (INT → PROD) → ITI-18 → ITI-43 → CDA w magazynie + raport | żywy system (INT najpierw) | large | E2E SEQ-P1; negatywny: brak zgody = 403 | wellysa-api + P1 |
| T-P1-2 | FR-P1-2 | M | endpointy EDM w wellysa-api z testami; WELISKA bez certu P1 (grep konfiguracji) | spec + inwariant | medium | — | wellysa-api |
| T-P1-3 | FR-P1-3 | S | ZM „kolonoskopia" → temat = zrobione | spec | medium | decision-table | silnik |
| T-P1-4 | FR-P1-4 | S | PDF z IKP → tabela do zatwierdzenia | spec | large | — | SCR-WGRAJ |
| T-UDO-1 | FR-UDO-1 | M | grant z zakresem widoczny; pola wymagane | spec | medium | EP | SCR-UDO |
| T-UDO-2 | FR-UDO-2 | M | negatywny: odbiorca prosi o parametr spoza scope → 403/pusto; okres poza → pusto; access_log wpis per odczyt | negatywny + inwariant | large | decision-table (parametr × okres × źródło × wygasł) | 2 konta CF Access |
| T-UDO-3 | FR-UDO-3 | M | odwołanie → 403 w ≤ 1 s; wygaśnięcie automatyczne | spec; czas | large | state-transition | API |
| T-UDO-4 | FR-UDO-4 | S | lista odczytów przy grancie zgodna z access_log | porównanie | medium | — | SCR-UDO |
| T-APP-1 | FR-APP-1 | M | ten sam build web działa w przeglądarce i w apce Ionic (smoke ekranów) | spec | large | — | symulator + urządzenie |
| T-WATCH-1 | FR-WATCH-1 | M | na urządzeniu (symulator nie wspiera BG): próbki HR/HRV/sen/kroki/SpO2/waga/BP widoczne w portalu bez otwierania apki; podwójny batch = 0 duplikatów; completion handler wywołany (log) | żywy system + inwariant | large | E2E SEQ-HK; pomiar częstości wybudzeń (R2) | iPhone + Watch operatora |
| T-WATCH-2 | FR-WATCH-2 | S | próbki z Health Connect po ticku WorkManager | żywy system | large | E2E | Android |
| T-WATCH-3 | FR-WATCH-3 | S | export.xml (3 lata) → trend bez duplikatów; pamięć < 1 GB (iterparse) | inwariant + wydajność | medium | BVA (plik 1–2 GB) | import |
| T-APP-2 | FR-APP-2 | M | build z TestFlight internal na iPhonie operatora; zgoda + polityka prywatności w apce | żywy system | large | — | TestFlight |
| T-ALG-1 | FR-ALG-1 | M | HOMA-IR bez insuliny → inputs_missing=[20448-7] | spec | small | EP | API |
| T-ALG-2 | FR-ALG-2 | M | HOMA-IR = ręczne przeliczenie (glukoza·insulina/405); wynik ma proweniencję i zdanie o diagnozie | porównanie z referencją | medium | golden | silnik algorytmów Wellysy (kontrakt) |
| T-ALG-3 | FR-ALG-3 | M | wejście mmol/L i mg/dL → ten sam wynik | inwariant | small | EP | unit_conversion |
| T-MAG-1 | FR-MAG-1 | M | plik per import w art_list (albo lokalny indeks do fixu) | inwariant | medium | — | magazyn |
| T-MAG-2 | FR-MAG-2 | S | obserwacja bez document_id → flaga „bez oryginału" w tabeli | spec | small | EP | SCR-TREND |
| T-ART9-1 | FR-ART9-1 | M | audit_secrets = 0; ścieżki danych wyłącznie w wolumenie pudełka; RAG snowa bez rekordów pacjenta (grep) | inwariant | medium | — | CI |
| T-ART9-2 | FR-ART9-2 | M | grep logów po PESEL i kodzie Synevo = 0; kredki tylko z sejf poświadczeń | inwariant | large | negatywny | logi boxa |
| T-ART9-3 | FR-ART9-3 | M | każdy odczyt odbiorcy = wpis | inwariant | large | — | access_log |
| T-ART9-4 | FR-ART9-4 | M | eksport ZIP → import na czystej instancji = identyczne liczniki tabel | porównanie | medium | round-trip | API |
| T-ART9-5 | FR-ART9-5 | M | po usunięciu count(*)=0 w każdej tabeli danych; pliki zniknęły; kopie? (decyzja retencji) | inwariant | medium | — | API |
| T-ART9-6 | FR-ART9-6 | M | dokument przeglądu zespół compliance zamknięty; DPA (jeśli fallback); polityka prywatności opublikowana | żywy system (dokument) | large | — | dokument |
| etap | scenariusz | dowód |
|---|---|---|
| 1 | operator wpisuje 3 wyniki z ręki (web) → trend | zapis w bazce + widok + access_log |
| 2 | 10–20 zdjęć/PDF → macierz T-OCR-4 → zatwierdzone tabele | wynik ≥ 90% w kwicie ; oryginały w magazynie |
| 3 | profil operatora → lista CO DALEJ zgodna z ; jeden temat „zrobione" po imporcie | stan w bazce + widok SCR-COD |
| 4 | 6 komend TG + 1 powiadomienie | zrzuty rozmowy + logi |
| 6 | zgoda w P1 (INT) → dokument CDA w bazce | raport + oryginał |
| 7 | 2 konta CF Access: pozytywny + negatywny | 403 + access_log |
| 8 | build TestFlight → próbki z Watcha bez otwierania apki | wiersze source=apple-health + pomiar wybudzeń |
| 9 | SCORE2/HOMA-IR z proweniencją | algorithm_run |
| 10 | audit_secrets 0, eksport round-trip, usunięcie | raporty CI |
Ten krok domyka przegląd: akceptacja wersji specyfikacji = zamrożenie zakresu i start budowy etapu 1 (bazka + wpis z ręki + trend). Akceptować można krok po kroku.
Uwagi do dowolnego kroku: mailem do Cze (cze@itm8.pl) albo w rozmowie — każda uwaga wraca do dokumentu jako kolejna wersja z datą i autorem. Pinezki przy nagłówkach (uwagi inline) włączamy po podpięciu tego sajtu do rejestru pracy zespołu.
Że OCR osiągnie 90% na Państwa wydrukach (to mierzymy na 10–20 realnych zdjęciach w etapie 2), że zegarek będzie budził aplikację z pożądaną częstością (pomiar na urządzeniu) i że lista „CO DALEJ” jest zaleceniem lekarskim — jest informacją z programów profilaktycznych i towarzystw naukowych, z podanym źródłem.
| Brama | Wynik | Luki (jawnie) |
|---|---|---|
| Koherencja SPEC ↔ testy ↔ API ↔ makieta | zielona — cytowania bez definicji: testy 0, API 0, makiety 0 | v1: bez testu NFR-1, NFR-2, NFR-3, NFR-4, NFR-5, NFR-6, NFR-7, NFR-8; v2: bez testu FR-PROC-2, FR-PRYW-4, FR-PUB-4, FR-REZ-2; v3: bez testu FR-KOL-5, FR-KOM-2, FR-ZEW-2 |
| Makieta — brama kanonu dashboardu + audyt brandu | 18 / 18 ekranów-encji czystych | — |
| Model danych (DBML) — parse round-trip | zielony (38 tabel) | — |
| Kontrakt API (OpenAPI) — lint | uruchamiany osobno (spectral:oas), ostatni wynik: 0 błędów | — |
| Filtr widoczności (leak-check treści dla odbiorcy) | przebieg tego wydania: czysty (inaczej wydanie się nie składa) | — |
Luka „bez ekranu / bez API” w koherencji nie jest błędem: nie każde wymaganie ma ekran (np. szyfrowanie) ani ścieżkę API (np. reguła w silniku). Błędem byłoby cytowanie wymagania, którego nie ma — i tego jest zero.