#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Download all attachments for pozadavky where attachmentsProcessed IS NULL and (optionally) createdAt is newer than a configurable cutoff date. Store them in MySQL table `medevio_downloads`, and update pozadavky.attachmentsProcessed = NOW(). """ import zlib import json import requests import pymysql from pathlib import Path from datetime import datetime import time # ============================== # ๐Ÿ”ง CONFIGURATION # ============================== TOKEN_PATH = Path("token.txt") CLINIC_SLUG = "mudr-buzalkova" DB_CONFIG = { "host": "192.168.1.76", "port": 3307, "user": "root", "password": "Vlado9674+", "database": "medevio", "charset": "utf8mb4", "cursorclass": pymysql.cursors.DictCursor, } # โœ… Optional: Only process requests created after this date # Leave empty ("") to process all CREATED_AFTER = "2025-01-01" # ๐Ÿ•“ Adjust freely, or set to "" for no limit GRAPHQL_QUERY = r""" query ClinicRequestDetail_GetPatientRequest2($requestId: UUID!) { patientRequestMedicalRecords: listMedicalRecordsForPatientRequest( attachmentTypes: [ECRF_FILL_ATTACHMENT, MESSAGE_ATTACHMENT, PATIENT_REQUEST_ATTACHMENT] patientRequestId: $requestId pageInfo: {first: 100, offset: 0} ) { attachmentType id medicalRecord { contentType description downloadUrl id url visibleToPatient } } } """ # ============================== # ๐Ÿงฎ HELPERS # ============================== def short_crc8(uuid_str: str) -> str: """Return deterministic 8-char hex string from any input string (CRC32).""" return f"{zlib.crc32(uuid_str.encode('utf-8')) & 0xffffffff:08x}" def extract_filename_from_url(url: str) -> str: """Extracts filename from S3-style URL (between last '/' and first '?').""" try: return url.split("/")[-1].split("?")[0] except Exception: return "unknown_filename" def read_token(p: Path) -> str: """Read Bearer token from file.""" tok = p.read_text(encoding="utf-8").strip() if tok.startswith("Bearer "): tok = tok.split(" ", 1)[1] return tok # ============================== # ๐Ÿ“ก FETCH ATTACHMENTS # ============================== def fetch_attachments(headers, request_id): variables = {"requestId": request_id} payload = { "operationName": "ClinicRequestDetail_GetPatientRequest2", "query": GRAPHQL_QUERY, "variables": variables, } r = requests.post("https://api.medevio.cz/graphql", json=payload, headers=headers, timeout=30) if r.status_code != 200: print(f"โŒ HTTP {r.status_code} for request {request_id}") return [] data = r.json().get("data", {}).get("patientRequestMedicalRecords", []) return data # ============================== # ๐Ÿ’พ SAVE TO MYSQL (with skip) # ============================== def insert_download(cur, req_id, a, m, jmeno, prijmeni, created_date, existing_ids): attachment_id = a.get("id") if attachment_id in existing_ids: print(f" โญ๏ธ Skipping already downloaded attachment {attachment_id}") return False url = m.get("downloadUrl") if not url: print(" โš ๏ธ No download URL") return False filename = extract_filename_from_url(url) try: r = requests.get(url, timeout=30) r.raise_for_status() content = r.content except Exception as e: print(f" โš ๏ธ Failed to download {url}: {e}") return False file_size = len(content) attachment_type = a.get("attachmentType") content_type = m.get("contentType") cur.execute(""" INSERT INTO medevio_downloads ( request_id, attachment_id, attachment_type, filename, content_type, file_size, pacient_jmeno, pacient_prijmeni, created_at, file_content ) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) ON DUPLICATE KEY UPDATE file_content = VALUES(file_content), file_size = VALUES(file_size), downloaded_at = NOW() """, ( req_id, attachment_id, attachment_type, filename, content_type, file_size, jmeno, prijmeni, created_date, content )) existing_ids.add(attachment_id) print(f" ๐Ÿ’พ Saved {filename} ({file_size/1024:.1f} kB)") return True # ============================== # ๐Ÿง  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) print("๐Ÿ“ฆ Loading list of already downloaded attachments...") with conn.cursor() as cur: cur.execute("SELECT attachment_id FROM medevio_downloads") existing_ids = {row["attachment_id"] for row in cur.fetchall()} print(f"โœ… Found {len(existing_ids)} attachments already saved.") # โœ… Dynamic SQL with optional createdAt filter sql = """ SELECT id, displayTitle, pacient_prijmeni, pacient_jmeno, createdAt FROM pozadavky WHERE attachmentsProcessed IS NULL """ params = [] if CREATED_AFTER: sql += " AND createdAt >= %s" params.append(CREATED_AFTER) with conn.cursor() as cur: cur.execute(sql, params) rows = cur.fetchall() print(f"๐Ÿ“‹ Found {len(rows)} pozadavky to process (attachmentsProcessed IS NULL" + (f", created >= {CREATED_AFTER}" if CREATED_AFTER else "") + ")") for i, row in enumerate(rows, 1): time.sleep(1) # polite API delay req_id = row["id"] prijmeni = row.get("pacient_prijmeni") or "Neznamy" jmeno = row.get("pacient_jmeno") or "" created = row.get("createdAt") try: created_date = datetime.strptime(str(created), "%Y-%m-%d %H:%M:%S") except Exception: created_date = None print(f"\n[{i}/{len(rows)}] ๐Ÿงพ {prijmeni}, {jmeno} ({req_id})") attachments = fetch_attachments(headers, req_id) if not attachments: print(" โš ๏ธ No attachments found") with conn.cursor() as cur: cur.execute("UPDATE pozadavky SET attachmentsProcessed = NOW() WHERE id = %s", (req_id,)) conn.commit() continue with conn.cursor() as cur: for a in attachments: m = a.get("medicalRecord") or {} insert_download(cur, req_id, a, m, jmeno, prijmeni, created_date, existing_ids) conn.commit() # โœ… mark processed with conn.cursor() as cur: cur.execute("UPDATE pozadavky SET attachmentsProcessed = NOW() WHERE id = %s", (req_id,)) conn.commit() print(f" โœ… {len(attachments)} attachments processed for {prijmeni}, {jmeno}") time.sleep(0.3) # polite API delay conn.close() print("\nโœ… Done! All new attachments processed and pozadavky updated.") # ============================== if __name__ == "__main__": main()