#!/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 ` 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
" "ICON plc / Performing Local Trial Management Services for Janssen – Cilag s.r.o. " "/ Global Clinical Operations
" "Mobile: +420 775 735 276 / Fax: +420 227 012 284
" "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'

{esc(sec)}

') stem = esc((q.get("text") or "").replace("\n", " ")) out.append(f'

[{q["_id"]}] {stem}

') if len(pairs) == 1 and pairs[0][0] is None: out.append(f'

{esc(pairs[0][1])}

') else: out.append('') 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 = ( '

Vážený pane doktore,

' '

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.

' ) sig = f'

S pozdravem,
{SIG}

' sipiq = build_sipiq_html(db, resp) body = (intro + sig + '
' '

Vyplněné SIPIQ (kopie Vašich odpovědí):

' + 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'' f'{body}') 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()