z230
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
# sipiq_reminder_v1.0 — připomínka vyplnění SIPIQ (.eml draft)
|
||||
|
||||
**Verze:** 1.0 · **Datum:** 2026-06-18 · **Studie:** 77242113UCO3002 (ICONIC / DAWN)
|
||||
|
||||
## Co dělá
|
||||
.eml draft připomínky vyplnění SIPIQ pro centra na KROK 6 (SIPIQ odeslán), která dlouho nereagují.
|
||||
Vřelá prosba o vyplnění **co nejdříve** + odkaz na dotazník specifický pro centrum (`sipiq.link`
|
||||
z investigators) + datum původního odeslání + bezpečnostní věta „pokud jste již vyplnil(a), ignorujte".
|
||||
Skloňování dle pohlaví (oslovení, „vyplnil/vyplnila") — žena dle příjmení končícího „á/ová".
|
||||
|
||||
## Příjemci / odeslání
|
||||
- **To** = `email` + `email2` z investigators + ruční `EXTRA_TO` (z reply-checku / STATUS);
|
||||
**dd-proxy adresy vynechány**, dedup. **Cc** = koordinátorky. **From** = vbuzalka@its.jnj.com.
|
||||
- Tělo **BASE64** (Outlook čistě, žádné quoted-printable „="). `X-Unsent: 1` → compose.
|
||||
- Výstup `…\UploadToJNJ\pripominka_sipiq_<prijmeni>_<DDMMMYYYY>.eml`.
|
||||
- **Draft ≠ odesláno** — STATUS „připomínka SIPIQ odeslána" psát až po ověření odeslání.
|
||||
|
||||
## EXTRA_TO (18JUN2026, zjištěno reply-checkem v jnjemails SQLite)
|
||||
- Jungwirthová: + `a.jungwirthova@liberascientia.cz` (psala odtud, ne z egk.cz)
|
||||
- Krížová: + `Viera.Krizova@fnmh.cz` (psala odtud, ne z homolka.cz)
|
||||
- Mudr: + `petr.pekny@nmskb.cz` (dr. Pěkný komunikuje za dr. Mudra; SIPIQ šel To Mudr+Pěkný)
|
||||
|
||||
## Použití
|
||||
```
|
||||
.venv\Scripts\python.exe Feasibility\sipiq_reminder_v1.0.py --names "Matous,Kojecky,Jungwirthova,Krížová,Mudr" --apply
|
||||
```
|
||||
Bez `--apply` = dry-run. Mongo 192.168.1.76:27017, pymongo.
|
||||
|
||||
## Stav 18JUN2026
|
||||
Vygenerováno 5 připomínek (Matouš, Kojecký, Jungwirthová, Krížová, Mudr) — centra >3 dny na KROK 6
|
||||
bez posunu (Drastich vynechán, dovolená do 22JUN). Reply-check (jnjemails SQLite): žádná reakce po
|
||||
odeslání SIPIQ. Čekají na ruční odeslání → pak STATUS.
|
||||
@@ -0,0 +1,154 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
sipiq_reminder_v1.0.py
|
||||
======================
|
||||
Verze: 1.0
|
||||
Datum: 2026-06-18
|
||||
Autor: Claude Code (pro MUDr. Vladimíra Buzalku)
|
||||
|
||||
Popis
|
||||
-----
|
||||
Vygeneruje .eml draft PŘIPOMÍNKY vyplnění SIPIQ (studie 77242113UCO3002 / DAWN) pro centra
|
||||
na KROK 6 (SIPIQ odeslán), která dlouho nereagovala. Vřelá prosba o vyplnění co nejdříve
|
||||
+ odkaz na dotazník specifický pro centrum (sipiq.link z investigators) + bezpečnostní věta
|
||||
„pokud jste již vyplnil(a), ignorujte".
|
||||
|
||||
To = email + email2 z investigators (+ EXTRA_TO ručně, dd-proxy vynechány), Cc = koordinátorky,
|
||||
from = vbuzalka@its.jnj.com, tělo BASE64 (Outlook čistě). Draft ≠ odesláno — STATUS „připomínka
|
||||
SIPIQ odeslána" psát až po ověření odeslání.
|
||||
|
||||
Použití:
|
||||
python sipiq_reminder_v1.0.py --names "Matous,Kojecky,Jungwirthova,Krížová,Mudr" # dry-run
|
||||
python sipiq_reminder_v1.0.py --names "Matous,Kojecky,Jungwirthova,Krížová,Mudr" --apply
|
||||
Mongo 192.168.1.76:27017, bez auth, pymongo.
|
||||
"""
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import unicodedata
|
||||
from datetime import date
|
||||
from email.message import EmailMessage
|
||||
|
||||
from pymongo import MongoClient
|
||||
|
||||
OUT_DIR = r"u:\Dropbox\!!!Days\Downloads Z230\UploadToJNJ"
|
||||
CC = "AKocourk@ITS.JNJ.com, EBartoso@its.jnj.com"
|
||||
FROM = "vbuzalka@its.jnj.com"
|
||||
SIG = ("MUDr. Vladimír BUZALKA<br>"
|
||||
"ICON plc / Performing Local Trial Management Services for Janssen – Cilag s.r.o. "
|
||||
"/ Global Clinical Operations<br>"
|
||||
"Mobile: +420 775 735 276 / Fax: +420 227 012 284<br>"
|
||||
"E-mail: vbuzalka@its.jnj.com, vladimir.buzalka@iconplc.com")
|
||||
|
||||
# Ručně doplněné adresy (z reply-checku / STATUS) — komu navíc poslat
|
||||
EXTRA_TO = {
|
||||
"Jungwirthova": ["a.jungwirthova@liberascientia.cz"],
|
||||
"Krížová": ["Viera.Krizova@fnmh.cz"],
|
||||
"Mudr": ["petr.pekny@nmskb.cz"], # dr. Pěkný komunikuje za dr. Mudra (dle STATUS)
|
||||
}
|
||||
|
||||
CZ_MON = {1: "ledna", 2: "února", 3: "března", 4: "dubna", 5: "května", 6: "června",
|
||||
7: "července", 8: "srpna", 9: "září", 10: "října", 11: "listopadu", 12: "prosince"}
|
||||
MON = {m: i + 1 for i, m in enumerate(
|
||||
["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"])}
|
||||
|
||||
|
||||
def ascii_slug(s):
|
||||
return "".join(c for c in unicodedata.normalize("NFKD", str(s or ""))
|
||||
if not unicodedata.combining(c)).lower().replace(" ", "")
|
||||
|
||||
|
||||
def sent_date_cz(status):
|
||||
dates = []
|
||||
for ln in (status or "").splitlines():
|
||||
if "SIPIQ odesl" in ln:
|
||||
m = re.match(r"(\d{2})([A-Za-z]{3})(\d{4})", ln.strip())
|
||||
if m and m.group(2).upper() in MON:
|
||||
dates.append(date(int(m.group(3)), MON[m.group(2).upper()], int(m.group(1))))
|
||||
if not dates:
|
||||
return None
|
||||
d = max(dates)
|
||||
return f"{d.day}. {CZ_MON[d.month]} {d.year}"
|
||||
|
||||
|
||||
def is_female(prijmeni):
|
||||
p = (prijmeni or "").lower()
|
||||
return p.endswith("ová") or p.endswith("ova") or p.endswith("á")
|
||||
|
||||
|
||||
def build_body(jmeno, prijmeni, sent_cz, link):
|
||||
f = is_female(prijmeni)
|
||||
greet = "Vážená paní doktorko," if f else "Vážený pane doktore,"
|
||||
vyplnil = "vyplnila" if f else "vyplnil"
|
||||
sent_part = f" dne <b>{sent_cz}</b>" if sent_cz else ""
|
||||
return (
|
||||
f'<p>{greet}</p>'
|
||||
f'<p>dovoluji si zdvořile připomenout feasibility dotazník <b>SIPIQ</b> ke studii '
|
||||
f'<b>77242113UCO3002 (DAWN)</b> (přípravek icotrokinra, ulcerózní kolitida), '
|
||||
f'který jsme Vám zaslali{sent_part}.</p>'
|
||||
f'<p><b>Velmi bychom ocenili jeho vyplnění co nejdříve</b> — je pro nás klíčový pro '
|
||||
f'posouzení Vašeho centra a další postup. Níže najdete odkaz na dotazník specifický '
|
||||
f'pro Vaše centrum:</p>'
|
||||
f'<p><a href="{link}">{link}</a></p>'
|
||||
f'<p>Pokud jste dotazník již {vyplnil}, přijměte prosím poděkování a tento e-mail '
|
||||
f'ignorujte.</p>'
|
||||
f'<p>V případě jakýchkoli dotazů (např. potíže s odkazem) jsem Vám plně k dispozici. '
|
||||
f'Předem děkuji za Váš čas a vstřícnost.</p>'
|
||||
f'<p>S pozdravem,<br>{SIG}</p>'
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--names", required=True, help="příjmení čárkou (pi v investigators)")
|
||||
ap.add_argument("--apply", action="store_true")
|
||||
args = ap.parse_args()
|
||||
|
||||
db = MongoClient("mongodb://192.168.1.76:27017", serverSelectionTimeoutMS=8000).feasibility
|
||||
names = [n.strip() for n in args.names.split(",") if n.strip()]
|
||||
docs = list(db.investigators.find({"prijmeni": {"$in": names}}))
|
||||
found = {d["prijmeni"] for d in docs}
|
||||
for n in names:
|
||||
if n not in found:
|
||||
print(f" ! nenalezeno: {n}")
|
||||
|
||||
subject = "[Připomínka] 77242113UCO3002 (DAWN) — feasibility dotazník SIPIQ (prosba o vyplnění)"
|
||||
done = []
|
||||
for d in docs:
|
||||
link = (d.get("sipiq") or {}).get("link")
|
||||
if not link:
|
||||
print(f" ! {d['prijmeni']}: chybí sipiq.link — přeskakuji"); continue
|
||||
tos = [e for e in [d.get("email"), d.get("email2")] if e]
|
||||
tos += EXTRA_TO.get(d["prijmeni"], [])
|
||||
tos = [e for e in dict.fromkeys(tos) if "dd-proxy" not in e.lower()] # dedup + bez proxy
|
||||
to = ", ".join(tos)
|
||||
sent_cz = sent_date_cz(d.get("STATUS"))
|
||||
body = build_body(d.get("jmeno"), d["prijmeni"], sent_cz, link)
|
||||
print(f" {d.get('jmeno')} {d['prijmeni']:14} | To: {to}")
|
||||
|
||||
if not args.apply:
|
||||
continue
|
||||
msg = EmailMessage()
|
||||
msg["From"] = FROM
|
||||
msg["To"] = to
|
||||
msg["Cc"] = CC
|
||||
msg["Subject"] = subject
|
||||
msg["X-Unsent"] = "1"
|
||||
html = (f'<html><body style="font-family:Calibri,Arial,sans-serif;font-size:11pt">'
|
||||
f'{body}</body></html>')
|
||||
msg.set_content(html, subtype="html", charset="utf-8", cte="base64")
|
||||
os.makedirs(OUT_DIR, exist_ok=True)
|
||||
fn = f"pripominka_sipiq_{ascii_slug(d['prijmeni'])}_18JUN2026.eml"
|
||||
with open(os.path.join(OUT_DIR, fn), "wb") as fh:
|
||||
fh.write(bytes(msg))
|
||||
done.append(fn)
|
||||
|
||||
if not args.apply:
|
||||
print(f"\n[DRY-RUN] {len(docs)} center, nic nezapsáno. Ostrý: --apply")
|
||||
else:
|
||||
print(f"\n[APPLY] Vygenerováno {len(done)} připomínek do {OUT_DIR}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,38 @@
|
||||
# sipiq_thankyou_v1.0 — poděkování za vyplnění SIPIQ (.eml draft)
|
||||
|
||||
**Verze:** 1.0 · **Datum:** 2026-06-18 · **Studie:** 77242113UCO3002 (ICONIC / DAWN)
|
||||
|
||||
## Co dělá
|
||||
Vygeneruje `.eml` draft poděkování za vyplnění SIPIQ pro jedno centrum (čte z Mongo
|
||||
`feasibility.sipiq_responses` + `sipiq_questions` + `investigators`). Draft otevře Outlook
|
||||
jako rozepsaný e-mail (`X-Unsent: 1`); uživatel ho **ručně odešle**.
|
||||
|
||||
## Struktura e-mailu (domluva 18JUN2026, BEZ Shrnutí)
|
||||
1. **Úvod:** „Děkujeme za zaslání vyplněného dotazníku SIPIQ studie 77242113UCO3002 (DAWN).
|
||||
Vaše odpovědi uvádíme pro úplnost v plném znění níže, pod podpisem. Odeslaný dotazník již
|
||||
není možné editovat, takže kdybychom při dalším zpracování našli položky chybějící, nejasné
|
||||
nebo nekonzistentní, ještě bychom si vyžádali upřesnění e-mailem."
|
||||
2. **Podpis** (MUDr. Buzalka / ICON…).
|
||||
3. **ÚPLNĚ POD PODPISEM** = kompletní výpis vyplněného SIPIQ (sekce → otázka → odpověď, dle
|
||||
`order`/`section`; vynechána Confidentiality; jen neprázdné odpovědi).
|
||||
- **Shrnutí se NEpíše** (uživatel 18JUN2026). Větu „Shrnutí je níže" z původní šablony nahrazuje
|
||||
odkaz na plné odpovědi pod podpisem.
|
||||
|
||||
## Příjemci / odeslání
|
||||
- **To** = e-maily lékaře z `investigators` (`email` + `email2`), fallback `pi_email`. **Cc** =
|
||||
koordinátorky `AKocourk@ITS.JNJ.com, EBartoso@its.jnj.com`. **From** = vbuzalka@its.jnj.com.
|
||||
- Výstup `…\UploadToJNJ\podekovani_sipiq_<prijmeni>_<DDMMMYYYY>.eml`.
|
||||
- **Draft ≠ odesláno** — STATUS „poděkováno" psát až po ověření odeslání.
|
||||
- Generuje .eml přímo (Python `email` lib, multipart text+HTML, UTF-8) kvůli velkému tělu;
|
||||
není přes `create_draft_eml` (to je pro krátká těla). X-Unsent draft = Outlook compose.
|
||||
|
||||
## Použití
|
||||
```
|
||||
.venv\Scripts\python.exe Feasibility\sipiq_thankyou_v1.0.py --last Bruncak # dry-run
|
||||
.venv\Scripts\python.exe Feasibility\sipiq_thankyou_v1.0.py --last Bruncak --apply # zapíše .eml
|
||||
```
|
||||
`--last` = `pi_last_name` v sipiq_responses. Mongo 192.168.1.76:27017, pymongo.
|
||||
|
||||
## Stav 18JUN2026
|
||||
Vygenerován testovací draft pro Bruncáka (To obě adresy, Cc koordinátorky, 13 sekcí SIPIQ).
|
||||
Zbývajících 15 center na vyžádání.
|
||||
@@ -0,0 +1,149 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
sipiq_thankyou_v1.0.py
|
||||
======================
|
||||
Verze: 1.0
|
||||
Datum: 2026-06-18
|
||||
Autor: Claude Code (pro MUDr. Vladimíra Buzalku)
|
||||
|
||||
Popis
|
||||
-----
|
||||
Vygeneruje .eml draft PODĚKOVÁNÍ za vyplnění SIPIQ (studie 77242113UCO3002 / DAWN)
|
||||
pro jedno centrum. Struktura (domluva 18JUN2026, BEZ Shrnutí):
|
||||
1) úvod (poděkování + „dotazník už nelze editovat; při chybějících/nejasných/nekonzistentních
|
||||
položkách si vyžádáme upřesnění e-mailem"; odkaz na plné odpovědi pod podpisem),
|
||||
2) podpis,
|
||||
3) ÚPLNĚ POD PODPISEM = kompletní výpis vyplněného SIPIQ (sekce → otázka → odpověď,
|
||||
seřazené dle struktury dotazníku; vynechána sekce Confidentiality; jen neprázdné).
|
||||
|
||||
Cc = koordinátorky (AKocourk + EBartoso). To = e-maily lékaře z investigators (email + email2),
|
||||
fallback pi_email z odpovědi. from = vbuzalka@its.jnj.com. Výstup do …\\UploadToJNJ.
|
||||
Draft ≠ odesláno — STATUS „poděkováno" psát až po ověření odeslání.
|
||||
|
||||
Použití:
|
||||
python sipiq_thankyou_v1.0.py --last Bruncak # dry-run (náhled)
|
||||
python sipiq_thankyou_v1.0.py --last Bruncak --apply # zapíše .eml
|
||||
|
||||
Mongo 192.168.1.76:27017, bez auth, pymongo.
|
||||
"""
|
||||
import argparse
|
||||
import os
|
||||
from datetime import datetime
|
||||
from email.message import EmailMessage
|
||||
|
||||
from pymongo import MongoClient
|
||||
|
||||
OUT_DIR = r"u:\Dropbox\!!!Days\Downloads Z230\UploadToJNJ"
|
||||
CC = "AKocourk@ITS.JNJ.com, EBartoso@its.jnj.com"
|
||||
FROM = "vbuzalka@its.jnj.com"
|
||||
SKIP_SECTIONS = {"Confidentiality Statement"}
|
||||
SUBJECT = "77242113UCO3002 (DAWN) — poděkování za vyplnění feasibility dotazníku SIPIQ"
|
||||
SIG = ("MUDr. Vladimír BUZALKA<br>"
|
||||
"ICON plc / Performing Local Trial Management Services for Janssen – Cilag s.r.o. "
|
||||
"/ Global Clinical Operations<br>"
|
||||
"Mobile: +420 775 735 276 / Fax: +420 227 012 284<br>"
|
||||
"E‑mail: vbuzalka@its.jnj.com, vladimir.buzalka@iconplc.com")
|
||||
|
||||
|
||||
def esc(s):
|
||||
return str(s).replace("&", "&").replace("<", "<").replace(">", ">")
|
||||
|
||||
|
||||
def build_sipiq_html(db, resp):
|
||||
ans = resp.get("answers") or {}
|
||||
qs = list(db.sipiq_questions.find().sort("order", 1))
|
||||
out, cur = [], None
|
||||
for q in qs:
|
||||
sec = q.get("section") or "Other"
|
||||
if sec in SKIP_SECTIONS:
|
||||
continue
|
||||
items = q.get("items") or []
|
||||
pairs = []
|
||||
if not items:
|
||||
v = ans.get(q["_id"])
|
||||
if v not in (None, ""):
|
||||
pairs.append((None, v))
|
||||
else:
|
||||
for it in items:
|
||||
v = ans.get(it["key"])
|
||||
if v not in (None, ""):
|
||||
pairs.append((it.get("label") or "(odpověď)", v))
|
||||
if not pairs:
|
||||
continue
|
||||
if sec != cur:
|
||||
cur = sec
|
||||
out.append(f'<h3 style="margin:16px 0 4px;color:#1F4E78">{esc(sec)}</h3>')
|
||||
stem = esc((q.get("text") or "").replace("\n", " "))
|
||||
out.append(f'<p style="margin:6px 0 1px"><b>[{q["_id"]}]</b> {stem}</p>')
|
||||
if len(pairs) == 1 and pairs[0][0] is None:
|
||||
out.append(f'<p style="margin:0 0 0 18px"><b>{esc(pairs[0][1])}</b></p>')
|
||||
else:
|
||||
out.append('<ul style="margin:2px 0 6px">')
|
||||
for lbl, v in pairs:
|
||||
out.append(f'<li>{esc(lbl)}: <b>{esc(v)}</b></li>' if lbl
|
||||
else f'<li><b>{esc(v)}</b></li>')
|
||||
out.append('</ul>')
|
||||
return "\n".join(out)
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--last", required=True, help="příjmení v sipiq_responses (pi_last_name)")
|
||||
ap.add_argument("--apply", action="store_true")
|
||||
args = ap.parse_args()
|
||||
|
||||
db = MongoClient("mongodb://192.168.1.76:27017", serverSelectionTimeoutMS=8000).feasibility
|
||||
resp = db.sipiq_responses.find_one({"pi_last_name": args.last})
|
||||
if not resp:
|
||||
raise SystemExit(f"CHYBA: centrum '{args.last}' nenalezeno v sipiq_responses.")
|
||||
inv = db.investigators.find_one({"_id": resp.get("investigator_oid")},
|
||||
{"email": 1, "email2": 1, "prijmeni": 1, "jmeno": 1})
|
||||
tos = [e for e in [(inv or {}).get("email"), (inv or {}).get("email2")] if e]
|
||||
if not tos and resp.get("pi_email"):
|
||||
tos = [resp["pi_email"]]
|
||||
to = ", ".join(tos)
|
||||
|
||||
intro = (
|
||||
'<p>Vážený pane doktore,</p>'
|
||||
'<p>děkujeme za zaslání vyplněného dotazníku SIPIQ studie '
|
||||
'<b>77242113UCO3002 (DAWN)</b>. Vaše odpovědi uvádíme pro úplnost v plném znění '
|
||||
'níže, pod podpisem. Odeslaný dotazník již není možné editovat, takže kdybychom '
|
||||
'při dalším zpracování našli položky chybějící, nejasné nebo nekonzistentní, '
|
||||
'ještě bychom si vyžádali upřesnění e-mailem.</p>'
|
||||
)
|
||||
sig = f'<p>S pozdravem,<br>{SIG}</p>'
|
||||
sipiq = build_sipiq_html(db, resp)
|
||||
body = (intro + sig +
|
||||
'<hr>'
|
||||
'<p style="color:#888;font-style:italic">Vyplněné SIPIQ (kopie Vašich odpovědí):</p>'
|
||||
+ sipiq)
|
||||
|
||||
print(f"Centrum: {resp.get('pi_first_name')} {resp.get('pi_last_name')} | {resp.get('site_name')}")
|
||||
print(f"To: {to}\nCc: {CC}\nSubject: {SUBJECT}")
|
||||
print(f"SIPIQ – sekcí: {sipiq.count('<h3')} | délka těla: {len(body)} znaků")
|
||||
|
||||
if not args.apply:
|
||||
print("[DRY-RUN] .eml nezapsáno. Ostrý běh: --apply")
|
||||
return
|
||||
|
||||
msg = EmailMessage()
|
||||
msg["From"] = FROM
|
||||
msg["To"] = to
|
||||
msg["Cc"] = CC
|
||||
msg["Subject"] = SUBJECT
|
||||
msg["X-Unsent"] = "1"
|
||||
msg.set_content("Tento e-mail je ve formátu HTML.", subtype="plain", charset="utf-8")
|
||||
msg.add_alternative(f"<html><body style=\"font-family:Calibri,Arial,sans-serif;font-size:11pt\">{body}</body></html>",
|
||||
subtype="html", charset="utf-8")
|
||||
|
||||
os.makedirs(OUT_DIR, exist_ok=True)
|
||||
fname = f"podekovani_sipiq_{args.last.lower()}_{datetime.now().strftime('%d%b%Y').upper()}.eml"
|
||||
path = os.path.join(OUT_DIR, fname)
|
||||
with open(path, "wb") as f:
|
||||
f.write(bytes(msg))
|
||||
print(f"Uloženo: {path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,37 @@
|
||||
# sipiq_thankyou_v1.1 — poděkování za vyplnění SIPIQ (.eml draft)
|
||||
|
||||
**Verze:** 1.1 · **Datum:** 2026-06-18 · **Studie:** 77242113UCO3002 (ICONIC / DAWN)
|
||||
|
||||
## Změna proti v1.0
|
||||
- **OPRAVA KÓDOVÁNÍ:** tělo se kóduje **base64** (`cte="base64"`), jediná `text/html` část.
|
||||
v1.0 použila default quoted-printable → Outlook zobrazoval „=" místo písmen
|
||||
(`=ážený`, `=ladimír`, `Service=`, `jnj.co=`). Base64 = čistý text (jako `create_draft_eml`).
|
||||
v1.0 → `Feasibility\TRASH`.
|
||||
|
||||
## Co dělá
|
||||
.eml draft poděkování za vyplnění SIPIQ pro jedno centrum (čte `feasibility.sipiq_responses`
|
||||
+ `sipiq_questions` + `investigators`). `X-Unsent: 1` → Outlook otevře jako rozepsaný e-mail;
|
||||
uživatel **ručně odešle**.
|
||||
|
||||
## Struktura (BEZ Shrnutí)
|
||||
1. Úvod (poděkování + odkaz na plné odpovědi pod podpisem + „dotazník už nelze editovat;
|
||||
při chybějících/nejasných/nekonzistentních položkách si vyžádáme upřesnění e-mailem").
|
||||
2. Podpis.
|
||||
3. ÚPLNĚ POD PODPISEM = kompletní výpis SIPIQ (sekce → otázka → odpověď, dle `order`/`section`,
|
||||
vynechána Confidentiality, jen neprázdné).
|
||||
|
||||
## Příjemci / odeslání
|
||||
- **To** = `email` + `email2` z investigators (fallback `pi_email`). **Cc** = koordinátorky
|
||||
`AKocourk@ITS.JNJ.com, EBartoso@its.jnj.com`. **From** = vbuzalka@its.jnj.com.
|
||||
- Výstup `…\UploadToJNJ\podekovani_sipiq_<prijmeni>_<DDMMMYYYY>.eml`.
|
||||
- **Draft ≠ odesláno** — STATUS „poděkováno" psát až po ověření odeslání.
|
||||
|
||||
## Použití
|
||||
```
|
||||
.venv\Scripts\python.exe Feasibility\sipiq_thankyou_v1.1.py --last Bruncak # dry-run
|
||||
.venv\Scripts\python.exe Feasibility\sipiq_thankyou_v1.1.py --last Bruncak --apply # zapíše .eml
|
||||
```
|
||||
`--last` = `pi_last_name` v sipiq_responses. Mongo 192.168.1.76:27017, pymongo.
|
||||
|
||||
## Stav 18JUN2026
|
||||
Bruncák draft přegenerován s base64 (ověřeno: 0 „=" artefaktů). Zbývajících 15 na vyžádání.
|
||||
@@ -0,0 +1,150 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
sipiq_thankyou_v1.1.py
|
||||
======================
|
||||
Verze: 1.1
|
||||
Datum: 2026-06-18
|
||||
Autor: Claude Code (pro MUDr. Vladimíra Buzalku)
|
||||
|
||||
Změna proti v1.0
|
||||
----------------
|
||||
- OPRAVA KÓDOVÁNÍ: tělo se kóduje BASE64 (cte="base64"), jediná text/html část.
|
||||
v1.0 použil default quoted-printable → Outlook zobrazoval „=" místo písmen
|
||||
(=ážený, =ladimír, Service=, jnj.co=). Base64 = čistý text (jako create_draft_eml).
|
||||
v1.0 přesunuta do TRASH.
|
||||
|
||||
Popis
|
||||
-----
|
||||
.eml draft poděkování za vyplnění SIPIQ (77242113UCO3002 / DAWN) pro jedno centrum.
|
||||
Struktura (BEZ Shrnutí): úvod → podpis → ÚPLNĚ POD PODPISEM kompletní výpis SIPIQ
|
||||
(sekce → otázka → odpověď, dle order/section, vynechána Confidentiality, jen neprázdné).
|
||||
To = email+email2 z investigators (fallback pi_email), Cc = koordinátorky, from = its.jnj.com.
|
||||
Draft ≠ odesláno — STATUS „poděkováno" psát až po ověření odeslání.
|
||||
|
||||
Použití:
|
||||
python sipiq_thankyou_v1.1.py --last Bruncak # dry-run
|
||||
python sipiq_thankyou_v1.1.py --last Bruncak --apply # zapíše .eml
|
||||
Mongo 192.168.1.76:27017, bez auth, pymongo.
|
||||
"""
|
||||
import argparse
|
||||
import os
|
||||
from datetime import datetime
|
||||
from email.message import EmailMessage
|
||||
|
||||
from pymongo import MongoClient
|
||||
|
||||
OUT_DIR = r"u:\Dropbox\!!!Days\Downloads Z230\UploadToJNJ"
|
||||
CC = "AKocourk@ITS.JNJ.com, EBartoso@its.jnj.com"
|
||||
FROM = "vbuzalka@its.jnj.com"
|
||||
SKIP_SECTIONS = {"Confidentiality Statement"}
|
||||
SUBJECT = "77242113UCO3002 (DAWN) — poděkování za vyplnění feasibility dotazníku SIPIQ"
|
||||
SIG = ("MUDr. Vladimír BUZALKA<br>"
|
||||
"ICON plc / Performing Local Trial Management Services for Janssen – Cilag s.r.o. "
|
||||
"/ Global Clinical Operations<br>"
|
||||
"Mobile: +420 775 735 276 / Fax: +420 227 012 284<br>"
|
||||
"E-mail: vbuzalka@its.jnj.com, vladimir.buzalka@iconplc.com")
|
||||
|
||||
|
||||
def esc(s):
|
||||
return str(s).replace("&", "&").replace("<", "<").replace(">", ">")
|
||||
|
||||
|
||||
def build_sipiq_html(db, resp):
|
||||
ans = resp.get("answers") or {}
|
||||
qs = list(db.sipiq_questions.find().sort("order", 1))
|
||||
out, cur = [], None
|
||||
for q in qs:
|
||||
sec = q.get("section") or "Other"
|
||||
if sec in SKIP_SECTIONS:
|
||||
continue
|
||||
items = q.get("items") or []
|
||||
pairs = []
|
||||
if not items:
|
||||
v = ans.get(q["_id"])
|
||||
if v not in (None, ""):
|
||||
pairs.append((None, v))
|
||||
else:
|
||||
for it in items:
|
||||
v = ans.get(it["key"])
|
||||
if v not in (None, ""):
|
||||
pairs.append((it.get("label") or "(odpověď)", v))
|
||||
if not pairs:
|
||||
continue
|
||||
if sec != cur:
|
||||
cur = sec
|
||||
out.append(f'<h3 style="margin:16px 0 4px;color:#1F4E78">{esc(sec)}</h3>')
|
||||
stem = esc((q.get("text") or "").replace("\n", " "))
|
||||
out.append(f'<p style="margin:6px 0 1px"><b>[{q["_id"]}]</b> {stem}</p>')
|
||||
if len(pairs) == 1 and pairs[0][0] is None:
|
||||
out.append(f'<p style="margin:0 0 0 18px"><b>{esc(pairs[0][1])}</b></p>')
|
||||
else:
|
||||
out.append('<ul style="margin:2px 0 6px">')
|
||||
for lbl, v in pairs:
|
||||
out.append(f'<li>{esc(lbl)}: <b>{esc(v)}</b></li>' if lbl
|
||||
else f'<li><b>{esc(v)}</b></li>')
|
||||
out.append('</ul>')
|
||||
return "\n".join(out)
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--last", required=True, help="příjmení v sipiq_responses (pi_last_name)")
|
||||
ap.add_argument("--apply", action="store_true")
|
||||
args = ap.parse_args()
|
||||
|
||||
db = MongoClient("mongodb://192.168.1.76:27017", serverSelectionTimeoutMS=8000).feasibility
|
||||
resp = db.sipiq_responses.find_one({"pi_last_name": args.last})
|
||||
if not resp:
|
||||
raise SystemExit(f"CHYBA: centrum '{args.last}' nenalezeno v sipiq_responses.")
|
||||
inv = db.investigators.find_one({"_id": resp.get("investigator_oid")},
|
||||
{"email": 1, "email2": 1, "prijmeni": 1, "jmeno": 1})
|
||||
tos = [e for e in [(inv or {}).get("email"), (inv or {}).get("email2")] if e]
|
||||
if not tos and resp.get("pi_email"):
|
||||
tos = [resp["pi_email"]]
|
||||
to = ", ".join(tos)
|
||||
|
||||
intro = (
|
||||
'<p>Vážený pane doktore,</p>'
|
||||
'<p>děkujeme za zaslání vyplněného dotazníku SIPIQ studie '
|
||||
'<b>77242113UCO3002 (DAWN)</b>. Vaše odpovědi uvádíme pro úplnost v plném znění '
|
||||
'níže, pod podpisem. Odeslaný dotazník již není možné editovat, takže kdybychom '
|
||||
'při dalším zpracování našli položky chybějící, nejasné nebo nekonzistentní, '
|
||||
'ještě bychom si vyžádali upřesnění e-mailem.</p>'
|
||||
)
|
||||
sig = f'<p>S pozdravem,<br>{SIG}</p>'
|
||||
sipiq = build_sipiq_html(db, resp)
|
||||
body = (intro + sig +
|
||||
'<hr>'
|
||||
'<p style="color:#888;font-style:italic">Vyplněné SIPIQ (kopie Vašich odpovědí):</p>'
|
||||
+ sipiq)
|
||||
|
||||
print(f"Centrum: {resp.get('pi_first_name')} {resp.get('pi_last_name')} | {resp.get('site_name')}")
|
||||
print(f"To: {to}\nCc: {CC}\nSubject: {SUBJECT}")
|
||||
print(f"SIPIQ – sekcí: {sipiq.count('<h3')} | délka těla: {len(body)} znaků")
|
||||
|
||||
if not args.apply:
|
||||
print("[DRY-RUN] .eml nezapsáno. Ostrý běh: --apply")
|
||||
return
|
||||
|
||||
msg = EmailMessage()
|
||||
msg["From"] = FROM
|
||||
msg["To"] = to
|
||||
msg["Cc"] = CC
|
||||
msg["Subject"] = SUBJECT
|
||||
msg["X-Unsent"] = "1"
|
||||
html_doc = (f'<html><body style="font-family:Calibri,Arial,sans-serif;font-size:11pt">'
|
||||
f'{body}</body></html>')
|
||||
# base64 (cte) — Outlook zobrazí čistě, žádné quoted-printable „=" artefakty
|
||||
msg.set_content(html_doc, subtype="html", charset="utf-8", cte="base64")
|
||||
|
||||
os.makedirs(OUT_DIR, exist_ok=True)
|
||||
fname = f"podekovani_sipiq_{args.last.lower()}_{datetime.now().strftime('%d%b%Y').upper()}.eml"
|
||||
path = os.path.join(OUT_DIR, fname)
|
||||
with open(path, "wb") as f:
|
||||
f.write(bytes(msg))
|
||||
print(f"Uloženo: {path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,34 @@
|
||||
# sipiq_thankyou_v1.2 — poděkování za vyplnění SIPIQ (.eml draft, dávkový režim)
|
||||
|
||||
**Verze:** 1.2 · **Datum:** 2026-06-18 · **Studie:** 77242113UCO3002 (ICONIC / DAWN)
|
||||
|
||||
## Změny
|
||||
- **v1.2:** dávkový režim `--all` (+ `--skip prijmeni,...`) — iteruje po `_id` (rozliší i dva
|
||||
Konecnyovy), názvy souborů u shodného příjmení doplní křestním jménem. `--email <pi_email>`
|
||||
pro adresné cílení. Filtr na **dd-proxy** adresy (anonymizační proxy, ne reálná schránka → vynechat).
|
||||
- **v1.1:** kódování těla **base64** (`cte="base64"`) — Outlook čistě, žádné quoted-printable „=".
|
||||
(v1.0 měla QP bug.) v1.0/v1.1 → `Feasibility\TRASH`.
|
||||
|
||||
## Co dělá
|
||||
.eml draft poděkování za vyplnění SIPIQ. Struktura BEZ Shrnutí: úvod → podpis → ÚPLNĚ POD PODPISEM
|
||||
kompletní výpis SIPIQ (sekce → otázka → odpověď, dle `order`/`section`, vynechána Confidentiality,
|
||||
jen neprázdné). `X-Unsent: 1` → Outlook compose; uživatel **ručně odešle**.
|
||||
|
||||
## Příjemci / odeslání
|
||||
- **To** = `email` + `email2` z investigators (fallback `pi_email`); **dd-proxy adresy se vynechávají**.
|
||||
**Cc** = `AKocourk@ITS.JNJ.com, EBartoso@its.jnj.com`. **From** = vbuzalka@its.jnj.com.
|
||||
- Výstup `…\UploadToJNJ\podekovani_sipiq_<prijmeni>[_<jmeno>]_<DDMMMYYYY>.eml`.
|
||||
- **Draft ≠ odesláno** — STATUS „poděkováno" psát až po ověření odeslání.
|
||||
- Pozn. Baláž: dle STATUS preferovat `balaz@bystrica.sk` (skript dává obě adresy).
|
||||
|
||||
## Použití
|
||||
```
|
||||
.venv\Scripts\python.exe Feasibility\sipiq_thankyou_v1.2.py --last Bruncak --apply
|
||||
.venv\Scripts\python.exe Feasibility\sipiq_thankyou_v1.2.py --email konecny.stefan@fnbrno.cz --apply
|
||||
.venv\Scripts\python.exe Feasibility\sipiq_thankyou_v1.2.py --all --skip Bruncak --apply
|
||||
```
|
||||
Bez `--apply` = dry-run (vypíše příjemce). Mongo 192.168.1.76:27017, pymongo.
|
||||
|
||||
## Stav 18JUN2026
|
||||
Bruncák poděkování odesláno (STATUS zapsán). Zbývajících 15 vygenerováno do UploadToJNJ
|
||||
(`--all --skip Bruncak`), ověřeno base64 + čistý text. Čekají na ruční odeslání → pak STATUS „poděkováno".
|
||||
@@ -0,0 +1,195 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
sipiq_thankyou_v1.2.py
|
||||
======================
|
||||
Verze: 1.2
|
||||
Datum: 2026-06-18
|
||||
Autor: Claude Code (pro MUDr. Vladimíra Buzalku)
|
||||
|
||||
Změny proti v1.1
|
||||
----------------
|
||||
- DÁVKOVÝ REŽIM `--all` (+ `--skip prijmeni,...`): vygeneruje poděkování pro VŠECHNA
|
||||
centra v sipiq_responses (iteruje po _id, ne po příjmení → rozliší i dva Konecnyovy).
|
||||
Názvy souborů u shodného příjmení rozlišeny křestním jménem.
|
||||
- `--email <pi_email>` pro adresné cílení jedné odpovědi.
|
||||
- Kódování těla zůstává base64 (z v1.1) — Outlook čistě, žádné quoted-printable „=".
|
||||
v1.0/v1.1 v TRASH.
|
||||
|
||||
Popis
|
||||
-----
|
||||
.eml draft poděkování za vyplnění SIPIQ (77242113UCO3002 / DAWN). Struktura BEZ Shrnutí:
|
||||
úvod → podpis → ÚPLNĚ POD PODPISEM kompletní výpis SIPIQ (sekce → otázka → odpověď,
|
||||
dle order/section, vynechána Confidentiality, jen neprázdné). To = email+email2 z investigators
|
||||
(fallback pi_email), Cc = koordinátorky, from = its.jnj.com. Draft ≠ odesláno.
|
||||
|
||||
Použití:
|
||||
python sipiq_thankyou_v1.2.py --last Bruncak --apply
|
||||
python sipiq_thankyou_v1.2.py --email konecny.stefan@fnbrno.cz --apply
|
||||
python sipiq_thankyou_v1.2.py --all --skip Bruncak --apply
|
||||
Mongo 192.168.1.76:27017, bez auth, pymongo.
|
||||
"""
|
||||
import argparse
|
||||
import os
|
||||
import unicodedata
|
||||
from datetime import datetime
|
||||
from email.message import EmailMessage
|
||||
|
||||
from pymongo import MongoClient
|
||||
|
||||
OUT_DIR = r"u:\Dropbox\!!!Days\Downloads Z230\UploadToJNJ"
|
||||
CC = "AKocourk@ITS.JNJ.com, EBartoso@its.jnj.com"
|
||||
FROM = "vbuzalka@its.jnj.com"
|
||||
SKIP_SECTIONS = {"Confidentiality Statement"}
|
||||
SUBJECT = "77242113UCO3002 (DAWN) — poděkování za vyplnění feasibility dotazníku SIPIQ"
|
||||
SIG = ("MUDr. Vladimír BUZALKA<br>"
|
||||
"ICON plc / Performing Local Trial Management Services for Janssen – Cilag s.r.o. "
|
||||
"/ Global Clinical Operations<br>"
|
||||
"Mobile: +420 775 735 276 / Fax: +420 227 012 284<br>"
|
||||
"E-mail: vbuzalka@its.jnj.com, vladimir.buzalka@iconplc.com")
|
||||
|
||||
|
||||
def esc(s):
|
||||
return str(s).replace("&", "&").replace("<", "<").replace(">", ">")
|
||||
|
||||
|
||||
def ascii_slug(s):
|
||||
return "".join(c for c in unicodedata.normalize("NFKD", str(s or ""))
|
||||
if not unicodedata.combining(c)).lower().replace(" ", "")
|
||||
|
||||
|
||||
def build_sipiq_html(db, resp):
|
||||
ans = resp.get("answers") or {}
|
||||
qs = list(db.sipiq_questions.find().sort("order", 1))
|
||||
out, cur = [], None
|
||||
for q in qs:
|
||||
sec = q.get("section") or "Other"
|
||||
if sec in SKIP_SECTIONS:
|
||||
continue
|
||||
items = q.get("items") or []
|
||||
pairs = []
|
||||
if not items:
|
||||
v = ans.get(q["_id"])
|
||||
if v not in (None, ""):
|
||||
pairs.append((None, v))
|
||||
else:
|
||||
for it in items:
|
||||
v = ans.get(it["key"])
|
||||
if v not in (None, ""):
|
||||
pairs.append((it.get("label") or "(odpověď)", v))
|
||||
if not pairs:
|
||||
continue
|
||||
if sec != cur:
|
||||
cur = sec
|
||||
out.append(f'<h3 style="margin:16px 0 4px;color:#1F4E78">{esc(sec)}</h3>')
|
||||
stem = esc((q.get("text") or "").replace("\n", " "))
|
||||
out.append(f'<p style="margin:6px 0 1px"><b>[{q["_id"]}]</b> {stem}</p>')
|
||||
if len(pairs) == 1 and pairs[0][0] is None:
|
||||
out.append(f'<p style="margin:0 0 0 18px"><b>{esc(pairs[0][1])}</b></p>')
|
||||
else:
|
||||
out.append('<ul style="margin:2px 0 6px">')
|
||||
for lbl, v in pairs:
|
||||
out.append(f'<li>{esc(lbl)}: <b>{esc(v)}</b></li>' if lbl
|
||||
else f'<li><b>{esc(v)}</b></li>')
|
||||
out.append('</ul>')
|
||||
return "\n".join(out)
|
||||
|
||||
|
||||
def recipients(db, resp):
|
||||
inv = db.investigators.find_one({"_id": resp.get("investigator_oid")},
|
||||
{"email": 1, "email2": 1})
|
||||
tos = [e for e in [(inv or {}).get("email"), (inv or {}).get("email2")] if e]
|
||||
if not tos and resp.get("pi_email"):
|
||||
tos = [resp["pi_email"]]
|
||||
# vynech anonymizační proxy adresy (dd-proxy) — není to schránka, kterou lékař čte
|
||||
tos = [e for e in tos if "dd-proxy" not in e.lower()] or tos
|
||||
return ", ".join(tos)
|
||||
|
||||
|
||||
def process_one(db, resp, dup_last, apply):
|
||||
to = recipients(db, resp)
|
||||
intro = (
|
||||
'<p>Vážený pane doktore,</p>'
|
||||
'<p>děkujeme za zaslání vyplněného dotazníku SIPIQ studie '
|
||||
'<b>77242113UCO3002 (DAWN)</b>. Vaše odpovědi uvádíme pro úplnost v plném znění '
|
||||
'níže, pod podpisem. Odeslaný dotazník již není možné editovat, takže kdybychom '
|
||||
'při dalším zpracování našli položky chybějící, nejasné nebo nekonzistentní, '
|
||||
'ještě bychom si vyžádali upřesnění e-mailem.</p>'
|
||||
)
|
||||
sig = f'<p>S pozdravem,<br>{SIG}</p>'
|
||||
sipiq = build_sipiq_html(db, resp)
|
||||
body = (intro + sig + '<hr>'
|
||||
'<p style="color:#888;font-style:italic">Vyplněné SIPIQ (kopie Vašich odpovědí):</p>'
|
||||
+ sipiq)
|
||||
|
||||
last = resp.get("pi_last_name") or "x"
|
||||
first = resp.get("pi_first_name") or ""
|
||||
slug = ascii_slug(last) + (("_" + ascii_slug(first)) if last in dup_last else "")
|
||||
fname = f"podekovani_sipiq_{slug}_{datetime.now().strftime('%d%b%Y').upper()}.eml"
|
||||
print(f" {first} {last:14} | {resp.get('site_country')} | To: {to[:55]}")
|
||||
|
||||
if not apply:
|
||||
return None
|
||||
msg = EmailMessage()
|
||||
msg["From"] = FROM
|
||||
msg["To"] = to
|
||||
msg["Cc"] = CC
|
||||
msg["Subject"] = SUBJECT
|
||||
msg["X-Unsent"] = "1"
|
||||
html_doc = (f'<html><body style="font-family:Calibri,Arial,sans-serif;font-size:11pt">'
|
||||
f'{body}</body></html>')
|
||||
msg.set_content(html_doc, subtype="html", charset="utf-8", cte="base64")
|
||||
os.makedirs(OUT_DIR, exist_ok=True)
|
||||
path = os.path.join(OUT_DIR, fname)
|
||||
with open(path, "wb") as f:
|
||||
f.write(bytes(msg))
|
||||
return fname
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
g = ap.add_mutually_exclusive_group(required=True)
|
||||
g.add_argument("--last", help="příjmení v sipiq_responses")
|
||||
g.add_argument("--email", help="pi_email konkrétní odpovědi")
|
||||
g.add_argument("--all", action="store_true", help="všechna centra")
|
||||
ap.add_argument("--skip", default="", help="příjmení k vynechání (čárkou)")
|
||||
ap.add_argument("--apply", action="store_true")
|
||||
args = ap.parse_args()
|
||||
|
||||
db = MongoClient("mongodb://192.168.1.76:27017", serverSelectionTimeoutMS=8000).feasibility
|
||||
skip = {s.strip() for s in args.skip.split(",") if s.strip()}
|
||||
|
||||
if args.all:
|
||||
resps = [r for r in db.sipiq_responses.find() if r.get("pi_last_name") not in skip]
|
||||
elif args.email:
|
||||
resps = list(db.sipiq_responses.find({"pi_email": args.email.lower()}))
|
||||
else:
|
||||
resps = list(db.sipiq_responses.find({"pi_last_name": args.last}))
|
||||
if not resps:
|
||||
raise SystemExit("CHYBA: žádná odpovídající odpověď.")
|
||||
if (args.last or args.email) and len(resps) > 1:
|
||||
raise SystemExit(f"CHYBA: nejednoznačné ({len(resps)} odpovědí) — použij --email nebo --all.")
|
||||
|
||||
# duplicitní příjmení (kvůli disambiguaci názvů souborů)
|
||||
seen, dup = set(), set()
|
||||
for r in resps:
|
||||
ln = r.get("pi_last_name")
|
||||
if ln in seen:
|
||||
dup.add(ln)
|
||||
seen.add(ln)
|
||||
|
||||
resps.sort(key=lambda r: ((r.get("site_country") or ""), (r.get("pi_last_name") or "").lower()))
|
||||
print(f"Center ke zpracování: {len(resps)}" + (f" (skip: {', '.join(skip)})" if skip else ""))
|
||||
done = []
|
||||
for r in resps:
|
||||
fn = process_one(db, r, dup, args.apply)
|
||||
if fn:
|
||||
done.append(fn)
|
||||
if not args.apply:
|
||||
print(f"\n[DRY-RUN] {len(resps)} center, nic nezapsáno. Ostrý: --apply")
|
||||
else:
|
||||
print(f"\n[APPLY] Vygenerováno {len(done)} .eml do {OUT_DIR}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,28 @@
|
||||
# sipiq_reminder_v1.1 — připomínka vyplnění SIPIQ (.eml draft)
|
||||
|
||||
**Verze:** 1.1 · **Datum:** 2026-06-18 · **Studie:** 77242113UCO3002 (ICONIC / DAWN)
|
||||
|
||||
## Změna proti v1.0
|
||||
- **BEZ hlavičky From** — do `.eml` se NEdává odesílací účet. JNJ Outlook pak doplní odesílatele
|
||||
z adresáře (GAL) a **odešle přes JNJ Exchange** (jediná možnost) → správná GAL identita,
|
||||
žádné riziko buzalka.cz. Pravidlo 18JUN2026. v1.0 → `Feasibility\TRASH`.
|
||||
|
||||
## Co dělá
|
||||
.eml draft připomínky vyplnění SIPIQ pro centra na KROK 6 (SIPIQ odeslán), která dlouho nereagují.
|
||||
Vřelá prosba o vyplnění co nejdříve + odkaz na `sipiq.link` + datum původního odeslání + věta
|
||||
„pokud jste již vyplnil(a), ignorujte". Skloňování dle pohlaví. Tělo **base64**, `X-Unsent: 1`.
|
||||
|
||||
## Příjemci
|
||||
- **To** = `email` + `email2` z investigators + ruční `EXTRA_TO` (dd-proxy vynechány, dedup).
|
||||
**Cc** = koordinátorky (AKocourk + EBartoso). **From** = NEdává se.
|
||||
- Výstup `…\UploadToJNJ\pripominka_sipiq_<prijmeni>_<DDMMMYYYY>.eml`.
|
||||
- **Draft ≠ odesláno** — STATUS „připomínka SIPIQ odeslána" až po ověření odeslání.
|
||||
|
||||
## EXTRA_TO (z reply-checku jnjemails SQLite)
|
||||
Jungwirthová + liberascientia.cz · Krížová + fnmh.cz · Mudr + petr.pekny@nmskb.cz (dr. Pěkný za dr. Mudra).
|
||||
|
||||
## Použití
|
||||
```
|
||||
.venv\Scripts\python.exe Feasibility\sipiq_reminder_v1.1.py --names "Matous,Kojecky,Jungwirthova,Krížová,Mudr" --apply
|
||||
```
|
||||
Bez `--apply` = dry-run. Mongo 192.168.1.76:27017, pymongo.
|
||||
@@ -0,0 +1,159 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
sipiq_reminder_v1.1.py
|
||||
======================
|
||||
Verze: 1.1
|
||||
Datum: 2026-06-18
|
||||
Autor: Claude Code (pro MUDr. Vladimíra Buzalku)
|
||||
|
||||
Změna proti v1.0
|
||||
----------------
|
||||
- BEZ hlavičky From — do .eml se NEdává odesílací účet; JNJ Outlook doplní odesílatele
|
||||
z GAL a odešle přes JNJ Exchange (jediná možnost). Pravidlo 18JUN2026. v1.0 v TRASH.
|
||||
|
||||
Popis
|
||||
-----
|
||||
Vygeneruje .eml draft PŘIPOMÍNKY vyplnění SIPIQ (studie 77242113UCO3002 / DAWN) pro centra
|
||||
na KROK 6 (SIPIQ odeslán), která dlouho nereagovala. Vřelá prosba o vyplnění co nejdříve
|
||||
+ odkaz na dotazník specifický pro centrum (sipiq.link z investigators) + bezpečnostní věta
|
||||
„pokud jste již vyplnil(a), ignorujte".
|
||||
|
||||
To = email + email2 z investigators (+ EXTRA_TO ručně, dd-proxy vynechány), Cc = koordinátorky,
|
||||
from = vbuzalka@its.jnj.com, tělo BASE64 (Outlook čistě). Draft ≠ odesláno — STATUS „připomínka
|
||||
SIPIQ odeslána" psát až po ověření odeslání.
|
||||
|
||||
Použití:
|
||||
python sipiq_reminder_v1.1.py --names "Matous,Kojecky,Jungwirthova,Krížová,Mudr" # dry-run
|
||||
python sipiq_reminder_v1.1.py --names "Matous,Kojecky,Jungwirthova,Krížová,Mudr" --apply
|
||||
Mongo 192.168.1.76:27017, bez auth, pymongo.
|
||||
"""
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import unicodedata
|
||||
from datetime import date
|
||||
from email.message import EmailMessage
|
||||
|
||||
from pymongo import MongoClient
|
||||
|
||||
OUT_DIR = r"u:\Dropbox\!!!Days\Downloads Z230\UploadToJNJ"
|
||||
CC = "AKocourk@ITS.JNJ.com, EBartoso@its.jnj.com"
|
||||
FROM = "vbuzalka@its.jnj.com"
|
||||
SIG = ("MUDr. Vladimír BUZALKA<br>"
|
||||
"ICON plc / Performing Local Trial Management Services for Janssen – Cilag s.r.o. "
|
||||
"/ Global Clinical Operations<br>"
|
||||
"Mobile: +420 775 735 276 / Fax: +420 227 012 284<br>"
|
||||
"E-mail: vbuzalka@its.jnj.com, vladimir.buzalka@iconplc.com")
|
||||
|
||||
# Ručně doplněné adresy (z reply-checku / STATUS) — komu navíc poslat
|
||||
EXTRA_TO = {
|
||||
"Jungwirthova": ["a.jungwirthova@liberascientia.cz"],
|
||||
"Krížová": ["Viera.Krizova@fnmh.cz"],
|
||||
"Mudr": ["petr.pekny@nmskb.cz"], # dr. Pěkný komunikuje za dr. Mudra (dle STATUS)
|
||||
}
|
||||
|
||||
CZ_MON = {1: "ledna", 2: "února", 3: "března", 4: "dubna", 5: "května", 6: "června",
|
||||
7: "července", 8: "srpna", 9: "září", 10: "října", 11: "listopadu", 12: "prosince"}
|
||||
MON = {m: i + 1 for i, m in enumerate(
|
||||
["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"])}
|
||||
|
||||
|
||||
def ascii_slug(s):
|
||||
return "".join(c for c in unicodedata.normalize("NFKD", str(s or ""))
|
||||
if not unicodedata.combining(c)).lower().replace(" ", "")
|
||||
|
||||
|
||||
def sent_date_cz(status):
|
||||
dates = []
|
||||
for ln in (status or "").splitlines():
|
||||
if "SIPIQ odesl" in ln:
|
||||
m = re.match(r"(\d{2})([A-Za-z]{3})(\d{4})", ln.strip())
|
||||
if m and m.group(2).upper() in MON:
|
||||
dates.append(date(int(m.group(3)), MON[m.group(2).upper()], int(m.group(1))))
|
||||
if not dates:
|
||||
return None
|
||||
d = max(dates)
|
||||
return f"{d.day}. {CZ_MON[d.month]} {d.year}"
|
||||
|
||||
|
||||
def is_female(prijmeni):
|
||||
p = (prijmeni or "").lower()
|
||||
return p.endswith("ová") or p.endswith("ova") or p.endswith("á")
|
||||
|
||||
|
||||
def build_body(jmeno, prijmeni, sent_cz, link):
|
||||
f = is_female(prijmeni)
|
||||
greet = "Vážená paní doktorko," if f else "Vážený pane doktore,"
|
||||
vyplnil = "vyplnila" if f else "vyplnil"
|
||||
sent_part = f" dne <b>{sent_cz}</b>" if sent_cz else ""
|
||||
return (
|
||||
f'<p>{greet}</p>'
|
||||
f'<p>dovoluji si zdvořile připomenout feasibility dotazník <b>SIPIQ</b> ke studii '
|
||||
f'<b>77242113UCO3002 (DAWN)</b> (přípravek icotrokinra, ulcerózní kolitida), '
|
||||
f'který jsme Vám zaslali{sent_part}.</p>'
|
||||
f'<p><b>Velmi bychom ocenili jeho vyplnění co nejdříve</b> — je pro nás klíčový pro '
|
||||
f'posouzení Vašeho centra a další postup. Níže najdete odkaz na dotazník specifický '
|
||||
f'pro Vaše centrum:</p>'
|
||||
f'<p><a href="{link}">{link}</a></p>'
|
||||
f'<p>Pokud jste dotazník již {vyplnil}, přijměte prosím poděkování a tento e-mail '
|
||||
f'ignorujte.</p>'
|
||||
f'<p>V případě jakýchkoli dotazů (např. potíže s odkazem) jsem Vám plně k dispozici. '
|
||||
f'Předem děkuji za Váš čas a vstřícnost.</p>'
|
||||
f'<p>S pozdravem,<br>{SIG}</p>'
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--names", required=True, help="příjmení čárkou (pi v investigators)")
|
||||
ap.add_argument("--apply", action="store_true")
|
||||
args = ap.parse_args()
|
||||
|
||||
db = MongoClient("mongodb://192.168.1.76:27017", serverSelectionTimeoutMS=8000).feasibility
|
||||
names = [n.strip() for n in args.names.split(",") if n.strip()]
|
||||
docs = list(db.investigators.find({"prijmeni": {"$in": names}}))
|
||||
found = {d["prijmeni"] for d in docs}
|
||||
for n in names:
|
||||
if n not in found:
|
||||
print(f" ! nenalezeno: {n}")
|
||||
|
||||
subject = "[Připomínka] 77242113UCO3002 (DAWN) — feasibility dotazník SIPIQ (prosba o vyplnění)"
|
||||
done = []
|
||||
for d in docs:
|
||||
link = (d.get("sipiq") or {}).get("link")
|
||||
if not link:
|
||||
print(f" ! {d['prijmeni']}: chybí sipiq.link — přeskakuji"); continue
|
||||
tos = [e for e in [d.get("email"), d.get("email2")] if e]
|
||||
tos += EXTRA_TO.get(d["prijmeni"], [])
|
||||
tos = [e for e in dict.fromkeys(tos) if "dd-proxy" not in e.lower()] # dedup + bez proxy
|
||||
to = ", ".join(tos)
|
||||
sent_cz = sent_date_cz(d.get("STATUS"))
|
||||
body = build_body(d.get("jmeno"), d["prijmeni"], sent_cz, link)
|
||||
print(f" {d.get('jmeno')} {d['prijmeni']:14} | To: {to}")
|
||||
|
||||
if not args.apply:
|
||||
continue
|
||||
msg = EmailMessage()
|
||||
# ŽÁDNÝ From — JNJ Outlook doplní odesílatele z GAL (odešle přes JNJ Exchange)
|
||||
msg["To"] = to
|
||||
msg["Cc"] = CC
|
||||
msg["Subject"] = subject
|
||||
msg["X-Unsent"] = "1"
|
||||
html = (f'<html><body style="font-family:Calibri,Arial,sans-serif;font-size:11pt">'
|
||||
f'{body}</body></html>')
|
||||
msg.set_content(html, subtype="html", charset="utf-8", cte="base64")
|
||||
os.makedirs(OUT_DIR, exist_ok=True)
|
||||
fn = f"pripominka_sipiq_{ascii_slug(d['prijmeni'])}_18JUN2026.eml"
|
||||
with open(os.path.join(OUT_DIR, fn), "wb") as fh:
|
||||
fh.write(bytes(msg))
|
||||
done.append(fn)
|
||||
|
||||
if not args.apply:
|
||||
print(f"\n[DRY-RUN] {len(docs)} center, nic nezapsáno. Ostrý: --apply")
|
||||
else:
|
||||
print(f"\n[APPLY] Vygenerováno {len(done)} připomínek do {OUT_DIR}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,29 @@
|
||||
# sipiq_thankyou_v1.3 — poděkování za vyplnění SIPIQ (.eml draft, dávkový režim)
|
||||
|
||||
**Verze:** 1.3 · **Datum:** 2026-06-18 · **Studie:** 77242113UCO3002 (ICONIC / DAWN)
|
||||
|
||||
## Změny
|
||||
- **v1.3:** **BEZ hlavičky From** — do `.eml` se NEdává odesílací účet. JNJ Outlook doplní
|
||||
odesílatele z adresáře (GAL) a **odešle přes JNJ Exchange** (jediná možnost) → žádné riziko
|
||||
buzalka.cz, žádný „holé SMTP" artefakt. Pravidlo 18JUN2026. v1.2 → `Feasibility\TRASH`.
|
||||
- **v1.2:** dávkový režim `--all` (+ `--skip`), rozliší 2 Konecnyovy, filtr dd-proxy.
|
||||
- **v1.1:** tělo base64 (ne quoted-printable).
|
||||
|
||||
## Co dělá
|
||||
.eml draft poděkování za vyplnění SIPIQ. Struktura BEZ Shrnutí: úvod → podpis → ÚPLNĚ POD PODPISEM
|
||||
kompletní výpis SIPIQ (sekce → otázka → odpověď, dle `order`/`section`, vynechána Confidentiality,
|
||||
jen neprázdné). Tělo base64, `X-Unsent: 1`.
|
||||
|
||||
## Příjemci
|
||||
- **To** = `email` + `email2` z investigators (dd-proxy vynechány). **Cc** = koordinátorky.
|
||||
**From** = NEdává se.
|
||||
- Výstup `…\UploadToJNJ\podekovani_sipiq_<prijmeni>[_<jmeno>]_<DDMMMYYYY>.eml`.
|
||||
- **Draft ≠ odesláno** — STATUS „poděkováno" až po ověření odeslání.
|
||||
|
||||
## Použití
|
||||
```
|
||||
.venv\Scripts\python.exe Feasibility\sipiq_thankyou_v1.3.py --last Bruncak --apply
|
||||
.venv\Scripts\python.exe Feasibility\sipiq_thankyou_v1.3.py --email konecny.stefan@fnbrno.cz --apply
|
||||
.venv\Scripts\python.exe Feasibility\sipiq_thankyou_v1.3.py --all --skip Bruncak --apply
|
||||
```
|
||||
Bez `--apply` = dry-run. Mongo 192.168.1.76:27017, pymongo.
|
||||
@@ -0,0 +1,200 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
sipiq_thankyou_v1.3.py
|
||||
======================
|
||||
Verze: 1.3
|
||||
Datum: 2026-06-18
|
||||
Autor: Claude Code (pro MUDr. Vladimíra Buzalku)
|
||||
|
||||
Změna proti v1.2
|
||||
----------------
|
||||
- BEZ hlavičky From — do .eml se NEdává odesílací účet; JNJ Outlook doplní odesílatele
|
||||
z GAL a odešle přes JNJ Exchange (jediná možnost). Pravidlo 18JUN2026. v1.2 v TRASH.
|
||||
|
||||
Změny proti v1.1
|
||||
----------------
|
||||
- DÁVKOVÝ REŽIM `--all` (+ `--skip prijmeni,...`): vygeneruje poděkování pro VŠECHNA
|
||||
centra v sipiq_responses (iteruje po _id, ne po příjmení → rozliší i dva Konecnyovy).
|
||||
Názvy souborů u shodného příjmení rozlišeny křestním jménem.
|
||||
- `--email <pi_email>` pro adresné cílení jedné odpovědi.
|
||||
- Kódování těla zůstává base64 (z v1.1) — Outlook čistě, žádné quoted-printable „=".
|
||||
v1.0/v1.1 v TRASH.
|
||||
|
||||
Popis
|
||||
-----
|
||||
.eml draft poděkování za vyplnění SIPIQ (77242113UCO3002 / DAWN). Struktura BEZ Shrnutí:
|
||||
úvod → podpis → ÚPLNĚ POD PODPISEM kompletní výpis SIPIQ (sekce → otázka → odpověď,
|
||||
dle order/section, vynechána Confidentiality, jen neprázdné). To = email+email2 z investigators
|
||||
(fallback pi_email), Cc = koordinátorky, from = its.jnj.com. Draft ≠ odesláno.
|
||||
|
||||
Použití:
|
||||
python sipiq_thankyou_v1.3.py --last Bruncak --apply
|
||||
python sipiq_thankyou_v1.3.py --email konecny.stefan@fnbrno.cz --apply
|
||||
python sipiq_thankyou_v1.3.py --all --skip Bruncak --apply
|
||||
Mongo 192.168.1.76:27017, bez auth, pymongo.
|
||||
"""
|
||||
import argparse
|
||||
import os
|
||||
import unicodedata
|
||||
from datetime import datetime
|
||||
from email.message import EmailMessage
|
||||
|
||||
from pymongo import MongoClient
|
||||
|
||||
OUT_DIR = r"u:\Dropbox\!!!Days\Downloads Z230\UploadToJNJ"
|
||||
CC = "AKocourk@ITS.JNJ.com, EBartoso@its.jnj.com"
|
||||
FROM = "vbuzalka@its.jnj.com"
|
||||
SKIP_SECTIONS = {"Confidentiality Statement"}
|
||||
SUBJECT = "77242113UCO3002 (DAWN) — poděkování za vyplnění feasibility dotazníku SIPIQ"
|
||||
SIG = ("MUDr. Vladimír BUZALKA<br>"
|
||||
"ICON plc / Performing Local Trial Management Services for Janssen – Cilag s.r.o. "
|
||||
"/ Global Clinical Operations<br>"
|
||||
"Mobile: +420 775 735 276 / Fax: +420 227 012 284<br>"
|
||||
"E-mail: vbuzalka@its.jnj.com, vladimir.buzalka@iconplc.com")
|
||||
|
||||
|
||||
def esc(s):
|
||||
return str(s).replace("&", "&").replace("<", "<").replace(">", ">")
|
||||
|
||||
|
||||
def ascii_slug(s):
|
||||
return "".join(c for c in unicodedata.normalize("NFKD", str(s or ""))
|
||||
if not unicodedata.combining(c)).lower().replace(" ", "")
|
||||
|
||||
|
||||
def build_sipiq_html(db, resp):
|
||||
ans = resp.get("answers") or {}
|
||||
qs = list(db.sipiq_questions.find().sort("order", 1))
|
||||
out, cur = [], None
|
||||
for q in qs:
|
||||
sec = q.get("section") or "Other"
|
||||
if sec in SKIP_SECTIONS:
|
||||
continue
|
||||
items = q.get("items") or []
|
||||
pairs = []
|
||||
if not items:
|
||||
v = ans.get(q["_id"])
|
||||
if v not in (None, ""):
|
||||
pairs.append((None, v))
|
||||
else:
|
||||
for it in items:
|
||||
v = ans.get(it["key"])
|
||||
if v not in (None, ""):
|
||||
pairs.append((it.get("label") or "(odpověď)", v))
|
||||
if not pairs:
|
||||
continue
|
||||
if sec != cur:
|
||||
cur = sec
|
||||
out.append(f'<h3 style="margin:16px 0 4px;color:#1F4E78">{esc(sec)}</h3>')
|
||||
stem = esc((q.get("text") or "").replace("\n", " "))
|
||||
out.append(f'<p style="margin:6px 0 1px"><b>[{q["_id"]}]</b> {stem}</p>')
|
||||
if len(pairs) == 1 and pairs[0][0] is None:
|
||||
out.append(f'<p style="margin:0 0 0 18px"><b>{esc(pairs[0][1])}</b></p>')
|
||||
else:
|
||||
out.append('<ul style="margin:2px 0 6px">')
|
||||
for lbl, v in pairs:
|
||||
out.append(f'<li>{esc(lbl)}: <b>{esc(v)}</b></li>' if lbl
|
||||
else f'<li><b>{esc(v)}</b></li>')
|
||||
out.append('</ul>')
|
||||
return "\n".join(out)
|
||||
|
||||
|
||||
def recipients(db, resp):
|
||||
inv = db.investigators.find_one({"_id": resp.get("investigator_oid")},
|
||||
{"email": 1, "email2": 1})
|
||||
tos = [e for e in [(inv or {}).get("email"), (inv or {}).get("email2")] if e]
|
||||
if not tos and resp.get("pi_email"):
|
||||
tos = [resp["pi_email"]]
|
||||
# vynech anonymizační proxy adresy (dd-proxy) — není to schránka, kterou lékař čte
|
||||
tos = [e for e in tos if "dd-proxy" not in e.lower()] or tos
|
||||
return ", ".join(tos)
|
||||
|
||||
|
||||
def process_one(db, resp, dup_last, apply):
|
||||
to = recipients(db, resp)
|
||||
intro = (
|
||||
'<p>Vážený pane doktore,</p>'
|
||||
'<p>děkujeme za zaslání vyplněného dotazníku SIPIQ studie '
|
||||
'<b>77242113UCO3002 (DAWN)</b>. Vaše odpovědi uvádíme pro úplnost v plném znění '
|
||||
'níže, pod podpisem. Odeslaný dotazník již není možné editovat, takže kdybychom '
|
||||
'při dalším zpracování našli položky chybějící, nejasné nebo nekonzistentní, '
|
||||
'ještě bychom si vyžádali upřesnění e-mailem.</p>'
|
||||
)
|
||||
sig = f'<p>S pozdravem,<br>{SIG}</p>'
|
||||
sipiq = build_sipiq_html(db, resp)
|
||||
body = (intro + sig + '<hr>'
|
||||
'<p style="color:#888;font-style:italic">Vyplněné SIPIQ (kopie Vašich odpovědí):</p>'
|
||||
+ sipiq)
|
||||
|
||||
last = resp.get("pi_last_name") or "x"
|
||||
first = resp.get("pi_first_name") or ""
|
||||
slug = ascii_slug(last) + (("_" + ascii_slug(first)) if last in dup_last else "")
|
||||
fname = f"podekovani_sipiq_{slug}_{datetime.now().strftime('%d%b%Y').upper()}.eml"
|
||||
print(f" {first} {last:14} | {resp.get('site_country')} | To: {to[:55]}")
|
||||
|
||||
if not apply:
|
||||
return None
|
||||
msg = EmailMessage()
|
||||
# ŽÁDNÝ From — JNJ Outlook doplní odesílatele z GAL (odešle přes JNJ Exchange)
|
||||
msg["To"] = to
|
||||
msg["Cc"] = CC
|
||||
msg["Subject"] = SUBJECT
|
||||
msg["X-Unsent"] = "1"
|
||||
html_doc = (f'<html><body style="font-family:Calibri,Arial,sans-serif;font-size:11pt">'
|
||||
f'{body}</body></html>')
|
||||
msg.set_content(html_doc, subtype="html", charset="utf-8", cte="base64")
|
||||
os.makedirs(OUT_DIR, exist_ok=True)
|
||||
path = os.path.join(OUT_DIR, fname)
|
||||
with open(path, "wb") as f:
|
||||
f.write(bytes(msg))
|
||||
return fname
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
g = ap.add_mutually_exclusive_group(required=True)
|
||||
g.add_argument("--last", help="příjmení v sipiq_responses")
|
||||
g.add_argument("--email", help="pi_email konkrétní odpovědi")
|
||||
g.add_argument("--all", action="store_true", help="všechna centra")
|
||||
ap.add_argument("--skip", default="", help="příjmení k vynechání (čárkou)")
|
||||
ap.add_argument("--apply", action="store_true")
|
||||
args = ap.parse_args()
|
||||
|
||||
db = MongoClient("mongodb://192.168.1.76:27017", serverSelectionTimeoutMS=8000).feasibility
|
||||
skip = {s.strip() for s in args.skip.split(",") if s.strip()}
|
||||
|
||||
if args.all:
|
||||
resps = [r for r in db.sipiq_responses.find() if r.get("pi_last_name") not in skip]
|
||||
elif args.email:
|
||||
resps = list(db.sipiq_responses.find({"pi_email": args.email.lower()}))
|
||||
else:
|
||||
resps = list(db.sipiq_responses.find({"pi_last_name": args.last}))
|
||||
if not resps:
|
||||
raise SystemExit("CHYBA: žádná odpovídající odpověď.")
|
||||
if (args.last or args.email) and len(resps) > 1:
|
||||
raise SystemExit(f"CHYBA: nejednoznačné ({len(resps)} odpovědí) — použij --email nebo --all.")
|
||||
|
||||
# duplicitní příjmení (kvůli disambiguaci názvů souborů)
|
||||
seen, dup = set(), set()
|
||||
for r in resps:
|
||||
ln = r.get("pi_last_name")
|
||||
if ln in seen:
|
||||
dup.add(ln)
|
||||
seen.add(ln)
|
||||
|
||||
resps.sort(key=lambda r: ((r.get("site_country") or ""), (r.get("pi_last_name") or "").lower()))
|
||||
print(f"Center ke zpracování: {len(resps)}" + (f" (skip: {', '.join(skip)})" if skip else ""))
|
||||
done = []
|
||||
for r in resps:
|
||||
fn = process_one(db, r, dup, args.apply)
|
||||
if fn:
|
||||
done.append(fn)
|
||||
if not args.apply:
|
||||
print(f"\n[DRY-RUN] {len(resps)} center, nic nezapsáno. Ostrý: --apply")
|
||||
else:
|
||||
print(f"\n[APPLY] Vygenerováno {len(done)} .eml do {OUT_DIR}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user