# download_test_results_v1.4.py — dokumentace **Verze:** 1.4 · **Datum:** 2026-05-29 **Umístění:** `U:\PythonProject\Janssen\Covance\download_test_results_v1.4.py` > **Změny v1.4 oproti 1.3:** **retry na úrovni reportu.** > Při paralelním běhu server občas vrátí timeout (zejm. `page.goto` 30 s, > nebo `wait_for_function` 120 s na grid). Dřív byl takový report rovnou > zapsán jako selhaný. Teď se každý report zkusí až **2× (MAX_ATTEMPTS=2)**, > mezi pokusy 5 s pauza a `goto("about:blank")` jako reset. Většina > přechodných timeoutů projde napodruhé. > Launcher povýšen na `run_test_results_parallel_v1.2.py` (cílí na v1.4). > > **Změny v1.3 oproti 1.2:** robustní login (NEčekat na `networkidle`, > čekat přímo na pole Email/Password) + okno se při pádu nezavře > (try/except + input()). > **Změny v1.2 oproti 1.1:** paralelní běh přes sharding (`--shard N --of M`). > **Změny v1.1 oproti 1.0:** druhá studie 35472 (MDD) + report Microbiology. --- ## 1. Účel Automatické stažení reportů **Test Results** z portálu Labcorp Sponsor Portal (`xsp.labcorp.com`). Skript projde **2 studie × jejich centra × 2 typy reportu**, u každého vyexportuje grid do CSV a uloží ho timestampovaný do `Source/`. | Studie | Interní ID | Význam | Počet center | |---|---|---|---| | UC | `36940` | 77242113UCO3001 | 12 | | MDD | `35472` | druhá studie | 5 | Typy reportu: **Standard** (`/standard-test-results`) a **Microbiology** (`/microbiology`). **Celkem: (12 + 5) × 2 = 34 reportů** (prázdná centra se přeskakují, viz 6.5). --- ## 2. Spuštění ### 2a) Paralelně (doporučeno — rychlejší) ```bat U:\PythonProject\Janssen\.venv\Scripts\python.exe ^ U:\PythonProject\Janssen\Covance\run_test_results_parallel_v1.2.py ``` ### 2b) Serialně (jeden proces) ```bat U:\PythonProject\Janssen\.venv\Scripts\python.exe ^ U:\PythonProject\Janssen\Covance\download_test_results_v1.4.py ``` ### 2c) Jeden konkrétní shard ručně ```bat …\python.exe download_test_results_v1.4.py --shard 2 --of 4 ``` - Prohlížeč běží **viditelně** (`headless=False`), maximalizovaný. - Po dokončení (i po chybě) okno **čeká na Enter** — ať je vidět log. --- ## 3. Retry (v1.4) ### 3.1 Proč Z reálného běhu 4 shardů (29. 5. 2026) ze 34 reportů selhaly 2: - `930539/standard` — `page.goto` timeout 30 s (server vůbec neodpověděl) - `930547/standard` — `wait_for_function` timeout 120 s (grid se nedotáhl) Obojí jsou **přechodné serverové timeouty** (sharding ho zatěžuje). Stačí počkat a zkusit znovu — propadne to. ### 3.2 Jak Konstanty: ```python MAX_ATTEMPTS = 2 # 1. pokus + 1 retry RETRY_BACKOFF_S = 5 # pauza pred opakovanym pokusem ``` `download_report(page, report)` je wrapper, který volá `download_report_once(...)` v cyklu 1..MAX_ATTEMPTS. Při výjimce zaloguje `POKUS X/N SELHAL: …`, počká `RETRY_BACKOFF_S`, navštíví `about:blank` (reset gridu), a zkusí znovu. Když selže poslední pokus, výjimka se propustí ven a vnější smyčka v `main()` to zapíše do `failed`. ### 3.3 Co se nezopakuje Login — ten je mimo retry. Pokud spadne login, shard skončí přes vnější `try/except` v `__main__` s `FATAL: ...` a okno čeká na Enter. --- ## 4. Paralelní běh (sharding) — beze změny vůči v1.3 ### 4.1 Rozdělení práce ```python REPORTS = ALL_REPORTS[SHARD - 1::OF] ``` Shard 1 z 4 vezme indexy 0, 4, 8, … atd. Bez argumentů = serial. ### 4.2 Profil per shard | Běh | Profil | |---|---| | serialní (`--of 1`) | `browser_profile/` | | shard N (`--of M>1`) | `browser_profile_{N}/` | Chrome zamyká adresář profilu → 2 instance nesdílí 1 profil. ### 4.3 Login (varianta B) Každý shard se přihlásí sám (jen heslo, žádné MFA). Session se uloží do `browser_profile_{N}/` → další běh login přeskočí. Launcher startuje s rozestupem `STAGGER_S = 8 s`, aby se OKTA nezahltil. ### 4.4 Launcher `run_test_results_parallel_v1.2.py` - `N_SHARDS = 4`, `STAGGER_S = 8 s`. - Každý proces v novém okně (`CREATE_NEW_CONSOLE`) → logy se neprolínají. - Prefix logu: `[S{shard}/{of}]`. ### 4.5 Reálné zrychlení ~2–3× (server throttluje exporty, ne přesně 4×). --- ## 5. Výstupní soubory ``` {YYYY-MM-DD_HHMMSS} sponsor-study-{STUDY}-test-results-{SITE_ID}-{TYP}.csv ``` Všechny shardy ukládají do `Source/`. Staré se **nikdy nemažou**. --- ## 6. Klíčové technické poznatky — ověřeno přes Chrome MCP ### 6.1 Čekání na data = `.ag-row` - ❌ `wait_for_selector("div.ag-row")` (default visible) timeoutuje (řádky jsou `position-absolute`). - ✅ `wait_for_function("() => document.querySelectorAll('div.ag-row').length > 0")`. - Stabilizace: počet se čte co 2 s, dokud se 2× neshodne. - **Počet `.ag-row` je zastropený** (~153) virtuálním renderem. Export do CSV ale vyexportuje VŠECHNY řádky. ### 6.2 „Fetching Data" / spinner — NEPLATÍ pro tuto stránku Je jen na *samples* reportu. Signál = `.ag-row`. ### 6.3 Tři tečky (export) — DVA `` ✅ `page.locator("ag-export button:visible", has_text="more_horiz").first.click()` ### 6.4 „Export to CSV" — DVĚ položky v DOM ✅ `page.locator("mdl-menu-item:visible", has_text="Export to CSV").first.click()` ### 6.5 Prázdné centrum - No-rows overlay `.ag-overlay-no-rows-wrapper` (NE `-no-rows-center`). - 2 overlaye, 1 skrytý → kontrola `offsetParent !== null`. - KROK 2 čeká na `.ag-row` **NEBO** prázdný overlay → nečeká 120 s zbytečně. --- ## 7. Login logika (v1.3+) — beze změny ve v1.4 ```python page.goto(LOGIN_URL) try: page.get_by_label("Email").wait_for(state="visible", timeout=12000) except Exception: return # session aktivni → login přeskočit page.get_by_label("Email").fill(EMAIL) page.get_by_role("button", name="Next").click() page.get_by_label("Password").wait_for(state="visible", timeout=30000) page.get_by_label("Password").fill(PASSWORD) page.get_by_role("button", name="Verify").click() page.wait_for_url(lambda url: "code=" not in url or "xsp." in url, timeout=60000) ``` **NE** `networkidle`. **NE** kontrola URL (po redirectu zůstává `xsp.covance.com`). --- ## 8. Chrome flagy ``` --disable-restore-session-state --disable-session-crashed-bubble ``` --- ## 9. Robustnost - **Retry per report** (`MAX_ATTEMPTS = 2`) → přechodné timeouty propadnou. - Každý report v `try/except` → chyba u jednoho nezastaví zbytek shardu. - Celý běh shardu v `try/except` + `finally: input()` → okno se při pádu nezavře. - Souhrn na konci: `hotovo X/Y (shard N/M)` + `SELHALA centra: …`. - Launcher hlídá `returncode` každého shardu. --- ## 10. Možná budoucí rozšíření - **Více pokusů:** zvednout `MAX_ATTEMPTS` (3+) — pro hodně přetížený server. - **Více/méně procesů:** změnit `N_SHARDS` v launcheru (profily `_1.._N`). - **Další studie / centra / typ reportu:** přidat do `STUDIES` / `REPORT_TYPES`. - **Vyčistit profily:** smazat `browser_profile_*/` (vynutí nový login). --- ## 11. Příbuzné skripty | Skript | Co stahuje | |---|---| | `download_samples_report_v1.1.py` | All Samples (sampletracking) | | `download_kit_inventory_v2.1.py` | Kit inventory | | `download_equeries_report_v1.1.py` | eQuery reporty (zdroj SITE_IDS pro 36940) | | `download_test_results_v1.4.py` | **tento** — Test Results (Standard + Microbiology, 2 studie, sharding, retry) | | `run_test_results_parallel_v1.2.py` | launcher 4 paralelních shardů test-results |