Files
janssen/Covance/Trash/download_test_results_v1.3.md
T
2026-06-09 08:22:49 +02:00

264 lines
10 KiB
Markdown
Raw 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.
# download_test_results_v1.3.py — dokumentace
**Verze:** 1.3 · **Datum:** 2026-05-29
**Umístění:** `U:\PythonProject\Janssen\Covance_UCO3001\download_test_results_v1.3.py`
> **Změny v1.3 oproti 1.2:** **robustní login + okno se při chybě nezavře.**
> - `login()` už **nečeká na `networkidle`** (login SPA labcorp/OKTA jí nikdy
> nedosáhne → dřív to vedlo k timeoutu a pádu procesu / „zmizelému" oknu).
> Místo toho čeká přímo na pole **Email** a **Password** přes
> `wait_for(state="visible")`.
> - Celý běh je obalen `try/except` a na konci je `input()` → **konzolové okno
> shardu se při chybě nezavře**, takže je vidět chybový log.
> - Launcher povýšen na `run_test_results_parallel_v1.1.py` (cílí na v1.3,
> `STAGGER_S = 8`).
>
> **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ší)
Přes launcher, který rozjede 4 procesy (každý ve vlastním okně):
```bat
U:\PythonProject\Janssen\.venv\Scripts\python.exe ^
U:\PythonProject\Janssen\Covance_UCO3001\run_test_results_parallel_v1.1.py
```
### 2b) Serialně (jeden proces, jako dřív)
```bat
U:\PythonProject\Janssen\.venv\Scripts\python.exe ^
U:\PythonProject\Janssen\Covance_UCO3001\download_test_results_v1.3.py
```
### 2c) Jeden konkrétní shard ručně
```bat
…\python.exe download_test_results_v1.3.py --shard 2 --of 4
```
- Prohlížeč běží **viditelně** (`headless=False`), maximalizovaný.
- **Persistent profile** — session přežívá (viz sekce 4).
- Po dokončení (i po chybě) okno **čeká na Enter** — schválně, ať je vidět log.
---
## 3. Paralelní běh (sharding) — jak funguje
### 3.1 Rozdělení práce
Argumenty `--shard N --of M` rozkrojí seznam všech 34 reportů:
```python
REPORTS = ALL_REPORTS[SHARD - 1::OF]
```
Tj. shard 1 z 4 vezme reporty na indexech 0, 4, 8, … ; shard 2 indexy 1, 5, 9, …
Rovnoměrné rozdělení, **žádný report neudělají dva shardy**. Bez argumentů
(`--of 1`) běží serialně přes všech 34.
### 3.2 Proč nestačily 4 taby v jednom procesu
Playwright **sync API je blokující**`wait_for_*` drží jediné vlákno celou
dobu čekání, takže 4 taby v jednom sync skriptu by se stejně střídaly sériově.
Skutečná paralelnost vyžaduje buď async API, nebo — jak je zvoleno zde —
**4 nezávislé procesy**, kde souběh zajišťuje OS.
### 3.3 Profil per shard (povinné)
Chrome zamyká adresář profilu → **dvě běžící instance nemohou sdílet jeden
profil**. Proto:
| Běh | Profil |
|---|---|
| serialní (`--of 1`) | `browser_profile/` (původní) |
| shard N (`--of M>1`) | `browser_profile_{N}/` |
### 3.4 Login (varianta B) — POZOR na fragilitu
Session se mezi profily **nesdílí**. Každý shard se proto při prvním spuštění
**přihlásí sám** (login je jen heslem, žádné MFA). Po prvním běhu už session
zůstane uložená v `browser_profile_{N}/`, takže další běhy login přeskočí.
**Klíčová oprava ve v1.3:** login stránka labcorp/OKTA je SPA, která
**nikdy nedosáhne stavu `networkidle`** (běží tam analytika/polling). Původní
`page.wait_for_load_state("networkidle")` se proto zasekl až do timeoutu a
shard spadl ještě před vyplněním přihlášení (okno „zmizelo"). Nové `login()`:
```python
page.goto(LOGIN_URL)
try:
page.get_by_label("Email").wait_for(state="visible", timeout=12000)
except Exception:
return # Email se neobjevil -> session aktivni
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)
```
Launcher startuje procesy s rozestupem (`STAGGER_S = 8 s`), aby se OKTA login
nezahltil souběžnými požadavky.
### 3.5 Okno se při chybě nezavře
`__main__` je obalen `try/except` + `finally: input(...)`. Když shard spadne,
vypíše `FATAL: ...` + traceback a **čeká na Enter** místo okamžitého zavření
okna. Tím je vždy vidět, co se pokazilo. (Důsledek: launcher reportuje
„HOTOVO" až po zavření všech oken Enterem.)
### 3.6 Launcher `run_test_results_parallel_v1.1.py`
- `N_SHARDS = 4` — počet souběžných procesů (oken).
- `STAGGER_S = 8` — rozestup mezi starty (s).
- Každý proces v **novém konzolovém okně** (`CREATE_NEW_CONSOLE`) → logy se
neprolínají; navíc každý log má prefix `[S{shard}/{of}]`.
- Launcher počká na všechny a vypíše souhrn (které shardy selhaly dle `returncode`).
### 3.7 Reálné zrychlení
Ne přesně 4× — export běží na serveru, velká centra generují hodně řádků →
reálně spíš 23×. Server může souběžné exporty throttlovat.
---
## 4. Výstupní soubory
```
{YYYY-MM-DD_HHMMSS} sponsor-study-{STUDY}-test-results-{SITE_ID}-{TYP}.csv
```
`{TYP}` = `standard` nebo `microbiology`. Všechny shardy ukládají do **stejného**
adresáře `Source/`; timestamp + unikátní názvy (site/typ) zaručí, že nedojde ke
kolizi. Staré soubory se **nikdy nemažou**.
---
## 5. Průběh jednoho reportu (kroky + logging)
| Fáze | Co dělá |
|---|---|
| START | vypíše shard/of, profil, počet reportů; spustí prohlížeč |
| LOGIN | čeká na pole Email; pokud není → session aktivní, login přeskočí |
| KROK 1/5 | navigace na report URL |
| KROK 2/5 | čeká na `.ag-row` **nebo** prázdný grid („No Data"); pak stabilizace počtu |
| KROK 3/5 | klik na viditelné tři tečky (`more_horiz`) → menu |
| KROK 4/5 | klik na viditelné „Export to CSV" → zachytí download |
| KROK 5/5 | uloží soubor do `OUT_DIR` |
| KONEC | souhrn `hotovo X/Y (shard N/M)` + seznam selhaných; pak čeká na Enter |
---
## 6. Klíčové technické poznatky (PROČ to tak je) — ověřeno přes Chrome MCP
Stránka test-results je **Angular SPA** s **MDL** a tabulkou **AG Grid**.
Microbiology záložka má **stejnou** strukturu jako Standard.
### 6.1 Čekání na data = řádky AG Gridu
- Data načtena, jakmile se objeví `div.ag-row` (count 0 → N).
- Řádky jsou `position-absolute` → Playwright je **nepovažuje za „visible"**:
-`wait_for_selector("div.ag-row")` (default visible) **timeoutuje**.
-`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 — NENÍ to skutečný
počet záznamů. **Export do CSV ale vyexportuje VŠECHNY řádky.**
### 6.2 „Fetching Data" / spinner — NEPLATÍ pro tuto stránku
Na test-results NENÍ text „Fetching Data" (je jen na *samples* reportu).
`<loading-bar>` jen problikne → nespoléhat. Signál = `.ag-row`.
### 6.3 Tři tečky (export) — DVA `<ag-export>`
2× `<ag-export>` (1 skrytý) → filtr na viditelnost:
`page.locator("ag-export button:visible", has_text="more_horiz").first.click()`
### 6.4 „Export to CSV" — DVĚ položky v DOM
2× `mdl-menu-item` (1 skrytá) →
`page.locator("mdl-menu-item:visible", has_text="Export to CSV").first.click()`
### 6.5 Prázdné centrum (ověřeno přes Chrome MCP na centru 930551)
- No-rows overlay s textem **„No Data"** ve `.ag-overlay-no-rows-wrapper`.
- ⚠️ Třída **NENÍ** `.ag-overlay-no-rows-center`.
- ⚠️ Text **NENÍ** v `.ag-body-viewport` (ten je prázdný, `height: 1px`).
- ⚠️ Na stránce **2 overlaye** (1 skrytý) → kontrola `offsetParent !== null`.
- Detekce (KROK 2):
```js
() => {
if (document.querySelectorAll('div.ag-row').length > 0) return false;
return [...document.querySelectorAll('.ag-overlay-no-rows-wrapper')]
.some(e => e.offsetParent !== null);
}
```
- KROK 2 čeká na `.ag-row` **NEBO** tuto detekci → prázdné centrum nečeká 120 s.
---
## 7. Login logika (sdílená napříč Covance skripty) — v1.3
```python
page.goto(LOGIN_URL)
# NEcekat na networkidle (login SPA ji nikdy nedosahne)
try:
page.get_by_label("Email").wait_for(state="visible", timeout=12000)
except Exception:
return # session aktivni → login přeskočit
# jinak: Email → Next → cekej na Password → Password → Verify → wait redirect
```
`wait_for(state="visible")` na pole (NE `networkidle`, NE kontrola URL — po
redirectu zůstává `xsp.covance.com`).
---
## 8. Chrome flagy proti „Restore pages" / broken session
```
--disable-restore-session-state
--disable-session-crashed-bubble
```
---
## 9. Robustnost
- 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/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 (stejná složka / portál)
| 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.3.py` | **tento** — Test Results (Standard + Microbiology, 2 studie, sharding) |
| `run_test_results_parallel_v1.1.py` | launcher 4 paralelních shardů test-results |