#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Query Medevio for the full agenda of 17 Oct 2025, print raw API response, and export to Excel. """ import re import json import time from pathlib import Path import requests import pandas as pd from openpyxl import load_workbook from openpyxl.styles import Font, Alignment, PatternFill, Border, Side from openpyxl.utils import get_column_letter from datetime import datetime, timedelta from dateutil.relativedelta import relativedelta from Functions import get_reports_folder from openpyxl.utils.dataframe import dataframe_to_rows GRAPHQL_URL = "https://api.medevio.cz/graphql" CALENDAR_ID = "144c4e12-347c-49ca-9ec0-8ca965a4470d" CLINIC_SLUG = "mudr-buzalkova" # ==================== Load Token ==================== def load_gateway_token(storage_path="medevio_storage.json"): """Return Medevio gateway-access-token from saved Playwright storage.""" path = Path(storage_path) if not path.exists(): raise SystemExit(f"❌ Storage file not found: {path}") with path.open("r", encoding="utf-8") as f: state = json.load(f) token = next( (c["value"] for c in state["cookies"] if c["name"] == "gateway-access-token"), None ) if not token: raise SystemExit("❌ gateway-access-token not found in storage file.") return token gateway_token = load_gateway_token() headers = { "content-type": "application/json", "origin": "https://my.medevio.cz", "referer": "https://my.medevio.cz/", "authorization": f"Bearer {gateway_token}", } # === Dynamic date range === dnes = datetime.utcnow().date() since = datetime.combine(dnes, datetime.min.time()).replace(microsecond=1) until = since + relativedelta(months=1) - timedelta(milliseconds=1) since_iso = since.isoformat() + "Z" until_iso = until.isoformat() + "Z" payload = { "operationName": "ClinicAgenda_ListClinicReservations", "variables": { "calendarIds": [CALENDAR_ID], "clinicSlug": CLINIC_SLUG, "since": since_iso, "until": "2025-11-30T21:59:59.999Z", "locale": "cs", "emptyCalendarIds": False, }, "query": """query ClinicAgenda_ListClinicReservations( $calendarIds: [UUID!], $clinicSlug: String!, $locale: Locale!, $since: DateTime!, $until: DateTime!, $emptyCalendarIds: Boolean! ) { reservations: listClinicReservations( clinicSlug: $clinicSlug, calendarIds: $calendarIds, since: $since, until: $until ) @skip(if: $emptyCalendarIds) { id start end note done color request { id displayTitle(locale: $locale) extendedPatient { name surname dob insuranceCompanyObject { shortName } } } } }""", } print("since:", since_iso) print("until:", until_iso) # ==================== Query API ==================== print("📡 Querying Medevio API for agenda...") r = requests.post(GRAPHQL_URL, headers=headers, data=json.dumps(payload)) print("Status:", r.status_code) try: data = r.json() except Exception as e: print("❌ Could not parse JSON:", e) print(r.text) raise SystemExit() if "data" not in data or "reservations" not in data["data"]: raise SystemExit("⚠️ No 'reservations' data found in response.") reservations = data["data"]["reservations"] from dateutil import parser, tz # ===== Process reservations into table ===== rows = [] for r in reservations: req = r.get("request") or {} patient = req.get("extendedPatient") or {} insurance = patient.get("insuranceCompanyObject") or {} try: start_dt = parser.isoparse(r.get("start")).astimezone(tz.gettz("Europe/Prague")) end_dt = parser.isoparse(r.get("end")).astimezone(tz.gettz("Europe/Prague")) except Exception: start_dt = end_dt = None date_str = start_dt.strftime("%Y-%m-%d") if start_dt else "" time_interval = f"{start_dt.strftime('%H:%M')}-{end_dt.strftime('%H:%M')}" if start_dt and end_dt else "" rows.append({ "Date": date_str, "Time": time_interval, "Title": req.get("displayTitle") or "", "Patient": f"{patient.get('surname','')} {patient.get('name','')}".strip(), "DOB": patient.get("dob") or "", "Insurance": insurance.get("shortName") or "", "Note": r.get("note") or "", "Color": r.get("color") or "", "Request_ID": req.get("id") or "", "Reservation_ID": r.get("id"), }) df = pd.DataFrame(rows).sort_values(["Date", "Time"]) def kw_pattern(kw: str) -> str: """ Match the exact phrase kw (case-insensitive), not as part of a '+something' continuation. Examples: 'žloutenka a' ✅ matches '… žloutenka a …' ❌ NOT '… žloutenka a+b …' 'žloutenka a+b' ✅ matches exactly that phrase """ # start boundary: not preceded by a word char # end guard: not followed by optional spaces + '+' + word return rf"(?