From 991be3f3e49c77e35fe4790add3e14f26148c13f Mon Sep 17 00:00:00 2001 From: Vladimir Buzalka Date: Tue, 4 Nov 2025 17:49:13 +0100 Subject: [PATCH] notebook --- 86 Agenda měsíc dopředu.py | 23 ++- 861 improvement formatting.py | 299 ++++++++++++++++++++++++++++++++++ 2 files changed, 318 insertions(+), 4 deletions(-) create mode 100644 861 improvement formatting.py diff --git a/86 Agenda měsíc dopředu.py b/86 Agenda měsíc dopředu.py index 103a3eb..94f8ebd 100644 --- a/86 Agenda měsíc dopředu.py +++ b/86 Agenda měsíc dopředu.py @@ -178,12 +178,27 @@ df.to_excel(xlsx_path, index=False) wb = load_workbook(xlsx_path) ws = wb.active -# style header -for col in range(1, len(df.columns) + 1): - c = ws.cell(row=1, column=col) +# === Apply styling and custom column widths === +widths = { + 1: 11, # A - Date + 2: 13, # B - Time + 3: 45, # C - Title + 4: 30, # D - Patient + 5: 15, # E - DOB + 6: 15, # F - Insurance + 7: 30, # G - Note + 8: 15, # H - Color + 9: 37, # I - Request_ID + 10: 37 # J - Reservation_ID +} + +for col_idx in range(1, len(df.columns) + 1): + col_letter = get_column_letter(col_idx) + c = ws.cell(row=1, column=col_idx) c.font = Font(bold=True) c.alignment = Alignment(horizontal="center") - ws.column_dimensions[get_column_letter(col)].width = 20 + ws.column_dimensions[col_letter].width = widths.get(col_idx, 20) + ws.freeze_panes = "A2" wb.save(xlsx_path) diff --git a/861 improvement formatting.py b/861 improvement formatting.py new file mode 100644 index 0000000..8f166e3 --- /dev/null +++ b/861 improvement formatting.py @@ -0,0 +1,299 @@ +#!/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"(?