notebookvb
This commit is contained in:
@@ -73,5 +73,10 @@
|
||||
"datum": "31.05.2026",
|
||||
"ref_cislo": "178213777",
|
||||
"podano_kdy": "2026-05-13 07:09:12"
|
||||
},
|
||||
{
|
||||
"datum": "30.06.2026",
|
||||
"ref_cislo": "178258393",
|
||||
"podano_kdy": "2026-05-13 21:03:20"
|
||||
}
|
||||
]
|
||||
@@ -1 +1 @@
|
||||
{"mesic": 5, "rok": 2026}
|
||||
{"mesic": 6, "rok": 2026}
|
||||
@@ -1,7 +1,7 @@
|
||||
[
|
||||
{
|
||||
"name": "SID",
|
||||
"value": "cfdefd7ad7d093aeeadee6402dff0fa8",
|
||||
"value": "0589c59247aa8fa221c380eec74c9cef",
|
||||
"domain": ".portal.zpskoda.cz",
|
||||
"path": "/",
|
||||
"expires": -1,
|
||||
@@ -14,7 +14,7 @@
|
||||
"value": "CERT",
|
||||
"domain": ".portal.zpskoda.cz",
|
||||
"path": "/",
|
||||
"expires": 1810184951,
|
||||
"expires": 1810234998,
|
||||
"secure": true,
|
||||
"httpOnly": false,
|
||||
"sameSite": "Lax"
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
CREATE TABLE IF NOT EXISTS medevio.medevio_pacient (
|
||||
patient_id VARCHAR(36) NOT NULL PRIMARY KEY,
|
||||
name VARCHAR(100),
|
||||
surname VARCHAR(100),
|
||||
identification_number VARCHAR(20) COMMENT 'Rodne cislo bez lomitka',
|
||||
sex VARCHAR(10) COMMENT 'Male / Female',
|
||||
dob DATE COMMENT 'Datum narozeni',
|
||||
email VARCHAR(200),
|
||||
phone VARCHAR(50),
|
||||
insurance_code INT COMMENT 'Kod pojistovny (111=VZP, 207=OZP, ...)',
|
||||
insurance_name VARCHAR(100) COMMENT 'Nazev pojistovny',
|
||||
status VARCHAR(20) COMMENT 'ACTIVE / INACTIVE',
|
||||
is_in_clinic TINYINT(1) DEFAULT 1,
|
||||
has_mobile_app TINYINT(1) DEFAULT 0,
|
||||
anamnesis_shared TINYINT(1) DEFAULT 0,
|
||||
note TEXT COMMENT 'Interni poznamka lekare',
|
||||
city VARCHAR(200),
|
||||
street VARCHAR(200),
|
||||
house_number VARCHAR(50),
|
||||
user_id VARCHAR(36) COMMENT 'ID uzivatelskeho uctu (PatientUser)',
|
||||
user_email VARCHAR(200) COMMENT 'Email spravujici osoby',
|
||||
user_phone VARCHAR(50) COMMENT 'Telefon spravujici osoby',
|
||||
created_at DATETIME COMMENT 'Kdy byl pacient vytvoren v Medeviu',
|
||||
synced_at DATETIME NOT NULL COMMENT 'Cas posledni synchronizace'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
@@ -0,0 +1,337 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Sync Medevio patients to MySQL table medevio.medevio_pacient.
|
||||
|
||||
Stahuje seznam pacientů přes GraphQL API (stránkovaně po 50),
|
||||
pro každého pacienta stáhne detail a uloží/aktualizuje v MySQL.
|
||||
"""
|
||||
|
||||
import sys
|
||||
try:
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
sys.stderr.reconfigure(encoding="utf-8")
|
||||
except AttributeError:
|
||||
import io
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
|
||||
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8")
|
||||
|
||||
import json
|
||||
import time
|
||||
import pymysql
|
||||
import requests
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from dateutil import parser as dtparser, tz
|
||||
|
||||
# ==================== CONFIG ====================
|
||||
GRAPHQL_URL = "https://api.medevio.cz/graphql"
|
||||
CLINIC_SLUG = "mudr-buzalkova"
|
||||
PAGE_SIZE = 50
|
||||
DELAY_BETWEEN_REQUESTS = 0.3
|
||||
|
||||
TOKEN_PATH = Path(__file__).resolve().parent.parent / "token.txt"
|
||||
|
||||
DB_CONFIG = {
|
||||
"host": "192.168.1.76",
|
||||
"port": 3306,
|
||||
"user": "root",
|
||||
"password": "Vlado9674+",
|
||||
"database": "medevio",
|
||||
"charset": "utf8mb4",
|
||||
"cursorclass": pymysql.cursors.DictCursor,
|
||||
}
|
||||
|
||||
PRAGUE_TZ = tz.gettz("Europe/Prague")
|
||||
|
||||
# ==================== TABLE ====================
|
||||
CREATE_TABLE_SQL = """
|
||||
CREATE TABLE IF NOT EXISTS medevio_pacient (
|
||||
patient_id VARCHAR(36) NOT NULL PRIMARY KEY,
|
||||
name VARCHAR(100),
|
||||
surname VARCHAR(100),
|
||||
identification_number VARCHAR(20) COMMENT 'Rodne cislo bez lomitka',
|
||||
sex VARCHAR(10) COMMENT 'Male / Female',
|
||||
dob DATE COMMENT 'Datum narozeni',
|
||||
email VARCHAR(200),
|
||||
phone VARCHAR(50),
|
||||
insurance_code INT COMMENT 'Kod pojistovny (111=VZP, 207=OZP, ...)',
|
||||
insurance_name VARCHAR(100) COMMENT 'Nazev pojistovny',
|
||||
status VARCHAR(20) COMMENT 'ACTIVE / INACTIVE',
|
||||
is_in_clinic TINYINT(1) DEFAULT 1,
|
||||
has_mobile_app TINYINT(1) DEFAULT 0,
|
||||
anamnesis_shared TINYINT(1) DEFAULT 0,
|
||||
note TEXT COMMENT 'Interni poznamka lekare',
|
||||
city VARCHAR(200),
|
||||
street VARCHAR(200),
|
||||
house_number VARCHAR(50),
|
||||
user_id VARCHAR(36) COMMENT 'ID uzivatelskeho uctu (PatientUser)',
|
||||
user_email VARCHAR(200) COMMENT 'Email spravujici osoby',
|
||||
user_phone VARCHAR(50) COMMENT 'Telefon spravujici osoby',
|
||||
created_at DATETIME COMMENT 'Kdy byl pacient vytvoren v Medeviu',
|
||||
synced_at DATETIME NOT NULL COMMENT 'Cas posledni synchronizace'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
"""
|
||||
|
||||
# ==================== UPSERT ====================
|
||||
UPSERT_SQL = """
|
||||
INSERT INTO medevio_pacient (
|
||||
patient_id, name, surname, identification_number,
|
||||
sex, dob, email, phone,
|
||||
insurance_code, insurance_name,
|
||||
status, is_in_clinic, has_mobile_app, anamnesis_shared,
|
||||
note, city, street, house_number,
|
||||
user_id, user_email, user_phone,
|
||||
created_at, synced_at
|
||||
) VALUES (
|
||||
%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s
|
||||
)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
name = VALUES(name),
|
||||
surname = VALUES(surname),
|
||||
identification_number = VALUES(identification_number),
|
||||
sex = VALUES(sex),
|
||||
dob = VALUES(dob),
|
||||
email = VALUES(email),
|
||||
phone = VALUES(phone),
|
||||
insurance_code = VALUES(insurance_code),
|
||||
insurance_name = VALUES(insurance_name),
|
||||
status = VALUES(status),
|
||||
is_in_clinic = VALUES(is_in_clinic),
|
||||
has_mobile_app = VALUES(has_mobile_app),
|
||||
anamnesis_shared = VALUES(anamnesis_shared),
|
||||
note = VALUES(note),
|
||||
city = VALUES(city),
|
||||
street = VALUES(street),
|
||||
house_number = VALUES(house_number),
|
||||
user_id = VALUES(user_id),
|
||||
user_email = VALUES(user_email),
|
||||
user_phone = VALUES(user_phone),
|
||||
created_at = VALUES(created_at),
|
||||
synced_at = VALUES(synced_at)
|
||||
"""
|
||||
|
||||
# ==================== GRAPHQL QUERIES ====================
|
||||
|
||||
LIST_PATIENTS_QUERY = """
|
||||
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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
DETAIL_PATIENT_QUERY = """
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
def parse_dt(iso_str):
|
||||
if not iso_str:
|
||||
return None
|
||||
try:
|
||||
return dtparser.isoparse(iso_str).astimezone(PRAGUE_TZ).replace(tzinfo=None)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def make_headers(token):
|
||||
return {
|
||||
"content-type": "application/json",
|
||||
"authorization": f"Bearer {token}",
|
||||
"origin": "https://my.medevio.cz",
|
||||
"referer": "https://my.medevio.cz/",
|
||||
}
|
||||
|
||||
|
||||
def gql_request(headers, query, variables):
|
||||
payload = {"query": query, "variables": variables}
|
||||
resp = requests.post(GRAPHQL_URL, headers=headers, json=payload, timeout=30)
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
if "errors" in data:
|
||||
print(f" GQL errors: {data['errors']}")
|
||||
return None
|
||||
return data.get("data")
|
||||
|
||||
|
||||
def fetch_patients_list(headers):
|
||||
"""Stáhne seznam všech patient IDs stránkovaně přes listPatients."""
|
||||
all_patients = []
|
||||
offset = 0
|
||||
total = None
|
||||
|
||||
while True:
|
||||
data = gql_request(headers, LIST_PATIENTS_QUERY, {
|
||||
"clinicSlug": CLINIC_SLUG,
|
||||
"filter": {},
|
||||
"pageInfo": {"first": PAGE_SIZE, "offset": offset},
|
||||
})
|
||||
if not data or "patientsList" not in data:
|
||||
print(f" Chyba pri stahovani seznamu (offset={offset}).")
|
||||
return None
|
||||
|
||||
result = data["patientsList"]
|
||||
if total is None:
|
||||
total = result.get("count", 0)
|
||||
print(f" Celkem pacientu v Medevio: {total}")
|
||||
|
||||
patients = result.get("patients") or []
|
||||
if not patients:
|
||||
break
|
||||
|
||||
all_patients.extend(patients)
|
||||
offset += PAGE_SIZE
|
||||
print(f" Stazeno {len(all_patients)}/{total}...")
|
||||
|
||||
if offset >= total:
|
||||
break
|
||||
time.sleep(DELAY_BETWEEN_REQUESTS)
|
||||
|
||||
return all_patients
|
||||
|
||||
|
||||
def fetch_patient_detail(headers, patient_id):
|
||||
"""Stáhne detail jednoho pacienta."""
|
||||
data = gql_request(headers, DETAIL_PATIENT_QUERY, {
|
||||
"clinicSlug": CLINIC_SLUG,
|
||||
"patientId": patient_id,
|
||||
})
|
||||
if data and "patient" in data:
|
||||
return data["patient"]
|
||||
return None
|
||||
|
||||
|
||||
def patient_to_row(p):
|
||||
"""Převede patient dict na tuple pro UPSERT."""
|
||||
insurance = p.get("insuranceCompanyObject") or {}
|
||||
user = p.get("user") or {}
|
||||
now = datetime.now().replace(microsecond=0)
|
||||
|
||||
return (
|
||||
p.get("id"),
|
||||
p.get("name"),
|
||||
p.get("surname"),
|
||||
p.get("identificationNumber"),
|
||||
p.get("sex"),
|
||||
p.get("dob"),
|
||||
p.get("email"),
|
||||
p.get("phone"),
|
||||
insurance.get("code"),
|
||||
insurance.get("name"),
|
||||
p.get("status") or p.get("status2"),
|
||||
1 if p.get("isInClinic") else 0,
|
||||
1 if p.get("hasMobileApp") else 0,
|
||||
1 if p.get("anamnesisShared") else 0,
|
||||
p.get("note"),
|
||||
p.get("city"),
|
||||
p.get("street"),
|
||||
p.get("houseNumber"),
|
||||
user.get("id"),
|
||||
user.get("email"),
|
||||
user.get("phone"),
|
||||
parse_dt(p.get("createdAt")),
|
||||
now,
|
||||
)
|
||||
|
||||
|
||||
def upsert_patients(conn, patients):
|
||||
rows = [patient_to_row(p) for p in patients]
|
||||
with conn.cursor() as cur:
|
||||
cur.executemany(UPSERT_SQL, rows)
|
||||
conn.commit()
|
||||
return len(rows)
|
||||
|
||||
|
||||
def main():
|
||||
token = TOKEN_PATH.read_text(encoding="utf-8").strip()
|
||||
if token.startswith("Bearer "):
|
||||
token = token.split(" ", 1)[1]
|
||||
|
||||
headers = make_headers(token)
|
||||
|
||||
conn = pymysql.connect(**DB_CONFIG)
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(CREATE_TABLE_SQL)
|
||||
conn.commit()
|
||||
print("Tabulka medevio_pacient pripravena.")
|
||||
|
||||
# --- Krok 1: stáhni seznam patient IDs ---
|
||||
print("\nStahuji seznam pacientu...")
|
||||
patients = fetch_patients_list(headers)
|
||||
|
||||
if patients is None or len(patients) == 0:
|
||||
print("Nepodařilo se stáhnout seznam pacientů.")
|
||||
conn.close()
|
||||
return
|
||||
|
||||
# --- Krok 2: pro každého stáhni detail ---
|
||||
print(f"\nStahuji detaily pro {len(patients)} pacientu...")
|
||||
detailed = []
|
||||
errors = 0
|
||||
for i, p in enumerate(patients):
|
||||
detail = fetch_patient_detail(headers, p["id"])
|
||||
if detail:
|
||||
detailed.append(detail)
|
||||
else:
|
||||
errors += 1
|
||||
|
||||
if (i + 1) % 50 == 0:
|
||||
print(f" Detail {i + 1}/{len(patients)}...")
|
||||
time.sleep(DELAY_BETWEEN_REQUESTS)
|
||||
|
||||
# --- Krok 3: upsert do MySQL ---
|
||||
count = upsert_patients(conn, detailed)
|
||||
print(f"\nHotovo — {count} pacientu upsertovano.")
|
||||
if errors:
|
||||
print(f" ({errors} pacientu se nepodařilo stáhnout)")
|
||||
|
||||
conn.close()
|
||||
print("\nDokonceno.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+235
-76
@@ -1,11 +1,11 @@
|
||||
# 🧭 MEDEVIO API – Working Notes
|
||||
# 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
|
||||
- **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
|
||||
|
||||
---
|
||||
|
||||
@@ -21,51 +21,6 @@ Use these cookies in every `requests.post(GRAPHQL_URL, headers, cookies, data=..
|
||||
|
||||
---
|
||||
|
||||
## Key GraphQL Operations
|
||||
|
||||
| Operation name | Purpose |
|
||||
|----------------|----------|
|
||||
| `AccessToken_AuthSelf` | Verify or refresh session |
|
||||
| `ClinicNavigation_GetClinic` | Get basic clinic info |
|
||||
| `ClinicAgenda_GetCalendarsForClinic` | List calendars within clinic |
|
||||
| **`ClinicAgenda_ListClinicReservations`** | **List reservations (appointments)** |
|
||||
| **`UpdateReservation_GetReservation`** | **Get full detail of one reservation** |
|
||||
| **`ClinicRequestDetail_GetPatientRequest2`** | **Fetch detailed “Požadavek” (request card)** |
|
||||
| `ClinicRequestNotes_Get` | Load Požadavek notes |
|
||||
| `UseMessages_ListMessages` | Load chat messages |
|
||||
| `Communication_GetClinicFooter` | UI footer text |
|
||||
|
||||
---
|
||||
|
||||
## Workflow for Automation
|
||||
|
||||
### 1️⃣ Extract reservations (agenda overview)
|
||||
```graphql
|
||||
operationName: "ClinicAgenda_ListClinicReservations"
|
||||
variables: {
|
||||
"clinicId": "<clinic-uuid>",
|
||||
"dateFrom": "YYYY-MM-DDT00:00:00Z",
|
||||
"dateTo": "YYYY-MM-DDT23:59:59Z"
|
||||
}
|
||||
```
|
||||
→ returns `id`, `startDateTime`, `endDateTime`, `patient {name, age}`, `reason`, `status`.
|
||||
|
||||
### 2️⃣ Get detail of a single reservation
|
||||
```graphql
|
||||
operationName: "UpdateReservation_GetReservation"
|
||||
variables: {"id": "<reservation-uuid>"}
|
||||
```
|
||||
→ returns reason, time, doctor, patient contact, note, etc.
|
||||
|
||||
### 3️⃣ Get detail of Požadavek
|
||||
```graphql
|
||||
operationName: "ClinicRequestDetail_GetPatientRequest2"
|
||||
variables: {"id": "<request-uuid>"}
|
||||
```
|
||||
→ returns text, attachments, communication, etc.
|
||||
|
||||
---
|
||||
|
||||
## HTTP Setup (Python)
|
||||
|
||||
```python
|
||||
@@ -80,56 +35,260 @@ response = requests.post(GRAPHQL_URL, headers=headers, cookies=cookies, data=jso
|
||||
|
||||
---
|
||||
|
||||
## Reservation Data Example
|
||||
## Key IDs
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "0e3a4f5c-1c15-40e6-b1b1-325de9269ef5",
|
||||
"reason": "Očkování - Chřipka",
|
||||
"startDateTime": "2025-10-17T09:10:00Z",
|
||||
"endDateTime": "2025-10-17T09:20:00Z",
|
||||
"status": "confirmed",
|
||||
"note": "přijde i s taťkou",
|
||||
"patient": {"id": "...", "name": "Černík Pavel", "age": 53},
|
||||
"doctor": {"id": "...", "name": "MUDr. Buzalková"},
|
||||
"location": {"name": "Ordinace Prosek"}
|
||||
| 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` |
|
||||
|
||||
### 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']` |
|
||||
|
||||
---
|
||||
|
||||
## Recommended Automation Pipeline
|
||||
## URL Patterns
|
||||
|
||||
1. **Agenda scrape (Playwright)** → extract `data-id`, patient, time, reason.
|
||||
2. **GraphQL fetch (Python)** → use `ClinicAgenda_ListClinicReservations`.
|
||||
3. **Detail enrichment** → call `UpdateReservation_GetReservation` and `ClinicRequestDetail_GetPatientRequest2`.
|
||||
4. **Storage/reporting** → save to MySQL/Excel for vaccine planning.
|
||||
| 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.
|
||||
- GraphQL introspection disabled.
|
||||
- Refresh session cookies periodically.
|
||||
- Keep data GDPR-compliant.
|
||||
|
||||
---
|
||||
|
||||
## Future Tasks
|
||||
|
||||
- Automate cookie renewal.
|
||||
- Build helper functions:
|
||||
- `get_reservations(date)` using `ClinicAgenda_ListClinicReservations`
|
||||
- `get_reservation_detail(reservation_id)`
|
||||
- Map vaccine keywords (`Očkování – Chřipka`, `COVID`, `Hepatitida`, …).
|
||||
- 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.
|
||||
|
||||
Reference in New Issue
Block a user