From a9ef60212d13b153f09ea734e4134e53e2e2b979 Mon Sep 17 00:00:00 2001 From: Vladimir Buzalka Date: Sun, 31 May 2026 07:51:29 +0200 Subject: [PATCH] notebookvb --- Medevio/agenda_dne.py | 385 ++++++++++++ .../agenda_2026-05-29_20260530_082836.json | 178 ++++++ .../agenda_2026-06-03_20260530_082810.json | 268 ++++++++ .../agenda_20260530_20260530_162529.json | 63 ++ .../agenda_20260530_20260530_163059.json | 295 +++++++++ .../agenda_20260530_20260530_164551.json | 232 +++++++ .../agenda_20260531_20260531_162640.json | 17 + .../agenda_20260531_20260531_163051.json | 247 ++++++++ .../agenda_20260603_20260603_162516.json | 278 ++++++++ .../agenda_20260603_20260603_164558.json | 278 ++++++++ Medevio/debug/poznamka_20260530_084008.json | 20 + Medevio/debug/poznamka_20260530_155737.json | 20 + Medevio/smaz_poznamku.py | 176 ++++++ Medevio/zapis_poznamky.py | 202 ++++++ Recepty/fill_poukaz_dp.py | 235 +++++++ Recepty/poukaz_dp_gen.py | 595 ++++++++++++++++++ Recepty/souradnice_picker.html | 101 +++ 17 files changed, 3590 insertions(+) create mode 100644 Medevio/agenda_dne.py create mode 100644 Medevio/debug/agenda_2026-05-29_20260530_082836.json create mode 100644 Medevio/debug/agenda_2026-06-03_20260530_082810.json create mode 100644 Medevio/debug/agenda_20260530_20260530_162529.json create mode 100644 Medevio/debug/agenda_20260530_20260530_163059.json create mode 100644 Medevio/debug/agenda_20260530_20260530_164551.json create mode 100644 Medevio/debug/agenda_20260531_20260531_162640.json create mode 100644 Medevio/debug/agenda_20260531_20260531_163051.json create mode 100644 Medevio/debug/agenda_20260603_20260603_162516.json create mode 100644 Medevio/debug/agenda_20260603_20260603_164558.json create mode 100644 Medevio/debug/poznamka_20260530_084008.json create mode 100644 Medevio/debug/poznamka_20260530_155737.json create mode 100644 Medevio/smaz_poznamku.py create mode 100644 Medevio/zapis_poznamky.py create mode 100644 Recepty/fill_poukaz_dp.py create mode 100644 Recepty/poukaz_dp_gen.py create mode 100644 Recepty/souradnice_picker.html diff --git a/Medevio/agenda_dne.py b/Medevio/agenda_dne.py new file mode 100644 index 0000000..42663b1 --- /dev/null +++ b/Medevio/agenda_dne.py @@ -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() diff --git a/Medevio/debug/agenda_2026-05-29_20260530_082836.json b/Medevio/debug/agenda_2026-05-29_20260530_082836.json new file mode 100644 index 0000000..1746632 --- /dev/null +++ b/Medevio/debug/agenda_2026-05-29_20260530_082836.json @@ -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" + } + } + } + } + ] + } + } +} \ No newline at end of file diff --git a/Medevio/debug/agenda_2026-06-03_20260530_082810.json b/Medevio/debug/agenda_2026-06-03_20260530_082810.json new file mode 100644 index 0000000..2e47da5 --- /dev/null +++ b/Medevio/debug/agenda_2026-06-03_20260530_082810.json @@ -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" + } + } + } + } + ] + } + } +} \ No newline at end of file diff --git a/Medevio/debug/agenda_20260530_20260530_162529.json b/Medevio/debug/agenda_20260530_20260530_162529.json new file mode 100644 index 0000000..a8a0598 --- /dev/null +++ b/Medevio/debug/agenda_20260530_20260530_162529.json @@ -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 + } + ] + } + } +} \ No newline at end of file diff --git a/Medevio/debug/agenda_20260530_20260530_163059.json b/Medevio/debug/agenda_20260530_20260530_163059.json new file mode 100644 index 0000000..c415fce --- /dev/null +++ b/Medevio/debug/agenda_20260530_20260530_163059.json @@ -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" + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/Medevio/debug/agenda_20260530_20260530_164551.json b/Medevio/debug/agenda_20260530_20260530_164551.json new file mode 100644 index 0000000..6be1423 --- /dev/null +++ b/Medevio/debug/agenda_20260530_20260530_164551.json @@ -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" + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/Medevio/debug/agenda_20260531_20260531_162640.json b/Medevio/debug/agenda_20260531_20260531_162640.json new file mode 100644 index 0000000..6c7443b --- /dev/null +++ b/Medevio/debug/agenda_20260531_20260531_162640.json @@ -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": [] + } + } +} \ No newline at end of file diff --git a/Medevio/debug/agenda_20260531_20260531_163051.json b/Medevio/debug/agenda_20260531_20260531_163051.json new file mode 100644 index 0000000..07bb5d9 --- /dev/null +++ b/Medevio/debug/agenda_20260531_20260531_163051.json @@ -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" + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/Medevio/debug/agenda_20260603_20260603_162516.json b/Medevio/debug/agenda_20260603_20260603_162516.json new file mode 100644 index 0000000..946037c --- /dev/null +++ b/Medevio/debug/agenda_20260603_20260603_162516.json @@ -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" + } + } + } + } + ] + } + } +} \ No newline at end of file diff --git a/Medevio/debug/agenda_20260603_20260603_164558.json b/Medevio/debug/agenda_20260603_20260603_164558.json new file mode 100644 index 0000000..8e0ca56 --- /dev/null +++ b/Medevio/debug/agenda_20260603_20260603_164558.json @@ -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": [] + } + } +} \ No newline at end of file diff --git a/Medevio/debug/poznamka_20260530_084008.json b/Medevio/debug/poznamka_20260530_084008.json new file mode 100644 index 0000000..28e0c59 --- /dev/null +++ b/Medevio/debug/poznamka_20260530_084008.json @@ -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" + } + } + } +} \ No newline at end of file diff --git a/Medevio/debug/poznamka_20260530_155737.json b/Medevio/debug/poznamka_20260530_155737.json new file mode 100644 index 0000000..3d53bcd --- /dev/null +++ b/Medevio/debug/poznamka_20260530_155737.json @@ -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" + } + } + } +} \ No newline at end of file diff --git a/Medevio/smaz_poznamku.py b/Medevio/smaz_poznamku.py new file mode 100644 index 0000000..12157ea --- /dev/null +++ b/Medevio/smaz_poznamku.py @@ -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("") + smaz_opakujici("", date="2026-05-30T14:40:00.000Z") # default ThisAndFuture + smaz_opakujici("", date="...Z", update_type="All") + smaz_opakujici("", date="...Z", update_type="Single") + +CLI: + python smaz_poznamku.py --jednorazova + python smaz_poznamku.py --opakujici --datum 2026-05-30T14:40:00.000Z + python smaz_poznamku.py --opakujici --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() diff --git a/Medevio/zapis_poznamky.py b/Medevio/zapis_poznamky.py new file mode 100644 index 0000000..c546835 --- /dev/null +++ b/Medevio/zapis_poznamky.py @@ -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() diff --git a/Recepty/fill_poukaz_dp.py b/Recepty/fill_poukaz_dp.py new file mode 100644 index 0000000..cda530f --- /dev/null +++ b/Recepty/fill_poukaz_dp.py @@ -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) diff --git a/Recepty/poukaz_dp_gen.py b/Recepty/poukaz_dp_gen.py new file mode 100644 index 0000000..a52f282 --- /dev/null +++ b/Recepty/poukaz_dp_gen.py @@ -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) +dá 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) diff --git a/Recepty/souradnice_picker.html b/Recepty/souradnice_picker.html new file mode 100644 index 0000000..d9a84e8 --- /dev/null +++ b/Recepty/souradnice_picker.html @@ -0,0 +1,101 @@ + + + + +Picker souřadnic – Poukaz DP + + + + +
+ formulář +
+
+ +
+

📍 Picker souřadnic

+

Klikni na formulář → zobrazí se PDF souřadnice (body, počátek vlevo dole).
+ Zadej název pole a klikni Uložit.

+ +
x = –, y = –
+ + + + +
+
// zatím žádné body +
+ +
+ + + +