240 lines
5.9 KiB
Python
240 lines
5.9 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
|
||
import pymysql
|
||
import requests
|
||
from pathlib import Path
|
||
from datetime import datetime
|
||
from dateutil import parser
|
||
import time
|
||
import sys
|
||
|
||
# ================================
|
||
# UTF-8 SAFE OUTPUT (Windows friendly)
|
||
# ================================
|
||
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')
|
||
|
||
|
||
def safe_print(text: str):
|
||
enc = sys.stdout.encoding or ""
|
||
if not enc.lower().startswith("utf"):
|
||
text = ''.join(ch for ch in text if ord(ch) < 65536)
|
||
try:
|
||
print(text)
|
||
except UnicodeEncodeError:
|
||
text = ''.join(ch for ch in text if ord(ch) < 128)
|
||
print(text)
|
||
|
||
|
||
# ================================
|
||
# 🔧 CONFIG
|
||
# ================================
|
||
TOKEN_PATH = Path("token.txt")
|
||
CLINIC_SLUG = "mudr-buzalkova"
|
||
|
||
BATCH_SIZE = 500
|
||
STATES = ["ACTIVE", "DONE"] # explicitně – jinak API vrací jen ACTIVE
|
||
|
||
DB_CONFIG = {
|
||
"host": "192.168.1.50",
|
||
"port": 3306,
|
||
"user": "root",
|
||
"password": "Vlado9674+",
|
||
"database": "medevio",
|
||
"charset": "utf8mb4",
|
||
"cursorclass": pymysql.cursors.DictCursor,
|
||
}
|
||
|
||
GRAPHQL_QUERY = r"""
|
||
query ClinicRequestList2(
|
||
$clinicSlug: String!,
|
||
$queueId: String,
|
||
$queueAssignment: QueueAssignmentFilter!,
|
||
$state: PatientRequestState,
|
||
$pageInfo: PageInfo!,
|
||
$locale: Locale!
|
||
) {
|
||
requestsResponse: listPatientRequestsForClinic2(
|
||
clinicSlug: $clinicSlug,
|
||
queueId: $queueId,
|
||
queueAssignment: $queueAssignment,
|
||
state: $state,
|
||
pageInfo: $pageInfo
|
||
) {
|
||
count
|
||
patientRequests {
|
||
id
|
||
displayTitle(locale: $locale)
|
||
createdAt
|
||
updatedAt
|
||
doneAt
|
||
removedAt
|
||
extendedPatient {
|
||
name
|
||
surname
|
||
identificationNumber
|
||
}
|
||
lastMessage {
|
||
createdAt
|
||
}
|
||
}
|
||
}
|
||
}
|
||
"""
|
||
|
||
|
||
# ================================
|
||
# TOKEN
|
||
# ================================
|
||
def read_token(path: Path) -> str:
|
||
tok = path.read_text(encoding="utf-8").strip()
|
||
if tok.startswith("Bearer "):
|
||
return tok.split(" ", 1)[1]
|
||
return tok
|
||
|
||
|
||
# ================================
|
||
# DATETIME PARSER
|
||
# ================================
|
||
def to_mysql_dt(iso_str):
|
||
if not iso_str:
|
||
return None
|
||
try:
|
||
dt = parser.isoparse(iso_str)
|
||
if dt.tzinfo is None:
|
||
dt = dt.replace(tzinfo=datetime.now().astimezone().tzinfo)
|
||
return dt.astimezone().strftime("%Y-%m-%d %H:%M:%S")
|
||
except Exception:
|
||
return None
|
||
|
||
|
||
# ================================
|
||
# UPSERT
|
||
# ================================
|
||
def upsert(conn, r):
|
||
p = r.get("extendedPatient") or {}
|
||
|
||
api_updated = to_mysql_dt(r.get("updatedAt"))
|
||
msg_updated = to_mysql_dt((r.get("lastMessage") or {}).get("createdAt"))
|
||
|
||
final_updated = max(filter(None, [api_updated, msg_updated]), default=None)
|
||
|
||
sql = """
|
||
INSERT INTO pozadavky (
|
||
id, displayTitle, createdAt, updatedAt, doneAt, removedAt,
|
||
pacient_jmeno, pacient_prijmeni, pacient_rodnecislo
|
||
) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s)
|
||
ON DUPLICATE KEY UPDATE
|
||
displayTitle=VALUES(displayTitle),
|
||
updatedAt=VALUES(updatedAt),
|
||
doneAt=VALUES(doneAt),
|
||
removedAt=VALUES(removedAt),
|
||
pacient_jmeno=VALUES(pacient_jmeno),
|
||
pacient_prijmeni=VALUES(pacient_prijmeni),
|
||
pacient_rodnecislo=VALUES(pacient_rodnecislo)
|
||
"""
|
||
|
||
vals = (
|
||
r.get("id"),
|
||
r.get("displayTitle"),
|
||
to_mysql_dt(r.get("createdAt")),
|
||
final_updated,
|
||
to_mysql_dt(r.get("doneAt")),
|
||
to_mysql_dt(r.get("removedAt")),
|
||
p.get("name"),
|
||
p.get("surname"),
|
||
p.get("identificationNumber"),
|
||
)
|
||
|
||
with conn.cursor() as cur:
|
||
cur.execute(sql, vals)
|
||
conn.commit()
|
||
|
||
|
||
# ================================
|
||
# FETCH PAGE (per state)
|
||
# ================================
|
||
def fetch_state(headers, state, offset):
|
||
variables = {
|
||
"clinicSlug": CLINIC_SLUG,
|
||
"queueId": None,
|
||
"queueAssignment": "ANY",
|
||
"state": state,
|
||
"pageInfo": {"first": BATCH_SIZE, "offset": offset},
|
||
"locale": "cs",
|
||
}
|
||
|
||
payload = {
|
||
"operationName": "ClinicRequestList2",
|
||
"query": GRAPHQL_QUERY,
|
||
"variables": variables,
|
||
}
|
||
|
||
r = requests.post("https://api.medevio.cz/graphql", json=payload, headers=headers)
|
||
r.raise_for_status()
|
||
|
||
data = r.json()["data"]["requestsResponse"]
|
||
return data.get("patientRequests", []), data.get("count", 0)
|
||
|
||
|
||
# ================================
|
||
# MAIN
|
||
# ================================
|
||
def main():
|
||
token = read_token(TOKEN_PATH)
|
||
headers = {
|
||
"Authorization": f"Bearer {token}",
|
||
"Content-Type": "application/json",
|
||
"Accept": "application/json",
|
||
}
|
||
|
||
conn = pymysql.connect(**DB_CONFIG)
|
||
|
||
safe_print(f"\n=== FULL Medevio READ-ALL sync @ {datetime.now():%Y-%m-%d %H:%M:%S} ===")
|
||
|
||
grand_total = 0
|
||
|
||
for state in STATES:
|
||
safe_print(f"\n🔁 STATE = {state}")
|
||
offset = 0
|
||
total = None
|
||
processed = 0
|
||
|
||
while True:
|
||
batch, count = fetch_state(headers, state, offset)
|
||
|
||
if total is None:
|
||
total = count
|
||
safe_print(f"📡 {state}: celkem {total}")
|
||
|
||
if not batch:
|
||
break
|
||
|
||
for r in batch:
|
||
upsert(conn, r)
|
||
|
||
processed += len(batch)
|
||
safe_print(f" • {processed}/{total}")
|
||
|
||
offset += BATCH_SIZE
|
||
if offset >= count:
|
||
break
|
||
|
||
time.sleep(0.4)
|
||
|
||
grand_total += processed
|
||
|
||
conn.close()
|
||
safe_print(f"\n✅ HOTOVO – celkem zpracováno {grand_total} požadavků\n")
|
||
|
||
|
||
# ================================
|
||
if __name__ == "__main__":
|
||
main()
|