Files
janssen/Feasibility/sipiq_thankyou_v1.3.py
T
2026-06-18 11:10:00 +02:00

201 lines
8.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
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()