# PanoramaContacts — CLAUDE.md ## Účel adresáře Import kontaktů středisek (site contacts) z exportů systému PANORAMA (CTMS) do MySQL a jejich zobrazení ve Streamlit web reportu. Filtruje pouze záznamy pro **Czechia**. Aktuálně pokryté protokoly: | Protocol ID | TA | |---|---| | `77242113UCO3001` | Immunology | | `42847922MDD3003` | Neuroscience | --- ## Soubory | Soubor | Účel | |---|---| | `import_CZ_contacts.py` | Import xlsx → MySQL | | `webreport.py` | Streamlit web report | | `run_webreport.py` | PyCharm launcher (`streamlit run webreport.py`) | | `sql/create_CTMS_contacts.sql` | DDL tabulky `CTMS_contacts` | | `SourceData/*.xlsx` | PANORAMA Dashboard exporty (zdrojová data) | | `filter_state.json` | Automaticky ukládaný stav filtrů (generuje app) | --- ## MySQL - **Host:** 192.168.1.76:3306 · **DB:** `studie` · **Tabulka:** `CTMS_contacts` - **Sheet v xlsx:** `Site Contacts`, header na řádku 6 (0-based index 5) ### Klíčové sloupce tabulky | Sloupec | Typ | Poznámka | |---|---|---| | `file_date` | DATE | Z `dcterms:created` v docProps/core.xml xlsx | | `imported_at` | DATETIME | Auto timestamp importu | | `protocol_id` | VARCHAR(20) | Identifikátor studie | | `site_id` | VARCHAR(15) | Středisko (např. `DD5-CZ10006`) | | `contact_role` | VARCHAR(50) | Role kontaktu (PI, Study Coordinator, …) | | `contact_start_date` | DATE | Začátek platnosti kontaktu | | `contact_end_date` | DATE | Konec platnosti — NULL = stále aktivní | | `email` | VARCHAR(100) | Hlavní e-mail | --- ## import_CZ_contacts.py - Zpracuje všechny `*.xlsx` v `SourceData/` - Přeskočí soubory, jejichž `file_date` ≠ dnešní datum (UTC) - Přepis: DELETE + INSERT podle `(file_date, protocol_id, country_name)` - `clean_value()` převede NaN / NaT / Timestamp na typy přijatelné MySQL driverem --- ## webreport.py — Streamlit app ### Filtry (sidebar) | Filtr | Widget | Logika options | |---|---|---| | **Střediska** | radio | Aktivní / Neaktivní / Všechna | | **Protokol** | selectbox | Z celé DB | | **Role** | multiselect | Filtrováno dle protokolu + aktivní/neaktivní | | **Site** | multiselect | Filtrováno dle protokolu + aktivní/neaktivní | | **Hledání** | text_input | Fulltext přes všechny sloupce řádku | ### Logika filtru Střediska | Hodnota | Site podmínka | End Date podmínka | |---|---|---| | **Aktivní** | `site_id` v `ACTIVE_SITES` | `contact_end_date IS NULL` | | **Neaktivní** | `site_id` NOT v `ACTIVE_SITES` | bez omezení | | **Všechna** | bez omezení | bez omezení | ### Aktivní střediska (ACTIVE_SITES) ```python "77242113UCO3001": { "DD5-CZ10001", "DD5-CZ10003", "DD5-CZ10006", "DD5-CZ10009", "DD5-CZ10010", "DD5-CZ10012", "DD5-CZ10013", "DD5-CZ10015", "DD5-CZ10016", "DD5-CZ10020", "DD5-CZ10021", "DD5-CZ10022", } "42847922MDD3003": { "S10-CZ10004", "S10-CZ10008", "S10-CZ10011", "S10-CZ10012", } ``` ### Perzistence filtrů - Stav se ukládá do `filter_state.json` při každé změně filtru (`on_change=save_filter_state`) - Načítá se jednou za session přes flag `filters_initialized` v `st.session_state` - Při načítání se hodnoty validují vůči aktuálním options (ochrana před zastaralými daty) ### Clipboard tlačítko - Knihovna `pyperclip` — kopíruje přímo do Windows clipboardu ze serverové strany - Formát: `Jméno Příjmení ; …` - Reaguje na aktuálně zobrazené (filtrované) záznamy ### Cache - `@st.cache_data(ttl=300)` — data se drží 5 minut - Tlačítko 🔄 Obnovit data volá `st.cache_data.clear()` + `st.rerun()` --- ## Závislosti (venv) ``` mysql-connector-python pandas openpyxl streamlit pyperclip ```