Compare commits
5 Commits
81847166d2
...
46fbcdeff7
| Author | SHA1 | Date | |
|---|---|---|---|
| 46fbcdeff7 | |||
| 6478902172 | |||
| 68700ecd4d | |||
| e345e477d3 | |||
| be0d41ad01 |
Submodule .claude/worktrees/heuristic-lichterman updated: be0d41ad01...e345e477d3
@@ -30,7 +30,7 @@ CLINIC_SLUG = "mudr-buzalkova"
|
|||||||
|
|
||||||
DB_CONFIG = {
|
DB_CONFIG = {
|
||||||
"host": "192.168.1.76",
|
"host": "192.168.1.76",
|
||||||
"port": 3307,
|
"port": 3306,
|
||||||
"user": "root",
|
"user": "root",
|
||||||
"password": "Vlado9674+",
|
"password": "Vlado9674+",
|
||||||
"database": "medevio",
|
"database": "medevio",
|
||||||
@@ -40,26 +40,22 @@ DB_CONFIG = {
|
|||||||
|
|
||||||
EXPORT_DIR = Path(r"u:\Dropbox\Ordinace\Reporty")
|
EXPORT_DIR = Path(r"u:\Dropbox\Ordinace\Reporty")
|
||||||
EXPORT_DIR.mkdir(exist_ok=True, parents=True)
|
EXPORT_DIR.mkdir(exist_ok=True, parents=True)
|
||||||
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
|
||||||
xlsx_path = EXPORT_DIR / f"{timestamp} Agenda + Pozadavky (Merged).xlsx"
|
# Delete previous reports
|
||||||
|
for old in EXPORT_DIR.glob("* Agenda + Požadavky.xlsx"):
|
||||||
|
old.unlink()
|
||||||
|
print(f"🗑️ Deleted old report: {old.name}")
|
||||||
|
|
||||||
|
timestamp = datetime.now().strftime("%Y-%m-%d %H-%M-%S")
|
||||||
|
xlsx_path = EXPORT_DIR / f"{timestamp} Agenda + Požadavky.xlsx"
|
||||||
|
|
||||||
# ==================== LOAD TOKEN ====================
|
# ==================== LOAD TOKEN ====================
|
||||||
def load_gateway_token(storage_path="medevio_storage.json"):
|
TOKEN_PATH = Path("token.txt")
|
||||||
path = Path(storage_path)
|
if not TOKEN_PATH.exists():
|
||||||
if not path.exists():
|
TOKEN_PATH = Path(__file__).parent / "token.txt"
|
||||||
raise SystemExit(f"❌ Storage file not found: {path}")
|
if not TOKEN_PATH.exists():
|
||||||
with path.open("r", encoding="utf-8") as f:
|
raise SystemExit(f"❌ token.txt not found")
|
||||||
state = json.load(f)
|
gateway_token = TOKEN_PATH.read_text(encoding="utf-8").strip()
|
||||||
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 = {
|
headers = {
|
||||||
"content-type": "application/json",
|
"content-type": "application/json",
|
||||||
@@ -80,8 +76,18 @@ thin_border = Border(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
REQUEST_URL_TEMPLATE = "https://my.medevio.cz/mudr-buzalkova/klinika/pozadavky?pozadavek={}"
|
||||||
|
link_font = Font(color="0563C1", underline="single")
|
||||||
|
|
||||||
|
|
||||||
def format_ws(ws, df):
|
def format_ws(ws, df):
|
||||||
"""Apply unified formatting to a worksheet."""
|
"""Apply unified formatting to a worksheet."""
|
||||||
|
# Find Request_ID column index (1-based)
|
||||||
|
req_id_col = None
|
||||||
|
columns = list(df.columns)
|
||||||
|
if "Request_ID" in columns:
|
||||||
|
req_id_col = columns.index("Request_ID") + 1
|
||||||
|
|
||||||
for col_idx in range(1, len(df.columns) + 1):
|
for col_idx in range(1, len(df.columns) + 1):
|
||||||
col_letter = get_column_letter(col_idx)
|
col_letter = get_column_letter(col_idx)
|
||||||
cell = ws.cell(row=1, column=col_idx)
|
cell = ws.cell(row=1, column=col_idx)
|
||||||
@@ -96,6 +102,10 @@ def format_ws(ws, df):
|
|||||||
cell.border = thin_border
|
cell.border = thin_border
|
||||||
if r_idx % 2 == 0:
|
if r_idx % 2 == 0:
|
||||||
cell.fill = alt_fill
|
cell.fill = alt_fill
|
||||||
|
# Add hyperlink to Request_ID cells
|
||||||
|
if req_id_col and cell.column == req_id_col and cell.value:
|
||||||
|
cell.hyperlink = REQUEST_URL_TEMPLATE.format(cell.value)
|
||||||
|
cell.font = link_font
|
||||||
ws.freeze_panes = "A2"
|
ws.freeze_panes = "A2"
|
||||||
|
|
||||||
|
|
||||||
@@ -138,7 +148,12 @@ payload = {
|
|||||||
|
|
||||||
r = requests.post(GRAPHQL_URL, headers=headers, data=json.dumps(payload))
|
r = requests.post(GRAPHQL_URL, headers=headers, data=json.dumps(payload))
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
reservations = r.json()["data"]["reservations"]
|
resp = r.json()
|
||||||
|
if "errors" in resp or "data" not in resp:
|
||||||
|
print("❌ API response:")
|
||||||
|
print(json.dumps(resp, indent=2, ensure_ascii=False))
|
||||||
|
raise SystemExit("API call failed - check token or query.")
|
||||||
|
reservations = resp["data"]["reservations"]
|
||||||
|
|
||||||
rows = []
|
rows = []
|
||||||
for r in reservations:
|
for r in reservations:
|
||||||
33
40 agenda a požadavky/check_mysql.py
Normal file
33
40 agenda a požadavky/check_mysql.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""Check one request in MySQL."""
|
||||||
|
|
||||||
|
import pymysql
|
||||||
|
import json
|
||||||
|
|
||||||
|
DB_CONFIG = {
|
||||||
|
"host": "192.168.1.76",
|
||||||
|
"port": 3306,
|
||||||
|
"user": "root",
|
||||||
|
"password": "Vlado9674+",
|
||||||
|
"database": "medevio",
|
||||||
|
"charset": "utf8mb4",
|
||||||
|
"cursorclass": pymysql.cursors.DictCursor,
|
||||||
|
}
|
||||||
|
|
||||||
|
REQUEST_ID = "6b46b5a8-b080-4821-86b0-39adabeec86b"
|
||||||
|
|
||||||
|
conn = pymysql.connect(**DB_CONFIG)
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute("SELECT * FROM pozadavky WHERE id = %s", (REQUEST_ID,))
|
||||||
|
row = cur.fetchone()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
if row:
|
||||||
|
# Convert datetime objects to strings for JSON serialization
|
||||||
|
for k, v in row.items():
|
||||||
|
if hasattr(v, 'isoformat'):
|
||||||
|
row[k] = v.isoformat()
|
||||||
|
print(json.dumps(row, indent=2, ensure_ascii=False, default=str))
|
||||||
|
else:
|
||||||
|
print(f"Not found: {REQUEST_ID}")
|
||||||
63
40 agenda a požadavky/check_request.py
Normal file
63
40 agenda a požadavky/check_request.py
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""Quick check: fetch one request from Medevio API and print all fields."""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import requests
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
TOKEN_PATH = Path(__file__).parent / "token.txt"
|
||||||
|
GRAPHQL_URL = "https://api.medevio.cz/graphql"
|
||||||
|
CLINIC_SLUG = "mudr-buzalkova"
|
||||||
|
REQUEST_ID = "6b46b5a8-b080-4821-86b0-39adabeec86b"
|
||||||
|
|
||||||
|
token = TOKEN_PATH.read_text(encoding="utf-8").strip()
|
||||||
|
headers = {
|
||||||
|
"content-type": "application/json",
|
||||||
|
"authorization": f"Bearer {token}",
|
||||||
|
"origin": "https://my.medevio.cz",
|
||||||
|
"referer": "https://my.medevio.cz/",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Query with as many fields as possible
|
||||||
|
QUERY = """
|
||||||
|
query GetPatientRequest2($requestId: UUID!, $clinicSlug: String!, $locale: Locale!) {
|
||||||
|
request: getPatientRequest2(patientRequestId: $requestId, clinicSlug: $clinicSlug) {
|
||||||
|
id
|
||||||
|
displayTitle(locale: $locale)
|
||||||
|
createdAt
|
||||||
|
updatedAt
|
||||||
|
doneAt
|
||||||
|
removedAt
|
||||||
|
userNote
|
||||||
|
eventType
|
||||||
|
extendedPatient(clinicSlug: $clinicSlug) {
|
||||||
|
name
|
||||||
|
surname
|
||||||
|
dob
|
||||||
|
identificationNumber
|
||||||
|
insuranceCompanyObject { shortName }
|
||||||
|
}
|
||||||
|
ecrfFilledData(locale: $locale) {
|
||||||
|
name
|
||||||
|
groups {
|
||||||
|
label
|
||||||
|
fields { name label type value }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"operationName": "GetPatientRequest2",
|
||||||
|
"query": QUERY,
|
||||||
|
"variables": {
|
||||||
|
"requestId": REQUEST_ID,
|
||||||
|
"clinicSlug": CLINIC_SLUG,
|
||||||
|
"locale": "cs",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
r = requests.post(GRAPHQL_URL, json=payload, headers=headers, timeout=30)
|
||||||
|
print(json.dumps(r.json(), indent=2, ensure_ascii=False))
|
||||||
178
40 agenda a požadavky/sync_open_requests.py
Normal file
178
40 agenda a požadavky/sync_open_requests.py
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
Sync open requests: checks each request marked as open in MySQL (doneAt IS NULL
|
||||||
|
AND removedAt IS NULL) against the Medevio API. If the API shows the request is
|
||||||
|
closed (doneAt) or removed (removedAt), updates MySQL accordingly.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import requests
|
||||||
|
import pymysql
|
||||||
|
from pathlib import Path
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
# ==============================
|
||||||
|
# UTF-8 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")
|
||||||
|
|
||||||
|
# ==============================
|
||||||
|
# DRY RUN - set to True to only print what would be updated, False to actually update
|
||||||
|
# ==============================
|
||||||
|
DRY_RUN = False
|
||||||
|
|
||||||
|
# ==============================
|
||||||
|
# CONFIG
|
||||||
|
# ==============================
|
||||||
|
GRAPHQL_URL = "https://api.medevio.cz/graphql"
|
||||||
|
CLINIC_SLUG = "mudr-buzalkova"
|
||||||
|
|
||||||
|
TOKEN_PATH = Path(__file__).parent / "token.txt"
|
||||||
|
if not TOKEN_PATH.exists():
|
||||||
|
raise SystemExit("❌ token.txt not found")
|
||||||
|
|
||||||
|
gateway_token = TOKEN_PATH.read_text(encoding="utf-8").strip()
|
||||||
|
headers = {
|
||||||
|
"content-type": "application/json",
|
||||||
|
"authorization": f"Bearer {gateway_token}",
|
||||||
|
"origin": "https://my.medevio.cz",
|
||||||
|
"referer": "https://my.medevio.cz/",
|
||||||
|
}
|
||||||
|
|
||||||
|
DB_CONFIG = {
|
||||||
|
"host": "192.168.1.76",
|
||||||
|
"port": 3306,
|
||||||
|
"user": "root",
|
||||||
|
"password": "Vlado9674+",
|
||||||
|
"database": "medevio",
|
||||||
|
"charset": "utf8mb4",
|
||||||
|
"cursorclass": pymysql.cursors.DictCursor,
|
||||||
|
}
|
||||||
|
|
||||||
|
GRAPHQL_QUERY = """
|
||||||
|
query GetPatientRequest2($requestId: UUID!, $clinicSlug: String!) {
|
||||||
|
request: getPatientRequest2(patientRequestId: $requestId, clinicSlug: $clinicSlug) {
|
||||||
|
id
|
||||||
|
doneAt
|
||||||
|
removedAt
|
||||||
|
updatedAt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def fix_datetime(dt_str):
|
||||||
|
if not dt_str:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
return datetime.fromisoformat(dt_str.replace("Z", "+00:00"))
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_request(request_id):
|
||||||
|
payload = {
|
||||||
|
"operationName": "GetPatientRequest2",
|
||||||
|
"query": GRAPHQL_QUERY,
|
||||||
|
"variables": {
|
||||||
|
"requestId": request_id,
|
||||||
|
"clinicSlug": CLINIC_SLUG,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for attempt in range(3):
|
||||||
|
try:
|
||||||
|
r = requests.post(GRAPHQL_URL, json=payload, headers=headers, timeout=30)
|
||||||
|
break
|
||||||
|
except (requests.ConnectionError, requests.Timeout, requests.exceptions.RequestException) as e:
|
||||||
|
print(f" ⚠️ Attempt {attempt+1}/3 failed: {e}")
|
||||||
|
time.sleep(2)
|
||||||
|
else:
|
||||||
|
print(f" ❌ Connection failed after 3 attempts for {request_id}")
|
||||||
|
return None
|
||||||
|
if r.status_code != 200:
|
||||||
|
print(f" ❌ HTTP {r.status_code} for {request_id}")
|
||||||
|
return None
|
||||||
|
data = r.json()
|
||||||
|
if "errors" in data:
|
||||||
|
print(f" ❌ API error for {request_id}: {data['errors']}")
|
||||||
|
return None
|
||||||
|
return data.get("data", {}).get("request")
|
||||||
|
|
||||||
|
|
||||||
|
# ==============================
|
||||||
|
# MAIN
|
||||||
|
# ==============================
|
||||||
|
conn = pymysql.connect(**DB_CONFIG)
|
||||||
|
|
||||||
|
# 1) Read all open requests from MySQL
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute(
|
||||||
|
"SELECT id, displayTitle, pacient_prijmeni, pacient_jmeno "
|
||||||
|
"FROM pozadavky WHERE doneAt IS NULL AND removedAt IS NULL"
|
||||||
|
)
|
||||||
|
open_requests = cur.fetchall()
|
||||||
|
|
||||||
|
mode = "DRY RUN" if DRY_RUN else "LIVE"
|
||||||
|
print(f"🔧 Mode: {mode}")
|
||||||
|
print(f"📋 Found {len(open_requests)} open requests in MySQL.\n")
|
||||||
|
|
||||||
|
updated = 0
|
||||||
|
errors = 0
|
||||||
|
|
||||||
|
for i, req in enumerate(open_requests, 1):
|
||||||
|
rid = req["id"]
|
||||||
|
name = f"{req.get('pacient_prijmeni', '')} {req.get('pacient_jmeno', '')}".strip()
|
||||||
|
title = req.get("displayTitle", "")
|
||||||
|
print(f"[{i}/{len(open_requests)}] {name} – {title} ({rid})")
|
||||||
|
|
||||||
|
api_data = fetch_request(rid)
|
||||||
|
if api_data is None:
|
||||||
|
errors += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
api_done = api_data.get("doneAt")
|
||||||
|
api_removed = api_data.get("removedAt")
|
||||||
|
api_updated = api_data.get("updatedAt")
|
||||||
|
|
||||||
|
if api_done or api_removed:
|
||||||
|
done_dt = fix_datetime(api_done)
|
||||||
|
removed_dt = fix_datetime(api_removed)
|
||||||
|
updated_dt = fix_datetime(api_updated)
|
||||||
|
|
||||||
|
status = "DONE" if api_done else "REMOVED"
|
||||||
|
|
||||||
|
if DRY_RUN:
|
||||||
|
print(f" 🔍 Would update → {status} (doneAt={api_done}, removedAt={api_removed})")
|
||||||
|
else:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute(
|
||||||
|
"UPDATE pozadavky SET doneAt = %s, removedAt = %s, updatedAt = %s WHERE id = %s",
|
||||||
|
(done_dt, removed_dt, updated_dt, rid),
|
||||||
|
)
|
||||||
|
conn.commit()
|
||||||
|
print(f" ✅ Updated → {status}")
|
||||||
|
|
||||||
|
updated += 1
|
||||||
|
else:
|
||||||
|
print(f" ⏳ Still open")
|
||||||
|
|
||||||
|
# Be gentle with the API
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
print(f"\n{'='*50}")
|
||||||
|
print(f"📊 Total open in MySQL: {len(open_requests)}")
|
||||||
|
print(f"✅ Updated (closed/removed): {updated}")
|
||||||
|
print(f"⏳ Still open: {len(open_requests) - updated - errors}")
|
||||||
|
print(f"❌ Errors: {errors}")
|
||||||
Reference in New Issue
Block a user