# jnj_tower_ingest v1.4.0 **Soubor:** `jnj_tower_ingest_v1.4.py` **Datum:** 2026-06-16 **Autor:** vladimir.buzalka **Běží:** Docker kontejner `python-runner` na Unraid Tower (192.168.1.76), u MongoDB. ## Co to je Sjednocený **Tower-side ingest** JNJ e-mailů — fáze v jednom běhu (cron `*/5`): | Fáze | Co dělá | |---|---| | **1. PARSE** | `.msg` z `/mnt/JNJEMAILS` → tělo do Mongo `emaily."vbuzalka@its.jnj.com"`. Inkrementálně přes mtime watermark. Přílohy do SeaweedFS (v1.3). **+ v1.4: detekce neodeslaného e-mailu.** | | **2. SYNC** | nejnovější SQLite (read-only) → zrcadlo `jnj_messages` + `jnj_folder`/stav do `emaily`. NULL-safe. | | **RECONCILE** (volitelně `--reconcile`) | **v1.4:** smaže provizorní no-ID Sent duplikáty, ke kterým existuje dvojče s reálným Message-ID. | | **3. ENRICH** | sdílený `5_enrich_fulltext_emails --mailbox` → PG fulltext. Jen při nových dokumentech. | Pořadí **parse → sync → (reconcile) → enrich**. Klíč = Internet Message-ID = Mongo `_id`. ## Novinka v1.4 (a) — detekce NEODESLANÉHO e-mailu PARSE při čtení těla hledá stopy chyby odeslání (`SendAsDenied`, „could not be sent", `TransportSend operation has failed`, `MapiExceptionSendAsDenied`). Když je najde, dokument dostane: - `send_failed: true` - `send_error: "SendAsDenied (ec=1244) 0x80070005-…"` (vytažený kód, pokud je) Dotaz na neodeslané: `{ send_failed: true }`. > Pozn.: chybové tělo se v `.msg` objeví **až** poté, co ho Outlook do položky dopíše; > na Tower ho přinese **re-upload z `jnj_mailbox_sync v1.3`** (+ overwrite na app.py v2.4). > Archivní kopie zachycená před selháním chybu nenese. ## Novinka v1.4 (b) — fáze RECONCILE (smaž provizorní duplikáty) Sent položka **bez Message-ID** (`_id` začíná `filename:`/`entryid:`) je jen **přechodný snímek** (zachycený dřív, než Exchange doplnil Message-ID). Když k ní existuje **dvojče s reálným Message-ID** — stejní `to` příjemci + stejný `normalized_subject` + `received_at` do **24 h** — je provizorní kopie redundantní a **smaže se**. **Neodeslané** (bez dvojčete) **zůstanou** (a mají `send_failed`). - Match je na **stabilním obsahu** (e-mailové adresy + normalizovaný předmět + čas), **ne na EntryID** (ten se mezi provizorní a finální kopií liší). - Běží **jen s `--reconcile`** (default vypnuto — bezpečné pro cron). - S `--dry-run` jen **vypíše plán** (nic nemaže). Bez `--dry-run` + s `--reconcile` **maže**. ## Argumenty `--dry-run`, `--full`, `--limit N`, `--reindex`, `--force`, `--parse-only` / `--sync-only` / `--enrich-only`, `--no-enrich`, `--enrich-always`, **`--reconcile`** (nově). ## Spouštění ```bash # Běžný inkrementální běh (cron) — reconcile NEběží: docker exec python-runner python3 /scripts/jnj_tower_ingest_v1.4.py # RECONCILE — nejdřív plán (nic nemaže): docker exec -it python-runner python3 /scripts/jnj_tower_ingest_v1.4.py --reconcile --dry-run --sync-only # RECONCILE — ostře (po kontrole plánu): docker exec -it python-runner python3 /scripts/jnj_tower_ingest_v1.4.py --reconcile --sync-only ``` (`--sync-only --reconcile` = jen sync + úklid duplikátů, bez parse/enrich; reconcile potřebuje `jnj_folder` ze sync. Pro samostatný úklid lze i bez `--sync-only`.) ## Revert `jnj_tower_ingest_v1.3.py` (bez send_failed + reconcile), starší v `Trash/`. ## Historie verzí - **1.0.0** — sjednocení parse + sync (mtime watermark). - **1.1.0** — + fáze ENRICH. - **1.2.0** — SYNC NULL-safe. - **1.3.0** — PARSE: přílohy do SeaweedFS. - **1.4.0** — (a) PARSE detekuje neodeslaný e-mail → `send_failed` + `send_error`. (b) Fáze RECONCILE (`--reconcile`): smaže provizorní no-ID Sent kopie s ID-dvojčetem (match to+předmět+čas, ne EntryID); neodeslané ponechá.