Z230
This commit is contained in:
118
ScrapePozadavkyJustManualScrolling.py
Normal file
118
ScrapePozadavkyJustManualScrolling.py
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import re
|
||||||
|
from urllib.parse import urlparse, parse_qs
|
||||||
|
from playwright.sync_api import sync_playwright, TimeoutError as PWTimeout, Page
|
||||||
|
|
||||||
|
STATE_FILE = "medevio_storage.json"
|
||||||
|
POZADAVKY_URL = "https://my.medevio.cz/mudr-buzalkova/klinika/pozadavky?neprirazene=1"
|
||||||
|
|
||||||
|
# ---------- helpers ----------
|
||||||
|
|
||||||
|
def get_uuid_from_href(href: str) -> str | None:
|
||||||
|
try:
|
||||||
|
q = parse_qs(urlparse(href).query)
|
||||||
|
val = q.get("pozadavek", [None])[0]
|
||||||
|
return val if val else None
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def is_flu_request(text: str) -> bool:
|
||||||
|
return bool(re.search(r"ch(r|ř)ipk", text, re.IGNORECASE))
|
||||||
|
|
||||||
|
def scrape_visible_rows(page: Page, seen: set) -> list[dict]:
|
||||||
|
"""Collect all *new* visible rows on the current screen."""
|
||||||
|
bucket: list[dict] = []
|
||||||
|
rows = page.locator('tr[data-testid="patient-request-row"]')
|
||||||
|
n = rows.count()
|
||||||
|
|
||||||
|
for i in range(n):
|
||||||
|
row = rows.nth(i)
|
||||||
|
href_el = row.locator('a[href*="pozadavky?pozadavek="]').first
|
||||||
|
href = href_el.get_attribute("href") if href_el.count() else None
|
||||||
|
req_id = get_uuid_from_href(href) if href else None
|
||||||
|
if not req_id or req_id in seen:
|
||||||
|
continue
|
||||||
|
|
||||||
|
name = (row.locator('td:nth-child(2) a span').first.text_content(timeout=0) or "").strip()
|
||||||
|
rc = (row.locator('a.MuiTypography-overline2').first.text_content(timeout=0) or "").strip()
|
||||||
|
|
||||||
|
text_p = row.locator('td:nth-child(3) p.MuiTypography-body1, td:nth-child(4) p.MuiTypography-body1').first
|
||||||
|
text_req = (text_p.text_content(timeout=0) or "").strip()
|
||||||
|
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 ""
|
||||||
|
|
||||||
|
avatar = row.locator('[data-testid="queue-avatar"]').first
|
||||||
|
assigned_to = (avatar.get_attribute("aria-label") or "").strip() if avatar.count() else ""
|
||||||
|
initials = (avatar.text_content(timeout=0) or "").strip() if avatar.count() else ""
|
||||||
|
|
||||||
|
seen.add(req_id)
|
||||||
|
bucket.append({
|
||||||
|
"id": req_id,
|
||||||
|
"name": name,
|
||||||
|
"rc": rc,
|
||||||
|
"text": text_req,
|
||||||
|
"assigned_to": assigned_to,
|
||||||
|
"initials": initials,
|
||||||
|
})
|
||||||
|
return bucket
|
||||||
|
|
||||||
|
def assign_request_to_buzalka(page: Page, request_uuid: str) -> None:
|
||||||
|
"""Open request detail by UUID and assign it to MUDr. Buzalka (já)."""
|
||||||
|
url = f"{POZADAVKY_URL.split('?')[0]}?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"✔ Assigned to MUDr. Buzalka: {request_uuid}")
|
||||||
|
|
||||||
|
# ---------- main ----------
|
||||||
|
|
||||||
|
def main():
|
||||||
|
with sync_playwright() as pw:
|
||||||
|
browser = pw.chromium.launch(headless=False) # we want to see the page
|
||||||
|
context = browser.new_context(storage_state=STATE_FILE)
|
||||||
|
page = context.new_page()
|
||||||
|
|
||||||
|
page.goto(POZADAVKY_URL, wait_until="domcontentloaded", timeout=60_000)
|
||||||
|
|
||||||
|
# check login
|
||||||
|
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("Not logged in – refresh medevio_storage.json.")
|
||||||
|
|
||||||
|
try:
|
||||||
|
page.wait_for_selector('tr[data-testid="patient-request-row"]', timeout=20_000)
|
||||||
|
except PWTimeout:
|
||||||
|
raise SystemExit("Rows not found: tr[data-testid=patient-request-row].")
|
||||||
|
|
||||||
|
seen: set[str] = set()
|
||||||
|
assigned_count = 0
|
||||||
|
|
||||||
|
print("\n>>> Scroll the page manually. Press Enter here any time to scrape current view.")
|
||||||
|
print(" Press Ctrl+C to finish.\n")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
input("Press Enter to scan visible rows...")
|
||||||
|
for item in scrape_visible_rows(page, seen):
|
||||||
|
text = item["text"]
|
||||||
|
initials = (item["initials"] or "").upper()
|
||||||
|
assigned_to = (item["assigned_to"] or "").lower()
|
||||||
|
|
||||||
|
if is_flu_request(text) and not ("buzalka" in assigned_to or initials == "VB"):
|
||||||
|
assign_request_to_buzalka(page, item["id"])
|
||||||
|
assigned_count += 1
|
||||||
|
print(f"Total newly assigned so far: {assigned_count}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
236
ScrapePozadavkyPrint01.py
Normal file
236
ScrapePozadavkyPrint01.py
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
#!/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()
|
||||||
41
ScrapePozadavkyZmenJeden.py
Normal file
41
ScrapePozadavkyZmenJeden.py
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import re
|
||||||
|
from playwright.sync_api import sync_playwright
|
||||||
|
|
||||||
|
STATE_FILE = "medevio_storage.json"
|
||||||
|
REQUEST_URL = (
|
||||||
|
"https://my.medevio.cz/mudr-buzalkova/klinika/"
|
||||||
|
"pozadavky?pozadavek=e28cbf71-8280-4078-a881-c44119bbccc2"
|
||||||
|
)
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
# otevři konkrétní požadavek
|
||||||
|
page.goto(REQUEST_URL, wait_until="domcontentloaded", timeout=60_000)
|
||||||
|
|
||||||
|
# combobox Fronta
|
||||||
|
combo = page.locator('div[role="combobox"][aria-labelledby="queue-select-label"]')
|
||||||
|
combo.wait_for(state="visible")
|
||||||
|
combo.click()
|
||||||
|
|
||||||
|
# vyber „MUDr. Buzalka (já)“
|
||||||
|
option = page.get_by_role("option", name=re.compile(r"MUDr\.?\s*Buzalka", re.I))
|
||||||
|
option.click()
|
||||||
|
|
||||||
|
# počkej, dokud síť neutichne (změna je odeslaná/uložená)
|
||||||
|
page.wait_for_load_state("networkidle")
|
||||||
|
|
||||||
|
# zavři dialog
|
||||||
|
page.locator("button.MuiDialog-close").click()
|
||||||
|
|
||||||
|
context.close()
|
||||||
|
browser.close()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user