Přidán sync_agenda_to_mysql.py — historický a inkrementální sync agendy do MySQL

- Nový skript pro ukládání Medevio rezervací do tabulky medevio_agenda
- Režim FULL: dávkový načet od 2025-01-01 po měsících
- Režim INCREMENTAL: jeden dotaz, -7 dní až +10 let dopředu
- Tabulka medevio_agenda se vytvoří automaticky (CREATE TABLE IF NOT EXISTS)
- UPSERT logika — bezpečné opakované spouštění

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-13 13:27:15 +02:00
parent f2b3ebe0b4
commit 46b7227b18
@@ -0,0 +1,227 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Sync Medevio agenda reservations to MySQL table medevio_agenda.
Modes (set MODE below):
FULL Load all reservations from 2025-01-01 to today (monthly batches)
INCREMENTAL Last 7 days to +30 days ahead
"""
import sys
try:
sys.stdout.reconfigure(encoding="utf-8")
sys.stderr.reconfigure(encoding="utf-8")
except AttributeError:
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8")
import json
import time
import pymysql
import requests
from pathlib import Path
from datetime import datetime, date
from dateutil import parser as dtparser, tz
from dateutil.relativedelta import relativedelta
# ==================== CONFIG ====================
GRAPHQL_URL = "https://api.medevio.cz/graphql"
CALENDAR_ID = "144c4e12-347c-49ca-9ec0-8ca965a4470d"
CLINIC_SLUG = "mudr-buzalkova"
TOKEN_PATH = Path(__file__).resolve().parent.parent / "token.txt"
DB_CONFIG = {
"host": "192.168.1.76",
"port": 3306,
"user": "root",
"password": "Vlado9674+",
"database": "medevio",
"charset": "utf8mb4",
"cursorclass": pymysql.cursors.DictCursor,
}
PRAGUE_TZ = tz.gettz("Europe/Prague")
FULL_SINCE = date(2025, 1, 1)
# ==================== MODE ====================
MODE = "FULL" # "FULL" nebo "INCREMENTAL"
# ==================== TABLE ====================
CREATE_TABLE_SQL = """
CREATE TABLE IF NOT EXISTS medevio_agenda (
reservation_id VARCHAR(36) NOT NULL PRIMARY KEY,
start_dt DATETIME,
end_dt DATETIME,
date DATE,
time_interval VARCHAR(20),
note TEXT,
done TINYINT(1),
color VARCHAR(50),
request_id VARCHAR(36),
request_title VARCHAR(500),
patient_name VARCHAR(200),
patient_surname VARCHAR(200),
patient_dob VARCHAR(50),
insurance_short VARCHAR(50),
synced_at DATETIME NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
"""
# ==================== UPSERT ====================
UPSERT_SQL = """
INSERT INTO medevio_agenda (
reservation_id, start_dt, end_dt, date, time_interval,
note, done, color,
request_id, request_title,
patient_name, patient_surname, patient_dob, insurance_short,
synced_at
) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
ON DUPLICATE KEY UPDATE
start_dt = VALUES(start_dt),
end_dt = VALUES(end_dt),
date = VALUES(date),
time_interval = VALUES(time_interval),
note = VALUES(note),
done = VALUES(done),
color = VALUES(color),
request_id = VALUES(request_id),
request_title = VALUES(request_title),
patient_name = VALUES(patient_name),
patient_surname = VALUES(patient_surname),
patient_dob = VALUES(patient_dob),
insurance_short = VALUES(insurance_short),
synced_at = VALUES(synced_at)
"""
def parse_dt(iso_str):
"""Parse ISO datetime, convert to Europe/Prague, return naive datetime for MySQL."""
if not iso_str:
return None
try:
return dtparser.isoparse(iso_str).astimezone(PRAGUE_TZ).replace(tzinfo=None)
except Exception:
return None
def fetch_reservations(headers, since: datetime, until: datetime):
payload = {
"operationName": "ClinicAgenda_ListClinicReservations",
"variables": {
"calendarIds": [CALENDAR_ID],
"clinicSlug": CLINIC_SLUG,
"since": since.strftime("%Y-%m-%dT%H:%M:%S") + "Z",
"until": until.strftime("%Y-%m-%dT%H:%M:%S") + "Z",
"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 }
}
}
}
}""",
}
response = requests.post(GRAPHQL_URL, headers=headers, json=payload, timeout=30)
response.raise_for_status()
resp = response.json()
if "errors" in resp or "data" not in resp:
print(f"❌ API error: {resp}")
return []
return resp["data"]["reservations"] or []
def upsert_reservations(conn, reservations):
now = datetime.now().replace(microsecond=0)
rows = []
for r in reservations:
req = r.get("request") or {}
patient = req.get("extendedPatient") or {}
insurance = patient.get("insuranceCompanyObject") or {}
start_dt = parse_dt(r.get("start"))
end_dt = parse_dt(r.get("end"))
rows.append((
r["id"],
start_dt,
end_dt,
start_dt.date() if start_dt else None,
f"{start_dt.strftime('%H:%M')}-{end_dt.strftime('%H:%M')}" if start_dt and end_dt else None,
r.get("note") or None,
1 if r.get("done") else 0,
r.get("color") or None,
req.get("id") or None,
req.get("displayTitle") or None,
patient.get("name") or None,
patient.get("surname") or None,
patient.get("dob") or None,
insurance.get("shortName") or None,
now,
))
with conn.cursor() as cur:
cur.executemany(UPSERT_SQL, rows)
conn.commit()
return len(rows)
def main():
token = TOKEN_PATH.read_text(encoding="utf-8").strip()
if token.startswith("Bearer "):
token = token.split(" ", 1)[1]
headers = {
"content-type": "application/json",
"authorization": f"Bearer {token}",
"origin": "https://my.medevio.cz",
"referer": "https://my.medevio.cz/",
}
conn = pymysql.connect(**DB_CONFIG)
with conn.cursor() as cur:
cur.execute(CREATE_TABLE_SQL)
conn.commit()
print("✅ Tabulka medevio_agenda připravena.")
today = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0)
if MODE == "FULL":
batch_start = datetime(FULL_SINCE.year, FULL_SINCE.month, FULL_SINCE.day)
print(f"📥 Režim FULL: {FULL_SINCE}{today.date()}")
total = 0
while batch_start < today:
batch_end = min(batch_start + relativedelta(months=1), today)
reservations = fetch_reservations(headers, batch_start, batch_end)
count = upsert_reservations(conn, reservations)
total += count
print(f" {batch_start.date()}{batch_end.date()}: {count} rezervací")
batch_start = batch_end
time.sleep(0.5)
print(f"\n✅ FULL hotovo — celkem {total} rezervací uloženo.")
else:
since = today - relativedelta(days=7)
until = today + relativedelta(years=10)
print(f"🔄 Režim INCREMENTAL: {since.date()}{until.date()}")
reservations = fetch_reservations(headers, since, until)
count = upsert_reservations(conn, reservations)
print(f"✅ INCREMENTAL hotovo — {count} rezervací upsertováno.")
conn.close()
if __name__ == "__main__":
main()