Compare commits

..

19 Commits

Author SHA1 Message Date
7c08ad8e35 Z230 2026-01-29 11:34:52 +01:00
30d64680be notebook 2026-01-28 07:26:08 +01:00
55e723788b notebook 2026-01-16 17:06:54 +01:00
2d2a60a845 Z230 2026-01-16 15:34:12 +01:00
186c98fd0d Z230 2026-01-07 08:30:39 +01:00
d1bfe92e28 Z230 2025-12-14 21:30:10 +01:00
21c11d2336 Remove PyCharm config from repo and add proper .gitignore 2025-12-14 21:29:23 +01:00
a95f8ae1f9 Z230 2025-12-14 21:23:43 +01:00
139e867a6d Z230 2025-12-14 21:22:42 +01:00
65c90750dc reporter 2025-12-07 12:11:50 +01:00
4c68c095db Z230 2025-12-05 09:49:59 +01:00
eeea6740ac Z230 2025-12-02 16:50:06 +01:00
c8e58a0246 reporter 2025-12-02 07:31:16 +01:00
f159120175 reporter 2025-12-02 06:24:27 +01:00
f8ada463a2 reporter 2025-11-30 20:21:01 +01:00
ac16eedde9 notebook 2025-11-21 07:14:49 +01:00
8fce419afd Z230 2025-11-20 10:07:30 +01:00
7c185fec68 Z230 2025-11-19 19:59:44 +01:00
michaela.buzalkova
ea32ea0bc1 pohoda 2025-11-17 11:28:31 +01:00
54 changed files with 2852 additions and 3351 deletions

13
.gitignore vendored Normal file
View File

@@ -0,0 +1,13 @@
# Virtual environment
.venv/
# Python
__pycache__/
*.pyc
# PyCharm / IDE
.idea/
# OS
.DS_Store
Thumbs.db

3
.idea/.gitignore generated vendored
View File

@@ -1,3 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml

10
.idea/Medevio.iml generated
View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.venv" />
</content>
<orderEntry type="jdk" jdkName="Python 3.12" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@@ -1,6 +0,0 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

7
.idea/misc.xml generated
View File

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.12 (Medevio)" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12" project-jdk-type="Python SDK" />
</project>

8
.idea/modules.xml generated
View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/Medevio.iml" filepath="$PROJECT_DIR$/.idea/Medevio.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated
View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

@@ -8,6 +8,18 @@ from datetime import datetime, timezone
import time import time
from dateutil import parser from dateutil import parser
# Force UTF-8 output even under Windows Task Scheduler
import sys
try:
sys.stdout.reconfigure(encoding='utf-8')
sys.stderr.reconfigure(encoding='utf-8')
except AttributeError:
# Python < 3.7 fallback (not needed for you, but safe)
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
# ================================ # ================================
# 🔧 CONFIGURATION # 🔧 CONFIGURATION
# ================================ # ================================
@@ -16,7 +28,7 @@ CLINIC_SLUG = "mudr-buzalkova"
BATCH_SIZE = 100 BATCH_SIZE = 100
DB_CONFIG = { DB_CONFIG = {
"host": "127.0.0.1", "host": "192.168.1.76",
"port": 3307, "port": 3307,
"user": "root", "user": "root",
"password": "Vlado9674+", "password": "Vlado9674+",

View File

@@ -6,16 +6,44 @@ import requests
from pathlib import Path from pathlib import Path
from datetime import datetime from datetime import datetime
from dateutil import parser from dateutil import parser
import sys
# Force UTF-8 output even under Windows Task Scheduler
import sys
try:
sys.stdout.reconfigure(encoding='utf-8')
sys.stderr.reconfigure(encoding='utf-8')
except AttributeError:
# Python < 3.7 fallback (not needed for you, but safe)
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
# ================================
# 🛡 SAFE PRINT FOR CP1250 / Emoji
# ================================
def safe_print(text: str):
enc = sys.stdout.encoding or ""
if not enc.lower().startswith("utf"):
# strip emoji + characters outside BMP
text = ''.join(ch for ch in text if ord(ch) < 65536)
try:
print(text)
except UnicodeEncodeError:
# final fallback to ASCII only
text = ''.join(ch for ch in text if ord(ch) < 128)
print(text)
# ================================ # ================================
# 🔧 CONFIGURATION # 🔧 CONFIGURATION
# ================================ # ================================
TOKEN_PATH = Path("token.txt") TOKEN_PATH = Path("token.txt")
CLINIC_SLUG = "mudr-buzalkova" CLINIC_SLUG = "mudr-buzalkova"
LIMIT = 300 # stáhneme posledních 300 ukončených požadavků LIMIT = 300
DB_CONFIG = { DB_CONFIG = {
"host": "127.0.0.1", "host": "192.168.1.76",
"port": 3307, "port": 3307,
"user": "root", "user": "root",
"password": "Vlado9674+", "password": "Vlado9674+",
@@ -24,7 +52,7 @@ DB_CONFIG = {
"cursorclass": pymysql.cursors.DictCursor, "cursorclass": pymysql.cursors.DictCursor,
} }
# ⭐ Ověřený dotaz s lastMessage # ⭐ GraphQL query
GRAPHQL_QUERY = r""" GRAPHQL_QUERY = r"""
query ClinicRequestList2( query ClinicRequestList2(
$clinicSlug: String!, $clinicSlug: String!,
@@ -71,33 +99,31 @@ def read_token(path: Path) -> str:
return tok.split(" ", 1)[1] return tok.split(" ", 1)[1]
return tok return tok
# ================================ # ================================
# DATETIME PARSER (UTC → MySQL) # DATETIME PARSER
# ================================ # ================================
def to_mysql_dt(iso_str): def to_mysql_dt(iso_str):
if not iso_str: if not iso_str:
return None return None
try: try:
dt = parser.isoparse(iso_str) # ISO8601 → aware datetime (UTC) dt = parser.isoparse(iso_str)
dt = dt.astimezone() # převede na lokální čas (CET/CEST) dt = dt.astimezone()
return dt.strftime("%Y-%m-%d %H:%M:%S") return dt.strftime("%Y-%m-%d %H:%M:%S")
except: except:
return None return None
# ================================ # ================================
# UPSERT WITH MERGED UPDATED TIME # UPSERT
# ================================ # ================================
def upsert(conn, r): def upsert(conn, r):
p = r.get("extendedPatient") or {} p = r.get("extendedPatient") or {}
# API pole
api_updated = to_mysql_dt(r.get("updatedAt")) api_updated = to_mysql_dt(r.get("updatedAt"))
# poslední zpráva
last_msg = r.get("lastMessage") or {} last_msg = r.get("lastMessage") or {}
msg_at = to_mysql_dt(last_msg.get("createdAt")) msg_at = to_mysql_dt(last_msg.get("createdAt"))
# vybereme novější čas
def max_dt(a, b): def max_dt(a, b):
if a and b: if a and b:
return max(a, b) return max(a, b)
@@ -137,6 +163,7 @@ def upsert(conn, r):
conn.commit() conn.commit()
# ================================ # ================================
# FETCH LAST 300 DONE REQUESTS # FETCH LAST 300 DONE REQUESTS
# ================================ # ================================
@@ -162,6 +189,7 @@ def fetch_done(headers):
data = r.json()["data"]["requestsResponse"] data = r.json()["data"]["requestsResponse"]
return data.get("patientRequests", []) return data.get("patientRequests", [])
# ================================ # ================================
# MAIN # MAIN
# ================================ # ================================
@@ -175,17 +203,18 @@ def main():
conn = pymysql.connect(**DB_CONFIG) conn = pymysql.connect(**DB_CONFIG)
print(f"\n=== Downloading last {LIMIT} DONE requests @ {datetime.now():%Y-%m-%d %H:%M:%S} ===") safe_print(f"\n=== Downloading last {LIMIT} DONE requests @ {datetime.now():%Y-%m-%d %H:%M:%S} ===")
requests_list = fetch_done(headers) requests_list = fetch_done(headers)
print(f"📌 Requests returned: {len(requests_list)}") safe_print(f"📌 Requests returned: {len(requests_list)}")
for r in requests_list: for r in requests_list:
upsert(conn, r) upsert(conn, r)
conn.close() conn.close()
print("\n DONE - latest closed requests synced.\n") safe_print("\n\u2705 DONE - latest closed requests synced.\n")
# ================================
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@@ -12,6 +12,35 @@ import pymysql
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
import time import time
import sys
# Force UTF-8 output even under Windows Task Scheduler
import sys
try:
sys.stdout.reconfigure(encoding='utf-8')
sys.stderr.reconfigure(encoding='utf-8')
except AttributeError:
# Python < 3.7 fallback (not needed for you, but safe)
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
# ==============================
# 🛡 SAFE PRINT FOR CP1250 / EMOJI
# ==============================
def safe_print(text: str):
enc = sys.stdout.encoding or ""
if not enc.lower().startswith("utf"):
# strip emoji + anything above BMP
text = ''.join(ch for ch in text if ord(ch) < 65536)
try:
print(text)
except UnicodeEncodeError:
# final ASCII fallback
text = ''.join(ch for ch in text if ord(ch) < 128)
print(text)
# ============================== # ==============================
# 🔧 CONFIGURATION # 🔧 CONFIGURATION
@@ -21,7 +50,7 @@ CLINIC_SLUG = "mudr-buzalkova"
GRAPHQL_URL = "https://api.medevio.cz/graphql" GRAPHQL_URL = "https://api.medevio.cz/graphql"
DB_CONFIG = { DB_CONFIG = {
"host": "127.0.0.1", "host": "192.168.1.76",
"port": 3307, "port": 3307,
"user": "root", "user": "root",
"password": "Vlado9674+", "password": "Vlado9674+",
@@ -31,29 +60,30 @@ DB_CONFIG = {
} }
from datetime import datetime # ==============================
# 🕒 DATETIME FIXER
# ==============================
def fix_datetime(dt_str): def fix_datetime(dt_str):
"""Convert ISO 8601 string with 'Z' or ms into MySQL DATETIME format.""" """Convert ISO 8601 string with 'Z' or ms into MySQL DATETIME format."""
if not dt_str: if not dt_str:
return None return None
try: try:
# Remove trailing Z and parse flexible ISO format
return datetime.fromisoformat(dt_str.replace("Z", "").replace("+00:00", "")) return datetime.fromisoformat(dt_str.replace("Z", "").replace("+00:00", ""))
except Exception: except Exception:
return None return None
# ✅ Optional: limit which requests to process
CREATED_AFTER = "2025-01-01" # set "" to disable # Optional filter
CREATED_AFTER = "2025-01-01"
# ============================== # ==============================
# 🧮 HELPERS # 🧮 HELPERS
# ============================== # ==============================
def read_token(p: Path) -> str: def read_token(p: Path) -> str:
"""Read Bearer token from file."""
tok = p.read_text(encoding="utf-8").strip() tok = p.read_text(encoding="utf-8").strip()
if tok.startswith("Bearer "): if tok.startswith("Bearer "):
tok = tok.split(" ", 1)[1] return tok.split(" ", 1)[1]
return tok return tok
@@ -101,7 +131,7 @@ def fetch_questionnaire(headers, request_id, clinic_slug):
} }
r = requests.post(GRAPHQL_URL, json=payload, headers=headers, timeout=40) r = requests.post(GRAPHQL_URL, json=payload, headers=headers, timeout=40)
if r.status_code != 200: if r.status_code != 200:
print(f"❌ HTTP {r.status_code} for {request_id}: {r.text}") safe_print(f"❌ HTTP {r.status_code} for {request_id}: {r.text}")
return None return None
return r.json().get("data", {}).get("request") return r.json().get("data", {}).get("request")
@@ -118,23 +148,24 @@ def insert_questionnaire(cur, req):
updated_at = fix_datetime(req.get("updatedAt")) updated_at = fix_datetime(req.get("updatedAt"))
cur.execute(""" cur.execute("""
INSERT INTO medevio_questionnaires ( INSERT INTO medevio_questionnaires (
request_id, created_at, updated_at, user_note, ecrf_json request_id, created_at, updated_at, user_note, ecrf_json
) )
VALUES (%s,%s,%s,%s,%s) VALUES (%s,%s,%s,%s,%s)
ON DUPLICATE KEY UPDATE ON DUPLICATE KEY UPDATE
updated_at = VALUES(updated_at), updated_at = VALUES(updated_at),
user_note = VALUES(user_note), user_note = VALUES(user_note),
ecrf_json = VALUES(ecrf_json), ecrf_json = VALUES(ecrf_json),
updated_local = NOW() updated_local = NOW()
""", ( """, (
req.get("id"), req.get("id"),
created_at, created_at,
updated_at, updated_at,
req.get("userNote"), req.get("userNote"),
json.dumps(ecrf_data, ensure_ascii=False), json.dumps(ecrf_data, ensure_ascii=False),
)) ))
print(f" 💾 Stored questionnaire for {patient.get('surname','')} {patient.get('name','')}")
safe_print(f" 💾 Stored questionnaire for {patient.get('surname','')} {patient.get('name','')}")
# ============================== # ==============================
@@ -149,6 +180,8 @@ def main():
} }
conn = pymysql.connect(**DB_CONFIG) conn = pymysql.connect(**DB_CONFIG)
# load list of requests
with conn.cursor() as cur: with conn.cursor() as cur:
sql = """ sql = """
SELECT id, pacient_jmeno, pacient_prijmeni, createdAt, updatedAt, questionnaireprocessed SELECT id, pacient_jmeno, pacient_prijmeni, createdAt, updatedAt, questionnaireprocessed
@@ -163,26 +196,30 @@ def main():
rows = cur.fetchall() rows = cur.fetchall()
print(f"📋 Found {len(rows)} requests needing questionnaire check.") safe_print(f"📋 Found {len(rows)} requests needing questionnaire check.")
# process each one
for i, row in enumerate(rows, 1): for i, row in enumerate(rows, 1):
req_id = row["id"] req_id = row["id"]
print(f"\n[{i}/{len(rows)}] 🔍 Fetching questionnaire for {req_id} ...") safe_print(f"\n[{i}/{len(rows)}] 🔍 Fetching questionnaire for {req_id} ...")
req = fetch_questionnaire(headers, req_id, CLINIC_SLUG) req = fetch_questionnaire(headers, req_id, CLINIC_SLUG)
if not req: if not req:
print(" ⚠️ No questionnaire data found.") safe_print(" ⚠️ No questionnaire data found.")
continue continue
with conn.cursor() as cur: with conn.cursor() as cur:
insert_questionnaire(cur, req) insert_questionnaire(cur, req)
cur.execute("UPDATE pozadavky SET questionnaireprocessed = NOW() WHERE id = %s", (req_id,)) cur.execute(
"UPDATE pozadavky SET questionnaireprocessed = NOW() WHERE id = %s",
(req_id,)
)
conn.commit() conn.commit()
time.sleep(0.6) # polite pacing time.sleep(0.6)
conn.close() conn.close()
print("\n✅ Done! All questionnaires stored in MySQL table `medevio_questionnaires`.") safe_print("\n✅ Done! All questionnaires stored in MySQL table `medevio_questionnaires`.")
# ============================== # ==============================

View File

@@ -15,6 +15,34 @@ import pymysql
from pathlib import Path from pathlib import Path
from datetime import datetime from datetime import datetime
import time import time
import sys
# Force UTF-8 output even under Windows Task Scheduler
import sys
try:
sys.stdout.reconfigure(encoding='utf-8')
sys.stderr.reconfigure(encoding='utf-8')
except AttributeError:
# Python < 3.7 fallback (not needed for you, but safe)
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
# ==============================
# 🛡 SAFE PRINT FOR CP1250 / EMOJI
# ==============================
def safe_print(text: str):
enc = sys.stdout.encoding or ""
if not enc or not enc.lower().startswith("utf"):
# strip emoji + characters outside BMP for Task Scheduler (CP1250)
text = ''.join(ch for ch in text if ord(ch) < 65536)
try:
print(text)
except UnicodeEncodeError:
# fallback pure ASCII
text = ''.join(ch for ch in text if ord(ch) < 128)
print(text)
# ============================== # ==============================
# 🔧 CONFIGURATION # 🔧 CONFIGURATION
@@ -22,7 +50,7 @@ import time
TOKEN_PATH = Path("token.txt") TOKEN_PATH = Path("token.txt")
DB_CONFIG = { DB_CONFIG = {
"host": "127.0.0.1", "host": "192.168.1.76",
"port": 3307, "port": 3307,
"user": "root", "user": "root",
"password": "Vlado9674+", "password": "Vlado9674+",
@@ -94,7 +122,7 @@ def fetch_messages(headers, request_id):
r = requests.post("https://api.medevio.cz/graphql", json=payload, headers=headers, timeout=30) r = requests.post("https://api.medevio.cz/graphql", json=payload, headers=headers, timeout=30)
if r.status_code != 200: if r.status_code != 200:
print("❌ HTTP", r.status_code, "for request", request_id) safe_print(f"❌ HTTP {r.status_code} for request {request_id}")
return [] return []
return r.json().get("data", {}).get("messages", []) or [] return r.json().get("data", {}).get("messages", []) or []
@@ -158,7 +186,7 @@ def insert_download(cur, req_id, msg, existing_ids):
return return
if attachment_id in existing_ids: if attachment_id in existing_ids:
return # skip duplicates return
url = mr.get("downloadUrl") or mr.get("url") url = mr.get("downloadUrl") or mr.get("url")
if not url: if not url:
@@ -169,7 +197,7 @@ def insert_download(cur, req_id, msg, existing_ids):
r.raise_for_status() r.raise_for_status()
data = r.content data = r.content
except Exception as e: except Exception as e:
print("⚠️ Failed to download:", e) safe_print(f"⚠️ Failed to download: {e}")
return return
filename = url.split("/")[-1].split("?")[0] filename = url.split("/")[-1].split("?")[0]
@@ -216,7 +244,7 @@ def main():
cur.execute("SELECT attachment_id FROM medevio_downloads") cur.execute("SELECT attachment_id FROM medevio_downloads")
existing_ids = {row["attachment_id"] for row in cur.fetchall()} existing_ids = {row["attachment_id"] for row in cur.fetchall()}
print(f"📦 Already downloaded attachments: {len(existing_ids)}\n") safe_print(f"📦 Already downloaded attachments: {len(existing_ids)}\n")
# ---- Select pozadavky needing message sync # ---- Select pozadavky needing message sync
sql = """ sql = """
@@ -229,12 +257,12 @@ def main():
cur.execute(sql) cur.execute(sql)
requests_to_process = cur.fetchall() requests_to_process = cur.fetchall()
print(f"📋 Found {len(requests_to_process)} pozadavků requiring message sync.\n") safe_print(f"📋 Found {len(requests_to_process)} pozadavků requiring message sync.\n")
# ---- Process each pozadavek # ---- Process each record
for idx, row in enumerate(requests_to_process, 1): for idx, row in enumerate(requests_to_process, 1):
req_id = row["id"] req_id = row["id"]
print(f"[{idx}/{len(requests_to_process)}] Processing {req_id}") safe_print(f"[{idx}/{len(requests_to_process)}] Processing {req_id}")
messages = fetch_messages(headers, req_id) messages = fetch_messages(headers, req_id)
@@ -248,11 +276,11 @@ def main():
cur.execute("UPDATE pozadavky SET messagesProcessed = NOW() WHERE id = %s", (req_id,)) cur.execute("UPDATE pozadavky SET messagesProcessed = NOW() WHERE id = %s", (req_id,))
conn.commit() conn.commit()
print(f"{len(messages)} messages saved\n") safe_print(f"{len(messages)} messages saved\n")
time.sleep(0.25) time.sleep(0.25)
conn.close() conn.close()
print("🎉 Done!") safe_print("🎉 Done!")
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -0,0 +1,293 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Delta sync Medevio communication.
Stáhne pouze zprávy změněné po messagesProcessed pro každý požadavek.
"""
import json
import requests
import pymysql
from pathlib import Path
from datetime import datetime
import time
import sys
# ==============================
# UTF-8 SAFE OUTPUT
# ==============================
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")
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
createdAt
updatedAt
}
}
}
"""
# ==============================
# HELPERS
# ==============================
def parse_dt(s):
if not s:
return None
try:
return datetime.fromisoformat(s.replace("Z", "+00:00"))
except Exception:
return None
def read_token(path: Path) -> str:
tok = path.read_text(encoding="utf-8").strip()
return tok.replace("Bearer ", "")
# ==============================
# FETCH MESSAGES (DELTA)
# ==============================
def fetch_messages(headers, request_id, updated_since):
payload = {
"operationName": "UseMessages_ListMessages",
"query": GRAPHQL_QUERY_MESSAGES,
"variables": {
"requestId": request_id,
"updatedSince": updated_since,
},
}
r = requests.post(
"https://api.medevio.cz/graphql",
json=payload,
headers=headers,
timeout=30
)
if r.status_code != 200:
safe_print(f"❌ HTTP {r.status_code} for request {request_id}")
return []
j = r.json()
if "errors" in j:
safe_print(f"❌ GraphQL error for {request_id}: {j['errors']}")
return []
return j.get("data", {}).get("messages", []) or []
# ==============================
# INSERT 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
mr = msg.get("medicalRecord") or {}
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)
"""
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")
))
# ==============================
# INSERT ATTACHMENT (DEDUP)
# ==============================
def insert_download(cur, req_id, msg, existing_ids):
mr = msg.get("medicalRecord") or {}
attachment_id = mr.get("id")
if not attachment_id or attachment_id in existing_ids:
return
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:
safe_print(f"⚠️ Attachment download failed: {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)
# existing attachments
with conn.cursor() as cur:
cur.execute("SELECT attachment_id FROM medevio_downloads")
existing_ids = {r["attachment_id"] for r in cur.fetchall()}
# select requests needing sync
with conn.cursor() as cur:
cur.execute("""
SELECT id, messagesProcessed
FROM pozadavky
WHERE messagesProcessed IS NULL
OR messagesProcessed < updatedAt
""")
rows = cur.fetchall()
safe_print(f"📋 Found {len(rows)} requests for message delta-sync\n")
for i, row in enumerate(rows, 1):
req_id = row["id"]
updated_since = row["messagesProcessed"]
if updated_since:
updated_since = updated_since.replace(microsecond=0).isoformat() + "Z"
safe_print(f"[{i}/{len(rows)}] {req_id}")
messages = fetch_messages(headers, req_id, updated_since)
if not messages:
safe_print(" ⏭ No new messages")
else:
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()
safe_print(f"{len(messages)} new/updated messages")
with conn.cursor() as cur:
cur.execute(
"UPDATE pozadavky SET messagesProcessed = NOW() WHERE id = %s",
(req_id,)
)
conn.commit()
time.sleep(0.25)
conn.close()
safe_print("\n🎉 Delta message sync DONE")
# ==============================
if __name__ == "__main__":
main()

View File

@@ -14,6 +14,36 @@ import pymysql
from pathlib import Path from pathlib import Path
from datetime import datetime from datetime import datetime
import time import time
import sys
# Force UTF-8 output even under Windows Task Scheduler
import sys
try:
sys.stdout.reconfigure(encoding='utf-8')
sys.stderr.reconfigure(encoding='utf-8')
except AttributeError:
# Python < 3.7 fallback (not needed for you, but safe)
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
# ==============================
# 🛡 SAFE PRINT FOR CP1250 / EMOJI
# ==============================
def safe_print(text: str):
enc = sys.stdout.encoding or ""
if not enc or not enc.lower().startswith("utf"):
# strip emoji + characters outside BMP
text = ''.join(ch for ch in text if ord(ch) < 65536)
try:
print(text)
except UnicodeEncodeError:
# ASCII fallback
text = ''.join(ch for ch in text if ord(ch) < 128)
print(text)
# ============================== # ==============================
# 🔧 CONFIGURATION # 🔧 CONFIGURATION
@@ -22,7 +52,7 @@ TOKEN_PATH = Path("token.txt")
CLINIC_SLUG = "mudr-buzalkova" CLINIC_SLUG = "mudr-buzalkova"
DB_CONFIG = { DB_CONFIG = {
"host": "127.0.0.1", "host": "192.168.1.76",
"port": 3307, "port": 3307,
"user": "root", "user": "root",
"password": "Vlado9674+", "password": "Vlado9674+",
@@ -67,6 +97,7 @@ def read_token(p: Path) -> str:
tok = p.read_text(encoding="utf-8").strip() tok = p.read_text(encoding="utf-8").strip()
return tok.split(" ", 1)[1] if tok.startswith("Bearer ") else tok return tok.split(" ", 1)[1] if tok.startswith("Bearer ") else tok
# ============================== # ==============================
# 📡 FETCH ATTACHMENTS # 📡 FETCH ATTACHMENTS
# ============================== # ==============================
@@ -78,42 +109,40 @@ def fetch_attachments(headers, request_id):
} }
r = requests.post("https://api.medevio.cz/graphql", json=payload, headers=headers, timeout=30) r = requests.post("https://api.medevio.cz/graphql", json=payload, headers=headers, timeout=30)
if r.status_code != 200: if r.status_code != 200:
print(f"❌ HTTP {r.status_code} for request {request_id}") safe_print(f"❌ HTTP {r.status_code} for request {request_id}")
return [] return []
return r.json().get("data", {}).get("patientRequestMedicalRecords", []) return r.json().get("data", {}).get("patientRequestMedicalRecords", [])
# ============================== # ==============================
# 💾 SAVE TO MYSQL (clean version) # 💾 SAVE TO MYSQL
# ============================== # ==============================
def insert_download(cur, req_id, a, m, created_date, existing_ids): def insert_download(cur, req_id, a, m, created_date, existing_ids):
attachment_id = a.get("id") attachment_id = a.get("id")
if attachment_id in existing_ids: if attachment_id in existing_ids:
print(f" ⏭️ Already downloaded {attachment_id}") safe_print(f" ⏭️ Already downloaded {attachment_id}")
return False return False
url = m.get("downloadUrl") url = m.get("downloadUrl")
if not url: if not url:
print(" ⚠️ Missing download URL") safe_print(" ⚠️ Missing download URL")
return False return False
filename = extract_filename_from_url(url) filename = extract_filename_from_url(url)
# Download file
try: try:
r = requests.get(url, timeout=30) r = requests.get(url, timeout=30)
r.raise_for_status() r.raise_for_status()
content = r.content content = r.content
except Exception as e: except Exception as e:
print(f" ⚠️ Download failed {url}: {e}") safe_print(f" ⚠️ Download failed {url}: {e}")
return False return False
file_size = len(content) file_size = len(content)
attachment_type = a.get("attachmentType") attachment_type = a.get("attachmentType")
content_type = m.get("contentType") content_type = m.get("contentType")
# 🚨 CLEAN INSERT — no patient_jmeno/no patient_prijmeni
cur.execute(""" cur.execute("""
INSERT INTO medevio_downloads ( INSERT INTO medevio_downloads (
request_id, attachment_id, attachment_type, request_id, attachment_id, attachment_type,
@@ -136,7 +165,7 @@ def insert_download(cur, req_id, a, m, created_date, existing_ids):
)) ))
existing_ids.add(attachment_id) existing_ids.add(attachment_id)
print(f" 💾 Saved {filename} ({file_size/1024:.1f} kB)") safe_print(f" 💾 Saved {filename} ({file_size/1024:.1f} kB)")
return True return True
@@ -152,11 +181,12 @@ def main():
conn = pymysql.connect(**DB_CONFIG) conn = pymysql.connect(**DB_CONFIG)
# Load existing IDs # Load existing attachments
with conn.cursor() as cur: with conn.cursor() as cur:
cur.execute("SELECT attachment_id FROM medevio_downloads") cur.execute("SELECT attachment_id FROM medevio_downloads")
existing_ids = {row["attachment_id"] for row in cur.fetchall()} existing_ids = {row["attachment_id"] for row in cur.fetchall()}
print(f"{len(existing_ids)} attachments already saved.")
safe_print(f"{len(existing_ids)} attachments already saved.")
# Build query for pozadavky # Build query for pozadavky
sql = """ sql = """
@@ -173,7 +203,7 @@ def main():
cur.execute(sql, params) cur.execute(sql, params)
req_rows = cur.fetchall() req_rows = cur.fetchall()
print(f"📋 Found {len(req_rows)} pozadavky to process.") safe_print(f"📋 Found {len(req_rows)} pozadavky to process.")
# Process each pozadavek # Process each pozadavek
for i, row in enumerate(req_rows, 1): for i, row in enumerate(req_rows, 1):
@@ -182,12 +212,12 @@ def main():
jmeno = row.get("pacient_jmeno") or "" jmeno = row.get("pacient_jmeno") or ""
created_date = row.get("createdAt") or datetime.now() created_date = row.get("createdAt") or datetime.now()
print(f"\n[{i}/{len(req_rows)}] 🧾 {prijmeni}, {jmeno} ({req_id})") safe_print(f"\n[{i}/{len(req_rows)}] 🧾 {prijmeni}, {jmeno} ({req_id})")
attachments = fetch_attachments(headers, req_id) attachments = fetch_attachments(headers, req_id)
if not attachments: if not attachments:
print(" ⚠️ No attachments found") safe_print(" ⚠️ No attachments found")
with conn.cursor() as cur: with conn.cursor() as cur:
cur.execute("UPDATE pozadavky SET attachmentsProcessed = NOW() WHERE id = %s", (req_id,)) cur.execute("UPDATE pozadavky SET attachmentsProcessed = NOW() WHERE id = %s", (req_id,))
conn.commit() conn.commit()
@@ -199,17 +229,16 @@ def main():
insert_download(cur, req_id, a, m, created_date, existing_ids) insert_download(cur, req_id, a, m, created_date, existing_ids)
conn.commit() conn.commit()
# Mark processed
with conn.cursor() as cur: with conn.cursor() as cur:
cur.execute("UPDATE pozadavky SET attachmentsProcessed = NOW() WHERE id = %s", (req_id,)) cur.execute("UPDATE pozadavky SET attachmentsProcessed = NOW() WHERE id = %s", (req_id,))
conn.commit() conn.commit()
print(f" ✅ Done ({len(attachments)} attachments)") safe_print(f" ✅ Done ({len(attachments)} attachments)")
time.sleep(0.3) time.sleep(0.3)
conn.close() conn.close()
print("\n🎯 All attachments processed.") safe_print("\n🎯 All attachments processed.")
# ============================== # ==============================
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -8,12 +8,40 @@ import re
from pathlib import Path from pathlib import Path
from datetime import datetime from datetime import datetime
import time import time
import sys
# Force UTF-8 output even under Windows Task Scheduler
import sys
try:
sys.stdout.reconfigure(encoding='utf-8')
sys.stderr.reconfigure(encoding='utf-8')
except AttributeError:
# Python < 3.7 fallback (not needed for you, but safe)
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
# ==============================
# 🛡 SAFE PRINT FOR CP1250 / EMOJI
# ==============================
def safe_print(text: str = ""):
enc = sys.stdout.encoding or ""
if not enc.lower().startswith("utf"):
# Strip emoji and characters outside BMP for Task Scheduler
text = ''.join(ch for ch in text if ord(ch) < 65536)
try:
print(text)
except UnicodeEncodeError:
# ASCII fallback
text = ''.join(ch for ch in text if ord(ch) < 128)
print(text)
# ============================== # ==============================
# ⚙️ CONFIGURATION # ⚙️ CONFIGURATION
# ============================== # ==============================
DB_CONFIG = { DB_CONFIG = {
"host": "127.0.0.1", "host": "192.168.1.76",
"port": 3307, "port": 3307,
"user": "root", "user": "root",
"password": "Vlado9674+", "password": "Vlado9674+",
@@ -21,7 +49,7 @@ DB_CONFIG = {
"charset": "utf8mb4", "charset": "utf8mb4",
} }
BASE_DIR = Path(r"z:\Dropbox\Ordinace\Dokumentace_ke_zpracování\MP") BASE_DIR = Path(r"u:\Dropbox\Ordinace\Dokumentace_ke_zpracování\MP")
BASE_DIR.mkdir(parents=True, exist_ok=True) BASE_DIR.mkdir(parents=True, exist_ok=True)
@@ -31,24 +59,15 @@ def sanitize_name(name: str) -> str:
def make_abbrev(title: str) -> str: def make_abbrev(title: str) -> str:
"""
Create abbreviation from displayTitle:
- First letter of each word
- Keep digits together
- Uppercase
"""
if not title: if not title:
return "" return ""
words = re.findall(r"[A-Za-zÁ-Žá-ž0-9]+", title) words = re.findall(r"[A-Za-zÁ-Žá-ž0-9]+", title)
abbr = "" abbr = ""
for w in words: for w in words:
if w.isdigit(): if w.isdigit():
abbr += w abbr += w
else: else:
abbr += w[0] abbr += w[0]
return abbr.upper() return abbr.upper()
@@ -56,28 +75,20 @@ def make_abbrev(title: str) -> str:
# 🧹 DELETE UNEXPECTED FILES # 🧹 DELETE UNEXPECTED FILES
# ============================== # ==============================
def clean_folder(folder: Path, valid_files: set): def clean_folder(folder: Path, valid_files: set):
"""
Remove unexpected files.
RULE:
- Files starting with `▲` are ALWAYS kept.
"""
if not folder.exists(): if not folder.exists():
return return
for f in folder.iterdir(): for f in folder.iterdir():
if f.is_file(): if f.is_file():
# zpracované soubory (▲filename.pdf) nikdy nemažeme
if f.name.startswith(""): if f.name.startswith(""):
continue continue
sanitized = sanitize_name(f.name) sanitized = sanitize_name(f.name)
if sanitized not in valid_files: if sanitized not in valid_files:
print(f"🗑️ Removing unexpected file: {f.name}") safe_print(f"🗑️ Removing unexpected file: {f.name}")
try: try:
f.unlink() f.unlink()
except Exception as e: except Exception as e:
print(f"⚠️ Could not delete {f}: {e}") safe_print(f"⚠️ Could not delete {f}: {e}")
# ============================== # ==============================
@@ -88,7 +99,7 @@ conn = pymysql.connect(**DB_CONFIG)
cur_meta = conn.cursor(pymysql.cursors.DictCursor) cur_meta = conn.cursor(pymysql.cursors.DictCursor)
cur_blob = conn.cursor() cur_blob = conn.cursor()
print("🔍 Loading metadata from DB (FAST)…") safe_print("🔍 Loading metadata from DB (FAST)…")
cur_meta.execute(""" cur_meta.execute("""
SELECT d.id AS download_id, SELECT d.id AS download_id,
@@ -105,13 +116,25 @@ cur_meta.execute("""
""") """)
rows = cur_meta.fetchall() rows = cur_meta.fetchall()
print(f"📋 Found {len(rows)} attachment records.\n") safe_print(f"📋 Found {len(rows)} attachment records.\n")
# ============================== # ==============================
# 🧠 MAIN LOOP # 🧠 MAIN LOOP WITH PROGRESS
# ============================== # ==============================
unique_request_ids = []
seen = set()
for r in rows:
req_id = r["request_id"]
if req_id not in seen:
unique_request_ids.append(req_id)
seen.add(req_id)
total_requests = len(unique_request_ids)
safe_print(f"🔄 Processing {total_requests} unique requests...\n")
processed_requests = set() processed_requests = set()
current_index = 0
for r in rows: for r in rows:
req_id = r["request_id"] req_id = r["request_id"]
@@ -120,14 +143,19 @@ for r in rows:
continue continue
processed_requests.add(req_id) processed_requests.add(req_id)
# ========== FETCH ALL VALID FILES FOR THIS REQUEST ========== current_index += 1
percent = (current_index / total_requests) * 100
safe_print(f"\n[ {percent:5.1f}% ] Processing request {current_index} / {total_requests}{req_id}")
# ========== FETCH VALID FILENAMES ==========
cur_meta.execute( cur_meta.execute(
"SELECT filename FROM medevio_downloads WHERE request_id=%s", "SELECT filename FROM medevio_downloads WHERE request_id=%s",
(req_id,) (req_id,)
) )
valid_files = {sanitize_name(row["filename"]) for row in cur_meta.fetchall()} valid_files = {sanitize_name(row["filename"]) for row in cur_meta.fetchall()}
# ========== FOLDER NAME BASED ON UPDATEDAT ========== # ========== BUILD FOLDER NAME ==========
updated_at = r["req_updated_at"] or datetime.now() updated_at = r["req_updated_at"] or datetime.now()
date_str = updated_at.strftime("%Y-%m-%d") date_str = updated_at.strftime("%Y-%m-%d")
@@ -140,17 +168,14 @@ for r in rows:
f"{date_str} {prijmeni}, {jmeno} [{abbr}] {req_id}" f"{date_str} {prijmeni}, {jmeno} [{abbr}] {req_id}"
) )
# ========== DETECT EXISTING FOLDER (WITH OR WITHOUT ▲) ========== # ========== DETECT EXISTING FOLDER ==========
existing_folder = None existing_folder = None
folder_has_flag = False
for f in BASE_DIR.iterdir(): for f in BASE_DIR.iterdir():
if f.is_dir() and req_id in f.name: if f.is_dir() and req_id in f.name:
existing_folder = f existing_folder = f
folder_has_flag = ("" in f.name)
break break
# pokud složka existuje → pracujeme v ní
main_folder = existing_folder if existing_folder else BASE_DIR / clean_folder_name main_folder = existing_folder if existing_folder else BASE_DIR / clean_folder_name
# ========== MERGE DUPLICATES ========== # ========== MERGE DUPLICATES ==========
@@ -160,14 +185,13 @@ for r in rows:
] ]
for dup in possible_dups: for dup in possible_dups:
print(f"♻️ Merging duplicate folder: {dup.name}") safe_print(f"♻️ Merging duplicate folder: {dup.name}")
clean_folder(dup, valid_files) clean_folder(dup, valid_files)
main_folder.mkdir(parents=True, exist_ok=True) main_folder.mkdir(parents=True, exist_ok=True)
for f in dup.iterdir(): for f in dup.iterdir():
if f.is_file(): if f.is_file():
# prostě přesuneme, ▲ případně zůstane v názvu
target = main_folder / f.name target = main_folder / f.name
if not target.exists(): if not target.exists():
f.rename(target) f.rename(target)
@@ -185,11 +209,9 @@ for r in rows:
dest_plain = main_folder / filename dest_plain = main_folder / filename
dest_marked = main_folder / ("" + filename) dest_marked = main_folder / ("" + filename)
# soubor už existuje (buď filename, nebo ▲filename)
if dest_plain.exists() or dest_marked.exists(): if dest_plain.exists() or dest_marked.exists():
continue continue
# stáhneme nový soubor → znamená že se má odstranit ▲ složky
added_new_file = True added_new_file = True
cur_blob.execute( cur_blob.execute(
@@ -208,31 +230,22 @@ for r in rows:
with open(dest_plain, "wb") as f: with open(dest_plain, "wb") as f:
f.write(content) f.write(content)
print(f"💾 Wrote: {dest_plain.relative_to(BASE_DIR)}") safe_print(f"💾 Wrote: {dest_plain.relative_to(BASE_DIR)}")
# ============================== # ========== REMOVE ▲ FLAG IF NEW FILES ADDED ==========
# 🔵 REMOVE FOLDER-LEVEL ▲ ONLY IF NEW FILE ADDED if added_new_file and "" in main_folder.name:
# ============================== new_name = main_folder.name.replace("", "").strip()
if added_new_file: new_path = main_folder.parent / new_name
# složka se má přejmenovat bez ▲
if "" in main_folder.name:
new_name = main_folder.name.replace("", "")
new_name = new_name.strip() # pro jistotu
new_path = main_folder.parent / new_name
if new_path != main_folder: if new_path != main_folder:
try: try:
main_folder.rename(new_path) main_folder.rename(new_path)
print(f"🔄 Folder flag ▲ removed → {new_name}") safe_print(f"🔄 Folder flag ▲ removed → {new_name}")
main_folder = new_path main_folder = new_path
except Exception as e: except Exception as e:
print(f"⚠️ Could not rename folder: {e}") safe_print(f"⚠️ Could not rename folder: {e}")
else:
# žádné nové soubory → NIKDY nesahat na název složky
pass
safe_print("\n🎯 Export complete.\n")
print("\n🎯 Export complete.\n")
cur_blob.close() cur_blob.close()
cur_meta.close() cur_meta.close()

View File

@@ -1,6 +1,17 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import importlib.util
import sys
from pathlib import Path
# Load FunctionsLoader
FUNCTIONS_LOADER_PATH = Path(r"C:\Reporting\Functions\FunctionsLoader.py")
spec = importlib.util.spec_from_file_location("FunctionsLoader", FUNCTIONS_LOADER_PATH)
FunctionsLoader = importlib.util.module_from_spec(spec)
sys.modules["FunctionsLoader"] = FunctionsLoader
spec.loader.exec_module(FunctionsLoader)
""" """
Spustí všechny PRAVIDELNÉ skripty v daném pořadí: Spustí všechny PRAVIDELNÉ skripty v daném pořadí:
@@ -12,6 +23,14 @@ Spustí všechny PRAVIDELNÉ skripty v daném pořadí:
5) PRAVIDELNE_5_SaveToFileSystem incremental.py 5) PRAVIDELNE_5_SaveToFileSystem incremental.py
""" """
import time, socket
for _ in range(30):
try:
socket.create_connection(("192.168.1.76", 3307), timeout=3).close()
break
except OSError:
time.sleep(10)
import sys import sys
import subprocess import subprocess
from pathlib import Path from pathlib import Path

136
10ReadPozadavky/test.py Normal file
View File

@@ -0,0 +1,136 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Orchestrator for all PRAVIDELNE scripts in exact order.
"""
import time, socket
for _ in range(30):
try:
socket.create_connection(("192.168.1.76", 3307), timeout=3).close()
break
except OSError:
time.sleep(10)
import sys
import subprocess
from pathlib import Path
from datetime import datetime
# =====================================================================
# Import EXACT Functions.py from: C:\Reporting\Fio\Functions.py
# This bypasses all other Functions.py files in the system.
# =====================================================================
import importlib.util
FUNCTIONS_FILE = Path(r"C:\Reporting\Fio\Functions.py")
spec = importlib.util.spec_from_file_location("Functions_FIO", FUNCTIONS_FILE)
Functions_FIO = importlib.util.module_from_spec(spec)
sys.modules["Functions_FIO"] = Functions_FIO
spec.loader.exec_module(Functions_FIO)
# correct WhatsApp function
SendWhatsAppMessage = Functions_FIO.SendWhatsAppMessage
# =====================================================================
# General Orchestrator Settings
# =====================================================================
# folder where orchestrator + sub-scripts live
BASE_DIR = Path(__file__).resolve().parent
SCRIPTS_IN_ORDER = [
"PRAVIDELNE_0_READ_ALL_ACTIVE_POZADAVKY.py",
"PRAVIDELNE_1_ReadLast300DonePozadavku.py",
"PRAVIDELNE_2_ReadPoznamky.py",
"PRAVIDELNE_3_StahniKomunikaci.py",
"PRAVIDELNE_4_StahniPrilohyUlozDoMySQL.py",
"PRAVIDELNE_5_SaveToFileSystem incremental.py",
]
LOG_FILE = BASE_DIR / "PRAVIDELNE_log.txt"
# =====================================================================
# Logging + WhatsApp wrappers
# =====================================================================
def log(msg: str):
ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
line = f"[{ts}] {msg}"
print(line)
try:
with LOG_FILE.open("a", encoding="utf-8") as f:
f.write(line + "\n")
except:
pass
def whatsapp_notify(text: str):
"""WhatsApp message wrapper — never allowed to crash orchestrator"""
try:
SendWhatsAppMessage(text)
except:
pass
# =====================================================================
# Main orchestrator
# =====================================================================
def main():
log("=== START pravidelného běhu ===")
whatsapp_notify("🏁 *PRAVIDELNÉ skripty: START*")
for script_name in SCRIPTS_IN_ORDER:
script_path = BASE_DIR / script_name
if not script_path.exists():
err = f"❌ Skript nenalezen: {script_path}"
log(err)
whatsapp_notify(err)
continue
log(f"▶ Spouštím: {script_path.name}")
whatsapp_notify(f"▶ *Spouštím:* {script_path.name}")
try:
result = subprocess.run(
[sys.executable, str(script_path)],
cwd=str(BASE_DIR),
capture_output=True,
text=True,
encoding="utf-8",
errors="ignore",
)
except Exception as e:
err = f"💥 Chyba při spouštění {script_path.name}: {e}"
log(err)
whatsapp_notify(err)
continue
# return code
rc_msg = f"{script_path.name} return code: {result.returncode}"
log(rc_msg)
whatsapp_notify(rc_msg)
# stderr (warnings/errors)
if result.stderr:
err_msg = f"⚠ stderr v {script_path.name}:\n{result.stderr.strip()}"
log(err_msg)
whatsapp_notify(err_msg)
log("=== KONEC pravidelného běhu ===")
whatsapp_notify("✅ *PRAVIDELNÉ skripty: KONEC*\n")
# =====================================================================
# Entry point
# =====================================================================
if __name__ == "__main__":
main()

View File

@@ -6,13 +6,40 @@ import requests
from pathlib import Path from pathlib import Path
from datetime import datetime from datetime import datetime
from dateutil import parser from dateutil import parser
import time
import sys
# ================================ # ================================
# 🔧 CONFIGURATION # 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") TOKEN_PATH = Path("token.txt")
CLINIC_SLUG = "mudr-buzalkova" CLINIC_SLUG = "mudr-buzalkova"
LIMIT = 300 # stáhneme posledních 300 ukončených požadavků
BATCH_SIZE = 500
STATES = ["ACTIVE", "DONE"] # explicitně jinak API vrací jen ACTIVE
DB_CONFIG = { DB_CONFIG = {
"host": "192.168.1.76", "host": "192.168.1.76",
@@ -24,7 +51,6 @@ DB_CONFIG = {
"cursorclass": pymysql.cursors.DictCursor, "cursorclass": pymysql.cursors.DictCursor,
} }
# ⭐ Ověřený dotaz s lastMessage
GRAPHQL_QUERY = r""" GRAPHQL_QUERY = r"""
query ClinicRequestList2( query ClinicRequestList2(
$clinicSlug: String!, $clinicSlug: String!,
@@ -62,6 +88,7 @@ query ClinicRequestList2(
} }
""" """
# ================================ # ================================
# TOKEN # TOKEN
# ================================ # ================================
@@ -71,39 +98,32 @@ def read_token(path: Path) -> str:
return tok.split(" ", 1)[1] return tok.split(" ", 1)[1]
return tok return tok
# ================================ # ================================
# DATETIME PARSER (UTC → MySQL) # DATETIME PARSER
# ================================ # ================================
def to_mysql_dt(iso_str): def to_mysql_dt(iso_str):
if not iso_str: if not iso_str:
return None return None
try: try:
dt = parser.isoparse(iso_str) # ISO8601 → aware datetime (UTC) dt = parser.isoparse(iso_str)
dt = dt.astimezone() # převede na lokální čas (CET/CEST) if dt.tzinfo is None:
return dt.strftime("%Y-%m-%d %H:%M:%S") dt = dt.replace(tzinfo=datetime.now().astimezone().tzinfo)
except: return dt.astimezone().strftime("%Y-%m-%d %H:%M:%S")
except Exception:
return None return None
# ================================ # ================================
# UPSERT WITH MERGED UPDATED TIME # UPSERT
# ================================ # ================================
def upsert(conn, r): def upsert(conn, r):
p = r.get("extendedPatient") or {} p = r.get("extendedPatient") or {}
# API pole
api_updated = to_mysql_dt(r.get("updatedAt")) api_updated = to_mysql_dt(r.get("updatedAt"))
msg_updated = to_mysql_dt((r.get("lastMessage") or {}).get("createdAt"))
# poslední zpráva final_updated = max(filter(None, [api_updated, msg_updated]), default=None)
last_msg = r.get("lastMessage") or {}
msg_at = to_mysql_dt(last_msg.get("createdAt"))
# vybereme novější čas
def max_dt(a, b):
if a and b:
return max(a, b)
return a or b
final_updated = max_dt(api_updated, msg_at)
sql = """ sql = """
INSERT INTO pozadavky ( INSERT INTO pozadavky (
@@ -134,33 +154,34 @@ def upsert(conn, r):
with conn.cursor() as cur: with conn.cursor() as cur:
cur.execute(sql, vals) cur.execute(sql, vals)
conn.commit() conn.commit()
# ================================ # ================================
# FETCH LAST 300 DONE REQUESTS # FETCH PAGE (per state)
# ================================ # ================================
def fetch_done(headers): def fetch_state(headers, state, offset):
vars = { variables = {
"clinicSlug": CLINIC_SLUG, "clinicSlug": CLINIC_SLUG,
"queueId": None, "queueId": None,
"queueAssignment": "ANY", "queueAssignment": "ANY",
"pageInfo": {"first": LIMIT, "offset": 0}, "state": state,
"pageInfo": {"first": BATCH_SIZE, "offset": offset},
"locale": "cs", "locale": "cs",
"state": "DONE",
} }
payload = { payload = {
"operationName": "ClinicRequestList2", "operationName": "ClinicRequestList2",
"query": GRAPHQL_QUERY, "query": GRAPHQL_QUERY,
"variables": vars, "variables": variables,
} }
r = requests.post("https://api.medevio.cz/graphql", json=payload, headers=headers) r = requests.post("https://api.medevio.cz/graphql", json=payload, headers=headers)
r.raise_for_status() r.raise_for_status()
data = r.json()["data"]["requestsResponse"] data = r.json()["data"]["requestsResponse"]
return data.get("patientRequests", []) return data.get("patientRequests", []), data.get("count", 0)
# ================================ # ================================
# MAIN # MAIN
@@ -175,17 +196,44 @@ def main():
conn = pymysql.connect(**DB_CONFIG) conn = pymysql.connect(**DB_CONFIG)
print(f"\n=== Downloading last {LIMIT} DONE requests @ {datetime.now():%Y-%m-%d %H:%M:%S} ===") safe_print(f"\n=== FULL Medevio READ-ALL sync @ {datetime.now():%Y-%m-%d %H:%M:%S} ===")
requests_list = fetch_done(headers) grand_total = 0
print(f"📌 Requests returned: {len(requests_list)}")
for r in requests_list: for state in STATES:
upsert(conn, r) 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() conn.close()
print("\nDONE - latest closed requests synced.\n") safe_print(f"\nHOTOVO celkem zpracováno {grand_total} požadavků\n")
# ================================
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@@ -0,0 +1,279 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Fetches messages from Medevio API.
Modes:
- Incremental (default): Only requests where messagesProcessed IS NULL or < updatedAt
- Full resync (--full): Fetches ALL messages for ALL pozadavky
"""
import zlib
import json
import requests
import pymysql
from pathlib import Path
from datetime import datetime
import time
import argparse
# ==============================
# 🔧 CONFIGURATION
# ==============================
TOKEN_PATH = Path("../10ReadPozadavky/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():
parser = argparse.ArgumentParser()
parser.add_argument("--full", action="store_true", help="Load messages for ALL pozadavky")
# Force full mode ON
args = parser.parse_args(args=["--full"])
# args = parser.parse_args()
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 to process
with conn.cursor() as cur:
if args.full:
print("🔁 FULL REFRESH MODE: Fetching messages for ALL pozadavky!\n")
cur.execute("SELECT id FROM pozadavky")
else:
print("📥 Incremental mode: Only syncing updated pozadavky.\n")
cur.execute("""
SELECT id FROM pozadavky
WHERE messagesProcessed IS NULL
OR messagesProcessed < updatedAt
""")
requests_to_process = cur.fetchall()
# =================================
# ⏩ SKIP FIRST 3100 AS YESTERDAY
# =================================
SKIP = 3100
if len(requests_to_process) > SKIP:
print(f"⏩ Skipping first {SKIP} pozadavky (already processed yesterday).")
requests_to_process = requests_to_process[SKIP:]
else:
print("⚠️ Not enough pozadavky to skip!")
print(f"📋 Requests to process: {len(requests_to_process)}\n")
# ---- Process each request
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()

View File

@@ -0,0 +1 @@
{"cookies": [{"name": "gateway-access-token", "value": "YwBgkf8McREDKs7vCZj0EZD2fJsuV8RyDPtYx7WiDoz0nFJ9kxId8kcNEPBLFSwM+Tiz80+SOdFwo+oj", "domain": "my.medevio.cz", "path": "/", "expires": 1763372319, "httpOnly": false, "secure": false, "sameSite": "Lax"}, {"name": "aws-waf-token", "value": "b6a1d4eb-4350-40e5-8e52-1f5f9600fbb8:CgoAr9pC8c6zAAAA:OYwXLY5OyitSQPl5v2oIlS+hIxsrb5LxV4VjCyE2gJCFFE5PQu+0Zbxse2ZIofrNv5QKs0TYUDTmxPhZyTr9Qtjnq2gsVQxWHXzrbebv3Z7RbzB63u6Ymn3Fo8IbDev3CfCNcNuxCKltFEXLqSCjI2vqNY+7HZkgQBIqy2wMgzli3aSLq0w8lWYtZzyyot7q8RPXWMGTfaBUo2reY0SOSffm9rAivE9PszNfPid71CvNrGAAoxRbwb25eVujlyIcDVWe5vZ9Iw==", "domain": ".my.medevio.cz", "path": "/", "expires": 1761125920, "httpOnly": false, "secure": true, "sameSite": "Lax"}], "origins": [{"origin": "https://my.medevio.cz", "localStorage": [{"name": "awswaf_token_refresh_timestamp", "value": "1760780309860"}, {"name": "awswaf_session_storage", "value": "b6a1d4eb-4350-40e5-8e52-1f5f9600fbb8:CgoAr9pC8c+zAAAA:+vw//1NzmePjPpbGCJzUB+orCRivtJd098DbDX4AnABiGRw/+ql6ShqvFY4YdCY7w2tegb5mEPBdAmc4sNi22kNR9BuEoAgCUiMhkU1AZWfzM51zPfTh7SveCrREZ7xdvxcqKPMmfVLRYX5E4+UWh22z/LKQ7+d9VERp3J+wWCUW3dFFirkezy3N7b2FVjTlY/RxsZwhejQziTG/L3CkIFFP3mOReNgBvDpj7aKoM1knY4IL4TZ8E7zNv3nTsvzACLYvnUutVOUcofN1TfOzwZshSKsEXsMzrQn8PzLccX1jM5VSzce7gfEzl0zSPsT8NB3Sna+rhMIttDNYgvbW1HsfG2LIeKMR27Zf8hkslDRVVkcU/Kp2jLOEdhhrBKGjKY2o9/uX3NExdzh5MEKQSSRtmue01BpWYILPH23rMsz4YSmF+Ough5OeQoC95rkcYwVXMhwvUN9Zfp9UZ4xCNfFUex5dOrg9aJntYRnaceeocGUttNI5AdT0i3+osV6XHXzKxeqO8zLCS9BIsCzxaHfdqqem5DorMceuGKz+QqksatIQAA=="}, {"name": "Application.Intl.locale", "value": "cs"}, {"name": "Password.prefill", "value": "{\"username\":\"vladimir.buzalka@buzalka.cz\",\"type\":\"email\"}"}]}]}

View File

@@ -0,0 +1 @@
nYvrvgflIKcDiQg8Hhpud+qG8iGZ8eH8su4nyT/Mgcm7XQp65ygY9s39+O01wIpk/7sKd6fBHkiKvsqH

View File

@@ -7,6 +7,16 @@ from pathlib import Path
from datetime import datetime, timezone from datetime import datetime, timezone
import time import time
from dateutil import parser from dateutil import parser
import sys
# Force UTF-8 output
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')
# ================================ # ================================
# 🔧 CONFIGURATION # 🔧 CONFIGURATION
@@ -16,8 +26,8 @@ CLINIC_SLUG = "mudr-buzalkova"
BATCH_SIZE = 100 BATCH_SIZE = 100
DB_CONFIG = { DB_CONFIG = {
"host": "192.168.1.76", "host": "192.168.1.50",
"port": 3307, "port": 3306,
"user": "root", "user": "root",
"password": "Vlado9674+", "password": "Vlado9674+",
"database": "medevio", "database": "medevio",
@@ -63,33 +73,21 @@ query ClinicRequestList2(
} }
""" """
# ================================ # ================================
# 🧿 SAFE DATETIME PARSER (ALWAYS UTC → LOCAL) # 🧿 SAFE DATETIME PARSER (ALWAYS UTC → LOCAL)
# ================================ # ================================
def to_mysql_dt_utc(iso_str): 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: if not iso_str:
return None return None
try: try:
dt = parser.isoparse(iso_str) dt = parser.isoparse(iso_str)
# If tz is missing → assume UTC
if dt.tzinfo is None: if dt.tzinfo is None:
dt = dt.replace(tzinfo=timezone.utc) dt = dt.replace(tzinfo=timezone.utc)
# Convert to local timezone
dt_local = dt.astimezone() dt_local = dt.astimezone()
return dt_local.strftime("%Y-%m-%d %H:%M:%S") return dt_local.strftime("%Y-%m-%d %H:%M:%S")
except: except:
return None return None
# ================================ # ================================
# 🔑 TOKEN # 🔑 TOKEN
# ================================ # ================================
@@ -99,20 +97,15 @@ def read_token(path: Path) -> str:
return tok.split(" ", 1)[1] return tok.split(" ", 1)[1]
return tok return tok
# ================================ # ================================
# 💾 UPSERT (včetně správného updatedAt) # 💾 UPSERT
# ================================ # ================================
def upsert(conn, r): def upsert(conn, r):
p = r.get("extendedPatient") or {} p = r.get("extendedPatient") or {}
# raw timestamps z API nyní přes nový parser
api_updated = to_mysql_dt_utc(r.get("updatedAt")) api_updated = to_mysql_dt_utc(r.get("updatedAt"))
last_msg = r.get("lastMessage") or {} last_msg = r.get("lastMessage") or {}
msg_updated = to_mysql_dt_utc(last_msg.get("createdAt")) msg_updated = to_mysql_dt_utc(last_msg.get("createdAt"))
# nejnovější změna
def max_dt(a, b): def max_dt(a, b):
if a and b: if a and b:
return max(a, b) return max(a, b)
@@ -151,7 +144,6 @@ def upsert(conn, r):
cur.execute(sql, vals) cur.execute(sql, vals)
conn.commit() conn.commit()
# ================================ # ================================
# 📡 FETCH ACTIVE PAGE # 📡 FETCH ACTIVE PAGE
# ================================ # ================================
@@ -173,11 +165,9 @@ def fetch_active(headers, offset):
r = requests.post("https://api.medevio.cz/graphql", json=payload, headers=headers) r = requests.post("https://api.medevio.cz/graphql", json=payload, headers=headers)
r.raise_for_status() 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) return data.get("patientRequests", []), data.get("count", 0)
# ================================ # ================================
# 🧠 MAIN # 🧠 MAIN
# ================================ # ================================
@@ -190,7 +180,6 @@ def main():
} }
conn = pymysql.connect(**DB_CONFIG) conn = pymysql.connect(**DB_CONFIG)
print(f"\n=== Sync ACTIVE požadavků @ {datetime.now():%Y-%m-%d %H:%M:%S} ===") print(f"\n=== Sync ACTIVE požadavků @ {datetime.now():%Y-%m-%d %H:%M:%S} ===")
offset = 0 offset = 0
@@ -199,7 +188,6 @@ def main():
while True: while True:
batch, count = fetch_active(headers, offset) batch, count = fetch_active(headers, offset)
if total_count is None: if total_count is None:
total_count = count total_count = count
print(f"📡 Celkem ACTIVE v Medevio: {count}") print(f"📡 Celkem ACTIVE v Medevio: {count}")
@@ -222,7 +210,5 @@ def main():
conn.close() conn.close()
print("\n✅ ACTIVE sync hotovo!\n") print("\n✅ ACTIVE sync hotovo!\n")
# ================================
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@@ -5,18 +5,45 @@ import pymysql
import requests import requests
from pathlib import Path from pathlib import Path
from datetime import datetime from datetime import datetime
from dateutil import parser
import time import time
import sys
# ================================ # ================================
# 🔧 CONFIGURATION # 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") TOKEN_PATH = Path("token.txt")
CLINIC_SLUG = "mudr-buzalkova" CLINIC_SLUG = "mudr-buzalkova"
BATCH_SIZE = 100
BATCH_SIZE = 500
STATES = ["ACTIVE", "DONE"] # explicitně jinak API vrací jen ACTIVE
DB_CONFIG = { DB_CONFIG = {
"host": "192.168.1.76", "host": "192.168.1.50",
"port": 3307, "port": 3306,
"user": "root", "user": "root",
"password": "Vlado9674+", "password": "Vlado9674+",
"database": "medevio", "database": "medevio",
@@ -25,20 +52,20 @@ DB_CONFIG = {
} }
GRAPHQL_QUERY = r""" GRAPHQL_QUERY = r"""
query ClinicRequestGrid_ListPatientRequestsForClinic2( query ClinicRequestList2(
$clinicSlug: String!, $clinicSlug: String!,
$queueId: String, $queueId: String,
$queueAssignment: QueueAssignmentFilter!, $queueAssignment: QueueAssignmentFilter!,
$state: PatientRequestState,
$pageInfo: PageInfo!, $pageInfo: PageInfo!,
$locale: Locale!, $locale: Locale!
$state: PatientRequestState
) { ) {
requestsResponse: listPatientRequestsForClinic2( requestsResponse: listPatientRequestsForClinic2(
clinicSlug: $clinicSlug, clinicSlug: $clinicSlug,
queueId: $queueId, queueId: $queueId,
queueAssignment: $queueAssignment, queueAssignment: $queueAssignment,
pageInfo: $pageInfo, state: $state,
state: $state pageInfo: $pageInfo
) { ) {
count count
patientRequests { patientRequests {
@@ -53,40 +80,51 @@ query ClinicRequestGrid_ListPatientRequestsForClinic2(
surname surname
identificationNumber identificationNumber
} }
lastMessage {
createdAt
}
} }
} }
} }
""" """
# ================================ # ================================
# 🔑 TOKEN # TOKEN
# ================================ # ================================
def read_token(path: Path) -> str: def read_token(path: Path) -> str:
tok = path.read_text(encoding="utf-8").strip() tok = path.read_text(encoding="utf-8").strip()
if tok.startswith("Bearer "): if tok.startswith("Bearer "):
tok = tok.split(" ", 1)[1] return tok.split(" ", 1)[1]
return tok return tok
# ================================ # ================================
# 🕒 DATETIME FORMAT # DATETIME PARSER
# ================================ # ================================
def to_mysql_dt(iso_str): def to_mysql_dt(iso_str):
if not iso_str: if not iso_str:
return None return None
try: try:
dt = datetime.fromisoformat(iso_str.replace("Z", "+00:00")) dt = parser.isoparse(iso_str)
return dt.strftime("%Y-%m-%d %H:%M:%S") if dt.tzinfo is None:
except: dt = dt.replace(tzinfo=datetime.now().astimezone().tzinfo)
return dt.astimezone().strftime("%Y-%m-%d %H:%M:%S")
except Exception:
return None return None
# ================================ # ================================
# 💾 UPSERT # UPSERT
# ================================ # ================================
def upsert(conn, r): def upsert(conn, r):
p = r.get("extendedPatient") or {} 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 = """ sql = """
INSERT INTO pozadavky ( INSERT INTO pozadavky (
id, displayTitle, createdAt, updatedAt, doneAt, removedAt, id, displayTitle, createdAt, updatedAt, doneAt, removedAt,
@@ -106,7 +144,7 @@ def upsert(conn, r):
r.get("id"), r.get("id"),
r.get("displayTitle"), r.get("displayTitle"),
to_mysql_dt(r.get("createdAt")), to_mysql_dt(r.get("createdAt")),
to_mysql_dt(r.get("updatedAt")), final_updated,
to_mysql_dt(r.get("doneAt")), to_mysql_dt(r.get("doneAt")),
to_mysql_dt(r.get("removedAt")), to_mysql_dt(r.get("removedAt")),
p.get("name"), p.get("name"),
@@ -120,33 +158,33 @@ def upsert(conn, r):
# ================================ # ================================
# 📡 FETCH ACTIVE PAGE # FETCH PAGE (per state)
# ================================ # ================================
def fetch_active(headers, offset): def fetch_state(headers, state, offset):
variables = { variables = {
"clinicSlug": CLINIC_SLUG, "clinicSlug": CLINIC_SLUG,
"queueId": None, "queueId": None,
"queueAssignment": "ANY", "queueAssignment": "ANY",
"state": state,
"pageInfo": {"first": BATCH_SIZE, "offset": offset}, "pageInfo": {"first": BATCH_SIZE, "offset": offset},
"locale": "cs", "locale": "cs",
"state": "ACTIVE",
} }
payload = { payload = {
"operationName": "ClinicRequestGrid_ListPatientRequestsForClinic2", "operationName": "ClinicRequestList2",
"query": GRAPHQL_QUERY, "query": GRAPHQL_QUERY,
"variables": variables, "variables": variables,
} }
r = requests.post("https://api.medevio.cz/graphql", json=payload, headers=headers) r = requests.post("https://api.medevio.cz/graphql", json=payload, headers=headers)
r.raise_for_status() r.raise_for_status()
data = r.json().get("data", {}).get("requestsResponse", {})
data = r.json()["data"]["requestsResponse"]
return data.get("patientRequests", []), data.get("count", 0) return data.get("patientRequests", []), data.get("count", 0)
# ================================ # ================================
# 🧠 MAIN # MAIN
# ================================ # ================================
def main(): def main():
token = read_token(TOKEN_PATH) token = read_token(TOKEN_PATH)
@@ -158,40 +196,44 @@ def main():
conn = pymysql.connect(**DB_CONFIG) conn = pymysql.connect(**DB_CONFIG)
print(f"\n=== Sync ACTIVE požadavků @ {datetime.now():%Y-%m-%d %H:%M:%S} ===") safe_print(f"\n=== FULL Medevio READ-ALL sync @ {datetime.now():%Y-%m-%d %H:%M:%S} ===")
# ------------------------------- grand_total = 0
# 🚀 FETCH ALL ACTIVE REQUESTS
# -------------------------------
offset = 0
total_processed = 0
total_count = None
while True: for state in STATES:
batch, count = fetch_active(headers, offset) safe_print(f"\n🔁 STATE = {state}")
offset = 0
total = None
processed = 0
if total_count is None: while True:
total_count = count batch, count = fetch_state(headers, state, offset)
print(f"📡 Celkem ACTIVE v Medevio: {count}")
if not batch: if total is None:
break total = count
safe_print(f"📡 {state}: celkem {total}")
for r in batch: if not batch:
upsert(conn, r) break
total_processed += len(batch) for r in batch:
print(f"{total_processed}/{total_count} ACTIVE processed") upsert(conn, r)
if offset + BATCH_SIZE >= count: processed += len(batch)
break safe_print(f"{processed}/{total}")
offset += BATCH_SIZE offset += BATCH_SIZE
time.sleep(0.4) if offset >= count:
break
time.sleep(0.4)
grand_total += processed
conn.close() conn.close()
print("\nACTIVE sync hotovo!\n") safe_print(f"\nHOTOVO celkem zpracováno {grand_total} požadavků\n")
# ================================
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@@ -0,0 +1,217 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Download and store Medevio questionnaires (userNote + eCRF) for all patient requests.
Uses the verified working query "GetPatientRequest2".
"""
import json
import requests
import pymysql
from datetime import datetime
from pathlib import Path
import time
import sys
# Force UTF-8 output even under Windows Task Scheduler
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')
# ==============================
# 🛡 SAFE PRINT FOR CP1250 / EMOJI
# ==============================
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)
# ==============================
# 🔧 CONFIGURATION (UPDATED TO 192.168.1.50)
# ==============================
TOKEN_PATH = Path("token.txt")
CLINIC_SLUG = "mudr-buzalkova"
GRAPHQL_URL = "https://api.medevio.cz/graphql"
DB_CONFIG = {
"host": "192.168.1.50",
"port": 3306,
"user": "root",
"password": "Vlado9674+",
"database": "medevio",
"charset": "utf8mb4",
"cursorclass": pymysql.cursors.DictCursor,
}
# ==============================
# 🕒 DATETIME FIXER
# ==============================
def fix_datetime(dt_str):
if not dt_str:
return None
try:
return datetime.fromisoformat(dt_str.replace("Z", "").replace("+00:00", ""))
except Exception:
return None
# Optional filter
CREATED_AFTER = "2025-01-01"
# ==============================
# 🧮 HELPERS
# ==============================
def read_token(p: Path) -> str:
tok = p.read_text(encoding="utf-8").strip()
if tok.startswith("Bearer "):
return tok.split(" ", 1)[1]
return tok
GRAPHQL_QUERY = r"""
query GetPatientRequest2($requestId: UUID!, $clinicSlug: String!, $locale: Locale!) {
request: getPatientRequest2(patientRequestId: $requestId, clinicSlug: $clinicSlug) {
id
displayTitle(locale: $locale)
createdAt
updatedAt
userNote
eventType
extendedPatient(clinicSlug: $clinicSlug) {
name
surname
identificationNumber
}
ecrfFilledData(locale: $locale) {
name
groups {
label
fields {
name
label
type
value
}
}
}
}
}
"""
def fetch_questionnaire(headers, request_id, clinic_slug):
payload = {
"operationName": "GetPatientRequest2",
"query": GRAPHQL_QUERY,
"variables": {
"requestId": request_id,
"clinicSlug": clinic_slug,
"locale": "cs",
},
}
r = requests.post(GRAPHQL_URL, json=payload, headers=headers, timeout=40)
if r.status_code != 200:
safe_print(f"❌ HTTP {r.status_code} for {request_id}: {r.text}")
return None
return r.json().get("data", {}).get("request")
def insert_questionnaire(cur, req):
if not req:
return
patient = req.get("extendedPatient") or {}
ecrf_data = req.get("ecrfFilledData")
created_at = fix_datetime(req.get("createdAt"))
updated_at = fix_datetime(req.get("updatedAt"))
cur.execute("""
INSERT INTO medevio_questionnaires (
request_id, created_at, updated_at, user_note, ecrf_json
)
VALUES (%s,%s,%s,%s,%s)
ON DUPLICATE KEY UPDATE
updated_at = VALUES(updated_at),
user_note = VALUES(user_note),
ecrf_json = VALUES(ecrf_json),
updated_local = NOW()
""", (
req.get("id"),
created_at,
updated_at,
req.get("userNote"),
json.dumps(ecrf_data, ensure_ascii=False),
))
safe_print(f" 💾 Stored questionnaire for {patient.get('surname','')} {patient.get('name','')}")
# ==============================
# 🧠 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 list of requests from the table we just filled
with conn.cursor() as cur:
sql = """
SELECT id, pacient_jmeno, pacient_prijmeni, createdAt, updatedAt, questionnaireprocessed
FROM pozadavky
WHERE (questionnaireprocessed IS NULL OR questionnaireprocessed < updatedAt)
"""
if CREATED_AFTER:
sql += " AND createdAt >= %s"
cur.execute(sql, (CREATED_AFTER,))
else:
cur.execute(sql)
rows = cur.fetchall()
safe_print(f"📋 Found {len(rows)} requests needing questionnaire check.")
for i, row in enumerate(rows, 1):
req_id = row["id"]
safe_print(f"\n[{i}/{len(rows)}] 🔍 Fetching questionnaire for {req_id} ...")
req = fetch_questionnaire(headers, req_id, CLINIC_SLUG)
if not req:
safe_print(" ⚠️ No questionnaire data found.")
continue
with conn.cursor() as cur:
insert_questionnaire(cur, req)
cur.execute(
"UPDATE pozadavky SET questionnaireprocessed = NOW() WHERE id = %s",
(req_id,)
)
conn.commit()
time.sleep(0.6)
conn.close()
safe_print("\n✅ Done! All questionnaires stored in MySQL table `medevio_questionnaires`.")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,147 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import json
import requests
import pymysql
from pathlib import Path
from datetime import datetime
import time
import sys
# UTF-8 SAFE OUTPUT
try:
sys.stdout.reconfigure(encoding='utf-8')
sys.stderr.reconfigure(encoding='utf-8')
except AttributeError:
pass
# ==============================
# CONFIG (.50)
# ==============================
TOKEN_PATH = Path("token.txt")
DB_CONFIG = {
"host": "192.168.1.50",
"port": 3306,
"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 createdAt updatedAt }
}
}
"""
def parse_dt(s):
if not s: return None
try: return datetime.fromisoformat(s.replace("Z", "+00:00"))
except: return None
def read_token(path: Path) -> str:
return path.read_text(encoding="utf-8").strip().replace("Bearer ", "")
def main():
token = read_token(TOKEN_PATH)
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
conn = pymysql.connect(**DB_CONFIG)
# 1. Seznam již stažených příloh (prevence duplicit)
with conn.cursor() as cur:
cur.execute("SELECT attachment_id FROM medevio_downloads")
existing_ids = {r["attachment_id"] for r in cur.fetchall()}
# 2. Seznam požadavků k synchronizaci
with conn.cursor() as cur:
cur.execute("""
SELECT id, messagesProcessed FROM pozadavky
WHERE messagesProcessed IS NULL OR messagesProcessed < updatedAt
""")
rows = cur.fetchall()
print(f"📋 Počet požadavků k synchronizaci zpráv: {len(rows)}")
for i, row in enumerate(rows, 1):
req_id = row["id"]
updated_since = row["messagesProcessed"]
if updated_since:
updated_since = updated_since.replace(microsecond=0).isoformat() + "Z"
print(f"[{i}/{len(rows)}] Synchronizuji: {req_id}")
payload = {
"operationName": "UseMessages_ListMessages",
"query": GRAPHQL_QUERY_MESSAGES,
"variables": {"requestId": req_id, "updatedSince": updated_since}
}
try:
r = requests.post("https://api.medevio.cz/graphql", json=payload, headers=headers, timeout=30)
messages = r.json().get("data", {}).get("messages", []) or []
if messages:
with conn.cursor() as cur:
for msg in messages:
# Uložení zprávy
sender = msg.get("sender") or {}
sender_name = " ".join(filter(None, [sender.get("name"), sender.get("surname")]))
mr = msg.get("medicalRecord") or {}
cur.execute("""
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
text = VALUES(text), updated_at = VALUES(updated_at), read_at = VALUES(read_at)
""", (
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")
))
# Uložení přílohy (pokud existuje a nemáme ji)
attachment_id = mr.get("id")
if attachment_id and attachment_id not in existing_ids:
url = mr.get("downloadUrl") or mr.get("url")
if url:
att_r = requests.get(url, timeout=30)
if att_r.status_code == 200:
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)
""", (
req_id, attachment_id, "MESSAGE_ATTACHMENT",
url.split("/")[-1].split("?")[0], mr.get("contentType"),
len(att_r.content), parse_dt(msg.get("createdAt")), att_r.content
))
existing_ids.add(attachment_id)
cur.execute("UPDATE pozadavky SET messagesProcessed = NOW() WHERE id = %s", (req_id,))
conn.commit()
else:
with conn.cursor() as cur:
cur.execute("UPDATE pozadavky SET messagesProcessed = NOW() WHERE id = %s", (req_id,))
conn.commit()
time.sleep(0.3)
except Exception as e:
print(f" ❌ Chyba u {req_id}: {e}")
conn.close()
print("\n🎉 Delta sync zpráv a příloh DOKONČEN")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,177 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Download all attachments for pozadavky where attachmentsProcessed IS NULL
Store them in MySQL table `medevio_downloads` on 192.168.1.50.
"""
import zlib
import json
import requests
import pymysql
from pathlib import Path
from datetime import datetime
import time
import sys
# Force UTF-8 output
try:
sys.stdout.reconfigure(encoding='utf-8')
sys.stderr.reconfigure(encoding='utf-8')
except AttributeError:
pass
# ==============================
# 🛡 SAFE PRINT
# ==============================
def safe_print(text: str):
enc = sys.stdout.encoding or ""
if not enc or 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)
# ==============================
# 🔧 CONFIGURATION (.50)
# ==============================
TOKEN_PATH = Path("token.txt")
CLINIC_SLUG = "mudr-buzalkova"
DB_CONFIG = {
"host": "192.168.1.50",
"port": 3306,
"user": "root",
"password": "Vlado9674+",
"database": "medevio",
"charset": "utf8mb4",
"cursorclass": pymysql.cursors.DictCursor,
}
CREATED_AFTER = "2024-12-01"
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
}
}
}
"""
def extract_filename_from_url(url: str) -> str:
try:
return url.split("/")[-1].split("?")[0]
except:
return "unknown_filename"
def read_token(p: Path) -> str:
tok = p.read_text(encoding="utf-8").strip()
return tok.split(" ", 1)[1] if tok.startswith("Bearer ") else tok
def main():
token = read_token(TOKEN_PATH)
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
conn = pymysql.connect(**DB_CONFIG)
# 1. Načtení ID již stažených příloh
with conn.cursor() as cur:
cur.execute("SELECT attachment_id FROM medevio_downloads")
existing_ids = {row["attachment_id"] for row in cur.fetchall()}
safe_print(f"✅ V databázi již máme {len(existing_ids)} příloh.")
# 2. Výběr požadavků ke zpracování
sql = "SELECT id, 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)
req_rows = cur.fetchall()
safe_print(f"📋 Počet požadavků ke stažení příloh: {len(req_rows)}")
for i, row in enumerate(req_rows, 1):
req_id = row["id"]
prijmeni = row.get("pacient_prijmeni") or "Neznamy"
created_date = row.get("createdAt") or datetime.now()
safe_print(f"\n[{i}/{len(req_rows)}] 🧾 {prijmeni} ({req_id})")
payload = {
"operationName": "ClinicRequestDetail_GetPatientRequest2",
"query": GRAPHQL_QUERY,
"variables": {"requestId": req_id},
}
try:
r = requests.post("https://api.medevio.cz/graphql", json=payload, headers=headers, timeout=30)
attachments = r.json().get("data", {}).get("patientRequestMedicalRecords", [])
if attachments:
with conn.cursor() as cur:
for a in attachments:
m = a.get("medicalRecord") or {}
att_id = a.get("id")
if att_id in existing_ids:
continue
url = m.get("downloadUrl")
if url:
att_r = requests.get(url, timeout=30)
if att_r.status_code == 200:
content = att_r.content
filename = extract_filename_from_url(url)
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)
""", (req_id, att_id, a.get("attachmentType"), filename,
m.get("contentType"), len(content), created_date, content))
existing_ids.add(att_id)
safe_print(f" 💾 Uloženo: {filename} ({len(content) / 1024:.1f} kB)")
conn.commit()
# Označíme jako zpracované i když nebyly nalezeny žádné přílohy
with conn.cursor() as cur:
cur.execute("UPDATE pozadavky SET attachmentsProcessed = NOW() WHERE id = %s", (req_id,))
conn.commit()
time.sleep(0.3)
except Exception as e:
print(f" ❌ Chyba u {req_id}: {e}")
conn.close()
safe_print("\n🎯 Všechny přílohy byly zpracovány.")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,252 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import shutil
import pymysql
import re
from pathlib import Path
from datetime import datetime
import time
import sys
# Force UTF-8 output even under Windows Task Scheduler
import sys
try:
sys.stdout.reconfigure(encoding='utf-8')
sys.stderr.reconfigure(encoding='utf-8')
except AttributeError:
# Python < 3.7 fallback (not needed for you, but safe)
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
# ==============================
# 🛡 SAFE PRINT FOR CP1250 / EMOJI
# ==============================
def safe_print(text: str = ""):
enc = sys.stdout.encoding or ""
if not enc.lower().startswith("utf"):
# Strip emoji and characters outside BMP for Task Scheduler
text = ''.join(ch for ch in text if ord(ch) < 65536)
try:
print(text)
except UnicodeEncodeError:
# ASCII fallback
text = ''.join(ch for ch in text if ord(ch) < 128)
print(text)
# ==============================
# ⚙️ CONFIGURATION
# ==============================
DB_CONFIG = {
"host": "192.168.1.50",
"port": 3306,
"user": "root",
"password": "Vlado9674+",
"database": "medevio",
"charset": "utf8mb4",
}
BASE_DIR = Path(r"u:\Dropbox\Ordinace\Dokumentace_ke_zpracování\MP")
BASE_DIR.mkdir(parents=True, exist_ok=True)
def sanitize_name(name: str) -> str:
"""Replace invalid filename characters with underscore."""
return re.sub(r'[<>:"/\\|?*\x00-\x1F]', "_", name).strip()
def make_abbrev(title: str) -> str:
if not title:
return ""
words = re.findall(r"[A-Za-zÁ-Žá-ž0-9]+", title)
abbr = ""
for w in words:
if w.isdigit():
abbr += w
else:
abbr += w[0]
return abbr.upper()
# ==============================
# 🧹 DELETE UNEXPECTED FILES
# ==============================
def clean_folder(folder: Path, valid_files: set):
if not folder.exists():
return
for f in folder.iterdir():
if f.is_file():
if f.name.startswith(""):
continue
sanitized = sanitize_name(f.name)
if sanitized not in valid_files:
safe_print(f"🗑️ Removing unexpected file: {f.name}")
try:
f.unlink()
except Exception as e:
safe_print(f"⚠️ Could not delete {f}: {e}")
# ==============================
# 📦 DB CONNECTION
# ==============================
conn = pymysql.connect(**DB_CONFIG)
cur_meta = conn.cursor(pymysql.cursors.DictCursor)
cur_blob = conn.cursor()
safe_print("🔍 Loading metadata from DB (FAST)…")
cur_meta.execute("""
SELECT d.id AS download_id,
d.request_id,
d.filename,
d.created_at,
p.updatedAt AS req_updated_at,
p.pacient_jmeno AS jmeno,
p.pacient_prijmeni AS prijmeni,
p.displayTitle
FROM medevio_downloads d
JOIN pozadavky p ON d.request_id = p.id
ORDER BY p.updatedAt DESC
""")
rows = cur_meta.fetchall()
safe_print(f"📋 Found {len(rows)} attachment records.\n")
# ==============================
# 🧠 MAIN LOOP WITH PROGRESS
# ==============================
unique_request_ids = []
seen = set()
for r in rows:
req_id = r["request_id"]
if req_id not in seen:
unique_request_ids.append(req_id)
seen.add(req_id)
total_requests = len(unique_request_ids)
safe_print(f"🔄 Processing {total_requests} unique requests...\n")
processed_requests = set()
current_index = 0
for r in rows:
req_id = r["request_id"]
if req_id in processed_requests:
continue
processed_requests.add(req_id)
current_index += 1
percent = (current_index / total_requests) * 100
safe_print(f"\n[ {percent:5.1f}% ] Processing request {current_index} / {total_requests}{req_id}")
# ========== FETCH VALID FILENAMES ==========
cur_meta.execute(
"SELECT filename FROM medevio_downloads WHERE request_id=%s",
(req_id,)
)
valid_files = {sanitize_name(row["filename"]) for row in cur_meta.fetchall()}
# ========== BUILD FOLDER NAME ==========
updated_at = r["req_updated_at"] or datetime.now()
date_str = updated_at.strftime("%Y-%m-%d")
prijmeni = sanitize_name(r["prijmeni"] or "Unknown")
jmeno = sanitize_name(r["jmeno"] or "")
title = r.get("displayTitle") or ""
abbr = make_abbrev(title)
clean_folder_name = sanitize_name(
f"{date_str} {prijmeni}, {jmeno} [{abbr}] {req_id}"
)
# ========== DETECT EXISTING FOLDER ==========
existing_folder = None
for f in BASE_DIR.iterdir():
if f.is_dir() and req_id in f.name:
existing_folder = f
break
main_folder = existing_folder if existing_folder else BASE_DIR / clean_folder_name
# ========== MERGE DUPLICATES ==========
possible_dups = [
f for f in BASE_DIR.iterdir()
if f.is_dir() and req_id in f.name and f != main_folder
]
for dup in possible_dups:
safe_print(f"♻️ Merging duplicate folder: {dup.name}")
clean_folder(dup, valid_files)
main_folder.mkdir(parents=True, exist_ok=True)
for f in dup.iterdir():
if f.is_file():
target = main_folder / f.name
if not target.exists():
f.rename(target)
shutil.rmtree(dup, ignore_errors=True)
# ========== CLEAN MAIN FOLDER ==========
clean_folder(main_folder, valid_files)
# ========== DOWNLOAD MISSING FILES ==========
added_new_file = False
main_folder.mkdir(parents=True, exist_ok=True)
for filename in valid_files:
dest_plain = main_folder / filename
dest_marked = main_folder / ("" + filename)
if dest_plain.exists() or dest_marked.exists():
continue
added_new_file = True
cur_blob.execute(
"SELECT file_content FROM medevio_downloads "
"WHERE request_id=%s AND filename=%s",
(req_id, filename)
)
row = cur_blob.fetchone()
if not row:
continue
content = row[0]
if not content:
continue
with open(dest_plain, "wb") as f:
f.write(content)
safe_print(f"💾 Wrote: {dest_plain.relative_to(BASE_DIR)}")
# ========== REMOVE ▲ FLAG IF NEW FILES ADDED ==========
if added_new_file and "" in main_folder.name:
new_name = main_folder.name.replace("", "").strip()
new_path = main_folder.parent / new_name
if new_path != main_folder:
try:
main_folder.rename(new_path)
safe_print(f"🔄 Folder flag ▲ removed → {new_name}")
main_folder = new_path
except Exception as e:
safe_print(f"⚠️ Could not rename folder: {e}")
safe_print("\n🎯 Export complete.\n")
cur_blob.close()
cur_meta.close()
conn.close()

View File

@@ -12,8 +12,8 @@ from datetime import datetime
# ⚙️ CONFIGURATION # ⚙️ CONFIGURATION
# ============================== # ==============================
DB_CONFIG = { DB_CONFIG = {
"host": "192.168.1.76", "host": "192.168.1.50",
"port": 3307, "port": 3306,
"user": "root", "user": "root",
"password": "Vlado9674+", "password": "Vlado9674+",
"database": "medevio", "database": "medevio",

1
12 Tower1/token.txt Normal file
View File

@@ -0,0 +1 @@
nYvrvgflIKcDiQg8Hhpud+qG8iGZ8eH8su4nyT/Mgcm7XQp65ygY9s39+O01wIpk/7sKd6fBHkiKvsqH

View File

@@ -0,0 +1,92 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import requests
from pathlib import Path
import sys
# UTF-8 safety
try:
sys.stdout.reconfigure(encoding='utf-8')
except:
pass
# === CONFIG ===
TOKEN_PATH = Path("token.txt")
GRAPHQL_URL = "https://api.medevio.cz/graphql"
REQUEST_ID = "e17536c4-ed22-4242-ada5-d03713e0b7ac" # požadavek který sledujeme
def read_token(path: Path) -> str:
t = path.read_text().strip()
if t.startswith("Bearer "):
return t.split(" ", 1)[1]
return t
# === QUERY ===
QUERY = r"""
query ClinicRequestNotes_Get($patientRequestId: String!) {
notes: getClinicPatientRequestNotes(requestId: $patientRequestId) {
id
content
createdAt
updatedAt
createdBy {
id
name
surname
}
}
}
"""
def run_query(request_id, token):
payload = {
"operationName": "ClinicRequestNotes_Get",
"query": QUERY,
"variables": {"patientRequestId": request_id},
}
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
}
r = requests.post(GRAPHQL_URL, json=payload, headers=headers)
r.raise_for_status()
return r.json()
def main():
token = read_token(TOKEN_PATH)
print(f"🔍 Čtu interní klinické poznámky k požadavku {REQUEST_ID} ...\n")
data = run_query(REQUEST_ID, token)
notes = data.get("data", {}).get("notes", [])
if not notes:
print("📭 Žádné klinické poznámky nejsou uložené.")
return
print(f"📌 Nalezeno {len(notes)} poznámek:\n")
for n in notes:
print("──────────────────────────────")
print(f"🆔 ID: {n['id']}")
print(f"👤 Vytvořil: {n['createdBy']['surname']} {n['createdBy']['name']}")
print(f"📅 createdAt: {n['createdAt']}")
print(f"🕒 updatedAt: {n['updatedAt']}")
print("📝 Obsah:")
print(n['content'])
print("")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,121 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import requests
from pathlib import Path
import sys
# UTF-8 handling
try:
sys.stdout.reconfigure(encoding='utf-8')
except:
pass
# === CONFIG ===
TOKEN_PATH = Path("token.txt")
GRAPHQL_URL = "https://api.medevio.cz/graphql"
REQUEST_ID = "e17536c4-ed22-4242-ada5-d03713e0b7ac" # požadavek
NOTE_PREPEND_TEXT = "🔥 NOVÝ TESTOVACÍ ŘÁDEK\n" # text, který se přidá NA ZAČÁTEK
# === Helpers ===
def read_token(p: Path) -> str:
t = p.read_text().strip()
if t.startswith("Bearer "):
return t.split(" ", 1)[1]
return t
# === Queries ===
QUERY_GET_NOTES = r"""
query ClinicRequestNotes_Get($patientRequestId: String!) {
notes: getClinicPatientRequestNotes(requestId: $patientRequestId) {
id
content
createdAt
updatedAt
createdBy {
id
name
surname
}
}
}
"""
MUTATION_UPDATE_NOTE = r"""
mutation ClinicRequestNotes_Update($noteInput: UpdateClinicPatientRequestNoteInput!) {
updateClinicPatientRequestNote(noteInput: $noteInput) {
id
}
}
"""
# === Core functions ===
def gql(query, variables, token):
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
}
payload = {"query": query, "variables": variables}
r = requests.post(GRAPHQL_URL, json=payload, headers=headers)
r.raise_for_status()
return r.json()
def get_internal_note(request_id, token):
data = gql(QUERY_GET_NOTES, {"patientRequestId": request_id}, token)
notes = data.get("data", {}).get("notes", [])
return notes[0] if notes else None
def update_internal_note(note_id, new_content, token):
variables = {"noteInput": {"id": note_id, "content": new_content}}
return gql(MUTATION_UPDATE_NOTE, variables, token)
# === MAIN ===
def main():
token = read_token(TOKEN_PATH)
print(f"🔍 Načítám interní poznámku pro požadavek {REQUEST_ID}...\n")
note = get_internal_note(REQUEST_ID, token)
if not note:
print("❌ Nebyla nalezena žádná interní klinická poznámka!")
return
note_id = note["id"]
old_content = note["content"] or ""
print("📄 Původní obsah:")
print(old_content)
print("────────────────────────────\n")
# ===============================
# PREPEND new text
# ===============================
new_content = NOTE_PREPEND_TEXT + old_content
print("📝 Nový obsah který odešlu:")
print(new_content)
print("────────────────────────────\n")
# UPDATE
result = update_internal_note(note_id, new_content, token)
print(f"✅ Hotovo! Poznámka {note_id} aktualizována.")
print(result)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,261 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import requests
import mysql.connector
from pathlib import Path
import sys
from datetime import datetime
# UTF-8 handling
try:
sys.stdout.reconfigure(encoding='utf-8')
except:
pass
# === KONFIGURACE ===
# --- Medevio API ---
TOKEN_PATH = Path("token.txt")
GRAPHQL_URL = "https://api.medevio.cz/graphql"
# --- ZPRACOVÁNÍ ---
# Zadejte počet požadavků ke zpracování.
# 0 znamená zpracovat VŠECHNY nesynchronizované požadavky.
PROCESS_LIMIT = 10 # <-- Používáme PROCESS_LIMIT
# --- MySQL DB ---
DB_CONFIG = {
"host": "192.168.1.76",
"port": 3307,
"user": "root",
"password": "Vlado9674+",
"database": "medevio",
}
# === Helpers ===
def read_token(p: Path) -> str:
"""Načte Bearer token z textového souboru."""
t = p.read_text().strip()
if t.startswith("Bearer "):
return t.split(" ", 1)[1]
return t
# === DB Funkce ===
def get_requests_to_process_from_db(limit):
"""
Získá seznam požadavků (ID, Titul, Jméno, Příjmení) k synchronizaci z MySQL.
Použije LIMIT, pokud limit > 0.
"""
if limit == 0:
print("🔍 Připojuji se k MySQL a hledám **VŠECHNY** nesynchronizované požadavky...")
else:
print(f"🔍 Připojuji se k MySQL a hledám **{limit}** nesynchronizovaných požadavků...")
requests_list = []
conn = None
try:
conn = mysql.connector.connect(**DB_CONFIG)
cursor = conn.cursor()
# Základní SQL dotaz
query = """
SELECT id, displayTitle, pacient_jmeno, pacient_prijmeni
FROM pozadavky
WHERE doneAt IS NULL
AND noteSyncedAt IS NULL
ORDER BY updatedAt DESC
"""
# Podmíněné přidání LIMIT klauzule
if limit > 0:
query += f"LIMIT {limit};"
else:
query += ";"
cursor.execute(query)
results = cursor.fetchall()
for result in results:
request_id, display_title, jmeno, prijmeni = result
requests_list.append({
"id": request_id,
"displayTitle": display_title,
"jmeno": jmeno,
"prijmeni": prijmeni
})
cursor.close()
if requests_list:
print(f"✅ Nalezeno {len(requests_list)} požadavků ke zpracování.")
else:
print("❌ Nebyl nalezen žádný nesynchronizovaný otevřený požadavek v DB.")
return requests_list
except mysql.connector.Error as err:
print(f"❌ Chyba při připojení/dotazu MySQL: {err}")
return []
finally:
if conn and conn.is_connected():
conn.close()
def update_db_sync_time(request_id, conn):
"""Aktualizuje sloupec noteSyncedAt v tabulce pozadavky. Používá existující připojení."""
cursor = conn.cursor()
current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
update_query = """
UPDATE pozadavky
SET noteSyncedAt = %s
WHERE id = %s;
"""
cursor.execute(update_query, (current_time, request_id))
conn.commit()
cursor.close()
print(f" (DB: Čas synchronizace pro {request_id} uložen)")
# === GraphQL Operace (Beze Změny) ===
QUERY_GET_NOTE = r"""
query ClinicRequestNotes_Get($patientRequestId: String!) {
notes: getClinicPatientRequestNotes(requestId: $patientRequestId) {
id
content
}
}
"""
MUTATION_UPDATE_NOTE = r"""
mutation ClinicRequestNotes_Update($noteInput: UpdateClinicPatientRequestNoteInput!) {
updateClinicPatientRequestNote(noteInput: $noteInput) {
id
}
}
"""
MUTATION_CREATE_NOTE = r"""
mutation ClinicRequestNotes_Create($noteInput: CreateClinicPatientRequestNoteInput!) {
createClinicPatientRequestNote(noteInput: $noteInput) {
id
}
}
"""
def gql(query, variables, token):
"""Obecná funkce pro volání GraphQL endpointu."""
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
}
payload = {"query": query, "variables": variables}
r = requests.post(GRAPHQL_URL, json=payload, headers=headers)
r.raise_for_status()
return r.json()
def get_internal_note(request_id, token):
"""Získá jedinou interní poznámku (obsah a ID) pro daný požadavek."""
data = gql(QUERY_GET_NOTE, {"patientRequestId": request_id}, token)
notes = data.get("data", {}).get("notes", [])
return notes[0] if notes else None
def update_internal_note(note_id, new_content, token):
"""Aktualizuje obsah poznámky v Medeviu."""
variables = {"noteInput": {"id": note_id, "content": new_content}}
return gql(MUTATION_UPDATE_NOTE, variables, token)
def create_internal_note(request_id, content, token):
"""Vytvoří novou interní poznámku k požadavku v Medeviu."""
variables = {"noteInput": {"requestId": request_id, "content": content}}
return gql(MUTATION_CREATE_NOTE, variables, token)
# === MAIN ===
def main():
token = read_token(TOKEN_PATH)
# 1. Získat seznam ID požadavků ke zpracování (používáme PROCESS_LIMIT)
requests_to_process = get_requests_to_process_from_db(PROCESS_LIMIT)
if not requests_to_process:
return
# Pro update DB time otevřeme připojení jednou a použijeme ho v cyklu
conn = mysql.connector.connect(**DB_CONFIG)
print("\n=============================================")
print(f"START ZPRACOVÁNÍ {len(requests_to_process)} POŽADAVKŮ")
print("=============================================\n")
for idx, request in enumerate(requests_to_process, 1):
request_id = request["id"]
print(
f"[{idx}/{len(requests_to_process)}] Zpracovávám požadavek: {request['prijmeni']} {request['jmeno']} (ID: {request_id})")
# 2. Vytvořit text, který chceme přidat/vytvořit
prepend_text = f"ID: {request_id}\n"
# 3. Pokusit se získat existující interní poznámku z Medevia
note = get_internal_note(request_id, token)
medevio_update_success = False
if note:
# A) POZNÁMKA EXISTUJE -> AKTUALIZOVAT
note_id = note["id"]
old_content = note["content"] or ""
new_content = prepend_text + old_content
try:
# Odeslání aktualizace
update_internal_note(note_id, new_content, token)
print(f" (Medevio: Poznámka {note_id} **aktualizována**.)")
medevio_update_success = True
except requests.exceptions.HTTPError as e:
print(f" ❌ Chyba při aktualizaci Medevio API: {e}")
else:
# B) POZNÁMKA NEEXISTUJE -> VYTVOŘIT
new_content = prepend_text.strip()
try:
# Odeslání vytvoření
result = create_internal_note(request_id, new_content, token)
new_note_id = result.get("data", {}).get("createClinicPatientRequestNote", {}).get("id", "N/A")
print(f" (Medevio: Nová poznámka {new_note_id} **vytvořena**.)")
medevio_update_success = True
except requests.exceptions.HTTPError as e:
print(f" ❌ Chyba při vytváření Medevio API: {e}")
# 4. AKTUALIZACE ČASOVÉHO RAZÍTKA V DB
if medevio_update_success:
update_db_sync_time(request_id, conn)
print("---------------------------------------------")
# Uzavřeme připojení k DB po dokončení cyklu
if conn and conn.is_connected():
conn.close()
print("\n✅ Všechny požadavky zpracovány. Připojení k DB uzavřeno.")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1 @@
nYvrvgflIKcDiQg8Hhpud+qG8iGZ8eH8su4nyT/Mgcm7XQp65ygY9s39+O01wIpk/7sKd6fBHkiKvsqH

View File

@@ -38,7 +38,7 @@ DB_CONFIG = {
"cursorclass": pymysql.cursors.DictCursor, "cursorclass": pymysql.cursors.DictCursor,
} }
EXPORT_DIR = Path(r"u:\Dropbox\Ordinace\Reporty") EXPORT_DIR = Path(r"z:\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") timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
xlsx_path = EXPORT_DIR / f"{timestamp} Agenda + Pozadavky (Merged).xlsx" xlsx_path = EXPORT_DIR / f"{timestamp} Agenda + Pozadavky (Merged).xlsx"

View File

@@ -0,0 +1 @@
{"cookies": [{"name": "gateway-access-token", "value": "YwBgkf8McREDKs7vCZj0EZD2fJsuV8RyDPtYx7WiDoz0nFJ9kxId8kcNEPBLFSwM+Tiz80+SOdFwo+oj", "domain": "my.medevio.cz", "path": "/", "expires": 1763372319, "httpOnly": false, "secure": false, "sameSite": "Lax"}, {"name": "aws-waf-token", "value": "b6a1d4eb-4350-40e5-8e52-1f5f9600fbb8:CgoAr9pC8c6zAAAA:OYwXLY5OyitSQPl5v2oIlS+hIxsrb5LxV4VjCyE2gJCFFE5PQu+0Zbxse2ZIofrNv5QKs0TYUDTmxPhZyTr9Qtjnq2gsVQxWHXzrbebv3Z7RbzB63u6Ymn3Fo8IbDev3CfCNcNuxCKltFEXLqSCjI2vqNY+7HZkgQBIqy2wMgzli3aSLq0w8lWYtZzyyot7q8RPXWMGTfaBUo2reY0SOSffm9rAivE9PszNfPid71CvNrGAAoxRbwb25eVujlyIcDVWe5vZ9Iw==", "domain": ".my.medevio.cz", "path": "/", "expires": 1761125920, "httpOnly": false, "secure": true, "sameSite": "Lax"}], "origins": [{"origin": "https://my.medevio.cz", "localStorage": [{"name": "awswaf_token_refresh_timestamp", "value": "1760780309860"}, {"name": "awswaf_session_storage", "value": "b6a1d4eb-4350-40e5-8e52-1f5f9600fbb8:CgoAr9pC8c+zAAAA:+vw//1NzmePjPpbGCJzUB+orCRivtJd098DbDX4AnABiGRw/+ql6ShqvFY4YdCY7w2tegb5mEPBdAmc4sNi22kNR9BuEoAgCUiMhkU1AZWfzM51zPfTh7SveCrREZ7xdvxcqKPMmfVLRYX5E4+UWh22z/LKQ7+d9VERp3J+wWCUW3dFFirkezy3N7b2FVjTlY/RxsZwhejQziTG/L3CkIFFP3mOReNgBvDpj7aKoM1knY4IL4TZ8E7zNv3nTsvzACLYvnUutVOUcofN1TfOzwZshSKsEXsMzrQn8PzLccX1jM5VSzce7gfEzl0zSPsT8NB3Sna+rhMIttDNYgvbW1HsfG2LIeKMR27Zf8hkslDRVVkcU/Kp2jLOEdhhrBKGjKY2o9/uX3NExdzh5MEKQSSRtmue01BpWYILPH23rMsz4YSmF+Ough5OeQoC95rkcYwVXMhwvUN9Zfp9UZ4xCNfFUex5dOrg9aJntYRnaceeocGUttNI5AdT0i3+osV6XHXzKxeqO8zLCS9BIsCzxaHfdqqem5DorMceuGKz+QqksatIQAA=="}, {"name": "Application.Intl.locale", "value": "cs"}, {"name": "Password.prefill", "value": "{\"username\":\"vladimir.buzalka@buzalka.cz\",\"type\":\"email\"}"}]}]}

View File

@@ -0,0 +1,46 @@
import os
from pathlib import Path
# Define the target directory
target_path = Path(r"U:\Dropbox\Ordinace\Dokumentace_ke_zpracování\MP")
def rename_folders():
# Ensure the path exists
if not target_path.exists():
print(f"Error: The path {target_path} does not exist.")
return
# Iterate through items in the directory
for folder in target_path.iterdir():
# Only process directories
if folder.is_dir():
original_name = folder.name
# Check if name starts with the triangle
if original_name.startswith(""):
# 1. Remove the triangle from the start
name_without_tri = original_name[1:]
# 2. Prepare the name to be at least 10 chars long
# (so the triangle can sit at index 10 / position 11)
clean_name = name_without_tri.ljust(10)
# 3. Construct new name: first 10 chars + triangle + the rest
new_name = clean_name[:10] + "" + clean_name[10:]
# Remove trailing spaces if the original name was short
# but you don't want extra spaces at the very end
new_name = new_name.rstrip()
new_folder_path = folder.parent / new_name
try:
print(f"Renaming: '{original_name}' -> '{new_name}'")
folder.rename(new_folder_path)
except Exception as e:
print(f"Could not rename {original_name}: {e}")
if __name__ == "__main__":
rename_folders()

View File

@@ -96,7 +96,18 @@ def fetch_messages(headers, request_id):
if r.status_code != 200: if r.status_code != 200:
print("❌ HTTP", r.status_code, "for request", request_id) print("❌ HTTP", r.status_code, "for request", request_id)
return [] return []
return r.json().get("data", {}).get("messages", []) or []
try:
data = r.json()
except Exception as e:
print(f"❌ Failed to parse JSON for {request_id}: {e}")
print(" Response text:", r.text[:500])
return []
messages = data.get("data", {}).get("messages", []) or []
print(f" 🌐 API returned {len(messages)} messages for {request_id}")
return messages
# ============================== # ==============================
@@ -218,18 +229,19 @@ def main():
print(f"📦 Already downloaded attachments: {len(existing_ids)}\n") print(f"📦 Already downloaded attachments: {len(existing_ids)}\n")
# ---- Select pozadavky needing message sync # ---- Select 10 oldest pozadavky (regardless of messagesProcessed)
sql = """ sql = """
SELECT id SELECT id
FROM pozadavky FROM pozadavky
WHERE messagesProcessed IS NULL ORDER BY updatedAt ASC
OR messagesProcessed < updatedAt LIMIT 10
""" """
with conn.cursor() as cur: with conn.cursor() as cur:
cur.execute(sql) cur.execute(sql)
requests_to_process = cur.fetchall() requests_to_process = cur.fetchall()
print(f"📋 Found {len(requests_to_process)} pozadavků requiring message sync.\n") print(f"📋 Will process {len(requests_to_process)} oldest pozadavků.\n")
# ---- Process each pozadavek # ---- Process each pozadavek
for idx, row in enumerate(requests_to_process, 1): for idx, row in enumerate(requests_to_process, 1):

View File

@@ -1,122 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Test: Read conversation messages for one request_id and save full JSON.
Uses same logic as the production messages downloader.
"""
import json
import requests
from pathlib import Path
from datetime import datetime
# ==============================
# ⚙️ CONFIGURATION
# ==============================
TOKEN_PATH = Path("token.txt") # same token file as your working script
REQUEST_ID = "092a0c63-28be-4c6b-ab3b-204e1e2641d4" # 🧾 replace as needed
OUTPUT_DIR = Path(r"u:\Dropbox\!!!Days\Downloads Z230") # where to save the JSON
GRAPHQL_QUERY = 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
}
}
}
"""
# ==============================
# 🔑 READ TOKEN
# ==============================
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
# ==============================
# 🚀 FETCH FROM API
# ==============================
def fetch_messages(headers, request_id):
variables = {"requestId": request_id, "updatedSince": None}
payload = {
"operationName": "UseMessages_ListMessages",
"query": GRAPHQL_QUERY,
"variables": variables,
}
r = requests.post("https://api.medevio.cz/graphql", json=payload, headers=headers, timeout=30)
print("HTTP status:", r.status_code)
if r.status_code != 200:
print("❌ Response preview:", r.text[:500])
return None
return r.json()
# ==============================
# 🧠 MAIN
# ==============================
def main():
token = read_token(TOKEN_PATH)
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
}
data = fetch_messages(headers, REQUEST_ID)
if not data:
print("⚠️ No data returned.")
return
# Save full JSON to file
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
output_path = OUTPUT_DIR / f"messages_{REQUEST_ID}.json"
output_path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
print(f"✅ JSON saved to {output_path}")
# Optional: print summary
messages = data.get("data", {}).get("messages", [])
print(f"💬 {len(messages)} messages found:")
print("" * 100)
for msg in messages:
sender = msg.get("sender") or {}
sender_name = " ".join(x for x in [sender.get("name"), sender.get("surname")] if x).strip() or "(unknown)"
text = (msg.get("text") or "").strip().replace("\n", " ")
created = msg.get("createdAt", "")[:16].replace("T", " ")
print(f"[{created}] {sender_name}: {text}")
if msg.get("medicalRecord"):
mr = msg["medicalRecord"]
print(f" 📎 {mr.get('description') or '(no description)'} ({mr.get('contentType')})")
print(f" URL: {mr.get('downloadUrl') or mr.get('url')}")
print("" * 100)
# ==============================
if __name__ == "__main__":
main()

View File

@@ -1,296 +0,0 @@
#!/usr/bin/env python3
# -*- 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().
"""
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,
}
# ✅ Optional: Only process requests created after this date ("" = no limit)
CREATED_AFTER = "2024-12-01"
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
}
}
}
"""
# ==============================
# 🧮 HELPERS
# ==============================
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
# ==============================
# 📡 FETCH MESSAGES
# ==============================
def fetch_messages(headers, request_id):
variables = {"requestId": request_id, "updatedSince": None}
payload = {
"operationName": "UseMessages_ListMessages",
"query": GRAPHQL_QUERY_MESSAGES,
"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 messages of request {request_id}")
return []
data = r.json().get("data", {}).get("messages", [])
return data or []
# ==============================
# 💾 SAVE: conversation row
# ==============================
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")
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)
"""
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
))
# ==============================
# 💾 SAVE: download attachment (from message)
# ==============================
def insert_download_from_message(cur, req_id, msg, existing_ids):
mr = msg.get("medicalRecord") or {}
attachment_id = mr.get("id")
if not attachment_id:
return False
if attachment_id in existing_ids:
print(f" ⏭️ Skipping already downloaded message-attachment {attachment_id}")
return False
url = mr.get("downloadUrl") or mr.get("url")
if not url:
return False
try:
r = requests.get(url, timeout=30)
r.raise_for_status()
content = r.content
except Exception as e:
print(f" ⚠️ Failed to download message attachment {attachment_id}: {e}")
return False
filename = extract_filename_from_url(url)
content_type = mr.get("contentType")
file_size = len(content)
created_date = parse_dt(msg.get("createdAt"))
# 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)
ON DUPLICATE KEY UPDATE
file_content = VALUES(file_content),
file_size = VALUES(file_size),
downloaded_at = NOW()
""", (
req_id,
attachment_id,
"MESSAGE_ATTACHMENT",
filename,
content_type,
file_size,
None,
None,
created_date,
content
))
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}",
"Content-Type": "application/json",
"Accept": "application/json",
}
conn = pymysql.connect(**DB_CONFIG)
# Load existing download IDs to skip duplicates (same logic as your script)
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.")
# Pull pozadavky where messagesProcessed IS NULL (optionally by createdAt)
sql = """
SELECT id, displayTitle, pacient_prijmeni, pacient_jmeno, createdAt
FROM pozadavky
WHERE messagesProcessed 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 (messagesProcessed IS NULL"
+ (f", created >= {CREATED_AFTER}" if CREATED_AFTER else "") + ")")
for i, row in enumerate(rows, 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})")
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
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
conn.close()
print("\n✅ Done! All new conversations processed and pozadavky updated.")
# ==============================
if __name__ == "__main__":
main()

View File

@@ -1,105 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import zlib
import pymysql
import re
from pathlib import Path
from datetime import datetime
# ==============================
# ⚙️ CONFIGURATION
# ==============================
DB_CONFIG = {
"host": "192.168.1.76",
"port": 3307,
"user": "root",
"password": "Vlado9674+",
"database": "medevio",
"charset": "utf8mb4",
}
BASE_DIR = Path(r"u:\Dropbox\Ordinace\Dokumentace_ke_zpracování\MP1")
BASE_DIR.mkdir(parents=True, exist_ok=True)
def sanitize_name(name: str) -> str:
"""Replace invalid filename characters with underscore."""
return re.sub(r'[<>:"/\\|?*\x00-\x1F]', "_", name).strip()
# ==============================
# 📦 STREAMING EXPORT WITH TRIANGLE CHECK
# ==============================
conn = pymysql.connect(**DB_CONFIG)
cur_meta = conn.cursor(pymysql.cursors.DictCursor)
cur_blob = conn.cursor()
cur_meta.execute("""
SELECT id, request_id, attachment_id, filename, pacient_jmeno,
pacient_prijmeni, created_at, downloaded_at
FROM medevio_downloads
WHERE file_content IS NOT NULL;
""")
rows = cur_meta.fetchall()
print(f"📋 Found {len(rows)} records to check/export")
skipped, exported = 0, 0
for r in rows:
try:
created = r["created_at"] or r["downloaded_at"] or datetime.now()
date_str = created.strftime("%Y-%m-%d")
prijmeni = sanitize_name(r["pacient_prijmeni"] or "Unknown")
jmeno = sanitize_name(r["pacient_jmeno"] or "")
# 🔥 NEW: use full request_id instead of CRC32
full_req_id = sanitize_name(r["request_id"])
# Base (non-triangle) and processed (triangle) folder variants
base_folder = f"{date_str} {prijmeni}, {jmeno} {full_req_id}"
tri_folder = f"{date_str}{prijmeni}, {jmeno} {full_req_id}"
base_folder = sanitize_name(base_folder)
tri_folder = sanitize_name(tri_folder)
base_path = BASE_DIR / base_folder
tri_path = BASE_DIR / tri_folder
filename = sanitize_name(r["filename"] or f"unknown_{r['id']}.bin")
file_path_base = base_path / filename
file_path_tri = tri_path / filename
# 🟡 Skip if exists in either version
if file_path_base.exists() or file_path_tri.exists():
skipped += 1
found_in = "" if file_path_tri.exists() else ""
print(f"⏭️ Skipping existing{found_in}: {filename}")
continue
# Make sure base folder exists before saving
base_path.mkdir(parents=True, exist_ok=True)
# 2⃣ Fetch blob
cur_blob.execute("SELECT file_content FROM medevio_downloads WHERE id = %s", (r["id"],))
blob = cur_blob.fetchone()[0]
if blob:
with open(file_path_base, "wb") as f:
f.write(blob)
exported += 1
print(f"✅ Saved: {file_path_base.relative_to(BASE_DIR)}")
else:
print(f"⚠️ No content for id={r['id']}")
except Exception as e:
print(f"❌ Error for id={r['id']}: {e}")
cur_blob.close()
cur_meta.close()
conn.close()
print(f"\n🎯 Export complete — {exported} new files saved, {skipped} skipped.\n")

View File

@@ -1,96 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import json
import requests
from pathlib import Path
# ============================
# CONFIG
# ============================
TOKEN_PATH = Path("token.txt")
# vlož libovolný existující request ID
REQUEST_ID = "3fc9b28c-ada2-4d21-ab2d-fe60ad29fd8f"
GRAPHQL_NOTES_QUERY = r"""
query ClinicRequestNotes_Get($patientRequestId: String!) {
notes: getClinicPatientRequestNotes(requestId: $patientRequestId) {
id
content
createdAt
updatedAt
createdBy {
id
name
surname
}
}
}
"""
# ============================
# TOKEN
# ============================
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
# ============================
# FETCH
# ============================
def fetch_notes(request_id, token):
url = "https://api.medevio.cz/graphql"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
}
variables = {"patientRequestId": request_id}
payload = {
"operationName": "ClinicRequestNotes_Get",
"query": GRAPHQL_NOTES_QUERY,
"variables": variables,
}
r = requests.post(url, json=payload, headers=headers)
r.raise_for_status()
return r.json()
# ============================
# MAIN
# ============================
def main():
token = read_token(TOKEN_PATH)
print(f"\n🔍 Fetching NOTES for request:\n ID = {REQUEST_ID}\n")
data = fetch_notes(REQUEST_ID, token)
print("📄 FULL RAW JSON:\n")
print(json.dumps(data, indent=2, ensure_ascii=False))
print("\n📝 Parsed notes:\n")
notes = data.get("data", {}).get("notes") or []
if not notes:
print(" (no notes found)")
return
for n in notes:
author = n.get("createdBy")
print(f"--- Note {n.get('id')} ---")
print(f"Created: {n.get('createdAt')}")
print(f"Updated: {n.get('updatedAt')}")
if author:
print(f"Author: {author.get('name')} {author.get('surname')}")
print("Content:")
print(n.get("content"))
print()
if __name__ == "__main__":
main()

View File

@@ -1,216 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Download all attachments for pozadavky where attachmentsProcessed IS NULL
and (optionally) createdAt is newer than a cutoff date.
Store them in MySQL table `medevio_downloads`, and update pozadavky.attachmentsProcessed.
"""
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,
}
CREATED_AFTER = "2024-12-01" # optional filter
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 extract_filename_from_url(url: str) -> str:
try:
return url.split("/")[-1].split("?")[0]
except:
return "unknown_filename"
def read_token(p: Path) -> str:
tok = p.read_text(encoding="utf-8").strip()
return tok.split(" ", 1)[1] if tok.startswith("Bearer ") else tok
# ==============================
# 📡 FETCH ATTACHMENTS
# ==============================
def fetch_attachments(headers, request_id):
payload = {
"operationName": "ClinicRequestDetail_GetPatientRequest2",
"query": GRAPHQL_QUERY,
"variables": {"requestId": request_id},
}
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 []
return r.json().get("data", {}).get("patientRequestMedicalRecords", [])
# ==============================
# 💾 SAVE TO MYSQL (clean version)
# ==============================
def insert_download(cur, req_id, a, m, created_date, existing_ids):
attachment_id = a.get("id")
if attachment_id in existing_ids:
print(f" ⏭️ Already downloaded {attachment_id}")
return False
url = m.get("downloadUrl")
if not url:
print(" ⚠️ Missing download URL")
return False
filename = extract_filename_from_url(url)
# Download file
try:
r = requests.get(url, timeout=30)
r.raise_for_status()
content = r.content
except Exception as e:
print(f" ⚠️ Download failed {url}: {e}")
return False
file_size = len(content)
attachment_type = a.get("attachmentType")
content_type = m.get("contentType")
# 🚨 CLEAN INSERT — no patient_jmeno/no patient_prijmeni
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,
attachment_type,
filename,
content_type,
file_size,
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",
}
conn = pymysql.connect(**DB_CONFIG)
# Load existing IDs
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"{len(existing_ids)} attachments already saved.")
# Build query for pozadavky
sql = """
SELECT id, 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)
req_rows = cur.fetchall()
print(f"📋 Found {len(req_rows)} pozadavky to process.")
# Process each pozadavek
for i, row in enumerate(req_rows, 1):
req_id = row["id"]
prijmeni = row.get("pacient_prijmeni") or "Neznamy"
jmeno = row.get("pacient_jmeno") or ""
created_date = row.get("createdAt") or datetime.now()
print(f"\n[{i}/{len(req_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, 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" ✅ Done ({len(attachments)} attachments)")
time.sleep(0.3)
conn.close()
print("\n🎯 All attachments processed.")
# ==============================
if __name__ == "__main__":
main()

View File

@@ -1,112 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import json
import requests
from pathlib import Path
TOKEN_PATH = Path("token.txt")
CLINIC_SLUG = "mudr-buzalkova"
GRAPHQL_URL = "https://api.medevio.cz/graphql" # ← správná URL
# 👉 nastav offset zde
OFFSET = 0 # 0 = první pacient, 1 = druhý, 100 = 101. pacient
FIRST = 1 # načteme jen jednoho
QUERY = """
query PatientGridImpl_ListClinicPatients(
$clinicSlug: String!,
$filter: ListPatientFilter!,
$pageInfo: PageInfo!,
$sort: [ListPatientsSort!]
) {
patientsList: listPatients(
clinicSlug: $clinicSlug
filter: $filter
pageInfo: $pageInfo
sort: $sort
) {
count
patients {
id
identificationNumber
insuranceCompanyObject {
id
shortName
}
lastReservation
locale
name
nextReservation
key
phone
sex
status2
surname
type
kind
isInClinic
isUnknownPatient
user {
id
name
surname
phone
registrationCompletedTime
}
owner {
name
surname
}
clinics {
id
name
slug
}
tags(onlyImportant: false) {
id
name
color
icon
}
}
}
}
"""
variables = {
"clinicSlug": CLINIC_SLUG,
"filter": {},
"pageInfo": {
"first": FIRST,
"offset": OFFSET
},
"sort": [
{
"field": "ReverseFullName",
"sort": "ASC"
}
]
}
token = Path("token.txt").read_text().strip()
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
}
print("⏳ Fetching patient...")
response = requests.post(
GRAPHQL_URL,
json={"query": QUERY, "variables": variables},
headers=headers
)
response.raise_for_status()
data = response.json()
print("\n📌 RAW JSON RESPONSE:\n")
print(json.dumps(data, indent=2, ensure_ascii=False))

View File

@@ -1,203 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import json
import requests
import pymysql
from pathlib import Path
# ==============================
# 🔧 KONFIGURACE
# ==============================
TOKEN_PATH = Path("token.txt")
CLINIC_SLUG = "mudr-buzalkova"
GRAPHQL_URL = "https://api.medevio.cz/graphql"
DB_CONFIG = {
"host": "192.168.1.76",
"port": 3307,
"user": "root",
"password": "Vlado9674+",
"database": "medevio",
"charset": "utf8mb4",
"cursorclass": pymysql.cursors.DictCursor,
}
# počet pacientů na stránku
PAGE_SIZE = 100
# ==============================
# 📌 GRAPHQL QUERY
# ==============================
QUERY = """
query PatientGridImpl_ListClinicPatients(
$clinicSlug: String!,
$filter: ListPatientFilter!,
$pageInfo: PageInfo!,
$sort: [ListPatientsSort!]
) {
patientsList: listPatients(
clinicSlug: $clinicSlug
filter: $filter
pageInfo: $pageInfo
sort: $sort
) {
count
patients {
id
identificationNumber
insuranceCompanyObject {
id
shortName
}
name
surname
phone
sex
status2
type
kind
isInClinic
isUnknownPatient
user {
registrationCompletedTime
}
clinics {
id
name
slug
}
tags(onlyImportant: false) {
id
name
color
icon
}
}
}
}
"""
from datetime import datetime
def normalize_dt(dt_str):
if not dt_str:
return None
# remove Z
dt_str = dt_str.replace("Z", "")
# replace T with space
dt_str = dt_str.replace("T", " ")
# remove fractional seconds
if "." in dt_str:
dt_str = dt_str.split(".")[0]
return dt_str # MySQL can accept "YYYY-MM-DD HH:MM:SS"
# ==============================
# 💾 ULOŽENÍ PACIENTA
# ==============================
def save_patient_summary(cur, p):
sql = """
REPLACE INTO medevio_pacienti (
id, jmeno, prijmeni, rodne_cislo, telefon, pohlavi,
pojistovna_id, pojistovna_nazev,
status, typ, kind,
is_in_clinic, is_unknown,
registration_time,
tags_json, clinics_json,
last_update
)
VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,NOW())
"""
ins = p.get("insuranceCompanyObject") or {}
user = p.get("user") or {}
cur.execute(sql, (
p.get("id"),
p.get("name"),
p.get("surname"),
p.get("identificationNumber"),
p.get("phone"),
p.get("sex"),
ins.get("id"),
ins.get("shortName"),
p.get("status2"),
p.get("type"),
p.get("kind"),
1 if p.get("isInClinic") else 0,
1 if p.get("isUnknownPatient") else 0,
normalize_dt(user.get("registrationCompletedTime")),
json.dumps(p.get("tags"), ensure_ascii=False),
json.dumps(p.get("clinics"), ensure_ascii=False),
))
# ==============================
# 🧠 MAIN
# ==============================
def main():
token = TOKEN_PATH.read_text().strip()
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
}
conn = pymysql.connect(**DB_CONFIG)
cur = conn.cursor()
offset = 0
total = None
print("⏳ Starting patient sync...\n")
while True:
print(f"➡️ Fetching patients {offset}{offset + PAGE_SIZE} ...")
variables = {
"clinicSlug": CLINIC_SLUG,
"filter": {},
"pageInfo": {"first": PAGE_SIZE, "offset": offset},
"sort": [{"field": "ReverseFullName", "sort": "ASC"}],
}
response = requests.post(
GRAPHQL_URL,
json={"query": QUERY, "variables": variables},
headers=headers,
timeout=30
)
response.raise_for_status()
data = response.json()
block = data["data"]["patientsList"]
if total is None:
total = block["count"]
print(f"📌 Total patients: {total}\n")
patients = block["patients"]
if not patients:
print("✅ No more patients. Finished.")
break
# save each patient
for p in patients:
save_patient_summary(cur, p)
conn.commit()
print(f" ✓ Saved {len(patients)} patients.")
offset += PAGE_SIZE
if offset >= total:
print("\n✅ All patients downloaded.")
break
cur.close()
conn.close()
# ==============================
if __name__ == "__main__":
main()

View File

@@ -1,251 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import json
import requests
from pathlib import Path
TOKEN_PATH = Path("token.txt")
GRAPHQL_URL = "https://api.medevio.cz/graphql"
CLINIC_SLUG = "mudr-buzalkova"
PATIENT_ID = "5a0a9ff0-bbe8-4fc7-a27d-b7b475ee2189"
QUERY = """
query ClinicPatientDetailModal_GetData(
$clinicSlug: String!,
$patientId: String!,
$patientUuid: UUID!,
$challengesStatus: ECRFChallengeStatus!,
$locale: Locale!
) {
clinic: getClinic(clinicSlug: $clinicSlug) {
id
features
sslSUKLCertificateId
type
ais
slug
...ClinicWithTypeAndFeatures_Clinic
...PatientInfo_Clinic
__typename
}
patient: getPatientForClinic(clinicSlug: $clinicSlug, patientId: $patientId) {
...ClinicPatientDetailModal_Patient
__typename
}
challenges: listPatientChallenges2(
clinicSlug: $clinicSlug
patientId: $patientId
status: $challengesStatus
) {
...ChallengeTableList_EcrfChallenge
__typename
}
patientRequestsResponse: filterPatientRequestsForClinic(
clinicSlug: $clinicSlug
filter: {patientId: $patientUuid}
pageInfo: {first: 1, offset: 0}
) {
count
items { id __typename }
__typename
}
treatmentPlanPatients: listTreatmentPlanPatients(
clinicSlug: $clinicSlug
patientId: $patientUuid
) {
...ClinicPlanPatientList_PlanPatient
__typename
}
premiumPlans: listClinicPremiumPlans(clinicSlug: $clinicSlug) {
id
__typename
}
mergeSuggestions: findMergeSuggestions(
clinicSlug: $clinicSlug
input: {existingPatientId: $patientUuid}
) {
...MergeSuggestionAlert_MergeSuggestionResult
__typename
}
insuranceCards: getPatientDocuments(
patientId: $patientUuid
type: InsuranceCard
) {
...PatientInfo_InsuranceCard
__typename
}
}
fragment ClinicWithTypeAndFeatures_Clinic on Clinic {
id
type
features
__typename
}
fragment PatientInfo_Clinic on Clinic {
country
id
slug
ais
...ClinicWithTypeAndFeatures_Clinic
__typename
}
fragment ClinicPatientDetailModal_Patient on ExtendedPatient {
id
isInClinic
kind
name
isUnknownPatient
sex
surname
identificationNumber
editableByDoctor
type
key
user { id name surname __typename }
...ClinicPatientDetail_Patient
...PatientInfo_AccountPatient
...ClinicPatientInfo_Patient
__typename
}
fragment ClinicPatientDetail_Patient on ExtendedPatient {
name
surname
email
id
identificationNumber
isInClinic
key
phone
sex
type
dob
user { id __typename }
isUnknownPatient
hasMobileApp
__typename
}
fragment PatientInfo_AccountPatient on ExtendedPatient {
id
createdAt
key
user {
registrationCompletedTime
deactivatedTime
__typename
}
__typename
}
fragment ClinicPatientInfo_Patient on ExtendedPatient {
anamnesisShared
anamnesisStatusForClinic { updatedAt __typename }
clinics { id name slug __typename }
id
isInClinic
dob
city
familyMembers: family { __typename }
houseNumber
identificationNumber
insuranceCompanyObject { id code name shortName __typename }
kind
name
note
owner { name surname __typename }
key
status
street
surname
user { id email name phone surname __typename }
userRelationship
premiumPlanPatient { id __typename }
sex
tags(onlyImportant: false) { id name color icon __typename }
type
isUnknownPatient
hasMobileApp
__typename
}
fragment ChallengeTableList_EcrfChallenge on ECRFChallenge {
id
createdAt
sentAt
issuedToPatient {
id
identificationNumber
name
surname
__typename
}
userECRF(locale: $locale) { id name __typename }
patientRequestId
status
__typename
}
fragment MergeSuggestionAlert_MergeSuggestionResult on MergeSuggestionResult {
extendedPatient { id __typename }
matchResult
__typename
}
fragment ClinicPlanPatientList_PlanPatient on TreatmentPlanPatient {
id
createdAt
listPatient { id identificationNumber name key status surname __typename }
treatmentPlan { id slug name __typename }
__typename
}
fragment PatientInfo_InsuranceCard on PatientDocument {
id
contentType
url
downloadUrl
__typename
}
"""
def main():
token = TOKEN_PATH.read_text().strip()
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
}
variables = {
"clinicSlug": CLINIC_SLUG,
"patientId": PATIENT_ID,
"patientUuid": PATIENT_ID,
"challengesStatus": "SENT",
"locale": "cs",
}
print("⏳ Fetching patient detail…")
r = requests.post(
GRAPHQL_URL,
json={"query": QUERY, "variables": variables},
headers=headers,
timeout=30
)
r.raise_for_status()
data = r.json()
print("\n📌 RAW DETAIL JSON:\n")
print(json.dumps(data, indent=2, ensure_ascii=False))
if __name__ == "__main__":
main()

View File

@@ -1,365 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import json
import requests
import pymysql
from pathlib import Path
# ==============================
# CONFIG
# ==============================
TOKEN_PATH = Path("token.txt")
GRAPHQL_URL = "https://api.medevio.cz/graphql"
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,
}
PATIENT_ID = "5a0a9ff0-bbe8-4fc7-a27d-b7b475ee2189"
# ==============================
# HELPERS
# ==============================
def normalize_dt(dt_str):
if not dt_str:
return None
dt_str = dt_str.replace("Z", "").replace("T", " ")
if "." in dt_str:
dt_str = dt_str.split(".")[0]
return dt_str
def save_patient_detail(cur, p):
user = p.get("user") or {}
ins = p.get("insuranceCompanyObject") or {}
tags = p.get("tags") or []
clinics = p.get("clinics") or []
sql = """
UPDATE medevio_pacienti SET
email = %s,
telefon = %s,
dob = %s,
street = %s,
house_number = %s,
city = %s,
user_id = %s,
user_email = %s,
user_name = %s,
user_surname = %s,
user_phone = %s,
user_reg_time = %s,
user_deactivated_time = %s,
created_at = %s,
note = %s,
has_mobile_app = %s,
user_relationship = %s,
pojistovna_code = %s,
tags_json = %s,
clinics_json = %s,
last_update = NOW()
WHERE id = %s
"""
cur.execute(sql, (
p.get("email"),
p.get("phone"),
p.get("dob"),
p.get("street"),
p.get("houseNumber"),
p.get("city"),
user.get("id"),
user.get("email"),
user.get("name"),
user.get("surname"),
user.get("phone"),
normalize_dt(user.get("registrationCompletedTime")),
normalize_dt(user.get("deactivatedTime")),
normalize_dt(p.get("createdAt")),
p.get("note"),
1 if p.get("hasMobileApp") else 0,
p.get("userRelationship"),
ins.get("code"),
json.dumps(tags, ensure_ascii=False),
json.dumps(clinics, ensure_ascii=False),
p.get("id")
))
# ==============================
# FULL EXACT WORKING GRAPHQL QUERY
# ==============================
QUERY = """
query ClinicPatientDetailModal_GetData(
$clinicSlug: String!,
$patientId: String!,
$patientUuid: UUID!,
$challengesStatus: ECRFChallengeStatus!,
$locale: Locale!
) {
clinic: getClinic(clinicSlug: $clinicSlug) {
id
features
sslSUKLCertificateId
type
ais
slug
...ClinicWithTypeAndFeatures_Clinic
...PatientInfo_Clinic
__typename
}
patient: getPatientForClinic(clinicSlug: $clinicSlug, patientId: $patientId) {
...ClinicPatientDetailModal_Patient
__typename
}
challenges: listPatientChallenges2(
clinicSlug: $clinicSlug
patientId: $patientId
status: $challengesStatus
) {
...ChallengeTableList_EcrfChallenge
__typename
}
patientRequestsResponse: filterPatientRequestsForClinic(
clinicSlug: $clinicSlug
filter: {patientId: $patientUuid}
pageInfo: {first: 1, offset: 0}
) {
count
items { id __typename }
__typename
}
treatmentPlanPatients: listTreatmentPlanPatients(
clinicSlug: $clinicSlug
patientId: $patientUuid
) {
...ClinicPlanPatientList_PlanPatient
__typename
}
premiumPlans: listClinicPremiumPlans(clinicSlug: $clinicSlug) {
id
__typename
}
mergeSuggestions: findMergeSuggestions(
clinicSlug: $clinicSlug
input: {existingPatientId: $patientUuid}
) {
...MergeSuggestionAlert_MergeSuggestionResult
__typename
}
insuranceCards: getPatientDocuments(
patientId: $patientUuid
type: InsuranceCard
) {
...PatientInfo_InsuranceCard
__typename
}
}
fragment ClinicWithTypeAndFeatures_Clinic on Clinic {
id
type
features
__typename
}
fragment PatientInfo_Clinic on Clinic {
country
id
slug
ais
...ClinicWithTypeAndFeatures_Clinic
__typename
}
fragment ClinicPatientDetailModal_Patient on ExtendedPatient {
id
isInClinic
kind
name
isUnknownPatient
sex
surname
identificationNumber
editableByDoctor
type
key
user { id name surname __typename }
...ClinicPatientDetail_Patient
...PatientInfo_AccountPatient
...ClinicPatientInfo_Patient
__typename
}
fragment ClinicPatientDetail_Patient on ExtendedPatient {
name
surname
email
id
identificationNumber
isInClinic
key
phone
sex
type
dob
user { id __typename }
isUnknownPatient
hasMobileApp
__typename
}
fragment PatientInfo_AccountPatient on ExtendedPatient {
id
createdAt
key
user {
registrationCompletedTime
deactivatedTime
__typename
}
__typename
}
fragment ClinicPatientInfo_Patient on ExtendedPatient {
anamnesisShared
anamnesisStatusForClinic { updatedAt __typename }
clinics { id name slug __typename }
id
isInClinic
dob
city
familyMembers: family { __typename }
houseNumber
identificationNumber
insuranceCompanyObject { id code name shortName __typename }
kind
name
note
owner { name surname __typename }
key
status
street
surname
user { id email name phone surname __typename }
userRelationship
premiumPlanPatient { id __typename }
sex
tags(onlyImportant: false) { id name color icon __typename }
type
isUnknownPatient
hasMobileApp
__typename
}
fragment ChallengeTableList_EcrfChallenge on ECRFChallenge {
id
createdAt
sentAt
issuedToPatient {
id
identificationNumber
name
surname
__typename
}
userECRF(locale: $locale) { id name __typename }
patientRequestId
status
__typename
}
fragment MergeSuggestionAlert_MergeSuggestionResult on MergeSuggestionResult {
extendedPatient { id __typename }
matchResult
__typename
}
fragment ClinicPlanPatientList_PlanPatient on TreatmentPlanPatient {
id
createdAt
listPatient { id identificationNumber name key status surname __typename }
treatmentPlan { id slug name __typename }
__typename
}
fragment PatientInfo_InsuranceCard on PatientDocument {
id
contentType
url
downloadUrl
__typename
}
"""
# ==============================
# MAIN
# ==============================
def main():
token = TOKEN_PATH.read_text().strip()
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
}
variables = {
"clinicSlug": CLINIC_SLUG,
"patientId": PATIENT_ID,
"patientUuid": PATIENT_ID,
"challengesStatus": "SENT",
"locale": "cs",
}
print(f"⏳ Fetching patient detail {PATIENT_ID}")
r = requests.post(
GRAPHQL_URL,
json={"query": QUERY, "variables": variables},
headers=headers,
timeout=30
)
r.raise_for_status()
data = r.json()
patient = data["data"]["patient"]
if not patient:
print("❌ Patient not found in API response!")
return
print("📥 Patient detail downloaded.")
conn = pymysql.connect(**DB_CONFIG)
cur = conn.cursor()
save_patient_detail(cur, patient)
conn.commit()
print("✅ Patient detail saved to DB.")
cur.close()
conn.close()
if __name__ == "__main__":
main()

View File

@@ -1,183 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import json
import time
import random
import requests
import pymysql
from pathlib import Path
from datetime import datetime
# Patients with details_updated_at older than this date will be refreshed
# UpdateOlderThan = datetime(2025, 2, 20) # example date
UpdateOlderThan = None #když chceš všechny aktualizovat
TOKEN_PATH = Path("token.txt")
GRAPHQL_URL = "https://api.medevio.cz/graphql"
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,
}
def normalize_dt(dt_str):
if not dt_str:
return None
dt_str = dt_str.replace("Z", "").replace("T", " ")
if "." in dt_str:
dt_str = dt_str.split(".")[0]
return dt_str
def save_patient_detail(cur, p):
user = p.get("user") or {}
ins = p.get("insuranceCompanyObject") or {}
tags = p.get("tags") or []
clinics = p.get("clinics") or []
sql = """
UPDATE medevio_pacienti SET
email = %s,
telefon = %s,
dob = %s,
street = %s,
house_number = %s,
city = %s,
user_id = %s,
user_email = %s,
user_name = %s,
user_surname = %s,
user_phone = %s,
user_reg_time = %s,
user_deactivated_time = %s,
created_at = %s,
note = %s,
has_mobile_app = %s,
user_relationship = %s,
pojistovna_code = %s,
tags_json = %s,
clinics_json = %s,
last_update = NOW(),
details_updated_at = NOW()
WHERE id = %s
"""
cur.execute(sql, (
p.get("email"),
p.get("phone"),
p.get("dob"),
p.get("street"),
p.get("houseNumber"),
p.get("city"),
user.get("id"),
user.get("email"),
user.get("name"),
user.get("surname"),
user.get("phone"),
normalize_dt(user.get("registrationCompletedTime")),
normalize_dt(user.get("deactivatedTime")),
normalize_dt(p.get("createdAt")),
p.get("note"),
1 if p.get("hasMobileApp") else 0,
p.get("userRelationship"),
ins.get("code"),
json.dumps(tags, ensure_ascii=False),
json.dumps(clinics, ensure_ascii=False),
p.get("id")
))
# === PLACEHOLDER: Insert your full GraphQL query here ===
QUERY = Path("patient_detail.graphql").read_text(encoding="utf-8")
def main():
token = TOKEN_PATH.read_text().strip()
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
}
conn = pymysql.connect(**DB_CONFIG)
cur = conn.cursor()
if UpdateOlderThan:
print(f"🔎 Updating patients with details_updated_at NULL or < {UpdateOlderThan}")
cur.execute("""
SELECT id, prijmeni, jmeno, details_updated_at
FROM medevio_pacienti
WHERE details_updated_at IS NULL OR details_updated_at < %s
ORDER BY prijmeni, jmeno
""", (UpdateOlderThan,))
else:
print("🔎 Updating only patients with details_updated_at NULL")
cur.execute("""
SELECT id, prijmeni, jmeno, details_updated_at
FROM medevio_pacienti
WHERE details_updated_at IS NULL
ORDER BY prijmeni, jmeno
""")
patients = cur.fetchall()
total = len(patients)
print(f"⏳ Starting full patient detail sync for {total} patients...")
for idx, row in enumerate(patients, start=1):
pid = row["id"]
name = f"{row.get('prijmeni','')}, {row.get('jmeno','')}"
print(f"[{idx}/{total}] Updating: {name} ({pid})")
variables = {
"clinicSlug": CLINIC_SLUG,
"patientId": pid,
"patientUuid": pid,
"challengesStatus": "SENT",
"locale": "cs",
}
try:
r = requests.post(
GRAPHQL_URL,
json={"query": QUERY, "variables": variables},
headers=headers,
timeout=30
)
r.raise_for_status()
js = r.json()
p = js["data"]["patient"]
if p:
save_patient_detail(cur, p)
conn.commit()
print(" ✔ saved")
else:
print(" ⚠ no patient data returned")
except Exception as e:
print(f" ❌ ERROR: {e}")
time.sleep(2)
continue
time.sleep(random.uniform(0.5, 1.5))
conn.close()
print("✅ DONE full detail sync completed.")
if __name__ == "__main__":
main()

View File

@@ -1,87 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import json
import requests
from pathlib import Path
TOKEN_PATH = Path("token.txt")
GRAPHQL_URL = "https://api.medevio.cz/graphql"
CLINIC_SLUG = "mudr-buzalkova"
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
displayTitle(locale: $locale)
createdAt
doneAt
removedAt
extendedPatient {
name
surname
}
}
}
"""
def main():
token = TOKEN_PATH.read_text().strip()
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json",
}
variables = {
"clinicSlug": CLINIC_SLUG,
"queueId": None,
"queueAssignment": "ANY",
"state": "ACTIVE",
"pageInfo": {"first": 200, "offset": 100},
"locale": "cs",
}
print("⏳ Testing ACTIVE request fetch (LEGACY API)…")
r = requests.post(
GRAPHQL_URL,
json={"query": QUERY, "variables": variables},
headers=headers,
timeout=30
)
r.raise_for_status()
js = r.json()
# extract list
requests_list = js.get("data", {}).get("requests", [])
print("\n📌 Number of ACTIVE requests returned:", len(requests_list))
print("\n📌 First 5 request IDs:")
for item in requests_list[:5]:
print("", item.get("id"))
# debug dump if needed
# print(json.dumps(js, indent=2, ensure_ascii=False))
print("\n✅ Test completed.\n")
if __name__ == "__main__":
main()

View File

@@ -1,168 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import pymysql
import requests
from pathlib import Path
from datetime import datetime
# ================================
# 🔧 CONFIGURATION
# ================================
TOKEN_PATH = Path("token.txt")
CLINIC_SLUG = "mudr-buzalkova"
LIMIT = 300 # download the latest 300 requests
DB_CONFIG = {
"host": "192.168.1.76",
"port": 3307,
"user": "root",
"password": "Vlado9674+",
"database": "medevio",
"charset": "utf8mb4",
"cursorclass": pymysql.cursors.DictCursor,
}
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
}
}
}
}
"""
# ================================
# TOKEN
# ================================
def read_token(p: Path) -> str:
tok = p.read_text(encoding="utf-8").strip()
if tok.startswith("Bearer "):
return tok.split(" ", 1)[1]
return tok
# ================================
# DATE PARSER
# ================================
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
# ================================
def upsert(conn, r):
p = (r.get("extendedPatient") or {})
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")),
to_mysql_dt(r.get("updatedAt")),
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 LATEST 300 REQUESTS
# ================================
def fetch_latest_requests(headers):
vars = {
"clinicSlug": CLINIC_SLUG,
"queueId": None,
"queueAssignment": "ANY",
"pageInfo": {"first": LIMIT, "offset": 0},
"locale": "cs",
"state": "DONE" # ALL STATES (ACTIVE, DONE, REMOVED)
}
payload = {
"operationName": "ClinicRequestGrid_ListPatientRequestsForClinic2",
"query": GRAPHQL_QUERY,
"variables": vars,
}
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", [])
# ================================
# 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=== Downloading last {LIMIT} requests @ {datetime.now():%Y-%m-%d %H:%M:%S} ===")
requests_list = fetch_latest_requests(headers)
print(f"📌 Requests returned: {len(requests_list)}")
for r in requests_list:
upsert(conn, r)
conn.close()
print("\n✅ Done. Latest requests synced.\n")
if __name__ == "__main__":
main()

View File

@@ -1,226 +0,0 @@
#!/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()

View File

@@ -1,227 +0,0 @@
#!/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()

View File

@@ -1,136 +0,0 @@
#!/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()

View File

@@ -1,102 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import pymysql
from pathlib import Path
import os
import re
# ==============================
# ⚙️ CONFIGURATION
# ==============================
DB_CONFIG = {
"host": "192.168.1.76",
"port": 3307,
"user": "root",
"password": "Vlado9674+",
"database": "medevio",
"charset": "utf8mb4",
}
OUTPUT = Path(r"d:\Dropbox\Ordinace\Dokumentace_ke_zpracování\mp1")
OUTPUT.mkdir(exist_ok=True)
def sanitize(name: str) -> str:
return re.sub(r'[<>:"/\\|?*\x00-\x1F]', "_", name).strip()
# ==============================
# 📥 LOAD EVERYTHING IN ONE QUERY
# ==============================
def load_all_files():
conn = pymysql.connect(**DB_CONFIG)
cur = conn.cursor(pymysql.cursors.DictCursor)
print("📥 Loading ALL medevio_downloads including BLOBs… (can take a few seconds)")
cur.execute("""
SELECT
d.id AS download_id,
d.request_id,
d.attachment_id,
d.filename,
d.content_type,
d.file_size,
d.created_at,
p.pacient_jmeno,
p.pacient_prijmeni,
d.file_content
FROM medevio_downloads d
JOIN pozadavky p ON d.request_id = p.id
ORDER BY d.created_at
""")
rows = cur.fetchall()
conn.close()
print(f"📦 Loaded {len(rows)} BLOB records.")
return rows
# ==============================
# 💾 SAVE ALL TO FILESYSTEM
# ==============================
def save_all(rows):
saved = 0
for r in rows:
req_id = r["request_id"]
jmeno = sanitize(r["pacient_jmeno"] or "")
prijmeni = sanitize(r["pacient_prijmeni"] or "")
filename = sanitize(r["filename"] or f"{r['download_id']}.bin")
# Folder for each request
folder = OUTPUT / f"{prijmeni}, {jmeno} {req_id}"
folder.mkdir(exist_ok=True)
dest = folder / filename
# Skip existing
if dest.exists():
continue
data = r["file_content"]
if not data:
continue
with open(dest, "wb") as f:
f.write(data)
print(f"💾 Saved {dest}")
saved += 1
print(f"\n🎯 Done — {saved} files saved.")
# ==============================
# MAIN
# ==============================
if __name__ == "__main__":
rows = load_all_files() # ONE query
save_all(rows) # then write to disk

View File

@@ -1,173 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import shutil
import pymysql
import re
from pathlib import Path
from datetime import datetime
import time
# ==============================
# ⚙️ CONFIGURATION
# ==============================
DB_CONFIG = {
"host": "192.168.1.76",
"port": 3307,
"user": "root",
"password": "Vlado9674+",
"database": "medevio",
"charset": "utf8mb4",
}
BASE_DIR = Path(r"d:\Dropbox\Ordinace\Dokumentace_ke_zpracování\MP")
BASE_DIR.mkdir(parents=True, exist_ok=True)
def sanitize_name(name: str) -> str:
"""Replace invalid filename characters with underscore."""
return re.sub(r'[<>:"/\\|?*\x00-\x1F]', "_", name).strip()
# ==============================
# 🧹 DELETE UNEXPECTED FILES
# ==============================
def clean_folder(folder: Path, valid_files: set):
"""Remove all files in folder that are NOT present in valid_files."""
if not folder.exists():
return
for f in folder.iterdir():
if f.is_file():
if sanitize_name(f.name) not in valid_files:
print(f"🗑️ Removing unexpected file: {f.name}")
try:
f.unlink()
except Exception as e:
print(f"⚠️ Could not delete {f}: {e}")
# ==============================
# 📦 DB CONNECTION
# ==============================
conn = pymysql.connect(**DB_CONFIG)
cur_meta = conn.cursor(pymysql.cursors.DictCursor)
cur_blob = conn.cursor()
print("🔍 Loading metadata from DB (FAST)…")
cur_meta.execute("""
SELECT d.id AS download_id,
d.request_id,
d.filename,
d.created_at,
p.updatedAt AS req_updated_at,
p.pacient_jmeno AS jmeno,
p.pacient_prijmeni AS prijmeni
FROM medevio_downloads d
JOIN pozadavky p ON d.request_id = p.id
ORDER BY p.updatedAt DESC
""")
rows = cur_meta.fetchall()
print(f"📋 Found {len(rows)} attachment records.\n")
# ==============================
# 🧠 MAIN LOOP
# ==============================
processed_requests = set()
for r in rows:
req_id = r["request_id"]
if req_id in processed_requests:
continue
processed_requests.add(req_id)
# ========== FETCH ALL VALID FILES FOR THIS REQUEST ==========
cur_meta.execute(
"SELECT filename FROM medevio_downloads WHERE request_id=%s",
(req_id,)
)
valid_files = {sanitize_name(row["filename"]) for row in cur_meta.fetchall()}
# ========== FOLDER NAME BASED ON UPDATEDAT ==========
updated_at = r["req_updated_at"] or datetime.now()
date_str = updated_at.strftime("%Y-%m-%d")
prijmeni = sanitize_name(r["prijmeni"] or "Unknown")
jmeno = sanitize_name(r["jmeno"] or "")
folder_name = f"{date_str} {prijmeni}, {jmeno} {req_id}"
folder_name = sanitize_name(folder_name)
main_folder = BASE_DIR / folder_name
# ========== FIND OLD FOLDER (DUPLICATE) ==========
# Any folder that contains "_<req_id>" and is not main_folder is duplicate
possible_dups = [
f for f in BASE_DIR.iterdir()
if f.is_dir() and req_id in f.name and f != main_folder
]
# ========== MERGE DUPLICATES ==========
for dup in possible_dups:
print(f"♻️ Merging duplicate folder: {dup.name}")
# 1) Clean unexpected files in dup
clean_folder(dup, valid_files)
# 2) Move files from dup to main folder
main_folder.mkdir(parents=True, exist_ok=True)
for f in dup.iterdir():
if f.is_file():
target = main_folder / f.name
if not target.exists():
f.rename(target)
# 3) Remove the duplicate folder
try:
shutil.rmtree(dup, ignore_errors=True)
except Exception as e:
print(f"⚠️ Could not delete duplicate folder {dup}: {e}")
# ========== CLEAN MAIN FOLDER ==========
clean_folder(main_folder, valid_files)
# ========== DOWNLOAD MISSING FILES ==========
main_folder.mkdir(parents=True, exist_ok=True)
for filename in valid_files:
dest = main_folder / filename
if dest.exists():
continue
# fetch blob only now
start = time.perf_counter()
cur_blob.execute(
"SELECT file_content FROM medevio_downloads "
"WHERE request_id=%s AND filename=%s",
(req_id, filename)
)
row = cur_blob.fetchone()
if not row:
continue
end = time.perf_counter()
print(f"⏱ Took {end - start:.4f} seconds")
content = row[0]
if not content:
continue
with open(dest, "wb") as f:
f.write(content)
print(f"💾 Wrote: {dest.relative_to(BASE_DIR)}")
print("\n🎯 Export complete.\n")
cur_blob.close()
cur_meta.close()
conn.close()

315
dddddd.py Normal file
View File

@@ -0,0 +1,315 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
FAST FILE HASH INDEXER UNRAID (BLAKE3 ONLY, ALL SHARES)
- HARDCODED SINGLE SHARE MODE
- SQL OPTIMIZATION
- STRICT MODE (NO TOLERANCE) - Updates DB on any mismatch
"""
import os
import pymysql
import socket
import platform
from blake3 import blake3
# ==============================
# ENV / HOST
# ==============================
HOSTNAME = socket.gethostname()
OS_NAME = platform.system()
# ZDE JE TO NATVRDO PRO TESTOVÁNÍ:
# SCAN_ONLY_THIS = None #"#Fotky"
SCAN_ONLY_THIS = '#Library' # "#Fotky"
# ==============================
# CONFIG
# ==============================
EXCLUDED_SHARES = {"domains", "appdata", "system", "isos"}
# --- File size limits (bytes) ---
FILE_MIN_SIZE = 0
FILE_MAX_SIZE = 1024 * 1024 * 1024 * 1024 # 50MB
DB_CONFIG = {
"host": "192.168.1.76",
"port": 3307,
"user": "root",
"password": "Vlado9674+",
"database": "torrents",
"charset": "utf8mb4",
"autocommit": True,
}
CHUNK_SIZE = 4 * 1024 * 1024 # 4 MB
PRINT_SKIPPED = False
# ==============================
# HASH
# ==============================
def compute_blake3(path: str) -> bytes:
h = blake3()
with open(path, "rb") as f:
for chunk in iter(lambda: f.read(CHUNK_SIZE), b""):
h.update(chunk)
return h.digest()
# ==============================
# SHARE / PATH HELPERS
# ==============================
def get_user_shares():
if SCAN_ONLY_THIS:
path = f"/mnt/user/{SCAN_ONLY_THIS}"
if os.path.isdir(path):
print(f"🎯 SINGLE SHARE MODE ACTIVE: Scanning only '{SCAN_ONLY_THIS}'")
return [SCAN_ONLY_THIS]
else:
print(f"⚠️ ERROR: Requested share '{SCAN_ONLY_THIS}' not found in /mnt/user!")
return []
shares = []
if not os.path.exists("/mnt/user"):
return []
for name in os.listdir("/mnt/user"):
if name.startswith("."):
continue
if name in EXCLUDED_SHARES:
continue
path = f"/mnt/user/{name}"
if os.path.isdir(path):
shares.append(name)
return sorted(shares)
def find_physical_roots(shares):
roots = []
if not os.path.exists("/mnt"):
return []
for disk in os.listdir("/mnt"):
if not disk.startswith("disk"):
continue
for share in shares:
path = f"/mnt/{disk}/{share}"
if os.path.isdir(path):
roots.append((share, path))
return sorted(roots)
def logical_path_from_disk_path(disk_path: str) -> str:
if not disk_path.startswith("/mnt/disk"):
raise ValueError(f"Unexpected disk path: {disk_path}")
parts = disk_path.split("/", 3)
return f"/mnt/user/{parts[3]}"
def size_allowed(size: int) -> bool:
if FILE_MIN_SIZE is not None and size < FILE_MIN_SIZE:
return False
if FILE_MAX_SIZE is not None and size > FILE_MAX_SIZE:
return False
return True
# ==============================
# MAIN
# ==============================
def main():
print("🚀 BLAKE3 indexer starting", flush=True)
print(f"🖥 Host: {HOSTNAME} | OS: {OS_NAME}", flush=True)
if FILE_MIN_SIZE or FILE_MAX_SIZE:
print(f"📏 File size limits: min={FILE_MIN_SIZE} max={FILE_MAX_SIZE}", flush=True)
shares = get_user_shares()
if not shares:
print("❌ No user shares to index!", flush=True)
return
print("📦 User shares to index:", flush=True)
for s in shares:
print(f" - {s}", flush=True)
scan_roots = find_physical_roots(shares)
if not scan_roots:
print("❌ No physical disk roots found!", flush=True)
return
print("📂 Physical scan roots:", flush=True)
for _, path in scan_roots:
print(f" - {path}", flush=True)
try:
db = pymysql.connect(**DB_CONFIG)
cur = db.cursor()
# === TOTO JE TEN PŘÍKAZ "NEPŘEMÝŠLEJ" ===
# Nastaví relaci na UTC. MySQL přestane posouvat časy o hodinu sem a tam.
# cur.execute("SET time_zone = '+00:00'")
# =========================================
except Exception as e:
print(f"❌ Database connection failed: {e}")
return
print("📥 Loading already indexed files into memory...", flush=True)
# === OPTIMALIZACE SQL ===
if SCAN_ONLY_THIS:
search_pattern = f"/mnt/user/{SCAN_ONLY_THIS}%"
print(f"⚡ OPTIMIZATION: Fetching only DB records for '{search_pattern}'", flush=True)
cur.execute("""
SELECT full_path, file_size, UNIX_TIMESTAMP(mtime)
FROM file_md5_index
WHERE host_name = %s AND full_path LIKE %s
""", (HOSTNAME, search_pattern))
else:
cur.execute("""
SELECT full_path, file_size, UNIX_TIMESTAMP(mtime)
FROM file_md5_index
WHERE host_name = %s
""", (HOSTNAME,))
# Načteme do slovníku pro rychlé vyhledávání
# Formát: { "cesta": (velikost, mtime) }
indexed_map = {row[0]: (row[1], row[2]) for row in cur.fetchall()}
print(f"✅ Loaded {len(indexed_map):,} indexed entries", flush=True)
print("======================================", flush=True)
new_files = 0
skipped = 0
filtered = 0
seen_paths = set()
# --- SCAN ---
for share, scan_root in scan_roots:
for root, _, files in os.walk(scan_root):
for fname in files:
disk_path = os.path.join(root, fname)
try:
stat = os.stat(disk_path)
except OSError:
continue
size = stat.st_size
if not size_allowed(size):
filtered += 1
continue
logical_path = logical_path_from_disk_path(disk_path)
if logical_path in seen_paths:
continue
seen_paths.add(logical_path)
mtime = int(stat.st_mtime)
# === PŘÍSNÁ KONTROLA (ŽÁDNÁ TOLERANCE) ===
# Pokud soubor v DB existuje a přesně sedí velikost i čas, přeskočíme ho.
# Vše ostatní (včetně posunu času o 1s) se považuje za změnu a aktualizuje se.
is_match = False
if logical_path in indexed_map:
db_size, db_mtime = indexed_map[logical_path]
if size == db_size and mtime == db_mtime:
is_match = True
if is_match:
skipped += 1
if PRINT_SKIPPED:
print(f"⏭ SKIP {logical_path}", flush=True)
continue
# ============================================
print(" NEW / UPDATED", flush=True)
print(f" File: {logical_path}", flush=True)
print(f" Size: {size:,} B", flush=True)
try:
b3 = compute_blake3(disk_path)
except Exception as e:
print(f"❌ BLAKE3 failed: {e}", flush=True)
continue
# Zde proběhne UPDATE mtime na hodnotu z disku
cur.execute("""
INSERT INTO file_md5_index
(os_name, host_name, full_path, file_name, directory,
file_size, mtime, blake3)
VALUES (%s, %s, %s, %s, %s, %s, FROM_UNIXTIME(%s), %s)
ON DUPLICATE KEY UPDATE
file_size = VALUES(file_size),
mtime = VALUES(mtime),
blake3 = VALUES(blake3),
updated_at = CURRENT_TIMESTAMP
""", (
OS_NAME,
HOSTNAME,
logical_path,
fname,
os.path.dirname(logical_path),
size,
mtime,
b3,
))
new_files += 1
print(f" B3 : {b3.hex()}", flush=True)
print("--------------------------------------", flush=True)
print("======================================", flush=True)
print(f"✅ New / updated : {new_files}", flush=True)
print(f"⏭ Skipped : {skipped}", flush=True)
print(f"🚫 Size filtered: {filtered}", flush=True)
print("🏁 Script finished", flush=True)
# ==============================
# DB CLEANUP REMOVE DELETED FILES
# ==============================
print("🧹 Checking for deleted files in DB...", flush=True)
db_paths = set(indexed_map.keys())
deleted_paths = db_paths - seen_paths
# Omezíme jen na aktuální share (pokud je aktivní)
if SCAN_ONLY_THIS:
prefix = f"/mnt/user/{SCAN_ONLY_THIS}/"
deleted_paths = {p for p in deleted_paths if p.startswith(prefix)}
if deleted_paths:
print(f"🗑 Removing {len(deleted_paths):,} deleted files from DB", flush=True)
BATCH_SIZE = 1000
deleted_paths = list(deleted_paths)
for i in range(0, len(deleted_paths), BATCH_SIZE):
batch = deleted_paths[i:i + BATCH_SIZE]
placeholders = ",".join(["%s"] * len(batch))
sql = f"""
DELETE FROM file_md5_index
WHERE host_name = %s
AND full_path IN ({placeholders})
"""
cur.execute(sql, (HOSTNAME, *batch))
print("✅ DB cleanup completed", flush=True)
else:
print("✅ No deleted files found in DB", flush=True)
cur.close()
db.close()
if __name__ == "__main__":
main()