203 lines
5.9 KiB
Python
203 lines
5.9 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
Fetch Medevio pending (ACTIVE) patient requests and return a pandas DataFrame.
|
|
Reads Bearer token from token.txt (single line, token only).
|
|
"""
|
|
|
|
import requests
|
|
import pandas as pd
|
|
import time
|
|
from typing import List, Dict, Any
|
|
|
|
# CONFIG ---------------------------------------------------------------------
|
|
TOKEN_FILE = "token.txt" # file with token (no "Bearer " prefix)
|
|
GRAPHQL_URL = "https://app.medevio.cz/graphql"
|
|
CLINIC_SLUG = "mudr-buzalkova" # adjust if needed
|
|
LOCALE = "cs"
|
|
PAGE_SIZE = 50 # how many items to request per page
|
|
REQUEST_WAIT = 0.2 # seconds between requests to be polite
|
|
# ---------------------------------------------------------------------------
|
|
|
|
GRAPHQL_QUERY = r"""
|
|
query ClinicLegacyRequestList_ListPatientRequestsForClinic(
|
|
$clinicSlug: String!,
|
|
$queueId: String,
|
|
$queueAssignment: QueueAssignmentFilter!,
|
|
$state: PatientRequestState,
|
|
$pageInfo: PageInfo!,
|
|
$locale: Locale!
|
|
) {
|
|
requests: listPatientRequestsForClinic(
|
|
clinicSlug: $clinicSlug,
|
|
queueId: $queueId,
|
|
queueAssignment: $queueAssignment,
|
|
state: $state,
|
|
pageInfo: $pageInfo
|
|
) {
|
|
id
|
|
createdAt
|
|
dueDate
|
|
displayTitle(locale: $locale)
|
|
doneAt
|
|
removedAt
|
|
priority
|
|
evaluationResult(locale: $locale) {
|
|
fields {
|
|
name
|
|
value
|
|
}
|
|
}
|
|
clinicId
|
|
extendedPatient {
|
|
id
|
|
identificationNumber
|
|
kind
|
|
name
|
|
note
|
|
owner { name surname }
|
|
key
|
|
status
|
|
surname
|
|
type
|
|
user { id name surname }
|
|
isUnknownPatient
|
|
}
|
|
lastMessage {
|
|
createdAt
|
|
id
|
|
readAt
|
|
sender { id name surname clinicId }
|
|
text
|
|
}
|
|
queue { id name }
|
|
reservations { id canceledAt done start }
|
|
tags(onlyImportant: true) { id name color icon }
|
|
priceWhenCreated
|
|
currencyWhenCreated
|
|
}
|
|
}
|
|
"""
|
|
|
|
def read_token(path: str) -> str:
|
|
with open(path, "r", encoding="utf-8") as f:
|
|
t = f.read().strip()
|
|
if t.startswith("Bearer "):
|
|
t = t.split(" ", 1)[1]
|
|
return t
|
|
|
|
def fetch_requests(token: str,
|
|
clinic_slug: str = CLINIC_SLUG,
|
|
locale: str = LOCALE,
|
|
page_size: int = PAGE_SIZE) -> List[Dict[str, Any]]:
|
|
headers = {
|
|
"Authorization": f"Bearer {token}",
|
|
"Content-Type": "application/json",
|
|
"Accept": "application/json",
|
|
}
|
|
|
|
all_items: List[Dict[str, Any]] = []
|
|
offset = 0
|
|
|
|
while True:
|
|
variables = {
|
|
"clinicSlug": clinic_slug,
|
|
"queueId": None,
|
|
"queueAssignment": "ANY",
|
|
"state": "ACTIVE",
|
|
"pageInfo": {"first": page_size, "offset": offset},
|
|
"locale": locale,
|
|
}
|
|
payload = {"query": GRAPHQL_QUERY, "variables": variables, "operationName": "ClinicLegacyRequestList_ListPatientRequestsForClinic"}
|
|
|
|
r = requests.post(GRAPHQL_URL, json=payload, headers=headers, timeout=30)
|
|
r.raise_for_status()
|
|
js = r.json()
|
|
|
|
# Basic error handling
|
|
if "errors" in js:
|
|
raise RuntimeError(f"GraphQL returned errors: {js['errors']}")
|
|
|
|
items = js.get("data", {}).get("requests", [])
|
|
if not items:
|
|
break
|
|
|
|
all_items.extend(items)
|
|
|
|
# If fewer than requested, we are at the end
|
|
if len(items) < page_size:
|
|
break
|
|
|
|
offset += page_size
|
|
time.sleep(REQUEST_WAIT)
|
|
|
|
return all_items
|
|
|
|
def flatten_item(item: Dict[str, Any]) -> Dict[str, Any]:
|
|
patient = item.get("extendedPatient") or {}
|
|
last_msg = item.get("lastMessage") or {}
|
|
queue = item.get("queue") or {}
|
|
|
|
# evaluationResult fields -> map of name:value (if exists)
|
|
eval_map = {}
|
|
eval_block = item.get("evaluationResult") or {}
|
|
for fld in (eval_block.get("fields") or []):
|
|
name = fld.get("name")
|
|
value = fld.get("value")
|
|
if name:
|
|
eval_map[name] = value
|
|
|
|
flat = {
|
|
"id": item.get("id"),
|
|
"createdAt": item.get("createdAt"),
|
|
"dueDate": item.get("dueDate"),
|
|
"displayTitle": item.get("displayTitle"),
|
|
"doneAt": item.get("doneAt"),
|
|
"removedAt": item.get("removedAt"),
|
|
"priority": item.get("priority"),
|
|
"clinicId": item.get("clinicId"),
|
|
"patient_id": patient.get("id"),
|
|
"patient_identificationNumber": patient.get("identificationNumber"),
|
|
"patient_name": patient.get("name"),
|
|
"patient_surname": patient.get("surname"),
|
|
"patient_status": patient.get("status"),
|
|
"lastMessage_id": last_msg.get("id"),
|
|
"lastMessage_createdAt": last_msg.get("createdAt"),
|
|
"lastMessage_text": last_msg.get("text"),
|
|
"queue_id": queue.get("id"),
|
|
"queue_name": queue.get("name"),
|
|
"priceWhenCreated": item.get("priceWhenCreated"),
|
|
"currencyWhenCreated": item.get("currencyWhenCreated"),
|
|
}
|
|
|
|
# merge evaluation fields (if any) prefixed by "eval_"
|
|
for k, v in eval_map.items():
|
|
flat[f"eval_{k}"] = v
|
|
|
|
return flat
|
|
|
|
def to_dataframe(items: List[Dict[str, Any]]) -> pd.DataFrame:
|
|
rows = [flatten_item(it) for it in items]
|
|
df = pd.DataFrame(rows)
|
|
# try parsing dates
|
|
for col in ("createdAt", "dueDate", "doneAt", "lastMessage_createdAt", "removedAt"):
|
|
if col in df.columns:
|
|
df[col] = pd.to_datetime(df[col], errors="coerce")
|
|
return df
|
|
|
|
def main():
|
|
token = read_token(TOKEN_FILE)
|
|
print("Fetching pending (ACTIVE) requests from Medevio...")
|
|
items = fetch_requests(token)
|
|
print(f"Fetched {len(items)} items.")
|
|
df = to_dataframe(items)
|
|
pd.set_option("display.max_rows", 20)
|
|
pd.set_option("display.max_colwidth", 160)
|
|
print(df.head(50))
|
|
# optionally save
|
|
df.to_excel("medevio_pending_requests.xlsx", index=False)
|
|
print("Saved medevio_pending_requests.xlsx")
|
|
|
|
if __name__ == "__main__":
|
|
main()
|