# MEDEVIO API -- Working Notes ## General Architecture - **Frontend:** React + Material-UI (MUI) + Apollo GraphQL - **Backend:** GraphQL API -> `https://api.medevio.cz/graphql` - **Authentication:** cookies/tokens stored in `medevio_storage.json` (Playwright session) - **Frontend base:** `https://my.medevio.cz/mudr-buzalkova/klinika/...` - **Session handling:** token refreshed through `AccessToken_AuthSelf` calls --- ## Authentication and Session Reuse session cookies from Playwright: ```python state = json.load(open("medevio_storage.json")) cookies = {c["name"]: c["value"] for c in state["cookies"] if "medevio" in c["domain"]} ``` Use these cookies in every `requests.post(GRAPHQL_URL, headers, cookies, data=...)` call. --- ## HTTP Setup (Python) ```python GRAPHQL_URL = "https://api.medevio.cz/graphql" headers = { "content-type": "application/json", "origin": "https://my.medevio.cz", "referer": "https://my.medevio.cz/", } response = requests.post(GRAPHQL_URL, headers=headers, cookies=cookies, data=json.dumps(payload)) ``` --- ## Key IDs | Entity | ID | |--------|----| | Clinic | `25f24970-dae3-4f80-9337-d3616e53fb10` | | Clinic slug | `mudr-buzalkova` | | Calendar MUDr. Buzalkova | `144c4e12-347c-49ca-9ec0-8ca965a4470d` | | AIS entity (Medicus) | `ef1549a5-d266-4f52-9a4d-7275e79ac82e` | --- ## Complete GraphQL Operations Catalog ### Session & Auth | Operation | Purpose | |-----------|---------| | `AccessToken_AuthSelf` | Verify or refresh session | ### Search | Operation | Variables | Response | |-----------|-----------|----------| | `Search` | `clinicSlug`, `locale`, `query` | `clinic`, `results` (patients + requests) | ### Patient Card (modal) | Operation | Variables | Response | |-----------|-----------|----------| | `ClinicPatientDetailModal_GetData` | `clinicSlug`, `patientId`, `patientUuid`, `locale`, `challengesStatus` | `clinic`, `calendars`, `patient` (full detail), `challenges`, `patientRequestsResponse`, `treatmentPlanPatients`, `premiumPlans`, `insuranceCards`, `pinnedRequests`, `canBeRemoved` | | `ClinicPatientRequestHistory_FilterPatientRequestsForClinic` | `clinicSlug`, `locale`, `patientId`, `patientRequestFilter`, `pageInfo {first, offset}` | `filterResponse`, `clinic`, `queues`, `calendars`, `userECRFs`, `substates`, `tags` | | `ClinicPatientDetailModal_FindMergeSuggestions` | `clinicSlug`, `patientUuid` | `mergeSuggestions` | #### Patient object structure (from GetData) ``` patient { id, name, surname, identificationNumber (rodne cislo bez lomitka), sex (Male/Female), dob (YYYY-MM-DD), email, phone, kind (Human), type (Human), status (ACTIVE), isInClinic, isInOrganizationOnly, hasMobileApp, anamnesisShared, anamnesisStatusForClinic { updatedAt }, insuranceCompanyObject { id, code, name, shortName }, tags [], familyMembers [], clinics [], note, organizationNotes [], city, street, houseNumber, user { id, name, surname, email, phone, registrationCompletedTime }, createdAt, editableByDoctor, userRelationship, premiumPlanPatient } ``` ### Patient Edit | Operation | Variables | Response | |-----------|-----------|----------| | `EditPatientModal_GetClinic` | `clinicSlug` | `clinic` | | `ClinicEditPatient_GetPatientForClinic` | `clinicSlug`, `patientId`, `includePatient` | `patient`, `clinic` | | `ClinicEditPatient_ListInsuranceCompanies` | `country` | `insuranceCompanies []` (id, code, name) | | `UseSupportedCountryCodes_Codes` | (none) | `codes` (phone country codes) | ### Patient List (Kartoteka) | Operation | Variables | Response | |-----------|-----------|----------| | `LegacyClinicPatientListPage_ListClinicPatients` | `clinicSlug`, `criteria {premiumPlanId, tagId}`, `pageInfo {first: 50, offset}` | `count`, `patients`, `clinic` | #### Fungující dotaz pro stránkovaný seznam pacientů (sync) Pozor: `listClinicPatients` nefunguje pro hromadný export. Fungující query je `listPatients`: ```graphql query SyncListPatients($clinicSlug: String!, $pageInfo: PageInfo!, $filter: ListPatientFilter!) { patientsList: listPatients(clinicSlug: $clinicSlug, filter: $filter, pageInfo: $pageInfo) { count patients { id identificationNumber name surname sex phone status2 isInClinic locale insuranceCompanyObject { id shortName } user { id phone name surname } } } } ``` Variables: ```json { "clinicSlug": "mudr-buzalkova", "filter": {}, "pageInfo": { "first": 50, "offset": 0 } } ``` Stránkování: `offset` se zvyšuje po 50, `count` v odpovědi = celkový počet pacientů. #### Dotaz pro detail jednoho pacienta ```graphql query GetPatientDetail($clinicSlug: String!, $patientId: String!) { patient: getPatientForClinic(clinicSlug: $clinicSlug, patientId: $patientId) { id name surname identificationNumber sex dob email phone status isInClinic hasMobileApp anamnesisShared note city street houseNumber createdAt insuranceCompanyObject { code name } user { id email phone } } } ``` Variables: ```json { "clinicSlug": "mudr-buzalkova", "patientId": "" } ``` ### Sync pacientů do MySQL Dvoustupňový proces: 1. `listPatients` stránkovaně stáhne seznam všech patient IDs (po 50) 2. Pro každého pacienta se zavolá `getPatientForClinic` pro detail 3. UPSERT do `medevio.medevio_pacient` (23 sloupců) Skript: `Medevio/80 Pacienti/sync_patients_to_mysql.py` Výsledek: 1963 pacientů synchronizováno (květen 2026). #### Známé problémy - **`hasMobileApp` je nefunkční na úrovni pacienta** — ověřeno na více endpointech: - `getPatientForClinic` → vždy `false` ❌ - `ClinicPatientDetailModal_GetData` → `patient.hasMobileApp` vždy `false` ❌ - `listPatients` → pole vůbec neexistuje v GraphQL schema ❌ - **Správná hodnota `hasMobileApp` je na objektu `request`**, nikoli pacienta: - `ClinicRequestDetail_GetPatientRequest2` → `request.hasMobileApp` vrací správně ✅ - `request.extendedPatient.hasMobileApp` je stále vždy `false` ❌ - UI komponenta `PatientRequest.MessageSend` čte hodnotu z `request.hasMobileApp` a rozhoduje: - `true` → zobrazí "Odeslat do aplikace" - `false` + telefon existuje → zobrazí "Odeslat přes SMS" - `false` + žádný telefon → tlačítko se nezobrazí vůbec - **Praktické získání `hasMobileApp` pro pacienty:** - Stáhnout všechny požadavky přes `ClinicLegacyRequestList_ListPatientRequestsForClinic` (aktivní i vyřízené) - Pro každého pacienta vzít nejnovější `hasMobileApp` z jeho požadavků - Nevýhoda: pacienti bez jediného požadavku budou `NULL`; hodnota odráží stav k době posledního požadavku ### Requests (Pozadavky) | Operation | Variables | Response | |-----------|-----------|----------| | `ClinicLegacyRequestList_ListPatientRequestsForClinic` | `clinicSlug`, `locale`, `pageInfo {first: 30, offset}`, `queueAssignment`, `queueId`, `state` (ACTIVE/DONE) | `requests`, `clinic` | #### Request list item structure ``` request { id, createdAt, dueDate, displayTitle, doneAt, removedAt, priority, evaluationResult, clinicId, extendedPatient { id, identificationNumber, kind, name, surname, note, owner, key, premiumPlanPatient, status, tags [] }, queue { id, name }, substate, pinType, ecrf { id, name, iconMaskColor { name } }, flags [], hasMobileApp } ``` ### Request Detail | Operation | Variables | Response | |-----------|-----------|----------| | `ClinicRequestDetail_GetPatientRequest2` | `clinicSlug`, `isDoctor`, `requestId`, `locale` | `request` (full detail) | | `UseMessages_ListMessages` | `requestId`, `updatedSince` | `messages` | | `Communication_GetClinicFooter` | `clinicSlug` | `footer` | | `ClinicRequestNotes_Get` | `patientRequestId` | `notes` | #### Request detail structure ``` request { id, doneAt, doneBy { id, name, surname }, removedAt, queue, isInClinic, clinic { id, paymentEnabled, hasInvoicingEnabled, slug, features }, clinicMedicalRecord, clinicMedicalRecordVisibleToPatient, extendedPatient { ... (same as patient) }, ecrf, ecrfFilledData, eventType (PATIENT_REQUEST), flags [], invoice, userNote, paymentData, recording, reservations [], evaluationResult, tags [], createdAt, createdBy, customTitle, createdByDoctor, displayTitle, referringClinic, pinType, dueDate, substate, hasMobileApp, priceWhenCreated, currencyWhenCreated } ``` ### Calendar (Kalendar) | Operation | Variables | Response | |-----------|-----------|----------| | `ClinicCalendar_ListClinicReservations` | `calendarIds []`, `clinicCountry`, `clinicSlug`, `locale`, `since` (ISO), `until` (ISO), `showTimeSlots`, `schedulePatientId`, `emptyCalendarIds` | `holidays`, `reservations`, `vacations` | | `ClinicCalendar_GetWindows` | `calendarIds []`, `clinicSlug`, `locale`, `since`, `until` | `calendarWindows` (ordinacni hodiny) | #### Reservation structure ``` reservation { id, calendarId, clinic { id, slug, aisEntity { id, slug } }, color, done, canceledAt, end (ISO), note, request { displayTitle, id, ecrf { id, name, iconMaskColor { name } }, extendedPatient { dob, email, id, identificationNumber, kind, name, surname, note, phone, sex, status, tags [], insuranceCompanyObject { code, name } } } } ``` ### References (Referovani) | Operation | Variables | Response | |-----------|-----------|----------| | `ClinicReferences_GetClinic` | `clinicSlug` | `clinic`, `substates` | | `ClinicReferences_ListReferencesSentByClinic` | `clinicSlug`, `locale`, `pageInfo {first: 51, offset}` | `references` | ### Treatment Plans (Lecebne plany) | Operation | Variables | Response | |-----------|-----------|----------| | `PlanPatientsPage_GetClinic` | `clinicSlug` | `clinic` | | `PlanPatientsPage_ListPlanPatients` | `clinicSlug`, `pageInfo {first: 51, offset}` | `planPatients` | ### Settings (Nastaveni) | Operation | Purpose | |-----------|---------| | `ClinicSettingsSubNavigation_GetClinic` | Navigation state for settings | | `ClinicRequestSettings_GetClinic` | Request settings | | `UserEcrfListing_GetClinicECRFMenu` | List request types (ECRF forms) | | `ClinicEcrfSet_ListECRFSets` | List request type groups | | `ClinicOpeningHoursEdit_GetClinicOpeningHours2` | Opening hours settings | | `ClinicPaymentsSettings_GetClinic` | Payment settings | | `StripeVerificationContext_GetClinicCompanyInfo` | Stripe payment verification | | `SettingsPage` | General settings | | `InviteSettings` | Invite settings | | `InviteSettings_GetInviteContent` | Invite content/templates | | `ImportAisSettings_GetClinic` | AIS integration settings | | `ImportAisSettings_GetAises` | Available AIS systems | | `ClinicUserGrid_Columns` | User grid columns (queues, calendars) | | `ClinicUserGrid_Rows` | User list for clinic | ### Clinic Info | Operation | Purpose | |-----------|---------| | `ClinicNavigation_GetClinic` | Basic clinic info | | `ClinicAgenda_GetCalendarsForClinic` | List calendars within clinic | --- ## Playwright Selectors | Purpose | Selector | |---------|----------| | Agenda rows | `div[data-testid='reservation-row']` | | Row ID | `data-id` | | Agenda fields | `div[data-field='Time']`, `div[data-field='Patient']`, `div[data-field='Reason']` | --- ## URL Patterns | Page | URL | |------|-----| | Search | `/mudr-buzalkova/klinika/hledat/?q={query}` | | Patient card | `/mudr-buzalkova/klinika/hledat/?q={query}&pacient={uuid}` | | Request detail | `/mudr-buzalkova/klinika/hledat/?q={query}&pozadavek={uuid}` | | Requests (active) | `/mudr-buzalkova/klinika/pozadavky/` | | Requests (done) | `/mudr-buzalkova/klinika/pozadavky?vyrizene=1` | | Calendar | `/mudr-buzalkova/klinika/kalendar/?kalendar={calendarId}&pohled=pracovni-tyden` | | Patient list | `/mudr-buzalkova/klinika/kartoteka/` | | References | `/mudr-buzalkova/klinika/reference` | | Treatment plans | `/mudr-buzalkova/klinika/lecebne-plany/pacienti` | | Settings - requests | `/mudr-buzalkova/klinika/nastaveni/pozadavky` | | Settings - groups | `/mudr-buzalkova/klinika/nastaveni/skupiny-pozadavku` | | Settings - invites | `/mudr-buzalkova/klinika/nastaveni/pozvanky` | | Settings - integrations | `/mudr-buzalkova/klinika/nastaveni/propojky` | | Settings - hours | `/mudr-buzalkova/klinika/nastaveni/ordinacni-doba` | | Settings - payments | `/mudr-buzalkova/klinika/nastaveni/platby` | | Users | `/mudr-buzalkova/klinika/uzivatele/` | --- ## Insurance Companies (CZ) | Code | Name | |------|------| | 0 | Ministerstvo zdravotnictvi CR | | 111 | VZP | | 201 | VoZP | | 205 | CPZP | | 207 | OZP | | 209 | ZPS | | 211 | ZPMV | | 213 | RBP | | 999 | Jina | --- ## Clinic Features (enabled) RESERVATION_REMINDERS, PUSH_NOTIFICATIONS, INVITES, ON_DEMAND_SLOT_GENERATOR, REQUEST_TYPES_ACCESS_RULES, CAN_REFER_PATIENTS, TREATMENT_PLANS_V2, MEDICAL_RECORDS, CAMPAIGNS, PATIENT_SEGMENTATION, ADVANCED_CALENDAR_FUNCTIONS, PATIENT_CLINIC_STATUS, STANDARD_TARIFF_FUNCTIONS, CLINIC_CLOSED_WARNINGS, ADVANCED_SWITCHBOARD, REQUEST_AUTOMATIONS, REQUEST_PINNING, AI_SWITCHBOARD_PROMO, PATIENT_REQUEST_SUBSTATES --- ## Security / Maintenance Notes - GraphQL introspection disabled. - Refresh session cookies periodically. - Keep data GDPR-compliant. --- ## Future Tasks - Automate cookie renewal. - Build helper functions: - `get_reservations(date)` using `ClinicCalendar_ListClinicReservations` - `get_reservation_detail(reservation_id)` - `get_patient(patient_id)` using `ClinicPatientDetailModal_GetData` - `list_patients(offset)` using `LegacyClinicPatientListPage_ListClinicPatients` - `search_patients(query)` using `Search` - `get_requests(state, offset)` using `ClinicLegacyRequestList_ListPatientRequestsForClinic` - `get_request_detail(request_id)` using `ClinicRequestDetail_GetPatientRequest2` - Map vaccine keywords. - Export to Excel/MySQL for vaccine order planning.