8.0 KiB
EmailAgent — agent na přijaté faktury
Hlídá schránku ordinace@buzalkova.cz a ukládá PDF přílohy přijatých faktur.
Ochrana proti duplicitám (2 vrstvy)
state.json—message_idzpracovaných mailů. Stejný mail se podruhé vůbec nestahuje (hlavní pojistka).- sha256 obsahu — při startu se načtou hashe všech PDF v cílové složce;
stažená příloha se porovná hned po stažení. Když je stejný obsah už ve
složce (i pod jiným názvem), soubor se přeskočí (
[DUPLIKÁT]) a ušetří se i AI volání za pojmenování. Řeší případ, kdy sestate.jsonztratí nebo dodavatel pošle fakturu dvakrát. Porovnává se obsah, ne název — AI název se mezi běhy může v části[popis]lehce lišit.
Nasazení na unraid (Tower, 24/7)
Cílový adresář: /mnt/user/Scripts/StahovaniFaktur/ (= /scripts/StahovaniFaktur/
v kontejneru python-runner). Běh: docker exec python-runner python3 /scripts/StahovaniFaktur/faktury_agent.py.
.envna serveru máSTORAGE=dropbox+ANTHROPIC_API_KEY+DROPBOX_*(na serveru neníMedevio/.envani Dropbox mount).- Závislosti v kontejneru:
dropbox msal pymupdf requests(_ensure_deps()je doinstaluje i samo). - Plánování přes unraid User Scripts plugin (ne cron v dockeru):
- wrapper
/boot/config/plugins/user.scripts/scripts/StahovaniFaktur/script(flock+docker exec, loguje do/mnt/user/Scripts/logs/stahovani_faktur.log), - rozvrh
0 6,18 * * *vschedule.json→customSchedule.cron→update_cron→/etc/cron.d/root. - Pozn.:
/bootje FAT32, skript NELZE spustit přímým execem — plugin ho pouští přesstartCustom.php(bash), proto to funguje.
- wrapper
- Pozor na dvojí běh: spouštět jen ze serveru, ne zároveň lokálně (oddělené
state.json).
Spuštění
cd U:\OrdinaceProjekt\EmailAgent
python faktury_agent.py
Skript je idempotentní — opakované spuštění nestáhne nic dvakrát
(viz state.json). Lze přidat do Windows Task Scheduleru.
Co dělá (tok)
- Graph API (
graph_mail.py) — načte nové maily s přílohou ze složekInbox+ přímé podsložky (vynechává Junk/Deleted/Sent/Drafts), od posledního běhu (state.json→last_run, s překryvem −1 den). - Levný předfiltr (Python, zdarma) — propustí jen maily, kde se slovo
faktur*vyskytuje v předmětu, těle, nebo v názvu přílohy. Maily, které neprojdou, se rovnou označí jako zpracované. - AI klasifikace (Claude, placené) — jen na propuštěné maily. Model
claude-haiku-4-5rozhodneje_faktura: true/falsea vybere správnou .pdf přílohu (ne ISDOC/XML/obrázek/VOP/dodací list/objednávku). - Stažení vybraného PDF přes Graph.
- Ověření obsahu PDF (Python, zdarma) — přečte text PDF (
fitz/PyMuPDF) a hledá slovofaktur*:ano→ potvrzeno, uloží se.ne→ text fakturu neobsahuje (AI nejspíš vybrala špatnou přílohu) → neukládá se, jen zaloguje[PDF NEPOTVRZENO].bez_textu→ PDF nemá textovou vrstvu (skenovaná faktura) → uloží se, ale zaloguje[PDF BEZ TEXTU]k ruční kontrole.
- Návrh názvu (Claude nad textem PDF) —
propose_filename()z textu faktury vytěží datum/typ/dodavatele/číslo/popis/částku a vrátí jednotný názevYYYY-MM-DD Typ Dodavatel ČÍSLO [popis] [částka MĚNA].pdf. Pravidla převzata zFaktury/FakturyRenameOpenAI.py(např. Distribuce CZ → Ptáček). Při chybě nebo u skenu bez textu se použije původní název přílohy. - Uložení pod navrženým názvem. Konflikty řeší přípona
(2),(3)… - Kategorie + přesun (Graph, destruktivní) — mail se označí kategorií
ClaudeProcessed(zelená) a přesune doInbox/ProcessedByAgent/Invoices. Děje se i u duplikátu (úklid Inboxu). Vyžaduje Mail.ReadWrite. - Zápis
state.json+ log_log_faktury.txt. - Summary e-mail — po každém běhu se z
SUMMARY_FROM(reports@buzalka.cz) pošle souhrn naSUMMARY_TO(vladimir.buzalka@buzalka.cz) přes Graph (graph_mail.send_mail, vyžaduje Mail.Send). Tělo = log běhu.
Na konci běhu skript vypíše cenu AI za běh — počet volání, tokeny a částku
v USD i Kč (kurz USD_TO_CZK, ceník modelů v PRICING). Orientačně ~0,1 Kč
na fakturu (klasifikace + pojmenování).
Konfigurace (faktury_agent.py, sekce NASTAVENÍ)
| Konstanta | Význam |
|---|---|
MAILBOX |
sledovaná schránka |
TARGET_SUBPATH |
podsložka v Dropboxu — root přes Knihovny/najdi_dropbox.py |
FIRST_RUN_DAYS |
kolik dní dozadu při prvním běhu (prázdný state) |
ANTHROPIC_MODEL |
model pro klasifikaci faktura ano/ne |
ANTHROPIC_NAMING_MODEL |
model pro návrh názvu souboru |
FAKTUR_RE |
regex předfiltru (faktur) |
CATEGORY / CATEGORY_COLOR |
kategorie přidaná zpracovanému mailu + barva |
PROCESSED_FOLDER_PARTS |
cílová podsložka přesunu (pod Inbox) |
SKIP_FOLDERS |
složky vynechané při skenování |
Úložiště — lokální vs. Dropbox API (storage.py)
Cíl je stejná složka, ale dvěma cestami podle prostředí (STORAGE):
STORAGE |
Backend | Kdy |
|---|---|---|
local (default) |
LocalStorage — filesystem přes najdi_dropbox.get_dropbox_root() + TARGET_SUBPATH |
Windows (je tu Dropbox mount) |
dropbox |
DropboxStorage — Dropbox HTTP API, cesta DROPBOX_TARGET_PATH |
unraid/server (bez Dropbox mountu) |
Společné rozhraní: load_hashes(), hash_bytes(data), save(name, data), describe().
Dedup: lokálně sha256 obsahu, přes API Dropbox content_hash (blokový sha256
z metadat — nestahuje soubory). Listing v Dropboxu stránkuje.
Cílová složka:
<Dropbox>\Ordinace\!!MUDr. Michaela Buzalková s.r.o\Prosek\#040 Faktury přijaté\
(Dropbox API: /Ordinace/.../#040 Faktury přijaté, Full Dropbox app).
Po zpracování (destruktivní): CATEGORY (= ClaudeProcessed, barva
CATEGORY_COLOR) + přesun do Inbox/ + PROCESSED_FOLDER_PARTS
(= ProcessedByAgent/Invoices). Tato složka se při skenování přeskakuje
(SKIP_FOLDERS).
Autentizace
- Microsoft Graph — app registrace (application permissions) sdílená s
Knihovny/EmailMessagingGraph.py. Vyžaduje granty Mail.Read (čtení/přílohy) a Mail.ReadWrite (kategorie + přesun mailů) a Mail.Send (summary e-mail). Credentials jsou natvrdo vgraph_mail.py(tenant/client/secret). - Anthropic — klíč
ANTHROPIC_API_KEYzMedevio/.env. - Dropbox API (jen
STORAGE=dropbox) —DROPBOX_APP_KEY/DROPBOX_APP_SECRET/DROPBOX_APP_REFRESH_TOKENzEmailAgent/.env(gitignored). Full Dropbox app, účet vladimir.buzalka@buzalka.cz. Refresh token = trvalý, access token se obnovuje sám.
Závislosti
msal, requests, fitz (PyMuPDF), a pro STORAGE=dropbox navíc dropbox
(pip install dropbox). Na unraid/python-runner je nutné dropbox doinstalovat.
Soubory
| Soubor | Popis |
|---|---|
faktury_agent.py |
hlavní skript |
graph_mail.py |
vrstva nad Graphem (čtení/zápis zpráv, stahování příloh) |
storage.py |
úložiště faktur — LocalStorage / DropboxStorage |
.env |
Dropbox credentials + volitelně STORAGE (gitignored) |
state.json |
ID zpracovaných mailů + last_run |
_log_faktury.txt |
log běhů |
Poznámky / TODO
- Destruktivní — zpracovaný mail se kategorizuje a přesouvá z Inboxu.
Maily se nemažou. Přesun mění
message_id; idempotenci hlídástate.json(původní id) i to, že přesunuté maily jsou v nesnímané podsložce. - Maily, které předfiltr nepropustí, se uloží do
processed_idsjako vyřízené — když se prompt/pravidla později změní, znovu se nepřehodnotí. Pro re-test smaž příslušné ID zestate.json. - Graph nezvládne
$orderbyspolu s filtremhasAttachments(InefficientFilter) — proto se na serveru neřadí.