notebookvb

This commit is contained in:
Vladimir Buzalka
2026-05-31 07:51:29 +02:00
parent ac37a6e6db
commit a9ef60212d
17 changed files with 3590 additions and 0 deletions
+385
View File
@@ -0,0 +1,385 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Cteni agendy z Medevio kalendare.
Hlavni funkce: `list_agendu(start, end=None, calendar=None)`
start date | datetime | str 'YYYY-MM-DD' nebo 'YYYY-MM-DD HH:MM'
end to stejne; pokud None, bere se konec dne `start` (23:59:59)
calendar None = oba (vlado + manzelka), nebo "vlado" / "manzelka" / UUID,
nebo list techto hodnot
Vraci list dictu serazenych podle start. Kazdy dict obsahuje pole `calendar`
(jmeno) navic.
CLI:
python agenda_dne.py # interaktivne se zepta
python agenda_dne.py --od 2026-06-03 --do 2026-06-03 --kalendar vlado
python agenda_dne.py --od 2026-06-01 --do 2026-06-07 # tyden, oba
python agenda_dne.py --den 2026-06-03 # zkratka pro jeden den
python agenda_dne.py --den dnes
python agenda_dne.py --den +1 --kalendar manzelka
"""
import sys
import json
import argparse
from pathlib import Path
from datetime import datetime, date, timedelta
import requests
from dateutil import parser as dtparser, tz
try:
sys.stdout.reconfigure(encoding="utf-8")
except AttributeError:
pass
GRAPHQL_URL = "https://api.medevio.cz/graphql"
CLINIC_SLUG = "mudr-buzalkova"
PRAGUE_TZ = tz.gettz("Europe/Prague")
# Pojmenovane kalendare - viz pamet project-medevio-kalendar
CALENDARS = {
"vlado": "b6555c7e-4e95-4657-b441-87c2c9a7b2ca",
"manzelka": "144c4e12-347c-49ca-9ec0-8ca965a4470d",
}
BASE_DIR = Path(__file__).resolve().parent
TOKEN_PATH = BASE_DIR / "token.txt"
DEBUG_DIR = BASE_DIR / "debug"
QUERY = """query Agenda_ListAll(
$calendarIds: [UUID!]!, $clinicSlug: String!,
$locale: Locale!, $since: DateTime!, $until: DateTime!
) {
reservations: listClinicReservations(
clinicSlug: $clinicSlug, calendarIds: $calendarIds,
since: $since, until: $until
) {
id start end note done color canceledAt calendarId
request {
id displayTitle(locale: $locale)
extendedPatient {
id name surname dob phone identificationNumber
insuranceCompanyObject { code shortName }
}
}
}
recurringReservations: listClinicRecurringReservations(
clinicSlug: $clinicSlug, calendarIds: $calendarIds,
since: $since, until: $until
) {
recurringReservation {
id calendarId color note
rrule { frequency interval dtstart tzid byweekday bymonthday byweekno }
}
instances { start end note color }
}
}"""
# ==================== Helpery ====================
def _load_token() -> str:
token = TOKEN_PATH.read_text(encoding="utf-8").strip()
if token.startswith("Bearer "):
token = token.split(" ", 1)[1]
return token
def _headers() -> dict:
return {
"content-type": "application/json",
"authorization": f"Bearer {_load_token()}",
"origin": "https://my.medevio.cz",
"referer": "https://my.medevio.cz/",
}
def _is_uuid(s: str) -> bool:
return isinstance(s, str) and len(s) == 36 and s.count("-") == 4
def _resolve_calendars(calendar) -> list[tuple[str, str]]:
"""Vraci list (jmeno, uuid). None = oba pojmenovane."""
if calendar is None:
return list(CALENDARS.items())
items = calendar if isinstance(calendar, list) else [calendar]
out = []
for c in items:
if c in CALENDARS:
out.append((c, CALENDARS[c]))
elif _is_uuid(c):
# zkusime najit jmeno
name = next((n for n, u in CALENDARS.items() if u == c), c)
out.append((name, c))
else:
raise ValueError(f"Neznamy kalendar: {c!r}")
return out
def _to_dt(value, end_of_day: bool = False) -> datetime:
"""Prevede date/datetime/str na datetime s Europe/Prague timezone."""
if isinstance(value, datetime):
dt = value
elif isinstance(value, date):
dt = datetime.combine(
value,
datetime.max.time().replace(microsecond=0) if end_of_day else datetime.min.time(),
)
elif isinstance(value, str):
s = value.strip().replace("T", " ")
if " " in s:
dt = datetime.strptime(s, "%Y-%m-%d %H:%M")
else:
d = datetime.strptime(s, "%Y-%m-%d").date()
dt = datetime.combine(
d,
datetime.max.time().replace(microsecond=0) if end_of_day else datetime.min.time(),
)
else:
raise TypeError(f"Nelze prevest na datetime: {value!r}")
if dt.tzinfo is None:
dt = dt.replace(tzinfo=PRAGUE_TZ)
return dt
def _to_utc_iso(dt: datetime) -> str:
return dt.astimezone(tz.UTC).strftime("%Y-%m-%dT%H:%M:%S.000Z")
# ==================== Hlavni funkce ====================
def list_agendu(
start,
end=None,
calendar=None,
save_debug: bool = True,
) -> list[dict]:
"""Vraci rezervace v zadanem rozmezi pro vybrane kalendare.
Pokud `end` neni zadan, pouzije se konec stejneho dne jako `start`.
Pokud `calendar` je None, vrati se oba pojmenovane kalendare slouceny.
"""
start_dt = _to_dt(start, end_of_day=False)
if end is None:
end_dt = _to_dt(start_dt.date(), end_of_day=True)
else:
end_dt = _to_dt(end, end_of_day=True)
cals = _resolve_calendars(calendar)
payload = {
"operationName": "Agenda_ListAll",
"variables": {
"calendarIds": [uuid for _, uuid in cals],
"clinicSlug": CLINIC_SLUG,
"since": _to_utc_iso(start_dt),
"until": _to_utc_iso(end_dt),
"locale": "cs",
},
"query": QUERY,
}
r = requests.post(GRAPHQL_URL, headers=_headers(), data=json.dumps(payload), timeout=30)
r.raise_for_status()
data = r.json()
if save_debug:
DEBUG_DIR.mkdir(exist_ok=True)
debug_path = DEBUG_DIR / f"agenda_{start_dt:%Y%m%d}_{end_dt:%Y%m%d}_{datetime.now():%H%M%S}.json"
debug_path.write_text(
json.dumps({"request": payload["variables"], "response": data}, ensure_ascii=False, indent=2),
encoding="utf-8",
)
print(f"[debug] {debug_path}")
if "errors" in data:
raise RuntimeError(f"GraphQL error: {data['errors']}")
uuid_to_name = {uuid: name for name, uuid in cals}
parsed = []
# 1) Jednorazove rezervace
for res in data["data"].get("reservations") or []:
if res.get("canceledAt"):
continue
s = dtparser.isoparse(res["start"]).astimezone(PRAGUE_TZ)
e = dtparser.isoparse(res["end"]).astimezone(PRAGUE_TZ)
req = res.get("request") or {}
pat = req.get("extendedPatient") or {}
ins = pat.get("insuranceCompanyObject") or {}
request_id = req.get("id") or ""
parsed.append({
"typ": "pacient" if request_id else "poznamka",
"calendar": uuid_to_name.get(res.get("calendarId"), res.get("calendarId")),
"calendar_id": res.get("calendarId"),
"start": s,
"end": e,
"title": req.get("displayTitle") or "",
"patient": f"{pat.get('surname','')} {pat.get('name','')}".strip(),
"dob": pat.get("dob") or "",
"rc": pat.get("identificationNumber") or "",
"phone": pat.get("phone") or "",
"insurance": ins.get("shortName") or "",
"insurance_code": ins.get("code") or "",
"note": (res.get("note") or "").strip(),
"done": bool(res.get("done")),
"color": res.get("color") or "",
"reservation_id": res["id"],
"request_id": request_id,
"patient_id": pat.get("id") or "",
"recurring": False,
})
# 2) Opakujici se - jednotlive instance v intervalu
for rr in data["data"].get("recurringReservations") or []:
rule = rr.get("recurringReservation") or {}
cal_id = rule.get("calendarId")
for inst in rr.get("instances") or []:
s = dtparser.isoparse(inst["start"]).astimezone(PRAGUE_TZ)
e = dtparser.isoparse(inst["end"]).astimezone(PRAGUE_TZ)
parsed.append({
"typ": "poznamka", # opakujici se vznikaji vzdy pres "Jina udalost"
"calendar": uuid_to_name.get(cal_id, cal_id),
"calendar_id": cal_id,
"start": s,
"end": e,
"title": "",
"patient": "",
"dob": "", "rc": "", "phone": "",
"insurance": "", "insurance_code": "",
"note": (inst.get("note") or rule.get("note") or "").strip(),
"done": False,
"color": inst.get("color") or rule.get("color") or "",
"reservation_id": rule.get("id"),
"request_id": "",
"patient_id": "",
"recurring": True,
"rrule": rule.get("rrule"),
})
parsed.sort(key=lambda x: (x["start"], x["calendar"]))
return parsed
# Zachovani zpetne kompatibility
def get_agenda(day, calendar=None) -> list[dict]:
"""Vraci agendu jednoho dne. Pro zpetnou kompat se starsim API."""
return list_agendu(day, end=day, calendar=calendar)
# ==================== Vystup ====================
def print_agenda(reservations: list[dict], header: str | None = None) -> None:
cz_dny = ["pondeli", "utery", "streda", "ctvrtek", "patek", "sobota", "nedele"]
if header is None:
if reservations:
dates = sorted({r["start"].date() for r in reservations})
if len(dates) == 1:
header = f"Agenda {dates[0].isoformat()} ({cz_dny[dates[0].weekday()]})"
else:
header = f"Agenda {dates[0].isoformat()} - {dates[-1].isoformat()}"
else:
header = "Agenda"
print(header)
print("=" * len(header))
if not reservations:
print("(zadne rezervace)")
return
last_date = None
for r in reservations:
if r["start"].date() != last_date:
if last_date is not None:
print()
print(f"--- {r['start'].date().isoformat()} ({cz_dny[r['start'].weekday()]}) ---")
last_date = r["start"].date()
time_str = f"{r['start']:%H:%M}-{r['end']:%H:%M}"
cal = f"[{r['calendar']}]" if r["calendar"] else ""
typ = f"<{r.get('typ','?')}>"
flags = []
if r["done"]:
flags.append("HOTOVO")
if r.get("recurring"):
flags.append("OPAKOVANE")
flag_str = f" [{', '.join(flags)}]" if flags else ""
label = r["patient"] if r.get("typ") == "pacient" else (r.get("note") or "(bez popisu)")
line = f"{time_str} {cal} {typ} {label}"
if r["dob"]:
line += f" *{r['dob']}"
if r["insurance"]:
line += f" {r['insurance']}"
line += flag_str
print(line)
if r["title"]:
print(f" {r['title']}")
if r["note"]:
for ln in r["note"].splitlines():
print(f" poznamka: {ln}")
print()
print(f"Celkem: {len(reservations)} rezervaci")
# ==================== CLI ====================
def _parse_day_arg(arg: str | None) -> date:
if not arg or arg in ("dnes", "today"):
return date.today()
if arg in ("zitra", "tomorrow"):
return date.today() + timedelta(days=1)
if arg in ("vcera", "yesterday"):
return date.today() - timedelta(days=1)
if arg.startswith(("+", "-")) and arg[1:].isdigit():
return date.today() + timedelta(days=int(arg))
return datetime.strptime(arg, "%Y-%m-%d").date()
def _prompt_interactive() -> tuple[date, date, str | None]:
cz_dny = ["pondeli", "utery", "streda", "ctvrtek", "patek", "sobota", "nedele"]
today = date.today()
print("Cteni agendy z Medevio")
print(f"Dnes je {today.isoformat()} ({cz_dny[today.weekday()]})")
print()
print("Od (YYYY-MM-DD / +N / -N / dnes / zitra / vcera, Enter = dnes):")
od = _parse_day_arg(input("> ").strip() or None)
print(f"Do (Enter = stejny den jako od = {od.isoformat()}):")
do_raw = input("> ").strip()
do = _parse_day_arg(do_raw) if do_raw else od
print(f"Kalendar (vlado / manzelka / Enter = oba):")
cal = input("> ").strip() or None
return od, do, cal
def main():
ap = argparse.ArgumentParser(description="Cteni agendy z Medevio.")
ap.add_argument("--od", help="Pocatecni datum 'YYYY-MM-DD' / +N / -N / dnes/zitra/vcera")
ap.add_argument("--do", dest="do_", help="Koncove datum (default = stejny jako --od)")
ap.add_argument("--den", help="Zkratka: --od i --do nastaveny na tento den")
ap.add_argument("--kalendar", default=None,
help=f"{'/'.join(CALENDARS)} nebo UUID. Bez parametru = oba.")
args = ap.parse_args()
if args.den:
od = do = _parse_day_arg(args.den)
cal = args.kalendar
elif args.od:
od = _parse_day_arg(args.od)
do = _parse_day_arg(args.do_) if args.do_ else od
cal = args.kalendar
else:
od, do, cal = _prompt_interactive()
reservations = list_agendu(od, do, calendar=cal)
print_agenda(reservations)
if __name__ == "__main__":
main()
@@ -0,0 +1,178 @@
{
"request": {
"calendarIds": [
"144c4e12-347c-49ca-9ec0-8ca965a4470d"
],
"clinicSlug": "mudr-buzalkova",
"since": "2026-05-28T22:00:00.000Z",
"until": "2026-05-29T22:00:00.000Z",
"locale": "cs",
"emptyCalendarIds": false
},
"response": {
"data": {
"reservations": [
{
"id": "6e02c736-6060-41bb-90f5-a9dcd309bee4",
"start": "2026-05-29T09:40:00.000Z",
"end": "2026-05-29T09:50:00.000Z",
"note": null,
"done": null,
"color": null,
"canceledAt": null,
"request": {
"id": "98b890a9-bc2b-42c8-859f-fe109f09e1f6",
"displayTitle": "Očkování - Černý kašel (kombinovaná vakcína Adacel [černý kašel, tetanus, záškrt])",
"extendedPatient": {
"id": "92e7be7e-e41b-4181-afc3-624a82bd94f2",
"name": "Nicol",
"surname": "Slaninová",
"dob": "1978-12-20",
"phone": "+420777145986",
"identificationNumber": "7862202920",
"insuranceCompanyObject": {
"code": 111,
"shortName": "VZP"
}
}
}
},
{
"id": "e47d9c64-b84e-4a89-bb3e-77e96db55e84",
"start": "2026-05-29T08:45:00.000Z",
"end": "2026-05-29T09:30:00.000Z",
"note": null,
"done": null,
"color": null,
"canceledAt": null,
"request": {
"id": "9154f33e-6724-450c-af58-3abd0a4778da",
"displayTitle": "Preventivní prohlídka nebo vstupní prohlídka",
"extendedPatient": {
"id": "17a5563e-4044-4c67-b9fd-928be546ea5a",
"name": "Martin",
"surname": "Štoček",
"dob": "1989-10-19",
"phone": "+420604131463",
"identificationNumber": "8910193336",
"insuranceCompanyObject": {
"code": 111,
"shortName": "VZP"
}
}
}
},
{
"id": "8f0a84e9-1f88-4f94-b826-7364095284fc",
"start": "2026-05-29T10:00:00.000Z",
"end": "2026-05-29T10:05:00.000Z",
"note": "ukončit PN Pelcová E.",
"done": null,
"color": "CHARCOAL",
"canceledAt": null,
"request": null
},
{
"id": "565cf3f0-ad44-485f-9433-18e271f429c1",
"start": "2026-05-29T09:30:00.000Z",
"end": "2026-05-29T09:50:00.000Z",
"note": "",
"done": null,
"color": null,
"canceledAt": null,
"request": {
"id": "4ae28457-ee0c-46f0-89b1-23075cfcbe81",
"displayTitle": "Konzultace zdravotního stavu ",
"extendedPatient": {
"id": "88a2c4f3-ad3e-472b-899c-c86b72d6f2e7",
"name": "Hana",
"surname": "Hlavsová",
"dob": "1941-09-06",
"phone": null,
"identificationNumber": "415906003",
"insuranceCompanyObject": {
"code": 111,
"shortName": "VZP"
}
}
}
},
{
"id": "c87c1cfe-2360-478c-8820-a9d92a47511e",
"start": "2026-05-29T09:50:00.000Z",
"end": "2026-05-29T10:00:00.000Z",
"note": null,
"done": null,
"color": null,
"canceledAt": null,
"request": {
"id": "0067cd89-ccb0-4b23-9548-260e2efd2d45",
"displayTitle": "Odběry",
"extendedPatient": {
"id": "fbb78b75-1c61-4915-8d06-1d7ceb6f9450",
"name": "Petra",
"surname": "Zelenková",
"dob": "1983-01-11",
"phone": "+420731585469",
"identificationNumber": "8351112693",
"insuranceCompanyObject": {
"code": 207,
"shortName": "OZP"
}
}
}
},
{
"id": "9b3ab4a3-47aa-4438-9dd7-5289d74a16a5",
"start": "2026-05-29T07:30:00.000Z",
"end": "2026-05-29T08:00:00.000Z",
"note": null,
"done": null,
"color": null,
"canceledAt": null,
"request": {
"id": "c2dd4666-4be3-4543-ad02-bafb05694bd3",
"displayTitle": "Předoperační vyšetření",
"extendedPatient": {
"id": "e6d965dd-b1ba-44c6-a0ba-915a83d45770",
"name": "Markéta",
"surname": "Bečicová",
"dob": "1967-08-12",
"phone": "+420736540111",
"identificationNumber": "6758120446",
"insuranceCompanyObject": {
"code": 205,
"shortName": "ČPZP"
}
}
}
},
{
"id": "86a379f8-3479-4c5e-86b8-550afc55491f",
"start": "2026-05-29T08:00:00.000Z",
"end": "2026-05-29T08:45:00.000Z",
"note": null,
"done": null,
"color": null,
"canceledAt": null,
"request": {
"id": "97e97780-1b9d-4c4d-be99-f05b25cad2fa",
"displayTitle": "Preventivní prohlídka nebo vstupní prohlídka",
"extendedPatient": {
"id": "a72e32cb-752e-4b3b-bcce-fca9f142c76a",
"name": "Eva",
"surname": "Zemanová",
"dob": "1939-09-12",
"phone": "+420739555303",
"identificationNumber": "395912079",
"insuranceCompanyObject": {
"code": 111,
"shortName": "VZP"
}
}
}
}
]
}
}
}
@@ -0,0 +1,268 @@
{
"request": {
"calendarIds": [
"144c4e12-347c-49ca-9ec0-8ca965a4470d"
],
"clinicSlug": "mudr-buzalkova",
"since": "2026-06-02T22:00:00.000Z",
"until": "2026-06-03T22:00:00.000Z",
"locale": "cs",
"emptyCalendarIds": false
},
"response": {
"data": {
"reservations": [
{
"id": "bc275236-970f-4b88-887e-b4c5f286e3a9",
"start": "2026-06-03T08:00:00.000Z",
"end": "2026-06-03T08:45:00.000Z",
"note": null,
"done": null,
"color": null,
"canceledAt": null,
"request": {
"id": "bebb68b3-f9e1-48e0-9e14-b72e5781b4c4",
"displayTitle": "Preventivní prohlídka nebo vstupní prohlídka",
"extendedPatient": {
"id": "86931ca3-cfd4-4aed-af66-244cecc691ee",
"name": "Sylvie",
"surname": "Rejfířová",
"dob": "1980-06-01",
"phone": "+420602266614",
"identificationNumber": "8056010149",
"insuranceCompanyObject": {
"code": 111,
"shortName": "VZP"
}
}
}
},
{
"id": "73844dfa-316f-47da-ac3d-2554ff5aec7f",
"start": "2026-06-03T08:45:00.000Z",
"end": "2026-06-03T09:00:00.000Z",
"note": "",
"done": null,
"color": null,
"canceledAt": null,
"request": {
"id": "97dd7ace-1668-479b-a09b-44d775e53227",
"displayTitle": "Kontrola INR (Quick)",
"extendedPatient": {
"id": "d8c2f2b6-fdf7-462e-91de-fce908aaf3de",
"name": "Jaroslav",
"surname": "Kameník",
"dob": "1940-05-10",
"phone": "+420776702345",
"identificationNumber": "400510088",
"insuranceCompanyObject": {
"code": 201,
"shortName": "VoZP"
}
}
}
},
{
"id": "c2c96faa-4129-4815-a213-ffdd887d42f8",
"start": "2026-06-03T07:00:00.000Z",
"end": "2026-06-03T07:30:00.000Z",
"note": "",
"done": null,
"color": null,
"canceledAt": null,
"request": {
"id": "4ee1cee1-5ccd-425d-85b1-3896d850a784",
"displayTitle": "Prohlídka při léčbě cukrovky",
"extendedPatient": {
"id": "0aeefc63-8599-4602-b731-b7c81e9106a2",
"name": "Miloslav",
"surname": "Hájek",
"dob": "1974-10-24",
"phone": "+420602262242",
"identificationNumber": "7410241014",
"insuranceCompanyObject": {
"code": 207,
"shortName": "OZP"
}
}
}
},
{
"id": "906ca466-eb83-4a65-bc2e-90020714d4e9",
"start": "2026-06-03T09:00:00.000Z",
"end": "2026-06-03T09:45:00.000Z",
"note": null,
"done": null,
"color": null,
"canceledAt": null,
"request": {
"id": "6790af69-4416-4ef8-9101-7e3f7d668320",
"displayTitle": "Preventivní prohlídka nebo vstupní prohlídka",
"extendedPatient": {
"id": "7bcf8cea-efe7-4f90-8e91-c8f88ba72ee6",
"name": "Denisa",
"surname": "Ryšavá",
"dob": "2007-10-24",
"phone": "+420606815415",
"identificationNumber": "0760245079",
"insuranceCompanyObject": {
"code": 111,
"shortName": "VZP"
}
}
}
},
{
"id": "a91481ba-cf7c-4968-bd54-8424565cae3d",
"start": "2026-06-03T09:45:00.000Z",
"end": "2026-06-03T09:55:00.000Z",
"note": null,
"done": null,
"color": null,
"canceledAt": null,
"request": {
"id": "c0ac9fdb-5a82-4306-9d1c-ed22110205fe",
"displayTitle": "Očkování - Žloutenka A",
"extendedPatient": {
"id": "ec4db98b-cce8-473c-a47e-ef24bf61d3d8",
"name": "Romana",
"surname": "Ratkiewiczová",
"dob": "1990-04-15",
"phone": "+420737261867",
"identificationNumber": "9054151128",
"insuranceCompanyObject": {
"code": 201,
"shortName": "VoZP"
}
}
}
},
{
"id": "b5f01f0c-0c9f-42bb-a7bd-f0a725638d38",
"start": "2026-06-03T10:00:00.000Z",
"end": "2026-06-03T10:10:00.000Z",
"note": "",
"done": null,
"color": null,
"canceledAt": null,
"request": {
"id": "48d490fa-0fd1-4a16-aba8-79f275043345",
"displayTitle": "Očkování - Žloutenka A",
"extendedPatient": {
"id": "d8d36c4d-19fc-4405-9bf9-f9a733fb2dad",
"name": "Jana",
"surname": "Ptáčková",
"dob": "1947-09-17",
"phone": "+420604363791",
"identificationNumber": "475917011",
"insuranceCompanyObject": {
"code": 207,
"shortName": "OZP"
}
}
}
},
{
"id": "44d6f66b-95b9-4bfb-b2c9-69338a7cae8f",
"start": "2026-06-03T10:10:00.000Z",
"end": "2026-06-03T10:20:00.000Z",
"note": "",
"done": null,
"color": null,
"canceledAt": null,
"request": {
"id": "30e1d256-eaaf-4a0b-aa13-cd511e4e309e",
"displayTitle": "Očkování - Žloutenka A",
"extendedPatient": {
"id": "bb27b85a-8703-4768-86f4-2cbf4c196dd1",
"name": "Karel",
"surname": "Galus",
"dob": "1946-06-14",
"phone": "+420605230820",
"identificationNumber": "460614110",
"insuranceCompanyObject": {
"code": 211,
"shortName": "ZPMV"
}
}
}
},
{
"id": "b128a573-849d-4bf2-9317-01142147e61e",
"start": "2026-06-03T10:30:00.000Z",
"end": "2026-06-03T10:50:00.000Z",
"note": null,
"done": null,
"color": null,
"canceledAt": null,
"request": {
"id": "bbd0e36a-5148-43d2-8095-fcf746586778",
"displayTitle": "Zdravotní obtíže",
"extendedPatient": {
"id": "5a0a9ff0-bbe8-4fc7-a27d-b7b475ee2189",
"name": "Lenka",
"surname": "Balousova",
"dob": "1972-03-28",
"phone": "+420603560064",
"identificationNumber": "7253282355",
"insuranceCompanyObject": {
"code": 207,
"shortName": "OZP"
}
}
}
},
{
"id": "c0bbc7bc-2491-4143-a6b9-59d4fab9df63",
"start": "2026-06-03T10:50:00.000Z",
"end": "2026-06-03T11:05:00.000Z",
"note": "",
"done": null,
"color": null,
"canceledAt": null,
"request": {
"id": "b0f01a56-6ae6-47fa-94bf-e693977e127b",
"displayTitle": "Zdravotní obtíže",
"extendedPatient": {
"id": "3e1090b7-d0c2-4dd8-bbfe-dc464d2f1671",
"name": "Lenka",
"surname": "Vaněčková",
"dob": "1943-03-17",
"phone": "+420776599498",
"identificationNumber": "435317067",
"insuranceCompanyObject": {
"code": 111,
"shortName": "VZP"
}
}
}
},
{
"id": "9d2f969b-da52-418c-8465-d29021beab49",
"start": "2026-06-03T11:15:00.000Z",
"end": "2026-06-03T11:25:00.000Z",
"note": "",
"done": null,
"color": null,
"canceledAt": null,
"request": {
"id": "019ec07d-b454-4231-9b49-9389fe675faa",
"displayTitle": "Očkování - Klíšťová encefalitida",
"extendedPatient": {
"id": "865e0404-b2c8-4635-8d8d-df7a14275b14",
"name": "Jiří",
"surname": "Chomát",
"dob": "1938-03-14",
"phone": "+420737217617",
"identificationNumber": "380314026",
"insuranceCompanyObject": {
"code": 111,
"shortName": "VZP"
}
}
}
}
]
}
}
}
@@ -0,0 +1,63 @@
{
"request": {
"calendarIds": [
"b6555c7e-4e95-4657-b441-87c2c9a7b2ca",
"144c4e12-347c-49ca-9ec0-8ca965a4470d"
],
"clinicSlug": "mudr-buzalkova",
"since": "2026-05-29T22:00:00.000Z",
"until": "2026-05-30T21:59:59.000Z",
"locale": "cs",
"emptyCalendarIds": false
},
"response": {
"data": {
"reservations": [
{
"id": "afe20509-c476-48e9-83eb-fb02e161c6d8",
"start": "2026-05-30T08:00:00.000Z",
"end": "2026-05-30T08:05:00.000Z",
"note": "TEST",
"done": null,
"color": "CHARCOAL",
"canceledAt": null,
"calendarId": "b6555c7e-4e95-4657-b441-87c2c9a7b2ca",
"request": null
},
{
"id": "aee6f9c4-1487-4934-a32a-8a7b1517271b",
"start": "2026-05-30T08:00:00.000Z",
"end": "2026-05-30T08:05:00.000Z",
"note": "TEST",
"done": null,
"color": "CHARCOAL",
"canceledAt": null,
"calendarId": "b6555c7e-4e95-4657-b441-87c2c9a7b2ca",
"request": null
},
{
"id": "c646d60b-40ca-4f32-8a11-052ab90f5d2c",
"start": "2026-05-30T07:00:00.000Z",
"end": "2026-05-30T07:05:00.000Z",
"note": "TEST poznamka z Claude Code",
"done": null,
"color": "CHARCOAL",
"canceledAt": null,
"calendarId": "b6555c7e-4e95-4657-b441-87c2c9a7b2ca",
"request": null
},
{
"id": "3ac6849a-12e2-4248-a99d-ec9a577ce820",
"start": "2026-05-30T08:00:00.000Z",
"end": "2026-05-30T08:05:00.000Z",
"note": "Test 2 z Claude Code",
"done": null,
"color": "CHARCOAL",
"canceledAt": null,
"calendarId": "b6555c7e-4e95-4657-b441-87c2c9a7b2ca",
"request": null
}
]
}
}
}
@@ -0,0 +1,295 @@
{
"request": {
"calendarIds": [
"b6555c7e-4e95-4657-b441-87c2c9a7b2ca"
],
"clinicSlug": "mudr-buzalkova",
"since": "2026-05-29T22:00:00.000Z",
"until": "2026-05-30T21:59:59.000Z",
"locale": "cs"
},
"response": {
"data": {
"reservations": [
{
"id": "afe20509-c476-48e9-83eb-fb02e161c6d8",
"start": "2026-05-30T08:00:00.000Z",
"end": "2026-05-30T08:05:00.000Z",
"note": "TEST",
"done": null,
"color": "CHARCOAL",
"canceledAt": null,
"calendarId": "b6555c7e-4e95-4657-b441-87c2c9a7b2ca",
"request": null
},
{
"id": "aee6f9c4-1487-4934-a32a-8a7b1517271b",
"start": "2026-05-30T08:00:00.000Z",
"end": "2026-05-30T08:05:00.000Z",
"note": "TEST",
"done": null,
"color": "CHARCOAL",
"canceledAt": null,
"calendarId": "b6555c7e-4e95-4657-b441-87c2c9a7b2ca",
"request": null
},
{
"id": "c646d60b-40ca-4f32-8a11-052ab90f5d2c",
"start": "2026-05-30T07:00:00.000Z",
"end": "2026-05-30T07:05:00.000Z",
"note": "TEST poznamka z Claude Code",
"done": null,
"color": "CHARCOAL",
"canceledAt": null,
"calendarId": "b6555c7e-4e95-4657-b441-87c2c9a7b2ca",
"request": null
},
{
"id": "3ac6849a-12e2-4248-a99d-ec9a577ce820",
"start": "2026-05-30T08:00:00.000Z",
"end": "2026-05-30T08:05:00.000Z",
"note": "Test 2 z Claude Code",
"done": null,
"color": "CHARCOAL",
"canceledAt": null,
"calendarId": "b6555c7e-4e95-4657-b441-87c2c9a7b2ca",
"request": null
}
],
"recurringReservations": [
{
"recurringReservation": {
"id": "ccadcdbd-d5b3-47ec-9b18-414c6dd0d105",
"calendarId": "b6555c7e-4e95-4657-b441-87c2c9a7b2ca",
"color": "CHARCOAL",
"note": "TEST denne",
"rrule": {
"frequency": "DAILY",
"interval": 1,
"dtstart": "2026-05-30T14:40:00.000Z",
"tzid": "Europe/Prague",
"byweekday": null,
"bymonthday": null,
"byweekno": null
}
},
"instances": [
{
"start": "2026-05-30T14:40:00.000Z",
"end": "2026-05-30T14:45:00.000Z",
"note": "TEST denne",
"color": "CHARCOAL"
}
]
},
{
"recurringReservation": {
"id": "280cc437-2ff0-4d74-b16a-a6175d2b1020",
"calendarId": "b6555c7e-4e95-4657-b441-87c2c9a7b2ca",
"color": "CHARCOAL",
"note": "T2",
"rrule": {
"frequency": "DAILY",
"interval": 1,
"dtstart": "2026-05-30T15:35:00.000Z",
"tzid": "Europe/Prague",
"byweekday": [
"MO",
"TU",
"WE",
"TH",
"FR"
],
"bymonthday": null,
"byweekno": null
}
},
"instances": []
},
{
"recurringReservation": {
"id": "89f850f5-19dc-48b8-a24d-9173b80aa095",
"calendarId": "b6555c7e-4e95-4657-b441-87c2c9a7b2ca",
"color": "CHARCOAL",
"note": "T3",
"rrule": {
"frequency": "WEEKLY",
"interval": 1,
"dtstart": "2026-05-30T15:05:00.000Z",
"tzid": "Europe/Prague",
"byweekday": [
"SA"
],
"bymonthday": null,
"byweekno": null
}
},
"instances": [
{
"start": "2026-05-30T15:05:00.000Z",
"end": "2026-05-30T15:10:00.000Z",
"note": "T3",
"color": "CHARCOAL"
}
]
},
{
"recurringReservation": {
"id": "f38f7185-8050-46ee-a2c4-0ca590cc3ab9",
"calendarId": "b6555c7e-4e95-4657-b441-87c2c9a7b2ca",
"color": "CHARCOAL",
"note": "T4",
"rrule": {
"frequency": "YEARLY",
"interval": 1,
"dtstart": "2026-05-30T15:20:00.000Z",
"tzid": "Europe/Prague",
"byweekday": [
"SA"
],
"bymonthday": null,
"byweekno": [
1,
3,
5,
7,
9,
11,
13,
15,
17,
19,
21,
23,
25,
27,
29,
31,
33,
35,
37,
39,
41,
43,
45,
47,
49,
51,
53
]
}
},
"instances": []
},
{
"recurringReservation": {
"id": "357c63bc-6278-4867-acf6-e6d3124eec53",
"calendarId": "b6555c7e-4e95-4657-b441-87c2c9a7b2ca",
"color": "CHARCOAL",
"note": "T5",
"rrule": {
"frequency": "YEARLY",
"interval": 1,
"dtstart": "2026-05-30T15:35:00.000Z",
"tzid": "Europe/Prague",
"byweekday": [
"SA"
],
"bymonthday": null,
"byweekno": [
2,
4,
6,
8,
10,
12,
14,
16,
18,
20,
22,
24,
26,
28,
30,
32,
34,
36,
38,
40,
42,
44,
46,
48,
50,
52
]
}
},
"instances": [
{
"start": "2026-05-30T15:35:00.000Z",
"end": "2026-05-30T15:40:00.000Z",
"note": "T5",
"color": "CHARCOAL"
}
]
},
{
"recurringReservation": {
"id": "eee3904f-475d-4932-914a-fff9ada8fb6c",
"calendarId": "b6555c7e-4e95-4657-b441-87c2c9a7b2ca",
"color": "CHARCOAL",
"note": "T6",
"rrule": {
"frequency": "MONTHLY",
"interval": 1,
"dtstart": "2026-05-30T15:45:00.000Z",
"tzid": "Europe/Prague",
"byweekday": null,
"bymonthday": [
30
],
"byweekno": null
}
},
"instances": [
{
"start": "2026-05-30T15:45:00.000Z",
"end": "2026-05-30T15:50:00.000Z",
"note": "T6",
"color": "CHARCOAL"
}
]
},
{
"recurringReservation": {
"id": "95c5c675-68a2-4181-9d6d-1ebc6d2e6a4c",
"calendarId": "b6555c7e-4e95-4657-b441-87c2c9a7b2ca",
"color": "CHARCOAL",
"note": "T7",
"rrule": {
"frequency": "MONTHLY",
"interval": 1,
"dtstart": "2026-05-30T15:20:00.000Z",
"tzid": "Europe/Prague",
"byweekday": [
"-1SA"
],
"bymonthday": null,
"byweekno": null
}
},
"instances": [
{
"start": "2026-05-30T15:20:00.000Z",
"end": "2026-05-30T15:25:00.000Z",
"note": "T7",
"color": "CHARCOAL"
}
]
}
]
}
}
}
@@ -0,0 +1,232 @@
{
"request": {
"calendarIds": [
"b6555c7e-4e95-4657-b441-87c2c9a7b2ca"
],
"clinicSlug": "mudr-buzalkova",
"since": "2026-05-29T22:00:00.000Z",
"until": "2026-05-30T21:59:59.000Z",
"locale": "cs"
},
"response": {
"data": {
"reservations": [
{
"id": "afe20509-c476-48e9-83eb-fb02e161c6d8",
"start": "2026-05-30T08:00:00.000Z",
"end": "2026-05-30T08:05:00.000Z",
"note": "TEST",
"done": null,
"color": "CHARCOAL",
"canceledAt": null,
"calendarId": "b6555c7e-4e95-4657-b441-87c2c9a7b2ca",
"request": null
},
{
"id": "aee6f9c4-1487-4934-a32a-8a7b1517271b",
"start": "2026-05-30T08:00:00.000Z",
"end": "2026-05-30T08:05:00.000Z",
"note": "TEST",
"done": null,
"color": "CHARCOAL",
"canceledAt": null,
"calendarId": "b6555c7e-4e95-4657-b441-87c2c9a7b2ca",
"request": null
},
{
"id": "3ac6849a-12e2-4248-a99d-ec9a577ce820",
"start": "2026-05-30T08:00:00.000Z",
"end": "2026-05-30T08:05:00.000Z",
"note": "Test 2 z Claude Code",
"done": null,
"color": "CHARCOAL",
"canceledAt": null,
"calendarId": "b6555c7e-4e95-4657-b441-87c2c9a7b2ca",
"request": null
}
],
"recurringReservations": [
{
"recurringReservation": {
"id": "280cc437-2ff0-4d74-b16a-a6175d2b1020",
"calendarId": "b6555c7e-4e95-4657-b441-87c2c9a7b2ca",
"color": "CHARCOAL",
"note": "T2",
"rrule": {
"frequency": "DAILY",
"interval": 1,
"dtstart": "2026-05-30T15:35:00.000Z",
"tzid": "Europe/Prague",
"byweekday": [
"MO",
"TU",
"WE",
"TH",
"FR"
],
"bymonthday": null,
"byweekno": null
}
},
"instances": []
},
{
"recurringReservation": {
"id": "f38f7185-8050-46ee-a2c4-0ca590cc3ab9",
"calendarId": "b6555c7e-4e95-4657-b441-87c2c9a7b2ca",
"color": "CHARCOAL",
"note": "T4",
"rrule": {
"frequency": "YEARLY",
"interval": 1,
"dtstart": "2026-05-30T15:20:00.000Z",
"tzid": "Europe/Prague",
"byweekday": [
"SA"
],
"bymonthday": null,
"byweekno": [
1,
3,
5,
7,
9,
11,
13,
15,
17,
19,
21,
23,
25,
27,
29,
31,
33,
35,
37,
39,
41,
43,
45,
47,
49,
51,
53
]
}
},
"instances": []
},
{
"recurringReservation": {
"id": "357c63bc-6278-4867-acf6-e6d3124eec53",
"calendarId": "b6555c7e-4e95-4657-b441-87c2c9a7b2ca",
"color": "CHARCOAL",
"note": "T5",
"rrule": {
"frequency": "YEARLY",
"interval": 1,
"dtstart": "2026-05-30T15:35:00.000Z",
"tzid": "Europe/Prague",
"byweekday": [
"SA"
],
"bymonthday": null,
"byweekno": [
2,
4,
6,
8,
10,
12,
14,
16,
18,
20,
22,
24,
26,
28,
30,
32,
34,
36,
38,
40,
42,
44,
46,
48,
50,
52
]
}
},
"instances": [
{
"start": "2026-05-30T15:35:00.000Z",
"end": "2026-05-30T15:40:00.000Z",
"note": "T5",
"color": "CHARCOAL"
}
]
},
{
"recurringReservation": {
"id": "eee3904f-475d-4932-914a-fff9ada8fb6c",
"calendarId": "b6555c7e-4e95-4657-b441-87c2c9a7b2ca",
"color": "CHARCOAL",
"note": "T6",
"rrule": {
"frequency": "MONTHLY",
"interval": 1,
"dtstart": "2026-05-30T15:45:00.000Z",
"tzid": "Europe/Prague",
"byweekday": null,
"bymonthday": [
30
],
"byweekno": null
}
},
"instances": [
{
"start": "2026-05-30T15:45:00.000Z",
"end": "2026-05-30T15:50:00.000Z",
"note": "T6",
"color": "CHARCOAL"
}
]
},
{
"recurringReservation": {
"id": "95c5c675-68a2-4181-9d6d-1ebc6d2e6a4c",
"calendarId": "b6555c7e-4e95-4657-b441-87c2c9a7b2ca",
"color": "CHARCOAL",
"note": "T7",
"rrule": {
"frequency": "MONTHLY",
"interval": 1,
"dtstart": "2026-05-30T15:20:00.000Z",
"tzid": "Europe/Prague",
"byweekday": [
"-1SA"
],
"bymonthday": null,
"byweekno": null
}
},
"instances": [
{
"start": "2026-05-30T15:20:00.000Z",
"end": "2026-05-30T15:25:00.000Z",
"note": "T7",
"color": "CHARCOAL"
}
]
}
]
}
}
}
@@ -0,0 +1,17 @@
{
"request": {
"calendarIds": [
"b6555c7e-4e95-4657-b441-87c2c9a7b2ca"
],
"clinicSlug": "mudr-buzalkova",
"since": "2026-05-30T22:00:00.000Z",
"until": "2026-05-31T21:59:59.000Z",
"locale": "cs",
"emptyCalendarIds": false
},
"response": {
"data": {
"reservations": []
}
}
}
@@ -0,0 +1,247 @@
{
"request": {
"calendarIds": [
"b6555c7e-4e95-4657-b441-87c2c9a7b2ca"
],
"clinicSlug": "mudr-buzalkova",
"since": "2026-05-30T22:00:00.000Z",
"until": "2026-05-31T21:59:59.000Z",
"locale": "cs"
},
"response": {
"data": {
"reservations": [],
"recurringReservations": [
{
"recurringReservation": {
"id": "ccadcdbd-d5b3-47ec-9b18-414c6dd0d105",
"calendarId": "b6555c7e-4e95-4657-b441-87c2c9a7b2ca",
"color": "CHARCOAL",
"note": "TEST denne",
"rrule": {
"frequency": "DAILY",
"interval": 1,
"dtstart": "2026-05-30T14:40:00.000Z",
"tzid": "Europe/Prague",
"byweekday": null,
"bymonthday": null,
"byweekno": null
}
},
"instances": [
{
"start": "2026-05-31T14:40:00.000Z",
"end": "2026-05-31T14:45:00.000Z",
"note": "TEST denne",
"color": "CHARCOAL"
}
]
},
{
"recurringReservation": {
"id": "280cc437-2ff0-4d74-b16a-a6175d2b1020",
"calendarId": "b6555c7e-4e95-4657-b441-87c2c9a7b2ca",
"color": "CHARCOAL",
"note": "T2",
"rrule": {
"frequency": "DAILY",
"interval": 1,
"dtstart": "2026-05-30T15:35:00.000Z",
"tzid": "Europe/Prague",
"byweekday": [
"MO",
"TU",
"WE",
"TH",
"FR"
],
"bymonthday": null,
"byweekno": null
}
},
"instances": []
},
{
"recurringReservation": {
"id": "89f850f5-19dc-48b8-a24d-9173b80aa095",
"calendarId": "b6555c7e-4e95-4657-b441-87c2c9a7b2ca",
"color": "CHARCOAL",
"note": "T3",
"rrule": {
"frequency": "WEEKLY",
"interval": 1,
"dtstart": "2026-05-30T15:05:00.000Z",
"tzid": "Europe/Prague",
"byweekday": [
"SA"
],
"bymonthday": null,
"byweekno": null
}
},
"instances": []
},
{
"recurringReservation": {
"id": "f38f7185-8050-46ee-a2c4-0ca590cc3ab9",
"calendarId": "b6555c7e-4e95-4657-b441-87c2c9a7b2ca",
"color": "CHARCOAL",
"note": "T4",
"rrule": {
"frequency": "YEARLY",
"interval": 1,
"dtstart": "2026-05-30T15:20:00.000Z",
"tzid": "Europe/Prague",
"byweekday": [
"SA"
],
"bymonthday": null,
"byweekno": [
1,
3,
5,
7,
9,
11,
13,
15,
17,
19,
21,
23,
25,
27,
29,
31,
33,
35,
37,
39,
41,
43,
45,
47,
49,
51,
53
]
}
},
"instances": []
},
{
"recurringReservation": {
"id": "357c63bc-6278-4867-acf6-e6d3124eec53",
"calendarId": "b6555c7e-4e95-4657-b441-87c2c9a7b2ca",
"color": "CHARCOAL",
"note": "T5",
"rrule": {
"frequency": "YEARLY",
"interval": 1,
"dtstart": "2026-05-30T15:35:00.000Z",
"tzid": "Europe/Prague",
"byweekday": [
"SA"
],
"bymonthday": null,
"byweekno": [
2,
4,
6,
8,
10,
12,
14,
16,
18,
20,
22,
24,
26,
28,
30,
32,
34,
36,
38,
40,
42,
44,
46,
48,
50,
52
]
}
},
"instances": []
},
{
"recurringReservation": {
"id": "eee3904f-475d-4932-914a-fff9ada8fb6c",
"calendarId": "b6555c7e-4e95-4657-b441-87c2c9a7b2ca",
"color": "CHARCOAL",
"note": "T6",
"rrule": {
"frequency": "MONTHLY",
"interval": 1,
"dtstart": "2026-05-30T15:45:00.000Z",
"tzid": "Europe/Prague",
"byweekday": null,
"bymonthday": [
30
],
"byweekno": null
}
},
"instances": []
},
{
"recurringReservation": {
"id": "95c5c675-68a2-4181-9d6d-1ebc6d2e6a4c",
"calendarId": "b6555c7e-4e95-4657-b441-87c2c9a7b2ca",
"color": "CHARCOAL",
"note": "T7",
"rrule": {
"frequency": "MONTHLY",
"interval": 1,
"dtstart": "2026-05-30T15:20:00.000Z",
"tzid": "Europe/Prague",
"byweekday": [
"-1SA"
],
"bymonthday": null,
"byweekno": null
}
},
"instances": []
},
{
"recurringReservation": {
"id": "f675ca4f-1169-4b3b-8266-b171d23849c8",
"calendarId": "b6555c7e-4e95-4657-b441-87c2c9a7b2ca",
"color": "CHARCOAL",
"note": "T8",
"rrule": {
"frequency": "YEARLY",
"interval": 1,
"dtstart": "2026-05-31T15:05:00.000Z",
"tzid": "Europe/Prague",
"byweekday": null,
"bymonthday": null,
"byweekno": null
}
},
"instances": [
{
"start": "2026-05-31T15:05:00.000Z",
"end": "2026-05-31T15:10:00.000Z",
"note": "T8",
"color": "CHARCOAL"
}
]
}
]
}
}
}
@@ -0,0 +1,278 @@
{
"request": {
"calendarIds": [
"144c4e12-347c-49ca-9ec0-8ca965a4470d"
],
"clinicSlug": "mudr-buzalkova",
"since": "2026-06-02T22:00:00.000Z",
"until": "2026-06-03T21:59:59.000Z",
"locale": "cs",
"emptyCalendarIds": false
},
"response": {
"data": {
"reservations": [
{
"id": "bc275236-970f-4b88-887e-b4c5f286e3a9",
"start": "2026-06-03T08:00:00.000Z",
"end": "2026-06-03T08:45:00.000Z",
"note": null,
"done": null,
"color": null,
"canceledAt": null,
"calendarId": "144c4e12-347c-49ca-9ec0-8ca965a4470d",
"request": {
"id": "bebb68b3-f9e1-48e0-9e14-b72e5781b4c4",
"displayTitle": "Preventivní prohlídka nebo vstupní prohlídka",
"extendedPatient": {
"id": "86931ca3-cfd4-4aed-af66-244cecc691ee",
"name": "Sylvie",
"surname": "Rejfířová",
"dob": "1980-06-01",
"phone": "+420602266614",
"identificationNumber": "8056010149",
"insuranceCompanyObject": {
"code": 111,
"shortName": "VZP"
}
}
}
},
{
"id": "73844dfa-316f-47da-ac3d-2554ff5aec7f",
"start": "2026-06-03T08:45:00.000Z",
"end": "2026-06-03T09:00:00.000Z",
"note": "",
"done": null,
"color": null,
"canceledAt": null,
"calendarId": "144c4e12-347c-49ca-9ec0-8ca965a4470d",
"request": {
"id": "97dd7ace-1668-479b-a09b-44d775e53227",
"displayTitle": "Kontrola INR (Quick)",
"extendedPatient": {
"id": "d8c2f2b6-fdf7-462e-91de-fce908aaf3de",
"name": "Jaroslav",
"surname": "Kameník",
"dob": "1940-05-10",
"phone": "+420776702345",
"identificationNumber": "400510088",
"insuranceCompanyObject": {
"code": 201,
"shortName": "VoZP"
}
}
}
},
{
"id": "c2c96faa-4129-4815-a213-ffdd887d42f8",
"start": "2026-06-03T07:00:00.000Z",
"end": "2026-06-03T07:30:00.000Z",
"note": "",
"done": null,
"color": null,
"canceledAt": null,
"calendarId": "144c4e12-347c-49ca-9ec0-8ca965a4470d",
"request": {
"id": "4ee1cee1-5ccd-425d-85b1-3896d850a784",
"displayTitle": "Prohlídka při léčbě cukrovky",
"extendedPatient": {
"id": "0aeefc63-8599-4602-b731-b7c81e9106a2",
"name": "Miloslav",
"surname": "Hájek",
"dob": "1974-10-24",
"phone": "+420602262242",
"identificationNumber": "7410241014",
"insuranceCompanyObject": {
"code": 207,
"shortName": "OZP"
}
}
}
},
{
"id": "906ca466-eb83-4a65-bc2e-90020714d4e9",
"start": "2026-06-03T09:00:00.000Z",
"end": "2026-06-03T09:45:00.000Z",
"note": null,
"done": null,
"color": null,
"canceledAt": null,
"calendarId": "144c4e12-347c-49ca-9ec0-8ca965a4470d",
"request": {
"id": "6790af69-4416-4ef8-9101-7e3f7d668320",
"displayTitle": "Preventivní prohlídka nebo vstupní prohlídka",
"extendedPatient": {
"id": "7bcf8cea-efe7-4f90-8e91-c8f88ba72ee6",
"name": "Denisa",
"surname": "Ryšavá",
"dob": "2007-10-24",
"phone": "+420606815415",
"identificationNumber": "0760245079",
"insuranceCompanyObject": {
"code": 111,
"shortName": "VZP"
}
}
}
},
{
"id": "a91481ba-cf7c-4968-bd54-8424565cae3d",
"start": "2026-06-03T09:45:00.000Z",
"end": "2026-06-03T09:55:00.000Z",
"note": null,
"done": null,
"color": null,
"canceledAt": null,
"calendarId": "144c4e12-347c-49ca-9ec0-8ca965a4470d",
"request": {
"id": "c0ac9fdb-5a82-4306-9d1c-ed22110205fe",
"displayTitle": "Očkování - Žloutenka A",
"extendedPatient": {
"id": "ec4db98b-cce8-473c-a47e-ef24bf61d3d8",
"name": "Romana",
"surname": "Ratkiewiczová",
"dob": "1990-04-15",
"phone": "+420737261867",
"identificationNumber": "9054151128",
"insuranceCompanyObject": {
"code": 201,
"shortName": "VoZP"
}
}
}
},
{
"id": "b5f01f0c-0c9f-42bb-a7bd-f0a725638d38",
"start": "2026-06-03T10:00:00.000Z",
"end": "2026-06-03T10:10:00.000Z",
"note": "",
"done": null,
"color": null,
"canceledAt": null,
"calendarId": "144c4e12-347c-49ca-9ec0-8ca965a4470d",
"request": {
"id": "48d490fa-0fd1-4a16-aba8-79f275043345",
"displayTitle": "Očkování - Žloutenka A",
"extendedPatient": {
"id": "d8d36c4d-19fc-4405-9bf9-f9a733fb2dad",
"name": "Jana",
"surname": "Ptáčková",
"dob": "1947-09-17",
"phone": "+420604363791",
"identificationNumber": "475917011",
"insuranceCompanyObject": {
"code": 207,
"shortName": "OZP"
}
}
}
},
{
"id": "44d6f66b-95b9-4bfb-b2c9-69338a7cae8f",
"start": "2026-06-03T10:10:00.000Z",
"end": "2026-06-03T10:20:00.000Z",
"note": "",
"done": null,
"color": null,
"canceledAt": null,
"calendarId": "144c4e12-347c-49ca-9ec0-8ca965a4470d",
"request": {
"id": "30e1d256-eaaf-4a0b-aa13-cd511e4e309e",
"displayTitle": "Očkování - Žloutenka A",
"extendedPatient": {
"id": "bb27b85a-8703-4768-86f4-2cbf4c196dd1",
"name": "Karel",
"surname": "Galus",
"dob": "1946-06-14",
"phone": "+420605230820",
"identificationNumber": "460614110",
"insuranceCompanyObject": {
"code": 211,
"shortName": "ZPMV"
}
}
}
},
{
"id": "b128a573-849d-4bf2-9317-01142147e61e",
"start": "2026-06-03T10:30:00.000Z",
"end": "2026-06-03T10:50:00.000Z",
"note": null,
"done": null,
"color": null,
"canceledAt": null,
"calendarId": "144c4e12-347c-49ca-9ec0-8ca965a4470d",
"request": {
"id": "bbd0e36a-5148-43d2-8095-fcf746586778",
"displayTitle": "Zdravotní obtíže",
"extendedPatient": {
"id": "5a0a9ff0-bbe8-4fc7-a27d-b7b475ee2189",
"name": "Lenka",
"surname": "Balousova",
"dob": "1972-03-28",
"phone": "+420603560064",
"identificationNumber": "7253282355",
"insuranceCompanyObject": {
"code": 207,
"shortName": "OZP"
}
}
}
},
{
"id": "c0bbc7bc-2491-4143-a6b9-59d4fab9df63",
"start": "2026-06-03T10:50:00.000Z",
"end": "2026-06-03T11:05:00.000Z",
"note": "",
"done": null,
"color": null,
"canceledAt": null,
"calendarId": "144c4e12-347c-49ca-9ec0-8ca965a4470d",
"request": {
"id": "b0f01a56-6ae6-47fa-94bf-e693977e127b",
"displayTitle": "Zdravotní obtíže",
"extendedPatient": {
"id": "3e1090b7-d0c2-4dd8-bbfe-dc464d2f1671",
"name": "Lenka",
"surname": "Vaněčková",
"dob": "1943-03-17",
"phone": "+420776599498",
"identificationNumber": "435317067",
"insuranceCompanyObject": {
"code": 111,
"shortName": "VZP"
}
}
}
},
{
"id": "9d2f969b-da52-418c-8465-d29021beab49",
"start": "2026-06-03T11:15:00.000Z",
"end": "2026-06-03T11:25:00.000Z",
"note": "",
"done": null,
"color": null,
"canceledAt": null,
"calendarId": "144c4e12-347c-49ca-9ec0-8ca965a4470d",
"request": {
"id": "019ec07d-b454-4231-9b49-9389fe675faa",
"displayTitle": "Očkování - Klíšťová encefalitida",
"extendedPatient": {
"id": "865e0404-b2c8-4635-8d8d-df7a14275b14",
"name": "Jiří",
"surname": "Chomát",
"dob": "1938-03-14",
"phone": "+420737217617",
"identificationNumber": "380314026",
"insuranceCompanyObject": {
"code": 111,
"shortName": "VZP"
}
}
}
}
]
}
}
}
@@ -0,0 +1,278 @@
{
"request": {
"calendarIds": [
"144c4e12-347c-49ca-9ec0-8ca965a4470d"
],
"clinicSlug": "mudr-buzalkova",
"since": "2026-06-02T22:00:00.000Z",
"until": "2026-06-03T21:59:59.000Z",
"locale": "cs"
},
"response": {
"data": {
"reservations": [
{
"id": "bc275236-970f-4b88-887e-b4c5f286e3a9",
"start": "2026-06-03T08:00:00.000Z",
"end": "2026-06-03T08:45:00.000Z",
"note": null,
"done": null,
"color": null,
"canceledAt": null,
"calendarId": "144c4e12-347c-49ca-9ec0-8ca965a4470d",
"request": {
"id": "bebb68b3-f9e1-48e0-9e14-b72e5781b4c4",
"displayTitle": "Preventivní prohlídka nebo vstupní prohlídka",
"extendedPatient": {
"id": "86931ca3-cfd4-4aed-af66-244cecc691ee",
"name": "Sylvie",
"surname": "Rejfířová",
"dob": "1980-06-01",
"phone": "+420602266614",
"identificationNumber": "8056010149",
"insuranceCompanyObject": {
"code": 111,
"shortName": "VZP"
}
}
}
},
{
"id": "73844dfa-316f-47da-ac3d-2554ff5aec7f",
"start": "2026-06-03T08:45:00.000Z",
"end": "2026-06-03T09:00:00.000Z",
"note": "",
"done": null,
"color": null,
"canceledAt": null,
"calendarId": "144c4e12-347c-49ca-9ec0-8ca965a4470d",
"request": {
"id": "97dd7ace-1668-479b-a09b-44d775e53227",
"displayTitle": "Kontrola INR (Quick)",
"extendedPatient": {
"id": "d8c2f2b6-fdf7-462e-91de-fce908aaf3de",
"name": "Jaroslav",
"surname": "Kameník",
"dob": "1940-05-10",
"phone": "+420776702345",
"identificationNumber": "400510088",
"insuranceCompanyObject": {
"code": 201,
"shortName": "VoZP"
}
}
}
},
{
"id": "c2c96faa-4129-4815-a213-ffdd887d42f8",
"start": "2026-06-03T07:00:00.000Z",
"end": "2026-06-03T07:30:00.000Z",
"note": "",
"done": null,
"color": null,
"canceledAt": null,
"calendarId": "144c4e12-347c-49ca-9ec0-8ca965a4470d",
"request": {
"id": "4ee1cee1-5ccd-425d-85b1-3896d850a784",
"displayTitle": "Prohlídka při léčbě cukrovky",
"extendedPatient": {
"id": "0aeefc63-8599-4602-b731-b7c81e9106a2",
"name": "Miloslav",
"surname": "Hájek",
"dob": "1974-10-24",
"phone": "+420602262242",
"identificationNumber": "7410241014",
"insuranceCompanyObject": {
"code": 207,
"shortName": "OZP"
}
}
}
},
{
"id": "906ca466-eb83-4a65-bc2e-90020714d4e9",
"start": "2026-06-03T09:00:00.000Z",
"end": "2026-06-03T09:45:00.000Z",
"note": null,
"done": null,
"color": null,
"canceledAt": null,
"calendarId": "144c4e12-347c-49ca-9ec0-8ca965a4470d",
"request": {
"id": "6790af69-4416-4ef8-9101-7e3f7d668320",
"displayTitle": "Preventivní prohlídka nebo vstupní prohlídka",
"extendedPatient": {
"id": "7bcf8cea-efe7-4f90-8e91-c8f88ba72ee6",
"name": "Denisa",
"surname": "Ryšavá",
"dob": "2007-10-24",
"phone": "+420606815415",
"identificationNumber": "0760245079",
"insuranceCompanyObject": {
"code": 111,
"shortName": "VZP"
}
}
}
},
{
"id": "a91481ba-cf7c-4968-bd54-8424565cae3d",
"start": "2026-06-03T09:45:00.000Z",
"end": "2026-06-03T09:55:00.000Z",
"note": null,
"done": null,
"color": null,
"canceledAt": null,
"calendarId": "144c4e12-347c-49ca-9ec0-8ca965a4470d",
"request": {
"id": "c0ac9fdb-5a82-4306-9d1c-ed22110205fe",
"displayTitle": "Očkování - Žloutenka A",
"extendedPatient": {
"id": "ec4db98b-cce8-473c-a47e-ef24bf61d3d8",
"name": "Romana",
"surname": "Ratkiewiczová",
"dob": "1990-04-15",
"phone": "+420737261867",
"identificationNumber": "9054151128",
"insuranceCompanyObject": {
"code": 201,
"shortName": "VoZP"
}
}
}
},
{
"id": "b5f01f0c-0c9f-42bb-a7bd-f0a725638d38",
"start": "2026-06-03T10:00:00.000Z",
"end": "2026-06-03T10:10:00.000Z",
"note": "",
"done": null,
"color": null,
"canceledAt": null,
"calendarId": "144c4e12-347c-49ca-9ec0-8ca965a4470d",
"request": {
"id": "48d490fa-0fd1-4a16-aba8-79f275043345",
"displayTitle": "Očkování - Žloutenka A",
"extendedPatient": {
"id": "d8d36c4d-19fc-4405-9bf9-f9a733fb2dad",
"name": "Jana",
"surname": "Ptáčková",
"dob": "1947-09-17",
"phone": "+420604363791",
"identificationNumber": "475917011",
"insuranceCompanyObject": {
"code": 207,
"shortName": "OZP"
}
}
}
},
{
"id": "44d6f66b-95b9-4bfb-b2c9-69338a7cae8f",
"start": "2026-06-03T10:10:00.000Z",
"end": "2026-06-03T10:20:00.000Z",
"note": "",
"done": null,
"color": null,
"canceledAt": null,
"calendarId": "144c4e12-347c-49ca-9ec0-8ca965a4470d",
"request": {
"id": "30e1d256-eaaf-4a0b-aa13-cd511e4e309e",
"displayTitle": "Očkování - Žloutenka A",
"extendedPatient": {
"id": "bb27b85a-8703-4768-86f4-2cbf4c196dd1",
"name": "Karel",
"surname": "Galus",
"dob": "1946-06-14",
"phone": "+420605230820",
"identificationNumber": "460614110",
"insuranceCompanyObject": {
"code": 211,
"shortName": "ZPMV"
}
}
}
},
{
"id": "b128a573-849d-4bf2-9317-01142147e61e",
"start": "2026-06-03T10:30:00.000Z",
"end": "2026-06-03T10:50:00.000Z",
"note": null,
"done": null,
"color": null,
"canceledAt": null,
"calendarId": "144c4e12-347c-49ca-9ec0-8ca965a4470d",
"request": {
"id": "bbd0e36a-5148-43d2-8095-fcf746586778",
"displayTitle": "Zdravotní obtíže",
"extendedPatient": {
"id": "5a0a9ff0-bbe8-4fc7-a27d-b7b475ee2189",
"name": "Lenka",
"surname": "Balousova",
"dob": "1972-03-28",
"phone": "+420603560064",
"identificationNumber": "7253282355",
"insuranceCompanyObject": {
"code": 207,
"shortName": "OZP"
}
}
}
},
{
"id": "c0bbc7bc-2491-4143-a6b9-59d4fab9df63",
"start": "2026-06-03T10:50:00.000Z",
"end": "2026-06-03T11:05:00.000Z",
"note": "",
"done": null,
"color": null,
"canceledAt": null,
"calendarId": "144c4e12-347c-49ca-9ec0-8ca965a4470d",
"request": {
"id": "b0f01a56-6ae6-47fa-94bf-e693977e127b",
"displayTitle": "Zdravotní obtíže",
"extendedPatient": {
"id": "3e1090b7-d0c2-4dd8-bbfe-dc464d2f1671",
"name": "Lenka",
"surname": "Vaněčková",
"dob": "1943-03-17",
"phone": "+420776599498",
"identificationNumber": "435317067",
"insuranceCompanyObject": {
"code": 111,
"shortName": "VZP"
}
}
}
},
{
"id": "9d2f969b-da52-418c-8465-d29021beab49",
"start": "2026-06-03T11:15:00.000Z",
"end": "2026-06-03T11:25:00.000Z",
"note": "",
"done": null,
"color": null,
"canceledAt": null,
"calendarId": "144c4e12-347c-49ca-9ec0-8ca965a4470d",
"request": {
"id": "019ec07d-b454-4231-9b49-9389fe675faa",
"displayTitle": "Očkování - Klíšťová encefalitida",
"extendedPatient": {
"id": "865e0404-b2c8-4635-8d8d-df7a14275b14",
"name": "Jiří",
"surname": "Chomát",
"dob": "1938-03-14",
"phone": "+420737217617",
"identificationNumber": "380314026",
"insuranceCompanyObject": {
"code": 111,
"shortName": "VZP"
}
}
}
}
],
"recurringReservations": []
}
}
}
@@ -0,0 +1,20 @@
{
"request": {
"clinicSlug": "mudr-buzalkova",
"color": "CHARCOAL",
"note": "TEST poznamka z Claude Code",
"timeSlotInput": {
"calendarId": "b6555c7e-4e95-4657-b441-87c2c9a7b2ca",
"start": "2026-05-30T07:00:00.000Z",
"end": "2026-05-30T07:05:00.000Z"
}
},
"response": {
"data": {
"reservation": {
"id": "c646d60b-40ca-4f32-8a11-052ab90f5d2c",
"__typename": "Reservation"
}
}
}
}
@@ -0,0 +1,20 @@
{
"request": {
"clinicSlug": "mudr-buzalkova",
"color": "CHARCOAL",
"note": "Test 2 z Claude Code",
"timeSlotInput": {
"calendarId": "b6555c7e-4e95-4657-b441-87c2c9a7b2ca",
"start": "2026-05-30T08:00:00.000Z",
"end": "2026-05-30T08:05:00.000Z"
}
},
"response": {
"data": {
"reservation": {
"id": "3ac6849a-12e2-4248-a99d-ec9a577ce820",
"__typename": "Reservation"
}
}
}
}
+176
View File
@@ -0,0 +1,176 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Smazani rezervace / poznamky v Medevio kalendari.
Defaultni chovani:
- jednorazova: smazat (cancel)
- opakujici se: smazat tuto a nasledujici (ThisAndFuture)
Pouziti jako modul:
from smaz_poznamku import smaz_jednorazovou, smaz_opakujici
smaz_jednorazovou("<reservation_uuid>")
smaz_opakujici("<recurring_id>", date="2026-05-30T14:40:00.000Z") # default ThisAndFuture
smaz_opakujici("<recurring_id>", date="...Z", update_type="All")
smaz_opakujici("<recurring_id>", date="...Z", update_type="Single")
CLI:
python smaz_poznamku.py --jednorazova <reservation_uuid>
python smaz_poznamku.py --opakujici <recurring_id> --datum 2026-05-30T14:40:00.000Z
python smaz_poznamku.py --opakujici <id> --datum ...Z --typ All
"""
import sys
import json
import argparse
from pathlib import Path
from datetime import datetime
import requests
from dateutil import parser as dtparser, tz
try:
sys.stdout.reconfigure(encoding="utf-8")
except AttributeError:
pass
GRAPHQL_URL = "https://api.medevio.cz/graphql"
CLINIC_SLUG = "mudr-buzalkova"
PRAGUE_TZ = tz.gettz("Europe/Prague")
BASE_DIR = Path(__file__).resolve().parent
TOKEN_PATH = BASE_DIR / "token.txt"
DEBUG_DIR = BASE_DIR / "debug"
VALID_UPDATE_TYPES = ("Single", "ThisAndFuture", "All")
MUTATION_SINGLE = """mutation UpdateReservation_CancelReservationByDoctor(
$clinicSlug: String!, $reservationId: UUID!
) {
reservation: cancelReservationByDoctor(
clinicSlug: $clinicSlug, reservationId: $reservationId
) { id __typename }
}"""
MUTATION_RECURRING = """mutation UpdateReservation_CancelRecurringReservationByDoctor(
$input: RemoveRecurringReservationInput!
) {
success: removeDateFromRecurringReservation(input: $input)
}"""
def _load_token() -> str:
token = TOKEN_PATH.read_text(encoding="utf-8").strip()
if token.startswith("Bearer "):
token = token.split(" ", 1)[1]
return token
def _headers() -> dict:
return {
"content-type": "application/json",
"authorization": f"Bearer {_load_token()}",
"origin": "https://my.medevio.cz",
"referer": "https://my.medevio.cz/",
}
def _to_utc_iso(value) -> str:
"""Prijme str/datetime/date a vrati '....000Z'."""
if isinstance(value, str):
if value.endswith("Z") and "T" in value:
return value
dt = dtparser.parse(value)
elif isinstance(value, datetime):
dt = value
else:
raise TypeError(f"Nelze prevest na datetime: {value!r}")
if dt.tzinfo is None:
dt = dt.replace(tzinfo=PRAGUE_TZ)
return dt.astimezone(tz.UTC).strftime("%Y-%m-%dT%H:%M:%S.000Z")
def _post(payload: dict, label: str, save_debug: bool = True) -> dict:
r = requests.post(GRAPHQL_URL, headers=_headers(), data=json.dumps(payload), timeout=30)
r.raise_for_status()
data = r.json()
if save_debug:
DEBUG_DIR.mkdir(exist_ok=True)
p = DEBUG_DIR / f"smaz_{label}_{datetime.now():%Y%m%d_%H%M%S}.json"
p.write_text(
json.dumps({"request": payload["variables"], "response": data}, ensure_ascii=False, indent=2),
encoding="utf-8",
)
print(f"[debug] {p}")
if "errors" in data:
raise RuntimeError(f"GraphQL error: {data['errors']}")
return data["data"]
def smaz_jednorazovou(reservation_id: str, clinic_slug: str = CLINIC_SLUG) -> dict:
"""Zrusi (cancel) jednorazovou rezervaci. Vraci {'id': ..., '__typename': ...}."""
payload = {
"operationName": "UpdateReservation_CancelReservationByDoctor",
"variables": {"clinicSlug": clinic_slug, "reservationId": reservation_id},
"query": MUTATION_SINGLE,
}
result = _post(payload, "single")["reservation"]
print(f"OK zruseno: {result['id']}")
return result
def smaz_opakujici(
recurring_id: str,
date,
update_type: str = "ThisAndFuture",
clinic_slug: str = CLINIC_SLUG,
) -> bool:
"""Smaze opakujici se rezervaci.
Args:
recurring_id: UUID `recurringReservation.id`
date: datum konkretni instance (ISO str s Z, nebo datetime, nebo 'YYYY-MM-DD HH:MM' v lokal cas)
update_type: 'Single' | 'ThisAndFuture' (default) | 'All'
"""
if update_type not in VALID_UPDATE_TYPES:
raise ValueError(f"update_type musi byt {VALID_UPDATE_TYPES}, dostal '{update_type}'")
payload = {
"operationName": "UpdateReservation_CancelRecurringReservationByDoctor",
"variables": {
"input": {
"clinicSlug": clinic_slug,
"recurringReservationId": recurring_id,
"date": _to_utc_iso(date),
"updateType": update_type,
}
},
"query": MUTATION_RECURRING,
}
success = _post(payload, f"recurring_{update_type}")["success"]
print(f"OK smazano ({update_type}): {recurring_id} @ {payload['variables']['input']['date']} -> success={success}")
return success
def main():
ap = argparse.ArgumentParser(description="Smazani rezervace v Medevio kalendari.")
g = ap.add_mutually_exclusive_group(required=True)
g.add_argument("--jednorazova", help="UUID jednorazove rezervace ke zruseni")
g.add_argument("--opakujici", help="UUID opakujiciho se pravidla")
ap.add_argument("--datum", help="Datum instance opakujici (ISO Z nebo 'YYYY-MM-DD HH:MM')")
ap.add_argument("--typ", choices=VALID_UPDATE_TYPES, default="ThisAndFuture",
help="Pro --opakujici. Default ThisAndFuture.")
args = ap.parse_args()
if args.jednorazova:
smaz_jednorazovou(args.jednorazova)
else:
if not args.datum:
ap.error("--opakujici vyzaduje --datum")
smaz_opakujici(args.opakujici, args.datum, update_type=args.typ)
if __name__ == "__main__":
main()
+202
View File
@@ -0,0 +1,202 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Zapise poznamku lekare do Medevio kalendare (color=CHARCOAL).
Hlavni funkce: `zapis_poznamku()` - bere parametry:
calendar_id UUID kalendare (Vlado / manzelka / ...)
den date nebo str 'YYYY-MM-DD'
cas str 'HH:MM' nebo time
trvani_min int - delka v minutach
poznamka str - text
perioda None | str - "DAILY", "WEEKDAYS", atd. (zatim NotImplemented)
color ECRFIconColor enum, default CHARCOAL
clinic_slug default mudr-buzalkova
Vrati id vytvorene rezervace (str).
CLI:
python zapis_poznamky.py --den 2026-06-03 --cas 09:00 --trvani 5 --poznamka "Text"
python zapis_poznamky.py --den 2026-06-03 --cas 09:00 --trvani 5 --poznamka "Text" --kalendar manzelka
"""
import sys
import json
import argparse
from pathlib import Path
from datetime import datetime, date, time as dtime, timedelta
import requests
from dateutil import tz
try:
sys.stdout.reconfigure(encoding="utf-8")
except AttributeError:
pass
GRAPHQL_URL = "https://api.medevio.cz/graphql"
CLINIC_SLUG = "mudr-buzalkova"
PRAGUE_TZ = tz.gettz("Europe/Prague")
# Pojmenovane kalendare - viz pamet project-medevio-kalendar
CALENDARS = {
"vlado": "b6555c7e-4e95-4657-b441-87c2c9a7b2ca",
"manzelka": "144c4e12-347c-49ca-9ec0-8ca965a4470d",
}
DEFAULT_CALENDAR = "vlado"
BASE_DIR = Path(__file__).resolve().parent
TOKEN_PATH = BASE_DIR / "token.txt"
DEBUG_DIR = BASE_DIR / "debug"
MUTATION = """mutation CreateReservation_MakeReservationByDoctor(
$clinicSlug: String!,
$color: ECRFIconColor,
$note: String!,
$timeSlotInput: TimeSlotInput!
) {
reservation: makeReservationByDoctor(
clinicSlug: $clinicSlug
color: $color
note: $note
timeSlotInput: $timeSlotInput
) {
id
__typename
}
}"""
def _load_token() -> str:
token = TOKEN_PATH.read_text(encoding="utf-8").strip()
if token.startswith("Bearer "):
token = token.split(" ", 1)[1]
return token
def _headers() -> dict:
return {
"content-type": "application/json",
"authorization": f"Bearer {_load_token()}",
"origin": "https://my.medevio.cz",
"referer": "https://my.medevio.cz/",
}
def _to_utc_iso(dt: datetime) -> str:
if dt.tzinfo is None:
dt = dt.replace(tzinfo=PRAGUE_TZ)
return dt.astimezone(tz.UTC).strftime("%Y-%m-%dT%H:%M:%S.000Z")
def _resolve_calendar(calendar) -> str:
"""Prijme UUID nebo nazev ('vlado', 'manzelka'). Vrati UUID."""
if calendar in CALENDARS:
return CALENDARS[calendar]
if isinstance(calendar, str) and len(calendar) == 36 and calendar.count("-") == 4:
return calendar
raise ValueError(f"Neznamy kalendar: {calendar!r}. Pouzij UUID nebo jeden z {list(CALENDARS)}.")
def _resolve_day(den) -> date:
if isinstance(den, date) and not isinstance(den, datetime):
return den
if isinstance(den, datetime):
return den.date()
return datetime.strptime(str(den), "%Y-%m-%d").date()
def _resolve_time(cas) -> dtime:
if isinstance(cas, dtime):
return cas
return datetime.strptime(str(cas), "%H:%M").time()
def zapis_poznamku(
calendar: str = DEFAULT_CALENDAR,
den=None,
cas=None,
trvani_min: int = 5,
poznamka: str = "",
perioda: str | None = None,
color: str = "CHARCOAL",
clinic_slug: str = CLINIC_SLUG,
save_debug: bool = True,
) -> str:
"""Vytvori poznamku lekare v Medevio kalendari. Vraci id rezervace."""
if perioda is not None:
raise NotImplementedError(
"Periodicita zatim neni implementovana - neznam format payloadu. "
"Zachyt jednu opakujici se rezervaci pres Claude in Chrome a doplnime."
)
if den is None or cas is None:
raise ValueError("Musis zadat 'den' a 'cas'.")
calendar_id = _resolve_calendar(calendar)
d = _resolve_day(den)
t = _resolve_time(cas)
start_dt = datetime.combine(d, t).replace(tzinfo=PRAGUE_TZ)
end_dt = start_dt + timedelta(minutes=int(trvani_min))
payload = {
"operationName": "CreateReservation_MakeReservationByDoctor",
"variables": {
"clinicSlug": clinic_slug,
"color": color,
"note": poznamka,
"timeSlotInput": {
"calendarId": calendar_id,
"start": _to_utc_iso(start_dt),
"end": _to_utc_iso(end_dt),
},
},
"query": MUTATION,
}
r = requests.post(GRAPHQL_URL, headers=_headers(), data=json.dumps(payload), timeout=30)
r.raise_for_status()
data = r.json()
if save_debug:
DEBUG_DIR.mkdir(exist_ok=True)
debug_path = DEBUG_DIR / f"poznamka_{datetime.now():%Y%m%d_%H%M%S}.json"
debug_path.write_text(
json.dumps({"request": payload["variables"], "response": data}, ensure_ascii=False, indent=2),
encoding="utf-8",
)
print(f"[debug] {debug_path}")
if "errors" in data:
raise RuntimeError(f"GraphQL error: {data['errors']}")
res_id = data["data"]["reservation"]["id"]
print(f"OK: {d} {t:%H:%M} +{trvani_min}min '{poznamka}' -> {res_id}")
return res_id
def main():
ap = argparse.ArgumentParser(description="Zapis poznamku lekare do Medevio kalendare.")
ap.add_argument("--kalendar", default=DEFAULT_CALENDAR,
help=f"Jmeno ({'/'.join(CALENDARS)}) nebo UUID. Default '{DEFAULT_CALENDAR}'.")
ap.add_argument("--den", required=True, help="YYYY-MM-DD")
ap.add_argument("--cas", required=True, help="HH:MM (lokalni cas Europe/Prague)")
ap.add_argument("--trvani", type=int, default=5, help="delka v minutach (default 5)")
ap.add_argument("--poznamka", required=True, help="text poznamky")
ap.add_argument("--perioda", default=None,
help="opakovani (zatim NotImplemented)")
ap.add_argument("--color", default="CHARCOAL")
args = ap.parse_args()
zapis_poznamku(
calendar=args.kalendar,
den=args.den,
cas=args.cas,
trvani_min=args.trvani,
poznamka=args.poznamka,
perioda=args.perioda,
color=args.color,
)
if __name__ == "__main__":
main()
+235
View File
@@ -0,0 +1,235 @@
"""
fill_poukaz_dp.py
-----------------
Vyplní formulář "Poukaz na vyšetření / ošetření DP" (VZP-06dp/2024).
Vstup: PDF šablona (plochý formulář)
Výstup: vyplněný PDF
Použití:
python fill_poukaz_dp.py
Závislosti:
pip install pypdf reportlab
"""
from pathlib import Path
from io import BytesIO
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from pypdf import PdfReader, PdfWriter
# ---------------------------------------------------------------------------
# NASTAVENÍ — sem doplňte cestu k šabloně a výstupnímu souboru
# ---------------------------------------------------------------------------
SABLONA = Path(__file__).parent / "Poukaz DP zdroj.pdf"
VYSTUP = Path(__file__).parent / "Poukaz DP vyplneny.pdf"
# ---------------------------------------------------------------------------
# DATA FORMULÁŘE — doplňte hodnoty
# ---------------------------------------------------------------------------
DATA = {
# ── Hlavička ─────────────────────────────────────────────────────────────
"kod_pojistovny": "111", # 3 číslice (VZP = 111)
"icp": "12345678", # IČP pracoviště
"odbornost": "001", # odbornost
"datum": "22.05.2026", # datum vystavení
"poradove_cislo": "42", # pořadové č. poukazu / nepřerušené DP
"platnost_do": "22.08.2026", # platnost poukazu
# ── Pacient ──────────────────────────────────────────────────────────────
"pacient": "Novák Jan",
"cislo_pojistence": "7001015432",
"zkladni_dg": "I10", # základní diagnóza (MKN kód)
"variabilni_symbol":"123456",
"ost_dg": "E11", # ostatní diagnózy
"kod_nahrady": "",
# ── Adresa a kontakty ────────────────────────────────────────────────────
"adresa_pacienta": "Dlouhá 12, 110 00 Praha 1, tel: 777 123 456",
"dalsi_prislusnici":"ne", # "ano" nebo "ne"
"kontaktni_osoba": "Nováková Marie (manželka), tel: 777 654 321",
# ── Klinické údaje ────────────────────────────────────────────────────────
"pecovatelska_sluzba": "ne", # "ano" nebo "ne"
"mobilita": "b) omezená: chodí s holí",
"smyslove_omezeni": "zraková vada brýle",
"sebeobsluha": "b) omezená: potřebuje pomoc při hygieně",
"medikace": "Metformin 1000 mg 1-0-1, Amlodipine 5 mg 1-0-0, inzulín Lantus 20j večer",
"dalsi_informace": "alergie: penicilin; inkontinence moči; byt 2. patro bez výtahu",
"cil_dp": "Edukace v aplikaci inzulínu, kontrola glykémie, péče o DM nohu",
# ── Požadované výkony (max 5 řádků) ─────────────────────────────────────
# Každý výkon: {"kod": "06101", "popis": "...", "popis2": "..."}
"pozadovano": [
{"kod": "06101", "popis": "Komplexní ošetřovatelská péče 1× denně, 5× týdně", "popis2": ""},
{"kod": "06129", "popis": "Aplikace inzulínu 1× denně, 7× týdně", "popis2": ""},
{"kod": "06111", "popis": "Odběr biologického materiálu 1× týdně", "popis2": ""},
{"kod": "", "popis": "", "popis2": ""},
{"kod": "", "popis": "", "popis2": ""},
],
}
# ---------------------------------------------------------------------------
# Pomocná funkce — zkrátí text pokud je moc dlouhý
# ---------------------------------------------------------------------------
def _fit(text: str, max_chars: int) -> str:
return text[:max_chars] + "" if len(text) > max_chars else text
# ---------------------------------------------------------------------------
# Vytvoří overlay stránku s textem
# ---------------------------------------------------------------------------
def _vytvor_overlay(data: dict) -> BytesIO:
buf = BytesIO()
W, H = A4 # 595.3 × 841.9 pt
c = canvas.Canvas(buf, pagesize=A4)
c.setFont("Helvetica", 8)
def txt(x, y, text, size=8, bold=False):
if not text:
return
font = "Helvetica-Bold" if bold else "Helvetica"
c.setFont(font, size)
c.drawString(x, y, str(text))
# ── Hlavička ─────────────────────────────────────────────────────────────
txt(58, 795, data["kod_pojistovny"], size=8) # Kód pojišťovny
txt(200, 800, data["icp"], size=8) # IČP
txt(200, 782, data["odbornost"], size=8) # Odbornost
txt(310, 800, data["datum"], size=8) # Datum
txt(425, 800, data["poradove_cislo"], size=8) # Pořadové č.
txt(470, 765, data["platnost_do"], size=8) # Platnost do
# ── Pacient ──────────────────────────────────────────────────────────────
txt(195, 726, _fit(data["pacient"], 40), size=8) # Pacient
txt(115, 709, data["cislo_pojistence"], size=8) # Č. pojištěnce
txt(390, 709, data["zkladni_dg"], size=8) # Základní dg.
txt(115, 692, data["variabilni_symbol"], size=8) # Variabilní symbol
txt(390, 692, data["ost_dg"], size=8) # Ost. dg.
txt(390, 675, data["kod_nahrady"], size=8) # Kód náhrady
# ── Adresa pacienta ───────────────────────────────────────────────────────
txt(335, 634, _fit(data["adresa_pacienta"], 70), size=8)
# Další příslušníci — podtrhne zvolenou možnost
if data["dalsi_prislusnici"].lower() == "ano":
txt(310, 600, "ano", size=8, bold=True)
else:
txt(323, 600, "ne", size=8, bold=True)
# ── Kontaktní osoba ───────────────────────────────────────────────────────
txt(420, 573, _fit(data["kontaktni_osoba"], 50), size=8)
# Pacient v péči pečovatelské služby
if data["pecovatelska_sluzba"].lower() == "ano":
txt(310, 536, "ano", size=8, bold=True)
else:
txt(323, 536, "ne", size=8, bold=True)
# ── Mobilita ──────────────────────────────────────────────────────────────
mob = data["mobilita"]
if mob.lower().startswith("a"):
txt(230, 521, "✓ plná", size=8, bold=True)
else:
# odstraní "b) omezená:" prefix pokud tam je
detail = mob.replace("b) omezená:", "").replace("b)omezená:", "").strip()
txt(280, 504, _fit(detail, 60), size=8)
# ── Smyslové omezení ──────────────────────────────────────────────────────
txt(280, 479, _fit(data["smyslove_omezeni"], 65), size=8)
# ── Sebeobsluha ───────────────────────────────────────────────────────────
sebo = data["sebeobsluha"]
if sebo.lower().startswith("a"):
txt(390, 452, "✓ plná", size=8, bold=True)
else:
detail = sebo.replace("b) omezená:", "").replace("b)omezená:", "").strip()
txt(280, 437, _fit(detail, 60), size=8)
# ── Medikace (max 3 řádky) ────────────────────────────────────────────────
med_lines = _rozlom(data["medikace"], 80)
for i, line in enumerate(med_lines[:3]):
txt(58, 413 - i * 14, line, size=8)
# ── Další informace (max 3 řádky) ─────────────────────────────────────────
info_lines = _rozlom(data["dalsi_informace"], 80)
for i, line in enumerate(info_lines[:3]):
txt(58, 370 - i * 14, line, size=8)
# ── Cíl DP (max 2 řádky) ──────────────────────────────────────────────────
cil_lines = _rozlom(data["cil_dp"], 80)
for i, line in enumerate(cil_lines[:2]):
txt(58, 321 - i * 14, line, size=8)
# ── Požadované výkony ─────────────────────────────────────────────────────
# Souřadnice každého řádku (y pro kód, y pro 1. text, y pro 2. text)
radky = [
(240, 263, 243), # řádek 1
(197, 210, 192), # řádek 2
(152, 163, 145), # řádek 3
(110, 120, 102), # řádek 4
( 68, 78, 60), # řádek 5
]
for i, vykon in enumerate(data["pozadovano"][:5]):
y_kod, y_txt1, y_txt2 = radky[i]
txt(58, y_kod, vykon.get("kod", ""), size=8)
txt(285, y_txt1, _fit(vykon.get("popis", ""), 75), size=8)
txt(285, y_txt2, _fit(vykon.get("popis2", ""), 75), size=8)
c.save()
buf.seek(0)
return buf
def _rozlom(text: str, sirka: int) -> list[str]:
"""Rozlomí dlouhý text na řádky po max `sirka` znacích (na hranici slova)."""
words = text.split()
lines, current = [], ""
for w in words:
if len(current) + len(w) + 1 <= sirka:
current = (current + " " + w).strip()
else:
if current:
lines.append(current)
current = w
if current:
lines.append(current)
return lines
# ---------------------------------------------------------------------------
# Hlavní funkce
# ---------------------------------------------------------------------------
def vyplnit_poukaz(data: dict, sablona: Path, vystup: Path):
if not sablona.exists():
raise FileNotFoundError(f"Šablona nenalezena: {sablona}")
overlay_buf = _vytvor_overlay(data)
reader = PdfReader(sablona)
writer = PdfWriter()
overlay = PdfReader(overlay_buf)
# Stránka 1 — překryjeme overlayem
page1 = reader.pages[0]
page1.merge_page(overlay.pages[0])
writer.add_page(page1)
# Stránka 2 — beze změny
if len(reader.pages) > 1:
writer.add_page(reader.pages[1])
vystup.parent.mkdir(parents=True, exist_ok=True)
with open(vystup, "wb") as f:
writer.write(f)
print(f"✓ Hotovo: {vystup}")
# ---------------------------------------------------------------------------
if __name__ == "__main__":
vyplnit_poukaz(DATA, SABLONA, VYSTUP)
+595
View File
@@ -0,0 +1,595 @@
#!/usr/bin/env python3
"""
poukaz_dp_gen.py
Generátor Poukazu na vyšetření/ošetření DP (VZP-06dp/2024).
Formulář je kreslen od nuly pomocí reportlab žádné překrývání PDF šablony.
Závislosti:
pip install reportlab
"""
from pathlib import Path
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
# ── Registrace fontu s českou podporou ────────────────────────────────────────
def _reg_font():
candidates = [
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",
"C:/Windows/Fonts/arial.ttf",
"C:/Windows/Fonts/arialbd.ttf",
]
reg, reg_bold = False, False
for p in candidates:
if Path(p).exists():
if not reg and "Bold" not in p and "bd" not in p:
pdfmetrics.registerFont(TTFont("Czech", p))
reg = True
elif not reg_bold and ("Bold" in p or "bd" in p):
pdfmetrics.registerFont(TTFont("Czech-Bold", p))
reg_bold = True
if not reg:
# fallback Helvetica (funguje pro základní znaky)
return "Helvetica", "Helvetica-Bold"
if not reg_bold:
return "Czech", "Czech"
return "Czech", "Czech-Bold"
FONT, FONT_BOLD = _reg_font()
# ═══════════════════════════════════════════════════════════════════════════════
# DATA FORMULÁŘE sem doplňte hodnoty z Medicusu
# ═══════════════════════════════════════════════════════════════════════════════
DATA = {
# ── Hlavička ──────────────────────────────────────────────────────────────
"kod_pojistovny": "111", # 3 číslice, VZP = 111
"icp": "12345678", # IČP pracoviště (max 8 číslic)
"odbornost": "001", # odbornost (3 číslice)
"datum": "22.05.2026", # datum vystavení
"poradove_cislo": "42", # pořadové číslo / nepřerušené DP
"platnost_do": "22.08.2026", # platnost poukazu
# ── Pacient ───────────────────────────────────────────────────────────────
"pacient": "Novák Jan",
"cislo_pojistence": "7001015432", # rodné číslo / č. pojištěnce
"zkladni_dg": "I10", # MKN kód základní diagnózy
"variabilni_symbol": "123456",
"ost_dg": "E11", # ostatní diagnózy
"kod_nahrady": "",
# ── Adresa a kontakty ─────────────────────────────────────────────────────
"adresa_pacienta": "Dlouhá 12, 110 00 Praha 1, tel: 777 123 456",
"dalsi_prislusnici": "ne", # "ano" / "ne"
"kontaktni_osoba": "Nováková Marie (manželka), tel: 777 654 321",
# ── Klinické údaje ────────────────────────────────────────────────────────
"pecovatelska_sluzba": "ne", # "ano" / "ne"
"mobilita": "b", # "a" = plná / "b" = omezená
"mobilita_detail": "chodí s holí",
"smyslove_omezeni": "zraková vada brýle",
"sebeobsluha": "b", # "a" = plná / "b" = omezená
"sebeobsluha_detail": "potřebuje pomoc při hygieně",
# Každá položka = jeden řádek (max 3)
"medikace": [
"Metformin 1000 mg 1-0-1, Amlodipine 5 mg 1-0-0",
"Inzulín Lantus 20j večer",
],
"dalsi_informace": [
"alergie: penicilin; inkontinence moči",
"byt 2. patro bez výtahu",
],
"cil_dp": [
"Edukace v aplikaci inzulínu, kontrola glykémie, péče o DM nohu",
],
# ── Požadované výkony (max 5) ─────────────────────────────────────────────
# kod … kód výkonu (max 5 číslic)
# popis … text na prvním řádku
# popis2 … pokračování na druhém řádku (nepovinné)
"pozadovano": [
{"kod": "06101", "popis": "Komplexní ošetřovatelská péče 1× denně, 5× týdně", "popis2": ""},
{"kod": "06129", "popis": "Aplikace inzulínu 1× denně, 7× týdně", "popis2": ""},
{"kod": "06111", "popis": "Odběr biologického materiálu 1× týdně", "popis2": ""},
{"kod": "", "popis": "", "popis2": ""},
{"kod": "", "popis": "", "popis2": ""},
],
}
# ═══════════════════════════════════════════════════════════════════════════════
# GEOMETRIE (souřadnice od SHORA, konverze na RL přes yt())
# ═══════════════════════════════════════════════════════════════════════════════
PAGE_W, PAGE_H = A4 # 595 × 842 pt
# Vnější zaoblený rámeček
FX1, FX2 = 28, 567 # levý / pravý okraj
FB, FT = 52, 818 # spodní / horní okraj (RL souřadnice od zdola)
IL = FX1 + 7 # inner left (odsazení textu od okraje)
IR = FX2 - 7 # inner right
def yt(from_top: float) -> float:
"""Převede y-od-shora (pt) na reportlab y-od-zdola."""
return PAGE_H - from_top
# ═══════════════════════════════════════════════════════════════════════════════
# POMOCNÉ FUNKCE
# ═══════════════════════════════════════════════════════════════════════════════
def txt(c, x, y_rl, text, size=7.5, bold=False, align="left"):
if not text:
return
c.setFont(FONT_BOLD if bold else FONT, size)
if align == "right":
c.drawRightString(x, y_rl, str(text))
elif align == "center":
c.drawCentredString(x, y_rl, str(text))
else:
c.drawString(x, y_rl, str(text))
def hline(c, x1, y_rl, x2=None, w=0.5):
if x2 is None:
x2 = IR
c.setLineWidth(w)
c.setDash([])
c.line(x1, y_rl, x2, y_rl)
def vline(c, x, y1_rl, y2_rl, w=0.5):
c.setLineWidth(w)
c.setDash([])
c.line(x, y1_rl, x, y2_rl)
def dotline(c, x1, y_rl, x2=None):
if x2 is None:
x2 = IR
c.setLineWidth(0.35)
c.setDash([1.5, 2.5])
c.line(x1, y_rl, x2, y_rl)
c.setDash([])
def chkbox(c, x, y_rl, checked: bool, size=7):
"""Zaškrtávací políčko."""
c.setLineWidth(0.6)
c.setDash([])
c.rect(x, y_rl - 1, size, size, stroke=1, fill=0)
if checked:
c.setFont(FONT_BOLD, size - 0.5)
c.drawString(x + 1, y_rl, "")
def digit_box(c, x, y_rl, width, height, digits, value_text=""):
"""Prostý rámeček pro zadávání hodnot (bez vnitřních oddělovačů)."""
c.setLineWidth(0.7)
c.setDash([])
c.rect(x, y_rl, width, height, stroke=1, fill=0)
if value_text:
c.setFont(FONT, height * 0.65)
c.drawString(x + 3, y_rl + height * 0.2, value_text[:digits])
def wrap(text: str, max_chars: int) -> list[str]:
"""Rozlomí text na řádky po max_chars znacích (na hranici slova)."""
if not text:
return [""]
words = text.split()
lines, cur = [], ""
for w in words:
if cur and len(cur) + 1 + len(w) > max_chars:
lines.append(cur)
cur = w
else:
cur = (cur + " " + w).strip()
if cur:
lines.append(cur)
return lines or [""]
# ═══════════════════════════════════════════════════════════════════════════════
# SEKCE 1: HLAVIČKA
# ═══════════════════════════════════════════════════════════════════════════════
#
# ┌─────────────────┬──────────┬──────────────────────┬──────────────────────┐
# │ Kód pojišťovny │ požaduje │ IČP [________] Datum [________] │ Pořadové číslo…
# │ [___] │ díl A │ Odbornost [___] │ nepřerušené DP:
# └─────────────────┴──────────┴──────────────────────┴──────────────────────┘
# Platnost do:
HDR_TOP = 25 # horní okraj formuláře (od shora)
HDR_MID = 50 # vodorovná čára mezi řádky hlavičky
HDR_BOT = 73 # dolní okraj hlavičky
# Svislé dělicí čáry v hlavičce
HX1 = 130 # po Kód pojišťovny
HX2 = 198 # po požaduje díl A
HX3 = 295 # mezi IČP a Datum
HX4 = 388 # konec IČP+Datum sekce (začátek Pořadové číslo)
def draw_hlavicka(c, d):
# Vnější rámeček hlavičky
hline(c, FX1, yt(HDR_BOT), FX2, w=1.2)
hline(c, FX1, yt(HDR_MID), HX4, w=0.5)
# Svislé čáry
for x in (HX1, HX2, HX4):
vline(c, x, yt(HDR_TOP), yt(HDR_BOT), w=0.8)
vline(c, HX3, yt(HDR_TOP), yt(HDR_MID), w=0.5)
# Kód pojišťovny
txt(c, IL, yt(HDR_TOP + 7), "Kód pojišťovny", size=6.5)
digit_box(c, IL, yt(HDR_MID - 5), 40, 12, 3, d.get("kod_pojistovny", ""))
# požaduje díl A
cx = (HX1 + HX2) / 2
txt(c, cx, yt(HDR_TOP + 12), "požaduje", size=7, bold=True, align="center")
txt(c, cx, yt(HDR_TOP + 21), "díl A", size=7, bold=True, align="center")
# IČP
txt(c, HX2 + 3, yt(HDR_TOP + 7), "IČP", size=6.5)
digit_box(c, HX2 + 22, yt(HDR_MID - 5), HX3 - HX2 - 27, 12, 8, d.get("icp", ""))
# Datum
txt(c, HX3 + 3, yt(HDR_TOP + 7), "Datum", size=6.5)
digit_box(c, HX3 + 28, yt(HDR_MID - 5), HX4 - HX3 - 32, 12, 10, d.get("datum", ""))
# Odbornost
txt(c, HX2 + 3, yt(HDR_MID + 7), "Odbornost", size=6.5)
digit_box(c, HX2 + 48, yt(HDR_BOT - 5), HX3 - HX2 - 52, 12, 3, d.get("odbornost", ""))
# Pořadové číslo (pravý sloupec)
txt(c, HX4 + 4, yt(HDR_TOP + 21), "Pořadové číslo poukazu", size=6.5)
txt(c, HX4 + 4, yt(HDR_TOP + 30), "nepřerušené DP:", size=6.5)
txt(c, HX4 + 80, yt(HDR_TOP + 36), d.get("poradove_cislo", ""), size=8)
# Platnost do
txt(c, HX4 + 4, yt(HDR_MID + 19), "Platnost do:", size=6.5)
hline(c, HX4 + 50, yt(HDR_MID + 13), FX2 - 4, w=0.4)
txt(c, HX4 + 54, yt(HDR_MID + 25), d.get("platnost_do", ""), size=8)
# ═══════════════════════════════════════════════════════════════════════════════
# SEKCE 2: NADPIS
# ═══════════════════════════════════════════════════════════════════════════════
TTL_TOP = HDR_BOT # 73
TTL_BOT = TTL_TOP + 22 # 95
def draw_nadpis(c):
hline(c, FX1, yt(TTL_BOT), FX2, w=0.8)
# "POUKAZ NA VYŠETŘENÍ / OŠETŘENÍ"
c.setFont(FONT_BOLD, 12)
c.drawCentredString(270, yt(TTL_TOP + 15), "POUKAZ NA VYŠETŘENÍ / OŠETŘENÍ")
# "DP" velké
c.setFont(FONT_BOLD, 22)
c.drawString(390, yt(TTL_TOP + 18), "DP")
# ═══════════════════════════════════════════════════════════════════════════════
# SEKCE 3: BLOK PACIENTA
# ═══════════════════════════════════════════════════════════════════════════════
PAC_TOP = TTL_BOT # 95
PAC_ROW = 19 # výška každého řádku
PAC_BOT = PAC_TOP + 4 * PAC_ROW # 171
# Svislé dělicí čáry v bloku pacienta
PX1 = 340 # Č. pojištěnce / Základní diagnóza
PX2 = 340 # (sdílí s PX1 pro 2. a 3. řádek)
PX3 = 410 # Ost. dg. / Kód náhrady
# Šířka bloku pacienta (bez pravého sloupce s razítkem)
PAC_RIGHT = 540 # pravý okraj tabulky pacienta
def draw_pacient(c, d):
# Vnější pravý okraj tabulky pacienta
vline(c, PAC_RIGHT, yt(PAC_TOP), yt(PAC_BOT), w=1.0)
# Řádek 1: Pacient
y1 = yt(PAC_TOP + PAC_ROW)
hline(c, FX1, y1, PAC_RIGHT, w=0.6)
txt(c, IL, y1 + 5, "Pacient", size=7)
txt(c, IL + 50, y1 + 5, d.get("pacient", ""), size=8.5)
# Řádek 2: Č. pojištěnce | Základní diagnóza
y2 = yt(PAC_TOP + 2 * PAC_ROW)
hline(c, FX1, y2, PAC_RIGHT, w=0.6)
vline(c, PX1, yt(PAC_TOP + PAC_ROW), y2, w=0.5)
txt(c, IL, y2 + 5, "Č. pojištěnce", size=7)
digit_box(c, IL + 68, y2 + 3, 130, 13, 10, d.get("cislo_pojistence", ""))
txt(c, PX1 + 4, y2 + 5, "Základní diagnóza", size=7)
digit_box(c, PX1 + 86, y2 + 3, 65, 13, 5, d.get("zkladni_dg", ""))
# Řádek 3: Variabilní symbol | Ost. dg. | Kód náhrady
y3 = yt(PAC_TOP + 3 * PAC_ROW)
hline(c, FX1, y3, PAC_RIGHT, w=0.6)
vline(c, PX2, yt(PAC_TOP + 2 * PAC_ROW), y3, w=0.5)
vline(c, PX3, yt(PAC_TOP + 2 * PAC_ROW), y3, w=0.5)
txt(c, IL, y3 + 5, "Variabilní symbol", size=7)
digit_box(c, IL + 83, y3 + 3, 100, 13, 8, d.get("variabilni_symbol", ""))
txt(c, PX2 + 4, y3 + 5, "Ost. dg.", size=7)
digit_box(c, PX2 + 38, y3 + 3, 60, 13, 5, d.get("ost_dg", ""))
txt(c, PX3 + 4, y3 + 5, "Kód náhrady", size=7)
digit_box(c, PX3 + 54, y3 + 3, 50, 13, 4, d.get("kod_nahrady", ""))
# Řádek 4: Ad zařízení domácí péče + razítko
y4 = yt(PAC_BOT)
hline(c, FX1, y4, PAC_RIGHT, w=0.6)
txt(c, IL, y4 + 5, "Ad zařízení domácí péče:", size=7, bold=True)
# razítko a podpis (pravý sloupec, malé kurzívní)
c.setFont(FONT, 6)
c.drawRightString(FX2 - 4, y4 + 4, "razítko a podpis požadujícího")
hline(c, PAC_RIGHT + 5, y4 + 2, FX2 - 4, w=0.4)
# ═══════════════════════════════════════════════════════════════════════════════
# SEKCE 4: TEXTOVÁ POLE
# ═══════════════════════════════════════════════════════════════════════════════
def draw_textova_pole(c, d) -> float:
"""Vrací cur_top (od shora) po posledním poli."""
top = PAC_BOT + 6 # 177 od shora
LBL = 7.5 # velikost fontu popisků
VAL = 8.0 # velikost fontu hodnot
GAP = 6 # mezera mezi sekcemi
def label_and_value(label_text, value_text, label_width, max_chars=70):
nonlocal top
top += GAP
y_rl = yt(top + 9)
txt(c, IL, y_rl, label_text, size=LBL)
txt(c, IL + label_width, y_rl, value_text[:max_chars] if value_text else "", size=VAL)
dotline(c, IL + label_width, y_rl - 2)
top += 16
def extra_dotline(value_text="", indent=0):
nonlocal top
top += 13
y_rl = yt(top)
dotline(c, IL + indent, y_rl)
if value_text:
txt(c, IL + indent, y_rl + 3, value_text[:80], size=VAL)
def checkbox_row(label_text, field_name, label_width=None):
nonlocal top
top += GAP
y_rl = yt(top + 9)
txt(c, IL, y_rl, label_text, size=LBL)
lw = label_width or (len(label_text) * 4.2)
cx = IL + lw + 5
val = d.get(field_name, "ne").lower()
chkbox(c, cx, y_rl, checked=(val == "ano"), size=7)
txt(c, cx + 9, y_rl, "ano", size=LBL)
chkbox(c, cx + 34, y_rl, checked=(val == "ne"), size=7)
txt(c, cx + 43, y_rl, "ne", size=LBL)
top += 16
# ── Adresa pacienta ───────────────────────────────────────────────────────
adresa_lines = wrap(d.get("adresa_pacienta", ""), 75)
label_and_value("Adresa pacienta (místo poskytování DP) a telefon:", adresa_lines[0], 260)
extra_dotline(adresa_lines[1] if len(adresa_lines) > 1 else "")
# ── Příslušníci domácnosti ────────────────────────────────────────────────
checkbox_row("Další příslušníci domácnosti na této adrese:", "dalsi_prislusnici", label_width=242)
# ── Kontaktní osoba ───────────────────────────────────────────────────────
kont_lines = wrap(d.get("kontaktni_osoba", ""), 55)
label_and_value(
"Kontaktní osoba pro DP (jméno, vztah k pacientovi, adresa a telefon je-li rozdílná od adresy pacienta):",
kont_lines[0], 432, max_chars=55
)
extra_dotline(kont_lines[1] if len(kont_lines) > 1 else "")
# ── Pečovatelská služba ───────────────────────────────────────────────────
checkbox_row("Pacient v péči pečovatelské služby:", "pecovatelska_sluzba", label_width=186)
# ── Mobilita ──────────────────────────────────────────────────────────────
top += GAP
y_mob = yt(top + 9)
txt(c, IL, y_mob, "Mobilita pacienta:", size=LBL)
mob = d.get("mobilita", "a").lower()
chkbox(c, IL + 98, y_mob, checked=(mob == "a"), size=7)
txt(c, IL + 107, y_mob, "a) plná", size=LBL)
top += 16
top += 2
y_mob2 = yt(top + 9)
chkbox(c, IL + 98, y_mob2, checked=(mob == "b"), size=7)
txt(c, IL + 107, y_mob2, "b) omezená:", size=LBL)
mob_detail = d.get("mobilita_detail", "") if mob == "b" else ""
txt(c, IL + 167, y_mob2, mob_detail[:60], size=VAL)
dotline(c, IL + 163, y_mob2 - 2)
top += 16
# ── Smyslové omezení ──────────────────────────────────────────────────────
label_and_value("Smyslové omezení:", d.get("smyslove_omezeni", ""), 95)
# ── Sebeobsluha ───────────────────────────────────────────────────────────
top += GAP
y_sebe = yt(top + 9)
txt(c, IL, y_sebe, "Schopnost základní sebeobsluhy, včetně dodržování léčebného režimu:", size=LBL)
sebe = d.get("sebeobsluha", "a").lower()
chkbox(c, IL + 374, y_sebe, checked=(sebe == "a"), size=7)
txt(c, IL + 383, y_sebe, "a) plná", size=LBL)
top += 16
top += 2
y_sebe2 = yt(top + 9)
chkbox(c, IL + 98, y_sebe2, checked=(sebe == "b"), size=7)
txt(c, IL + 107, y_sebe2, "b) omezená:", size=LBL)
sebe_detail = d.get("sebeobsluha_detail", "") if sebe == "b" else ""
txt(c, IL + 167, y_sebe2, sebe_detail[:60], size=VAL)
dotline(c, IL + 163, y_sebe2 - 2)
top += 16
# ── Medikace ──────────────────────────────────────────────────────────────
med = d.get("medikace", [])
if isinstance(med, str):
med = [med]
med_lines = []
for item in med:
med_lines.extend(wrap(item, 80))
top += GAP
y_med = yt(top + 9)
txt(c, IL, y_med, "Významné údaje o současné medikaci, včetně aplikace inzulínu a diety:", size=LBL)
txt(c, IL + 357, y_med, med_lines[0][:35] if med_lines else "", size=VAL)
dotline(c, IL + 353, y_med - 2)
top += 16
extra_dotline(med_lines[1] if len(med_lines) > 1 else "")
extra_dotline(med_lines[2] if len(med_lines) > 2 else "")
top += 3
# ── Další informace ───────────────────────────────────────────────────────
info = d.get("dalsi_informace", [])
if isinstance(info, str):
info = [info]
info_lines = []
for item in info:
info_lines.extend(wrap(item, 80))
top += GAP
y_info = yt(top + 9)
txt(c, IL, y_info, "Další informace (alergie, kontinence, údaje o bydlišti atd.):", size=LBL)
txt(c, IL + 315, y_info, info_lines[0][:40] if info_lines else "", size=VAL)
dotline(c, IL + 311, y_info - 2)
top += 16
extra_dotline(info_lines[1] if len(info_lines) > 1 else "")
extra_dotline(info_lines[2] if len(info_lines) > 2 else "")
top += 3
# ── Cíl DP ────────────────────────────────────────────────────────────────
cil = d.get("cil_dp", [])
if isinstance(cil, str):
cil = [cil]
cil_lines = []
for item in cil:
cil_lines.extend(wrap(item, 80))
top += GAP
y_cil = yt(top + 9)
txt(c, IL, y_cil, "Cíl předepsané DP, kterého má být dosaženo:", size=LBL)
txt(c, IL + 236, y_cil, cil_lines[0][:50] if cil_lines else "", size=VAL)
dotline(c, IL + 232, y_cil - 2)
top += 16
extra_dotline(cil_lines[1] if len(cil_lines) > 1 else "")
top += 5
return top
# ═══════════════════════════════════════════════════════════════════════════════
# SEKCE 5: POŽADOVÁNO
# ═══════════════════════════════════════════════════════════════════════════════
def draw_pozadovano(c, d, start_top: float):
top = start_top + 5
# Nadpis
top += 8
y_hdr = yt(top + 9)
txt(c, IL, y_hdr, "Požadováno:", size=8, bold=True)
txt(c, IL + 68, y_hdr,
"(Pro úhradu požadované péče zdravotní pojišťovnou je nezbytná jednoznačná specifikace požadavku,",
size=6.5)
top += 12
txt(c, IL + 68, yt(top + 5),
"včetně počtu v jednom dni a frekvence v týdnu)", size=6.5)
top += 10
# Výkony (5 řádků)
KOD_X = IL # x-start kódového rámečku
KOD_W = 108 # šířka kódového rámečku
KOD_H = 22 # výška kódového rámečku
TXT_X = KOD_X + KOD_W + 8 # x-start textové části
ROW_H = 42 # výška jednoho výkonu (rámeček + 2 řádky textu)
for vykon in (d.get("pozadovano", []) + [{}, {}, {}, {}, {}])[:5]:
y_top_row = yt(top)
# Kódový rámeček
c.setLineWidth(0.9)
c.setDash([])
c.rect(KOD_X, y_top_row - KOD_H - 4, KOD_W, KOD_H, stroke=1, fill=0)
# Hodnota kódu
kod_val = vykon.get("kod", "")
if kod_val:
txt(c, KOD_X + 5, y_top_row - KOD_H + 2, kod_val, size=9, bold=True)
# Textový řádek 1
txt(c, TXT_X, y_top_row - 10, vykon.get("popis", "")[:75], size=8)
dotline(c, TXT_X, y_top_row - 13, IR)
# Textový řádek 2 (pokračování)
txt(c, TXT_X, y_top_row - 25, vykon.get("popis2", "")[:75], size=8)
dotline(c, TXT_X, y_top_row - 28, IR)
top += ROW_H
# ═══════════════════════════════════════════════════════════════════════════════
# HLAVNÍ FUNKCE
# ═══════════════════════════════════════════════════════════════════════════════
def generuj_poukaz(data: dict, vystup: Path):
c = canvas.Canvas(str(vystup), pagesize=A4)
# Vnější zaoblený rámeček
c.setLineWidth(1.8)
c.roundRect(FX1, FB, FX2 - FX1, FT - FB, 6, stroke=1, fill=0)
draw_hlavicka(c, data)
draw_nadpis(c)
draw_pacient(c, data)
cur_top = draw_textova_pole(c, data)
draw_pozadovano(c, data, cur_top)
# Zápatí
txt(c, FX1 + 2, FB - 12, "VZP-06dp/2024", size=6)
c.save()
print(f"✓ Uloženo: {vystup}")
# ── Spuštění ──────────────────────────────────────────────────────────────────
if __name__ == "__main__":
folder = Path(__file__).parent
# Najde nejvyšší existující číslo verze a přidá 1
existing = [f for f in folder.glob("Poukaz DP vyplneny_v*.pdf")]
nums = []
for f in existing:
try:
nums.append(int(f.stem.rsplit("_v", 1)[1]))
except (ValueError, IndexError):
pass
next_v = max(nums, default=2) + 1
out = folder / f"Poukaz DP vyplneny_v{next_v}.pdf"
generuj_poukaz(DATA, out)
1
existing = [f for f in folder.glob("Poukaz DP vyplneny_v*.pdf")]
nums = []
for f in existing:
try:
nums.append(int(f.stem.rsplit("_v", 1)[1]))
except (ValueError, IndexError):
pass
next_v = max(nums, default=2) + 1
out = folder / f"Poukaz DP vyplneny_v{next_v}.pdf"
generuj_poukaz(DATA, out)
File diff suppressed because one or more lines are too long