z230
This commit is contained in:
@@ -0,0 +1,238 @@
|
||||
# download_test_results_v1.0.py — dokumentace
|
||||
|
||||
**Verze:** 1.0 · **Datum:** 2026-05-29
|
||||
**Umístění:** `U:\PythonProject\Janssen\Covance_UCO3001\download_test_results_v1.0.py`
|
||||
|
||||
---
|
||||
|
||||
## 1. Účel
|
||||
|
||||
Automatické stažení reportu **Standard Test Results** z portálu Labcorp Sponsor
|
||||
Portal (`xsp.labcorp.com`) pro studii **77242113UCO3001** (interní study ID
|
||||
**36940**). Skript projde **všech 12 center** studie, u každého vyexportuje grid
|
||||
do CSV a uloží ho timestampovaný do adresáře `Source/`.
|
||||
|
||||
> Pozn.: Stahuje se záložka **Standard** (ne Microbiology). URL končí
|
||||
> `/standard-test-results`.
|
||||
|
||||
---
|
||||
|
||||
## 2. Spuštění
|
||||
|
||||
```bat
|
||||
U:\PythonProject\Janssen\.venv\Scripts\python.exe ^
|
||||
U:\PythonProject\Janssen\Covance_UCO3001\download_test_results_v1.0.py
|
||||
```
|
||||
|
||||
- Prohlížeč běží **viditelně** (`headless=False`), maximalizovaný.
|
||||
- Používá **persistent profile** (`browser_profile/` vedle skriptu) — session
|
||||
(přihlášení) přežívá mezi spuštěními.
|
||||
|
||||
---
|
||||
|
||||
## 3. Konfigurace (konstanty v hlavičce skriptu)
|
||||
|
||||
| Konstanta | Hodnota / význam |
|
||||
|---|---|
|
||||
| `EMAIL` | `vbuzalka@its.jnj.com` (login přes iMedidata/OKTA na xsp.covance.com) |
|
||||
| `PASSWORD` | heslo k účtu (uložené přímo v kódu) |
|
||||
| `LOGIN_URL` | `https://xsp.covance.com/` — po přihlášení redirect na xsp.labcorp.com |
|
||||
| `OUT_DIR` | `U:\PythonProject\Janssen\Covance_UCO3001\Source` |
|
||||
| `PROFILE_DIR` | `browser_profile/` vedle skriptu (persistent Chromium profil) |
|
||||
| `STUDY` | `36940` (interní study ID pro 77242113UCO3001) |
|
||||
| `SITE_IDS` | seznam 12 interních čísel center — viz níže |
|
||||
|
||||
### Interní čísla center (SITE_IDS)
|
||||
|
||||
```
|
||||
930551, 930556, 930525, 930549, 930543, 930547,
|
||||
930555, 930557, 930539, 930536, 930553, 930531
|
||||
```
|
||||
|
||||
> **Zdroj:** převzato z `download_equeries_report_v1.1.py` (proměnná `SITES`).
|
||||
> Jsou to **interní ID** Labcorpu, **nikoli** čísla center typu CZ10001.
|
||||
> V URL test-results se používá právě toto interní ID:
|
||||
> `…/test-results/{SITE_ID}/standard-test-results`.
|
||||
|
||||
### Generování REPORTS (DRY)
|
||||
|
||||
`REPORTS` se sestaví automaticky z `SITE_IDS` — URL i název souboru mají vzor
|
||||
napsaný jen jednou. **Přidání/odebrání centra = úprava seznamu `SITE_IDS`.**
|
||||
|
||||
---
|
||||
|
||||
## 4. Výstupní soubory
|
||||
|
||||
Formát názvu (timestamp + popisný název):
|
||||
|
||||
```
|
||||
{YYYY-MM-DD_HHMMSS} sponsor-study-36940-test-results-{SITE_ID}-standard.csv
|
||||
```
|
||||
|
||||
Příklad:
|
||||
```
|
||||
2026-05-29_125710 sponsor-study-36940-test-results-930557-standard.csv
|
||||
```
|
||||
|
||||
- Timestamp se generuje pro **každý** report zvlášť (v okamžiku exportu).
|
||||
- Staré soubory se **nikdy nemažou** (verzování přes timestamp).
|
||||
- Browser dočasný název se na disk neukládá — `expect_download` zachytí download
|
||||
event a `save_as()` ho uloží pod naším názvem.
|
||||
|
||||
---
|
||||
|
||||
## 5. Průběh skriptu (kroky + logging)
|
||||
|
||||
Každý krok se loguje s časem (`[HH:MM:SS]`, `flush=True` → vypisuje průběžně).
|
||||
|
||||
| Fáze | Co dělá |
|
||||
|---|---|
|
||||
| START | spustí prohlížeč |
|
||||
| LOGIN | otevře login; **pokud je session aktivní, přihlášení přeskočí** |
|
||||
| Pro každé centrum: | |
|
||||
| KROK 1/5 | navigace na report URL |
|
||||
| KROK 2/5 | čeká na řádky gridu `.ag-row` **nebo** prázdný grid; pak stabilizace počtu |
|
||||
| KROK 3/5 | klik na viditelné tři tečky (`more_horiz`) → otevře menu |
|
||||
| KROK 4/5 | klik na viditelné „Export to CSV" → zachytí download |
|
||||
| KROK 5/5 | uloží soubor do `OUT_DIR` |
|
||||
| KONEC | souhrn `hotovo X/12` + seznam selhaných center |
|
||||
|
||||
### Příklad výstupu
|
||||
|
||||
```
|
||||
[12:56:35] START: prohlizec spusten.
|
||||
[12:56:35] LOGIN: otviram login stranku...
|
||||
[12:56:49] LOGIN: prihlaseni OK (...)
|
||||
[12:56:49] === Centrum 930557 (studie 36940) ===
|
||||
[12:56:59] KROK 1/5: stranka nactena (...)
|
||||
[12:57:05] KROK 2/5: radky se objevily, cekam na stabilizaci poctu...
|
||||
[12:57:06] ...kontrola #1: 153 radku
|
||||
[12:57:08] ...kontrola #2: 153 radku
|
||||
[12:57:10] KROK 2/5: data stabilni (153 radku v gridu).
|
||||
[12:57:10] KROK 3/5: menu otevreno.
|
||||
[12:57:11] KROK 4/5: stahovani zachyceno, ukladam soubor...
|
||||
[12:57:11] KROK 5/5: HOTOVO -> ...\2026-05-29_125710 sponsor-study-36940-test-results-930557-standard.csv
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Klíčové technické poznatky (PROČ to tak je) — ověřeno přes Chrome DevTools
|
||||
|
||||
Stránka test-results je **Angular SPA** s knihovnou **MDL** a tabulkou **AG Grid**.
|
||||
Tyto detaily byly zjištěny živou inspekcí DOM (Claude in Chrome MCP), ne hádáním:
|
||||
|
||||
### 6.1 Čekání na data = řádky AG Gridu
|
||||
- Grid je `<covance-ag-grid>` → `<ag-grid-angular>` (AG Grid).
|
||||
- Data jsou načtena, jakmile se objeví řádky **`div.ag-row`** (count jde z 0 → N;
|
||||
pro 930557 to bylo 153 řádků).
|
||||
- **Řádky jsou `position-absolute`** (virtuální render AG Gridu) → Playwright je
|
||||
**nepovažuje za „visible"**. Proto:
|
||||
- ❌ `wait_for_selector("div.ag-row")` (default `state="visible"`) **timeoutuje**
|
||||
i když řádky existují.
|
||||
- ✅ čekat na **přítomnost v DOM**:
|
||||
`wait_for_function("() => document.querySelectorAll('div.ag-row').length > 0")`.
|
||||
- Stabilizace: počet řádků se čte opakovaně co 2 s, dokud se 2× po sobě neshodne.
|
||||
|
||||
### 6.2 „Fetching Data" / spinner — POZOR, NEPLATÍ pro tuto stránku
|
||||
- Na test-results stránce **NENÍ** text „Fetching Data" (ten je na *samples*
|
||||
reportu, jiná stránka!).
|
||||
- Je tu element `<loading-bar>`, ale ten jen krátce problikne `<mdl-progress>`
|
||||
při route-loadingu, **ne** během načítání dat gridu → nelze na něj spoléhat.
|
||||
- Spolehlivý signál je výhradně **objevení `.ag-row`**.
|
||||
|
||||
### 6.3 Tři tečky (export) — na stránce jsou DVA `<ag-export>`
|
||||
- Na stránce existují **2× `<ag-export>`**: jeden **skrytý**, jeden viditelný.
|
||||
- Existují **3× ikona `more_horiz`**: 1 mimo export (toolbar), 1 ve skrytém
|
||||
ag-export, 1 ve viditelném.
|
||||
- Proto je nutný filtr na viditelnost:
|
||||
✅ `page.locator("ag-export button:visible", has_text="more_horiz").first.click()`
|
||||
|
||||
### 6.4 „Export to CSV" — v DOM jsou DVĚ položky
|
||||
- Po otevření menu existují **2× `mdl-menu-item` „Export to CSV"** (jedna skrytá
|
||||
z neviditelného ag-export, jedna viditelná). Stejně tak 2× „Export to Excel".
|
||||
- Proto:
|
||||
✅ `page.locator("mdl-menu-item:visible", has_text="Export to CSV").first.click()`
|
||||
- ❌ `get_by_text("Export to CSV")` → **strict mode violation** (2 elementy).
|
||||
|
||||
### 6.5 Prázdné centrum (ověřeno přes Chrome MCP na centru 930551)
|
||||
- AG Grid při 0 záznamech zobrazí no-rows overlay s textem **„No Data"**.
|
||||
- Struktura: `.ag-overlay` → `.ag-overlay-panel` →
|
||||
**`.ag-overlay-no-rows-wrapper`** → `<span>No Data</span>`.
|
||||
- ⚠️ Třída **NENÍ** `.ag-overlay-no-rows-center` (to byl chybný předpoklad,
|
||||
na který detekce nikdy nezabrala) — správně je **`.ag-overlay-no-rows-wrapper`**.
|
||||
- ⚠️ Text „No Data" **NENÍ** v `.ag-body-viewport` (ten je prázdný,
|
||||
`height: 1px`) — je v samostatném overlay sourozenci.
|
||||
- ⚠️ Na stránce jsou **2 overlaye** (jeden skrytý, jeden viditelný) — stejně
|
||||
jako u `ag-export`. Proto kontrola **viditelnosti** `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 → centrum bez dat
|
||||
necheká zbytečně 120 s a export se přeskočí.
|
||||
|
||||
---
|
||||
|
||||
## 7. Login logika (sdílená napříč Covance skripty)
|
||||
|
||||
```python
|
||||
page.goto(LOGIN_URL)
|
||||
page.wait_for_load_state("networkidle")
|
||||
if not page.get_by_label("Email").is_visible():
|
||||
return # session aktivní → login přeskočit
|
||||
# jinak: Email → Next → Password → Verify → wait redirect (code= zmizí z URL)
|
||||
```
|
||||
|
||||
- **Proč `is_visible()` a ne kontrola URL:** po `goto(LOGIN_URL)` zůstane URL
|
||||
`xsp.covance.com` i po redirectu na dashboard, takže URL test je nespolehlivý.
|
||||
Spolehlivé je, zda **existuje pole Email** (login formulář) nebo ne.
|
||||
- `is_visible()` neháže výjimku — když pole není, vrátí `False`.
|
||||
|
||||
---
|
||||
|
||||
## 8. Chrome flagy proti „Restore pages" / broken session
|
||||
|
||||
V `args` launchu:
|
||||
```
|
||||
--disable-restore-session-state # neobnovovat předchozí session
|
||||
--disable-session-crashed-bubble # potlačit "Chromium didn't shut down correctly"
|
||||
```
|
||||
Důvod: při natvrdo ukončeném skriptu (kill) Chromium jinak při dalším startu
|
||||
nabídne „Restore pages?" dialog, který rozbil interakci.
|
||||
|
||||
---
|
||||
|
||||
## 9. Robustnost smyčky
|
||||
|
||||
- Každé centrum je v `try/except` → **chyba u jednoho nezastaví zbytek**.
|
||||
- Na konci souhrn: `hotovo X/12` + seznam `SELHALA centra: …`.
|
||||
- Selhané centrum v logu = snadno se dohledá a vyřadí/opraví ID.
|
||||
|
||||
---
|
||||
|
||||
## 10. Možná budoucí rozšíření
|
||||
|
||||
- **Microbiology záložka**: existuje i `…/test-results/{SITE}/microbiology…`
|
||||
(analogická stránka, pravděpodobně stejná AG Grid logika).
|
||||
- **Druhá studie** (MDD3003, study 35472): přidat další `STUDY` + `SITE_IDS`
|
||||
a obalit do vnější smyčky (vzor viz `download_samples_report` se seznamem STUDIES).
|
||||
- **Ověření počtu**: počet `.ag-row` se loguje — dá se porovnat s počtem řádků
|
||||
ve staženém CSV jako sanity check.
|
||||
|
||||
---
|
||||
|
||||
## 11. Příbuzné skripty (stejná složka / portál)
|
||||
|
||||
| Skript | Co stahuje |
|
||||
|---|---|
|
||||
| `download_samples_report_v1.1.py` | All Samples (sampletracking, čeká na „Fetching Data") |
|
||||
| `download_kit_inventory_v2.1.py` | Kit inventory (on-hand expiration) |
|
||||
| `download_equeries_report_v1.1.py` | eQuery reporty (zdroj SITE_IDS) |
|
||||
| `download_test_results_v1.0.py` | **tento** — Standard Test Results |
|
||||
|
||||
Všechny sdílejí: persistent profile, login logiku s `is_visible()` checkem,
|
||||
`ag-export` + „Export to CSV" pattern (equeries/test-results).
|
||||
Reference in New Issue
Block a user