Files
medevio/ScrapePozadavkyPrint01.py
2025-09-24 16:40:34 +02:00

237 lines
8.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from urllib.parse import urlparse, parse_qs
import re
import time
from playwright.sync_api import sync_playwright, TimeoutError as PWTimeout, Page
# ===== funkce pro přiřazení jednoho požadavku =====
def assign_request_to_buzalka(page: Page, request_uuid: str) -> None:
"""
Otevře kartu požadavku podle UUID a přiřadí ji MUDr. Buzalka (já).
Po uložení změny zavře dialog a vypíše potvrzení.
"""
url = f"https://my.medevio.cz/mudr-buzalkova/klinika/pozadavky?pozadavek={request_uuid}"
page.goto(url, wait_until="domcontentloaded", timeout=60_000)
combo = page.locator('div[role="combobox"][aria-labelledby="queue-select-label"]')
combo.wait_for(state="visible")
combo.click()
option = page.get_by_role("option", name=re.compile(r"MUDr\.?\s*Buzalka", re.I))
option.click()
page.wait_for_load_state("networkidle")
page.locator("button.MuiDialog-close").click()
print(f"✔ Požadavek {request_uuid} přiřazen: MUDr. Buzalka (já)")
# ===== hlavní část: projít listing a řešit chřipku =====
POZADAVKY_URL = "https://my.medevio.cz/mudr-buzalkova/klinika/pozadavky"
# POZADAVKY_URL = "https://my.medevio.cz/mudr-buzalkova/klinika/pozadavky?neprirazene=1"
STATE_FILE = "medevio_storage.json"
from playwright.sync_api import Page
import time
def _find_scroll_container(page: Page):
"""Return an ElementHandle of the real scrollable container, or None -> use window."""
handle = page.evaluate_handle("""
() => {
const isScrollable = el => !!el && (el.scrollHeight > el.clientHeight || el.scrollWidth > el.clientWidth);
const row = document.querySelector('tr[data-testid="patient-request-row"]');
if (row) {
let el = row.parentElement;
while (el) {
const style = getComputedStyle(el);
const overflowY = style.overflowY;
if (isScrollable(el) && (overflowY === 'auto' || overflowY === 'scroll')) return el;
el = el.parentElement;
}
}
const guesses = [
'[role="rowgroup"]', '[role="table"]', '.MuiTableContainer-root',
'[data-testid="requests-table"]', '.MuiContainer-root', 'main'
];
for (const sel of guesses) {
const el = document.querySelector(sel);
if (el) {
const style = getComputedStyle(el);
const overflowY = style.overflowY;
if (isScrollable(el) && (overflowY === 'auto' || overflowY === 'scroll')) return el;
}
}
return null;
}
""")
# If JS returned null, convert to Python None
try:
if handle is None or handle.json_value() is None:
return None
except Exception:
return None
return handle
def _has_handle(page: Page, handle) -> bool:
"""Check the handle still points to a live element; else False -> use window."""
if not handle:
return False
try:
return bool(page.evaluate("(el)=>!!el", handle))
except Exception:
return False
def _scroll_step(page: Page, container_handle, px=800):
if _has_handle(page, container_handle):
try:
page.evaluate(
"(args) => { const [el, dy] = args; el.scrollBy(0, dy); }",
[container_handle, px]
)
return
except Exception:
pass
# Fallback to window
page.evaluate("dy => window.scrollBy(0, dy)", px)
def _scroll_to_bottom(page: Page, container_handle):
if _has_handle(page, container_handle):
try:
page.evaluate("(el) => el.scrollTo(0, el.scrollHeight)", container_handle)
return
except Exception:
pass
page.evaluate("() => window.scrollTo(0, document.body.scrollHeight)")
def _click_load_more_if_any(page: Page) -> bool:
btn = page.locator("button:has-text('Načíst více'), button:has-text('Zobrazit další'), button:has-text('Load more')")
if btn.count() and btn.is_visible():
btn.click()
return True
return False
def load_all_requests(page: Page, max_rounds: int = 200, stagnation_limit: int = 4) -> None:
"""
Incrementally loads the entire list of requests.
Stops after 'stagnation_limit' rounds without row growth, or after max_rounds.
"""
page.wait_for_selector('tr[data-testid="patient-request-row"]', timeout=20000)
container = _find_scroll_container(page)
prev_count = page.locator('tr[data-testid="patient-request-row"]').count()
stagnant = 0
for _ in range(max_rounds):
if _click_load_more_if_any(page):
page.wait_for_load_state("networkidle")
# small incremental scrolls
for _ in range(4):
_scroll_step(page, container, px=800)
time.sleep(0.15)
# touch bottom at least once
_scroll_to_bottom(page, container)
# settle
page.wait_for_load_state("networkidle")
spinners = page.locator('[role="progressbar"], .MuiCircularProgress-root')
if spinners.count():
try:
spinners.first.wait_for(state="detached", timeout=5000)
except Exception:
pass
# growth check
curr_count = page.locator('tr[data-testid="patient-request-row"]').count()
if curr_count <= prev_count:
stagnant += 1
else:
stagnant = 0
prev_count = curr_count
if stagnant >= stagnation_limit:
break
def get_uuid_from_href(href: str) -> str | None:
try:
q = parse_qs(urlparse(href).query)
val = q.get("pozadavek", [None])[0]
return val
except Exception:
return None
def is_flu_request(text: str) -> bool:
# libovolná varianta slova „chřipk“ (chřipka, chřipky, …), case-insensitive, s diakritikou i bez
return bool(re.search(r"ch(r|ř)ipk", text, re.IGNORECASE))
def main():
with sync_playwright() as pw:
browser = pw.chromium.launch(headless=False)
context = browser.new_context(storage_state=STATE_FILE)
page = context.new_page()
page.goto(POZADAVKY_URL, wait_until="domcontentloaded", timeout=60_000)
body = (page.text_content("body") or "").lower()
if any(x in body for x in ["přihlášení", "přihlásit", "sign in", "login"]):
raise SystemExit("Vypadá to, že nejsi přihlášený obnov prosím medevio_storage.json.")
try:
page.wait_for_selector('tr[data-testid="patient-request-row"]', timeout=20_000)
except PWTimeout:
raise SystemExit("Nenašel jsem řádky požadavků (selector tr[data-testid=patient-request-row]).")
# after navigating to the listing and ensuring first rows are visible:
load_all_requests(page)
rows = page.locator('tr[data-testid="patient-request-row"]')
print("Loaded rows:", rows.count())
for i in range(count):
row = rows.nth(i)
# UUID z href
a_with_req = row.locator('a[href*="pozadavky?pozadavek="]').first
href = a_with_req.get_attribute("href") if a_with_req.count() else None
req_id = get_uuid_from_href(href) if href else None
if not req_id:
continue
# Text požadavku (pro filtr „chřipka“)
text_p = row.locator('td:nth-child(3) p.MuiTypography-body1, td:nth-child(4) p.MuiTypography-body1').first
text_req = text_p.inner_text().strip() if text_p.count() else ""
if not text_req:
aria = row.locator('td:nth-child(3) [aria-label], td:nth-child(4) [aria-label]').first
text_req = (aria.get_attribute("aria-label") or "").strip() if aria.count() else ""
if not is_flu_request(text_req):
# není to chřipkový požadavek přeskočit
continue
# Zjištění přiřazení z avatara v listingu
avatar = row.locator('[data-testid="queue-avatar"]').first
assigned_to = (avatar.get_attribute("aria-label") or "").strip() if avatar.count() else ""
initials = avatar.inner_text().strip() if avatar.count() else ""
already_mine = ("buzalka" in assigned_to.lower()) or (initials.upper() == "VB")
if already_mine:
print(f"= SKIP (už přiřazeno mně): {req_id} | {text_req}")
continue
print(f"→ Přiřazuji chřipkový požadavek: {req_id} | {text_req}")
assign_request_to_buzalka(page, req_id)
time.sleep(1)
context.close()
browser.close()
if __name__ == "__main__":
main()