This commit is contained in:
2025-11-16 07:53:29 +01:00
parent 02cb5bb9f8
commit 585e38284b
9 changed files with 1223 additions and 153 deletions

2
.idea/Medevio.iml generated
View File

@@ -4,7 +4,7 @@
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.venv" />
</content>
<orderEntry type="jdk" jdkName="Python 3.12 virtualenv at U:\Medevio\.venv" jdkType="Python SDK" />
<orderEntry type="jdk" jdkName="Python 3.12 (Medevio)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

2
.idea/misc.xml generated
View File

@@ -3,5 +3,5 @@
<component name="Black">
<option name="sdkName" value="Python 3.12 (Medevio)" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 virtualenv at U:\Medevio\.venv" project-jdk-type="Python SDK" />
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 (Medevio)" project-jdk-type="Python SDK" />
</project>

View File

@@ -4,8 +4,9 @@
import pymysql
import requests
from pathlib import Path
from datetime import datetime
from datetime import datetime, timezone
import time
from dateutil import parser
# ================================
# 🔧 CONFIGURATION
@@ -24,21 +25,22 @@ DB_CONFIG = {
"cursorclass": pymysql.cursors.DictCursor,
}
# ⭐ NOVÝ TESTOVANÝ DOTAZ obsahuje lastMessage.createdAt
GRAPHQL_QUERY = r"""
query ClinicRequestGrid_ListPatientRequestsForClinic2(
query ClinicRequestList2(
$clinicSlug: String!,
$queueId: String,
$queueAssignment: QueueAssignmentFilter!,
$state: PatientRequestState,
$pageInfo: PageInfo!,
$locale: Locale!,
$state: PatientRequestState
$locale: Locale!
) {
requestsResponse: listPatientRequestsForClinic2(
clinicSlug: $clinicSlug,
queueId: $queueId,
queueAssignment: $queueAssignment,
pageInfo: $pageInfo,
state: $state
state: $state,
pageInfo: $pageInfo
) {
count
patientRequests {
@@ -53,40 +55,71 @@ query ClinicRequestGrid_ListPatientRequestsForClinic2(
surname
identificationNumber
}
lastMessage {
createdAt
}
}
}
}
"""
# ================================
# 🧿 SAFE DATETIME PARSER (ALWAYS UTC → LOCAL)
# ================================
def to_mysql_dt_utc(iso_str):
"""
Parse Medevio timestamps safely.
Treat timestamps WITHOUT timezone as UTC.
Convert to local time before saving to MySQL.
"""
if not iso_str:
return None
try:
dt = parser.isoparse(iso_str)
# If tz is missing → assume UTC
if dt.tzinfo is None:
dt = dt.replace(tzinfo=timezone.utc)
# Convert to local timezone
dt_local = dt.astimezone()
return dt_local.strftime("%Y-%m-%d %H:%M:%S")
except:
return None
# ================================
# 🔑 TOKEN
# ================================
def read_token(path: Path) -> str:
tok = path.read_text(encoding="utf-8").strip()
if tok.startswith("Bearer "):
tok = tok.split(" ", 1)[1]
return tok.split(" ", 1)[1]
return tok
# ================================
# 🕒 DATETIME FORMAT
# ================================
def to_mysql_dt(iso_str):
if not iso_str:
return None
try:
dt = datetime.fromisoformat(iso_str.replace("Z", "+00:00"))
return dt.strftime("%Y-%m-%d %H:%M:%S")
except:
return None
# ================================
# 💾 UPSERT
# 💾 UPSERT (včetně správného updatedAt)
# ================================
def upsert(conn, r):
p = r.get("extendedPatient") or {}
# raw timestamps z API nyní přes nový parser
api_updated = to_mysql_dt_utc(r.get("updatedAt"))
last_msg = r.get("lastMessage") or {}
msg_updated = to_mysql_dt_utc(last_msg.get("createdAt"))
# nejnovější změna
def max_dt(a, b):
if a and b:
return max(a, b)
return a or b
final_updated = max_dt(api_updated, msg_updated)
sql = """
INSERT INTO pozadavky (
id, displayTitle, createdAt, updatedAt, doneAt, removedAt,
@@ -105,10 +138,10 @@ def upsert(conn, r):
vals = (
r.get("id"),
r.get("displayTitle"),
to_mysql_dt(r.get("createdAt")),
to_mysql_dt(r.get("updatedAt")),
to_mysql_dt(r.get("doneAt")),
to_mysql_dt(r.get("removedAt")),
to_mysql_dt_utc(r.get("createdAt")),
final_updated,
to_mysql_dt_utc(r.get("doneAt")),
to_mysql_dt_utc(r.get("removedAt")),
p.get("name"),
p.get("surname"),
p.get("identificationNumber"),
@@ -133,15 +166,15 @@ def fetch_active(headers, offset):
}
payload = {
"operationName": "ClinicRequestGrid_ListPatientRequestsForClinic2",
"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().get("data", {}).get("requestsResponse", {})
data = r.json().get("data", {}).get("requestsResponse", {})
return data.get("patientRequests", []), data.get("count", 0)
@@ -160,9 +193,6 @@ def main():
print(f"\n=== Sync ACTIVE požadavků @ {datetime.now():%Y-%m-%d %H:%M:%S} ===")
# -------------------------------
# 🚀 FETCH ALL ACTIVE REQUESTS
# -------------------------------
offset = 0
total_processed = 0
total_count = None
@@ -193,5 +223,6 @@ def main():
print("\n✅ ACTIVE sync hotovo!\n")
# ================================
if __name__ == "__main__":
main()

View File

@@ -2,11 +2,10 @@
# -*- coding: utf-8 -*-
"""
Read conversation messages for pozadavky where messagesProcessed IS NULL
(Optionally filtered by createdAt), insert them into `medevio_conversation`,
and if a message has an attachment (medicalRecord), download it and save into
`medevio_downloads` (same logic as your attachments script).
Finally, mark pozadavky.messagesProcessed = NOW().
Stáhne konverzaci pro požadavky, kde:
messagesProcessed IS NULL OR messagesProcessed < updatedAt.
Vloží do medevio_conversation a přílohy do medevio_downloads.
"""
import zlib
@@ -21,6 +20,7 @@ import time
# 🔧 CONFIGURATION
# ==============================
TOKEN_PATH = Path("token.txt")
DB_CONFIG = {
"host": "192.168.1.76",
"port": 3307,
@@ -31,9 +31,6 @@ DB_CONFIG = {
"cursorclass": pymysql.cursors.DictCursor,
}
# ✅ Optional: Only process requests created after this date ("" = no limit)
CREATED_AFTER = "2024-01-01"
GRAPHQL_QUERY_MESSAGES = r"""
query UseMessages_ListMessages($requestId: String!, $updatedSince: DateTime) {
messages: listMessages(patientRequestId: $requestId, updatedSince: $updatedSince) {
@@ -64,75 +61,58 @@ query UseMessages_ListMessages($requestId: String!, $updatedSince: DateTime) {
"""
# ==============================
# 🧮 HELPERS
# ⏱ DATETIME PARSER
# ==============================
def short_crc8(uuid_str: str) -> str:
return f"{zlib.crc32(uuid_str.encode('utf-8')) & 0xffffffff:08x}"
def extract_filename_from_url(url: str) -> str:
try:
return url.split("/")[-1].split("?")[0]
except Exception:
return "unknown_filename"
def read_token(p: Path) -> str:
tok = p.read_text(encoding="utf-8").strip()
if tok.startswith("Bearer "):
tok = tok.split(" ", 1)[1]
return tok
def parse_dt(s):
if not s:
return None
# handle both "YYYY-mm-ddTHH:MM:SS" and "YYYY-mm-dd HH:MM:SS"
s = s.replace("T", " ")
fmts = ("%Y-%m-%d %H:%M:%S", "%Y-%m-%d %H:%M")
for f in fmts:
try:
return datetime.strptime(s[:19], f)
except Exception:
pass
return None
try:
return datetime.fromisoformat(s.replace("Z", "+00:00"))
except:
pass
try:
return datetime.strptime(s[:19], "%Y-%m-%dT%H:%M:%S")
except:
return None
# ==============================
# 🔐 TOKEN
# ==============================
def read_token(path: Path) -> str:
tok = path.read_text(encoding="utf-8").strip()
return tok.replace("Bearer ", "")
# ==============================
# 📡 FETCH MESSAGES
# ==============================
def fetch_messages(headers, request_id):
variables = {"requestId": request_id, "updatedSince": None}
payload = {
"operationName": "UseMessages_ListMessages",
"query": GRAPHQL_QUERY_MESSAGES,
"variables": variables,
"variables": {"requestId": request_id, "updatedSince": None},
}
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 messages of request {request_id}")
print("❌ HTTP", r.status_code, "for request", request_id)
return []
data = r.json().get("data", {}).get("messages", [])
return data or []
return r.json().get("data", {}).get("messages", []) or []
# ==============================
# 💾 SAVE: conversation row
# 💾 SAVE MESSAGE
# ==============================
def insert_message(cur, req_id, msg):
sender = msg.get("sender") or {}
sender_name = " ".join(x for x in [sender.get("name"), sender.get("surname")] if x).strip() or None
sender_id = sender.get("id")
sender_clinic_id = sender.get("clinicId")
text = msg.get("text")
created_at = parse_dt(msg.get("createdAt"))
read_at = parse_dt(msg.get("readAt"))
updated_at = parse_dt(msg.get("updatedAt"))
mr = msg.get("medicalRecord") or {}
attachment_url = mr.get("downloadUrl") or mr.get("url")
attachment_description = mr.get("description")
attachment_content_type = mr.get("contentType")
sender_name = " ".join(
x for x in [sender.get("name"), sender.get("surname")] if x
) or None
sql = """
INSERT INTO medevio_conversation (
id, request_id, sender_name, sender_id, sender_clinic_id,
id, request_id,
sender_name, sender_id, sender_clinic_id,
text, created_at, read_at, updated_at,
attachment_url, attachment_description, attachment_content_type
) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
@@ -148,57 +128,57 @@ def insert_message(cur, req_id, msg):
attachment_description = VALUES(attachment_description),
attachment_content_type = VALUES(attachment_content_type)
"""
mr = msg.get("medicalRecord") or {}
cur.execute(sql, (
msg.get("id"),
req_id,
sender_name,
sender_id,
sender_clinic_id,
text,
created_at,
read_at,
updated_at,
attachment_url,
attachment_description,
attachment_content_type
sender.get("id"),
sender.get("clinicId"),
msg.get("text"),
parse_dt(msg.get("createdAt")),
parse_dt(msg.get("readAt")),
parse_dt(msg.get("updatedAt")),
mr.get("downloadUrl") or mr.get("url"),
mr.get("description"),
mr.get("contentType")
))
# ==============================
# 💾 SAVE: download attachment (from message)
# 💾 DOWNLOAD MESSAGE ATTACHMENT
# ==============================
def insert_download_from_message(cur, req_id, msg, existing_ids):
def insert_download(cur, req_id, msg, existing_ids):
mr = msg.get("medicalRecord") or {}
attachment_id = mr.get("id")
if not attachment_id:
return False
return
if attachment_id in existing_ids:
print(f" ⏭️ Skipping already downloaded message-attachment {attachment_id}")
return False
return # skip duplicates
url = mr.get("downloadUrl") or mr.get("url")
if not url:
return False
return
try:
r = requests.get(url, timeout=30)
r.raise_for_status()
content = r.content
data = r.content
except Exception as e:
print(f" ⚠️ Failed to download message attachment {attachment_id}: {e}")
return False
print("⚠️ Failed to download:", e)
return
filename = extract_filename_from_url(url)
content_type = mr.get("contentType")
file_size = len(content)
created_date = parse_dt(msg.get("createdAt"))
filename = url.split("/")[-1].split("?")[0]
# We don't have patient names on the message level here; keep NULLs.
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)
request_id, attachment_id, attachment_type,
filename, content_type, file_size, created_at, file_content
) VALUES (%s,%s,%s,%s,%s,%s,%s,%s)
ON DUPLICATE KEY UPDATE
file_content = VALUES(file_content),
file_size = VALUES(file_size),
@@ -208,21 +188,20 @@ def insert_download_from_message(cur, req_id, msg, existing_ids):
attachment_id,
"MESSAGE_ATTACHMENT",
filename,
content_type,
file_size,
None,
None,
created_date,
content
mr.get("contentType"),
len(data),
parse_dt(msg.get("createdAt")),
data
))
existing_ids.add(attachment_id)
print(f" 💾 Saved msg attachment {filename} ({file_size/1024:.1f} kB)")
return True
# ==============================
# 🧠 MAIN
# ==============================
def main():
token = read_token(TOKEN_PATH)
headers = {
"Authorization": f"Bearer {token}",
@@ -232,65 +211,49 @@ def main():
conn = pymysql.connect(**DB_CONFIG)
# Load existing download IDs to skip duplicates (same logic as your script)
print("📦 Loading list of already downloaded attachments...")
# ---- Load existing 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.")
# Pull pozadavky where messagesProcessed IS NULL (optionally by createdAt)
print(f"📦 Already downloaded attachments: {len(existing_ids)}\n")
# ---- Select pozadavky needing message sync
sql = """
SELECT id, displayTitle, pacient_prijmeni, pacient_jmeno, createdAt
SELECT id
FROM pozadavky
WHERE messagesProcessed IS NULL
OR messagesProcessed < updatedAt
"""
params = []
if CREATED_AFTER:
sql += " AND createdAt >= %s"
params.append(CREATED_AFTER)
with conn.cursor() as cur:
cur.execute(sql, params)
rows = cur.fetchall()
cur.execute(sql)
requests_to_process = cur.fetchall()
print(f"📋 Found {len(rows)} pozadavky to process (messagesProcessed IS NULL"
+ (f", created >= {CREATED_AFTER}" if CREATED_AFTER else "") + ")")
print(f"📋 Found {len(requests_to_process)} pozadavků requiring message sync.\n")
for i, row in enumerate(rows, 1):
# ---- Process each pozadavek
for idx, row in enumerate(requests_to_process, 1):
req_id = row["id"]
prijmeni = row.get("pacient_prijmeni") or "Neznamy"
jmeno = row.get("pacient_jmeno") or ""
print(f"\n[{i}/{len(rows)}] 💬 {prijmeni}, {jmeno} ({req_id})")
print(f"[{idx}/{len(requests_to_process)}] Processing {req_id}")
messages = fetch_messages(headers, req_id)
if not messages:
print(" ⚠️ No messages found")
with conn.cursor() as cur:
cur.execute("UPDATE pozadavky SET messagesProcessed = NOW() WHERE id = %s", (req_id,))
conn.commit()
continue
inserted = 0
with conn.cursor() as cur:
for msg in messages:
insert_message(cur, req_id, msg)
# also pull any message attachments into downloads table
insert_download_from_message(cur, req_id, msg, existing_ids)
inserted += 1
insert_download(cur, req_id, msg, existing_ids)
conn.commit()
# mark processed
with conn.cursor() as cur:
cur.execute("UPDATE pozadavky SET messagesProcessed = NOW() WHERE id = %s", (req_id,))
conn.commit()
print(f"{inserted} messages processed for {prijmeni}, {jmeno}")
time.sleep(0.3) # polite API delay
print(f"{len(messages)} messages saved\n")
time.sleep(0.25)
conn.close()
print("\n✅ Done! All new conversations processed and pozadavky updated.")
print("🎉 Done!")
# ==============================
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,226 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import pymysql
import openpyxl
from openpyxl.styles import PatternFill, Font
from openpyxl.utils import get_column_letter
from datetime import datetime
timestamp = datetime.now().strftime("%Y-%m-%d %H-%M-%S")
OUTPUT_PATH = fr"U:\Dropbox\!!!Days\Downloads Z230\{timestamp} medevio_patients_report.xlsx"
# ============================
# CONFIGURATION
# ============================
DB_CONFIG = {
"host": "192.168.1.76",
"port": 3307,
"user": "root",
"password": "Vlado9674+",
"database": "medevio",
"charset": "utf8mb4",
"cursorclass": pymysql.cursors.DictCursor,
}
# ============================
# FUNCTIONS
# ============================
from openpyxl.styles import Border, Side
thin_border = Border(
left=Side(style='thin'),
right=Side(style='thin'),
top=Side(style='thin'),
bottom=Side(style='thin')
)
def apply_thin_borders(ws):
"""Apply thin borders to all cells in the worksheet."""
for row in ws.iter_rows():
for cell in row:
cell.border = thin_border
def autofit_columns(ws):
"""Auto-adjust column widths based on longest cell content."""
for col in ws.columns:
max_length = 0
col_letter = get_column_letter(col[0].column)
for cell in col:
try:
if cell.value:
max_length = max(max_length, len(str(cell.value)))
except:
pass
ws.column_dimensions[col_letter].width = max_length + 2
def apply_header_style(ws):
"""Make header BRIGHT YELLOW and bold."""
fill = PatternFill(start_color="FFFF00", end_color="FFFF00", fill_type="solid")
font = Font(bold=True)
for cell in ws[1]:
cell.fill = fill
cell.font = font
def create_compact_row(row):
"""Produce compact record with merged pojistovna, with user_relationship after prijmeni."""
# insurance merged
code = row.get("pojistovna_code") or ""
naz = row.get("pojistovna_nazev") or ""
if code and naz:
poj = f"{code} ({naz})"
elif code:
poj = code
elif naz:
poj = naz
else:
poj = ""
return {
"id": row["id"],
"jmeno": row["jmeno"],
"prijmeni": row["prijmeni"],
# 🔹 inserted here
"user_relationship": row.get("user_relationship"),
"rodne_cislo": row["rodne_cislo"],
"dob": row["dob"],
"telefon": row["telefon"],
"email": row["email"],
"pojistovna": poj,
"status": row["status"],
"has_mobile_app": row["has_mobile_app"],
"registration_time": row["registration_time"],
"last_update": row["last_update"],
}
def create_pozadavky_rows(rows):
"""Convert raw pozadavky SQL rows into rows for the Excel sheet."""
output = []
for r in rows:
output.append({
# 🔹 First the ID
"id": r["id"],
# 🔹 Your 3 patient columns immediately after ID
"pacient_jmeno": r["pacient_jmeno"],
"pacient_prijmeni": r["pacient_prijmeni"],
"pacient_rodnecislo": r["pacient_rodnecislo"],
# 🔹 Then all other fields in any order you prefer
"displayTitle": r["displayTitle"],
"createdAt": r["createdAt"],
"updatedAt": r["updatedAt"],
"doneAt": r["doneAt"],
"removedAt": r["removedAt"],
"attachmentsProcessed": r["attachmentsProcessed"],
"messagesProcessed": r["messagesProcessed"],
"communicationprocessed": r["communicationprocessed"],
"questionnaireprocessed": r["questionnaireprocessed"],
"lastSync": r["lastSync"],
})
return output
# ============================
# MAIN
# ============================
def main():
print("📥 Connecting to MySQL...")
conn = pymysql.connect(**DB_CONFIG)
with conn:
with conn.cursor() as cur:
cur.execute("SELECT * FROM medevio_pacienti ORDER BY prijmeni, jmeno")
patients = cur.fetchall()
print(f"📊 Loaded {len(patients)} patients.")
# Load pozadavky
with conn.cursor() as cur:
cur.execute("SELECT * FROM pozadavky ORDER BY createdAt DESC")
pozadavky_rows = cur.fetchall()
print(f"📄 Loaded {len(pozadavky_rows)} pozadavky.")
wb = openpyxl.Workbook()
# ---------------------------------
# 1) FULL SHEET
# ---------------------------------
ws_full = wb.active
ws_full.title = "Patients FULL"
if patients:
headers = list(patients[0].keys())
ws_full.append(headers)
for row in patients:
ws_full.append([row.get(h) for h in headers])
apply_header_style(ws_full)
ws_full.freeze_panes = "A2"
ws_full.auto_filter.ref = ws_full.dimensions
autofit_columns(ws_full)
apply_thin_borders(ws_full)
# ---------------------------------
# 2) COMPACT SHEET
# ---------------------------------
ws_compact = wb.create_sheet("Patients COMPACT")
compact_rows = [create_compact_row(r) for r in patients]
compact_headers = list(compact_rows[0].keys())
ws_compact.append(compact_headers)
for row in compact_rows:
ws_compact.append([row.get(h) for h in compact_headers])
apply_header_style(ws_compact)
ws_compact.freeze_panes = "A2"
ws_compact.auto_filter.ref = ws_compact.dimensions
autofit_columns(ws_compact)
# >>> ADD THIS <<<
ur_col_index = compact_headers.index("user_relationship") + 1
col_letter = get_column_letter(ur_col_index)
ws_compact.column_dimensions[col_letter].width = 7.14
apply_thin_borders(ws_compact)
# ---------------------------------
# 3) POZADAVKY SHEET
# ---------------------------------
ws_p = wb.create_sheet("Pozadavky")
poz_list = create_pozadavky_rows(pozadavky_rows)
headers_p = list(poz_list[0].keys()) if poz_list else []
if headers_p:
ws_p.append(headers_p)
for row in poz_list:
ws_p.append([row.get(h) for h in headers_p])
apply_header_style(ws_p)
ws_p.freeze_panes = "A2"
ws_p.auto_filter.ref = ws_p.dimensions
autofit_columns(ws_p)
apply_thin_borders(ws_p)
# ---------------------------------
# SAVE
# ---------------------------------
wb.save(OUTPUT_PATH)
print(f"✅ Excel report saved to:\n{OUTPUT_PATH}")
if __name__ == "__main__":
main()

227
Testy/14 Testy updateat.py Normal file
View File

@@ -0,0 +1,227 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import requests
from pathlib import Path
import json
TOKEN_PATH = Path("token.txt")
CLINIC_SLUG = "mudr-buzalkova"
BATCH_SIZE = 100
# přesně tvůj původní dotaz, beze změn
# GRAPHQL_QUERY = r"""
# query ClinicRequestGrid_ListPatientRequestsForClinic2(
# $clinicSlug: String!,
# $queueId: String,
# $queueAssignment: QueueAssignmentFilter!,
# $pageInfo: PageInfo!,
# $locale: Locale!,
# $state: PatientRequestState
# ) {
# requestsResponse: listPatientRequestsForClinic2(
# clinicSlug: $clinicSlug,
# queueId: $queueId,
# queueAssignment: $queueAssignment,
# pageInfo: $pageInfo,
# state: $state
# ) {
# count
# patientRequests {
# id
# displayTitle(locale: $locale)
# createdAt
# updatedAt
# doneAt
# removedAt
# extendedPatient {
# name
# surname
# identificationNumber
# }
# }
# }
# }
# """
GRAPHQL_QUERY = r"""
query ClinicRequestGrid_ListPatientRequestsForClinic2(
$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)
### TIME FIELDS ADDED
createdAt
updatedAt
doneAt
removedAt
extendedPatient {
id
identificationNumber
name
surname
kind
key
type
user {
id
name
surname
}
owner {
name
surname
}
dob
premiumPlanPatient {
id
premiumPlan {
id
}
}
status2
tags(onlyImportant: true) {
id
}
isUnknownPatient
}
invoice {
id
status
amount
currency
dueAmount
isOverdue
refundedAmount
settledAmount
}
lastMessage {
createdAt
id
readAt
sender {
id
name
surname
clinicId
}
text
}
priority
queue {
id
name
clinicPatientRequestQueueUsers {
accountable {
id
name
surname
}
id
}
}
reservations {
calendar {
id
internalName
name
}
id
canceledAt
done
start
}
tags(onlyImportant: true) {
id
}
userECRF(locale: $locale) {
id
sid
icon {
color
id
urlSvg
}
ecrfSet {
id
name
}
}
priceWhenCreated
currencyWhenCreated
createdByDoctor
eventType
clinicNotes {
id
}
clinicMedicalRecord
}
}
}
"""
def read_token(path: Path) -> str:
tok = path.read_text(encoding="utf-8").strip()
if tok.startswith("Bearer "):
tok = tok.split(" ", 1)[1]
return tok
def main():
token = read_token(TOKEN_PATH)
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
}
variables = {
"clinicSlug": CLINIC_SLUG,
"queueId": None,
"queueAssignment": "ANY",
"pageInfo": {"first": BATCH_SIZE, "offset": 0},
"locale": "cs",
"state": "ACTIVE",
}
payload = {
"operationName": "ClinicRequestGrid_ListPatientRequestsForClinic2",
"query": GRAPHQL_QUERY,
"variables": variables,
}
print("\n===== ČISTÁ ODPOVĚĎ SERVERU =====\n")
r = requests.post("https://api.medevio.cz/graphql", json=payload, headers=headers, timeout=30)
print(f"HTTP {r.status_code}\n")
print(r.text) # <-- TISK NEUPRAVENÉHO JSONU
print("\n===== KONEC ČISTÉ ODPOVĚDI =====\n")
if __name__ == "__main__":
main()

136
Testy/15 test.py Normal file
View File

@@ -0,0 +1,136 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import requests
from pathlib import Path
TOKEN_PATH = Path("token.txt")
CLINIC_SLUG = "mudr-buzalkova"
BATCH_SIZE = 100
TARGET_ID = "cbf6000d-a6ca-4059-88b7-dfdc27220762" # ← sem tvoje ID
# ⭐ Updated GraphQL with lastMessage included
GRAPHQL_QUERY = r"""
query ClinicRequestGrid_ListPatientRequestsForClinic2(
$clinicSlug: String!,
$queueId: String,
$queueAssignment: QueueAssignmentFilter!,
$pageInfo: PageInfo!,
$locale: Locale!,
$state: PatientRequestState
) {
requestsResponse: listPatientRequestsForClinic2(
clinicSlug: $clinicSlug,
queueId: $queueId,
queueAssignment: $queueAssignment,
pageInfo: $pageInfo,
state: $state
) {
count
patientRequests {
id
displayTitle(locale: $locale)
createdAt
updatedAt
doneAt
removedAt
lastMessage {
id
createdAt
updatedAt
}
extendedPatient {
name
surname
identificationNumber
}
}
}
}
"""
def read_token(path: Path) -> str:
tok = path.read_text(encoding="utf-8").strip()
if tok.startswith("Bearer "):
tok = tok.split(" ", 1)[1]
return tok
def fetch_active(headers, offset):
variables = {
"clinicSlug": CLINIC_SLUG,
"queueId": None,
"queueAssignment": "ANY",
"pageInfo": {"first": BATCH_SIZE, "offset": offset},
"locale": "cs",
"state": "ACTIVE",
}
payload = {
"operationName": "ClinicRequestGrid_ListPatientRequestsForClinic2",
"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("HTTP status:", r.status_code)
print(r.text)
r.raise_for_status()
data = r.json().get("data", {}).get("requestsResponse", {})
return data.get("patientRequests", []), data.get("count", 0)
def main():
token = read_token(TOKEN_PATH)
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
print(f"=== Hledám updatedAt a lastMessage pro pozadavek {TARGET_ID} ===\n")
offset = 0
total_count = None
found = False
while True:
batch, count = fetch_active(headers, offset)
if total_count is None:
total_count = count
if not batch:
break
for r in batch:
if r["id"] == TARGET_ID:
print("Nalezeno!\n")
print(f"id: {r['id']}")
print(f"updatedAt: {r['updatedAt']}")
lm = r.get("lastMessage") or {}
print(f"lastMessage.createdAt: {lm.get('createdAt')}")
print(f"lastMessage.updatedAt: {lm.get('updatedAt')}")
found = True
break
if found:
break
if offset + BATCH_SIZE >= count:
break
offset += BATCH_SIZE
if not found:
print("❌ Požadavek nebyl nalezen mezi ACTIVE.")
print("\n=== HOTOVO ===")
if __name__ == "__main__":
main()

228
Testy/16 test.py Normal file
View File

@@ -0,0 +1,228 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import pymysql
import requests
from pathlib import Path
from datetime import datetime, timezone
import time
from dateutil import parser
# ================================
# 🔧 CONFIGURATION
# ================================
TOKEN_PATH = Path("token.txt")
CLINIC_SLUG = "mudr-buzalkova"
BATCH_SIZE = 100
DB_CONFIG = {
"host": "192.168.1.76",
"port": 3307,
"user": "root",
"password": "Vlado9674+",
"database": "medevio",
"charset": "utf8mb4",
"cursorclass": pymysql.cursors.DictCursor,
}
# ⭐ NOVÝ TESTOVANÝ DOTAZ obsahuje lastMessage.createdAt
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
}
}
}
}
"""
# ================================
# 🧿 SAFE DATETIME PARSER (ALWAYS UTC → LOCAL)
# ================================
def to_mysql_dt_utc(iso_str):
"""
Parse Medevio timestamps safely.
Treat timestamps WITHOUT timezone as UTC.
Convert to local time before saving to MySQL.
"""
if not iso_str:
return None
try:
dt = parser.isoparse(iso_str)
# If tz is missing → assume UTC
if dt.tzinfo is None:
dt = dt.replace(tzinfo=timezone.utc)
# Convert to local timezone
dt_local = dt.astimezone()
return dt_local.strftime("%Y-%m-%d %H:%M:%S")
except:
return None
# ================================
# 🔑 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
# ================================
# 💾 UPSERT (včetně správného updatedAt)
# ================================
def upsert(conn, r):
p = r.get("extendedPatient") or {}
# raw timestamps z API nyní přes nový parser
api_updated = to_mysql_dt_utc(r.get("updatedAt"))
last_msg = r.get("lastMessage") or {}
msg_updated = to_mysql_dt_utc(last_msg.get("createdAt"))
# nejnovější změna
def max_dt(a, b):
if a and b:
return max(a, b)
return a or b
final_updated = max_dt(api_updated, msg_updated)
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_utc(r.get("createdAt")),
final_updated,
to_mysql_dt_utc(r.get("doneAt")),
to_mysql_dt_utc(r.get("removedAt")),
p.get("name"),
p.get("surname"),
p.get("identificationNumber"),
)
with conn.cursor() as cur:
cur.execute(sql, vals)
conn.commit()
# ================================
# 📡 FETCH ACTIVE PAGE
# ================================
def fetch_active(headers, offset):
variables = {
"clinicSlug": CLINIC_SLUG,
"queueId": None,
"queueAssignment": "ANY",
"pageInfo": {"first": BATCH_SIZE, "offset": offset},
"locale": "cs",
"state": "ACTIVE",
}
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().get("data", {}).get("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)
print(f"\n=== Sync ACTIVE požadavků @ {datetime.now():%Y-%m-%d %H:%M:%S} ===")
offset = 0
total_processed = 0
total_count = None
while True:
batch, count = fetch_active(headers, offset)
if total_count is None:
total_count = count
print(f"📡 Celkem ACTIVE v Medevio: {count}")
if not batch:
break
for r in batch:
upsert(conn, r)
total_processed += len(batch)
print(f"{total_processed}/{total_count} ACTIVE processed")
if offset + BATCH_SIZE >= count:
break
offset += BATCH_SIZE
time.sleep(0.4)
conn.close()
print("\n✅ ACTIVE sync hotovo!\n")
# ================================
if __name__ == "__main__":
main()

259
Testy/17 test.py Normal file
View File

@@ -0,0 +1,259 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Stáhne konverzaci pro požadavky, kde:
messagesProcessed IS NULL OR messagesProcessed < updatedAt.
Vloží do medevio_conversation a přílohy do medevio_downloads.
"""
import zlib
import json
import requests
import pymysql
from pathlib import Path
from datetime import datetime
import time
# ==============================
# 🔧 CONFIGURATION
# ==============================
TOKEN_PATH = Path("token.txt")
DB_CONFIG = {
"host": "192.168.1.76",
"port": 3307,
"user": "root",
"password": "Vlado9674+",
"database": "medevio",
"charset": "utf8mb4",
"cursorclass": pymysql.cursors.DictCursor,
}
GRAPHQL_QUERY_MESSAGES = r"""
query UseMessages_ListMessages($requestId: String!, $updatedSince: DateTime) {
messages: listMessages(patientRequestId: $requestId, updatedSince: $updatedSince) {
id
createdAt
updatedAt
readAt
text
type
sender {
id
name
surname
clinicId
}
medicalRecord {
id
description
contentType
url
downloadUrl
token
createdAt
updatedAt
}
}
}
"""
# ==============================
# ⏱ DATETIME PARSER
# ==============================
def parse_dt(s):
if not s:
return None
try:
return datetime.fromisoformat(s.replace("Z", "+00:00"))
except:
pass
try:
return datetime.strptime(s[:19], "%Y-%m-%dT%H:%M:%S")
except:
return None
# ==============================
# 🔐 TOKEN
# ==============================
def read_token(path: Path) -> str:
tok = path.read_text(encoding="utf-8").strip()
return tok.replace("Bearer ", "")
# ==============================
# 📡 FETCH MESSAGES
# ==============================
def fetch_messages(headers, request_id):
payload = {
"operationName": "UseMessages_ListMessages",
"query": GRAPHQL_QUERY_MESSAGES,
"variables": {"requestId": request_id, "updatedSince": None},
}
r = requests.post("https://api.medevio.cz/graphql", json=payload, headers=headers, timeout=30)
if r.status_code != 200:
print("❌ HTTP", r.status_code, "for request", request_id)
return []
return r.json().get("data", {}).get("messages", []) or []
# ==============================
# 💾 SAVE MESSAGE
# ==============================
def insert_message(cur, req_id, msg):
sender = msg.get("sender") or {}
sender_name = " ".join(
x for x in [sender.get("name"), sender.get("surname")] if x
) or None
sql = """
INSERT INTO medevio_conversation (
id, request_id,
sender_name, sender_id, sender_clinic_id,
text, created_at, read_at, updated_at,
attachment_url, attachment_description, attachment_content_type
) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
ON DUPLICATE KEY UPDATE
sender_name = VALUES(sender_name),
sender_id = VALUES(sender_id),
sender_clinic_id = VALUES(sender_clinic_id),
text = VALUES(text),
created_at = VALUES(created_at),
read_at = VALUES(read_at),
updated_at = VALUES(updated_at),
attachment_url = VALUES(attachment_url),
attachment_description = VALUES(attachment_description),
attachment_content_type = VALUES(attachment_content_type)
"""
mr = msg.get("medicalRecord") or {}
cur.execute(sql, (
msg.get("id"),
req_id,
sender_name,
sender.get("id"),
sender.get("clinicId"),
msg.get("text"),
parse_dt(msg.get("createdAt")),
parse_dt(msg.get("readAt")),
parse_dt(msg.get("updatedAt")),
mr.get("downloadUrl") or mr.get("url"),
mr.get("description"),
mr.get("contentType")
))
# ==============================
# 💾 DOWNLOAD MESSAGE ATTACHMENT
# ==============================
def insert_download(cur, req_id, msg, existing_ids):
mr = msg.get("medicalRecord") or {}
attachment_id = mr.get("id")
if not attachment_id:
return
if attachment_id in existing_ids:
return # skip duplicates
url = mr.get("downloadUrl") or mr.get("url")
if not url:
return
try:
r = requests.get(url, timeout=30)
r.raise_for_status()
data = r.content
except Exception as e:
print("⚠️ Failed to download:", e)
return
filename = url.split("/")[-1].split("?")[0]
cur.execute("""
INSERT INTO medevio_downloads (
request_id, attachment_id, attachment_type,
filename, content_type, file_size, created_at, file_content
) VALUES (%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,
"MESSAGE_ATTACHMENT",
filename,
mr.get("contentType"),
len(data),
parse_dt(msg.get("createdAt")),
data
))
existing_ids.add(attachment_id)
# ==============================
# 🧠 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)
# ---- Load existing 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"📦 Already downloaded attachments: {len(existing_ids)}\n")
# ---- Select pozadavky needing message sync
sql = """
SELECT id
FROM pozadavky
WHERE messagesProcessed IS NULL
OR messagesProcessed < updatedAt
"""
with conn.cursor() as cur:
cur.execute(sql)
requests_to_process = cur.fetchall()
print(f"📋 Found {len(requests_to_process)} pozadavků requiring message sync.\n")
# ---- Process each pozadavek
for idx, row in enumerate(requests_to_process, 1):
req_id = row["id"]
print(f"[{idx}/{len(requests_to_process)}] Processing {req_id}")
messages = fetch_messages(headers, req_id)
with conn.cursor() as cur:
for msg in messages:
insert_message(cur, req_id, msg)
insert_download(cur, req_id, msg, existing_ids)
conn.commit()
with conn.cursor() as cur:
cur.execute("UPDATE pozadavky SET messagesProcessed = NOW() WHERE id = %s", (req_id,))
conn.commit()
print(f"{len(messages)} messages saved\n")
time.sleep(0.25)
conn.close()
print("🎉 Done!")
if __name__ == "__main__":
main()