z230
This commit is contained in:
+550
File diff suppressed because one or more lines are too long
@@ -0,0 +1,216 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
sipiq_report_v1.0.py
|
||||||
|
====================
|
||||||
|
Verze: 1.0
|
||||||
|
Datum: 2026-06-17
|
||||||
|
Autor: Claude Code (pro MUDr. Vladimíra Buzalku)
|
||||||
|
|
||||||
|
Popis
|
||||||
|
-----
|
||||||
|
Přehledný Excel report SIPIQ odpovědí: OTÁZKY ve sloupci A (v pořadí SIPIQ, seskupené
|
||||||
|
dle sekcí), CENTRA ve sloupcích (B+). Buňka = odpověď daného centra na danou otázku.
|
||||||
|
Tj. vyplněné SIPIQ všech center vedle sebe (= transponovaná rekonstrukce dotazníku).
|
||||||
|
|
||||||
|
Zdroj: MongoDB feasibility (sipiq_questions = slovník + pořadí + sekce + items;
|
||||||
|
sipiq_responses = odpovědi + answers_supplement z doplnujici_dotazy).
|
||||||
|
|
||||||
|
Layout:
|
||||||
|
- hlavička 2 řádky: příjmení PI + název centra (a země)
|
||||||
|
- blok IDENTITA (země/město/PI/e-mail/sdl_site_id/datum vyplnění)
|
||||||
|
- sekce SIPIQ jako barevné mezititulky, otázky a položky matic v řádcích
|
||||||
|
- Yes = zelená, No = červená; doplněné odpovědi (answers_supplement) = oranžová + kurzíva + komentář
|
||||||
|
- freeze panes (sloupec A + hlavička), ohraničení, vhodné šířky
|
||||||
|
|
||||||
|
Výstup: u:\\Dropbox\\!!!Days\\Downloads Z230\\SIPIQ_prehled_center_<ts>.xlsx
|
||||||
|
Závislosti: pymongo, openpyxl (.venv). Mongo 192.168.1.76:27017, bez auth.
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from pymongo import MongoClient
|
||||||
|
from openpyxl import Workbook
|
||||||
|
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
|
||||||
|
from openpyxl.comments import Comment
|
||||||
|
from openpyxl.utils import get_column_letter
|
||||||
|
|
||||||
|
MONGO_URI = "mongodb://192.168.1.76:27017"
|
||||||
|
DB = "feasibility"
|
||||||
|
OUT_DIR = r"u:\Dropbox\!!!Days\Downloads Z230"
|
||||||
|
SKIP_SECTIONS = {"Confidentiality Statement"} # dlouhé právní souhlasy, ne data
|
||||||
|
COUNTRY_ABBR = {"Czech Republic": "CZ", "Slovakia": "SK"}
|
||||||
|
|
||||||
|
# barvy
|
||||||
|
C_SECTION = PatternFill("solid", fgColor="1F4E78") # tmavě modrá
|
||||||
|
C_STEM = PatternFill("solid", fgColor="DDEBF7") # světle modrá
|
||||||
|
C_IDENT = PatternFill("solid", fgColor="F2F2F2") # světle šedá
|
||||||
|
C_HEAD = PatternFill("solid", fgColor="305496") # hlavička center
|
||||||
|
C_YES = PatternFill("solid", fgColor="C6EFCE") # zelená
|
||||||
|
C_NO = PatternFill("solid", fgColor="FFC7CE") # červená
|
||||||
|
C_SUPP = PatternFill("solid", fgColor="FFE699") # oranžová (doplněno)
|
||||||
|
THIN = Side(style="thin", color="BFBFBF")
|
||||||
|
BORDER = Border(left=THIN, right=THIN, top=THIN, bottom=THIN)
|
||||||
|
WRAP_TOP = Alignment(wrap_text=True, vertical="top")
|
||||||
|
WRAP_CTR = Alignment(wrap_text=True, vertical="center", horizontal="center")
|
||||||
|
|
||||||
|
|
||||||
|
def fmt_date(s):
|
||||||
|
if not s:
|
||||||
|
return ""
|
||||||
|
try:
|
||||||
|
return datetime.strptime(str(s)[:10], "%Y-%m-%d").strftime("%d%b%Y").upper()
|
||||||
|
except Exception:
|
||||||
|
return str(s)[:10]
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
client = MongoClient(MONGO_URI, serverSelectionTimeoutMS=8000)
|
||||||
|
db = client[DB]
|
||||||
|
client.admin.command("ping")
|
||||||
|
|
||||||
|
questions = list(db.sipiq_questions.find().sort("order", 1))
|
||||||
|
resp = list(db.sipiq_responses.find())
|
||||||
|
# seřadit centra: CZ -> SK, pak příjmení
|
||||||
|
resp.sort(key=lambda r: (COUNTRY_ABBR.get(r.get("site_country"), "ZZ"),
|
||||||
|
(r.get("pi_last_name") or "").lower()))
|
||||||
|
n = len(resp)
|
||||||
|
print(f"Otázek: {len(questions)} | center: {n}")
|
||||||
|
|
||||||
|
wb = Workbook()
|
||||||
|
ws = wb.active
|
||||||
|
ws.title = "SIPIQ přehled"
|
||||||
|
|
||||||
|
# --- hlavička center (2 řádky) ---
|
||||||
|
ws.cell(1, 1, "OTÁZKA \\ CENTRUM").font = Font(bold=True, color="FFFFFF")
|
||||||
|
ws.cell(2, 1, "").font = Font(bold=True, color="FFFFFF")
|
||||||
|
ws.merge_cells("A1:A2")
|
||||||
|
for j, r in enumerate(resp):
|
||||||
|
col = 2 + j
|
||||||
|
c1 = ws.cell(1, col, r.get("pi_last_name") or "?")
|
||||||
|
cc = COUNTRY_ABBR.get(r.get("site_country"), "?")
|
||||||
|
c2 = ws.cell(2, col, f"{r.get('site_name') or ''} [{cc}]")
|
||||||
|
for c in (c1, c2):
|
||||||
|
c.font = Font(bold=True, color="FFFFFF", size=9)
|
||||||
|
c.fill = C_HEAD
|
||||||
|
c.alignment = WRAP_CTR
|
||||||
|
c.border = BORDER
|
||||||
|
ws.cell(1, 1).fill = C_HEAD
|
||||||
|
ws.cell(1, 1).alignment = WRAP_CTR
|
||||||
|
|
||||||
|
row = 3
|
||||||
|
|
||||||
|
def ident_row(label, getter):
|
||||||
|
nonlocal row
|
||||||
|
a = ws.cell(row, 1, label)
|
||||||
|
a.font = Font(bold=True)
|
||||||
|
a.fill = C_IDENT
|
||||||
|
a.alignment = WRAP_TOP
|
||||||
|
a.border = BORDER
|
||||||
|
for j, r in enumerate(resp):
|
||||||
|
c = ws.cell(row, 2 + j, getter(r))
|
||||||
|
c.fill = C_IDENT
|
||||||
|
c.alignment = WRAP_TOP
|
||||||
|
c.border = BORDER
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
# --- IDENTITA ---
|
||||||
|
sec = ws.cell(row, 1, "■ IDENTITA CENTRA")
|
||||||
|
sec.font = Font(bold=True, color="FFFFFF")
|
||||||
|
sec.fill = C_SECTION
|
||||||
|
for j in range(n):
|
||||||
|
ws.cell(row, 2 + j).fill = C_SECTION
|
||||||
|
row += 1
|
||||||
|
ident_row("Země", lambda r: COUNTRY_ABBR.get(r.get("site_country"), r.get("site_country") or ""))
|
||||||
|
ident_row("Město", lambda r: r.get("site_city") or "")
|
||||||
|
ident_row("PI", lambda r: f"{r.get('pi_first_name') or ''} {r.get('pi_last_name') or ''}".strip())
|
||||||
|
ident_row("E-mail PI", lambda r: r.get("pi_email") or "")
|
||||||
|
ident_row("sdl_site_id", lambda r: r.get("sdl_site_id") or "")
|
||||||
|
ident_row("Datum vyplnění", lambda r: fmt_date((r.get("meta") or {}).get("recorded_date")))
|
||||||
|
|
||||||
|
# --- OTÁZKY po sekcích ---
|
||||||
|
cur_section = None
|
||||||
|
for q in questions:
|
||||||
|
section = q.get("section") or "Other"
|
||||||
|
if section in SKIP_SECTIONS:
|
||||||
|
continue
|
||||||
|
if section != cur_section:
|
||||||
|
cur_section = section
|
||||||
|
sc = ws.cell(row, 1, f"■ {section.upper()}")
|
||||||
|
sc.font = Font(bold=True, color="FFFFFF")
|
||||||
|
sc.fill = C_SECTION
|
||||||
|
sc.alignment = WRAP_TOP
|
||||||
|
sc.border = BORDER
|
||||||
|
for j in range(n):
|
||||||
|
cc = ws.cell(row, 2 + j)
|
||||||
|
cc.fill = C_SECTION
|
||||||
|
cc.border = BORDER
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
base = q["_id"]
|
||||||
|
items = q.get("items") or []
|
||||||
|
if not items:
|
||||||
|
_emit_answer_row(ws, row, f'{base} — {q.get("text") or ""}', base, resp, stem=False)
|
||||||
|
row += 1
|
||||||
|
else:
|
||||||
|
# stem řádek (text otázky) — bez hodnot
|
||||||
|
sc = ws.cell(row, 1, f'{base} — {q.get("text") or ""}')
|
||||||
|
sc.font = Font(bold=True)
|
||||||
|
sc.fill = C_STEM
|
||||||
|
sc.alignment = WRAP_TOP
|
||||||
|
sc.border = BORDER
|
||||||
|
for j in range(n):
|
||||||
|
cc = ws.cell(row, 2 + j)
|
||||||
|
cc.fill = C_STEM
|
||||||
|
cc.border = BORDER
|
||||||
|
row += 1
|
||||||
|
for it in items:
|
||||||
|
lbl = it.get("label") or "(odpověď)"
|
||||||
|
_emit_answer_row(ws, row, f" – {lbl}", it["key"], resp, stem=False)
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
# --- formátování ---
|
||||||
|
ws.freeze_panes = "B3"
|
||||||
|
ws.column_dimensions["A"].width = 58
|
||||||
|
for j in range(n):
|
||||||
|
ws.column_dimensions[get_column_letter(2 + j)].width = 20
|
||||||
|
ws.row_dimensions[1].height = 28
|
||||||
|
ws.row_dimensions[2].height = 40
|
||||||
|
|
||||||
|
os.makedirs(OUT_DIR, exist_ok=True)
|
||||||
|
ts = datetime.now().strftime("%Y%m%d_%H%M")
|
||||||
|
out = os.path.join(OUT_DIR, f"SIPIQ_prehled_center_{ts}.xlsx")
|
||||||
|
wb.save(out)
|
||||||
|
print(f"Uloženo: {out}")
|
||||||
|
client.close()
|
||||||
|
|
||||||
|
|
||||||
|
def _emit_answer_row(ws, row, label, key, resp, stem):
|
||||||
|
a = ws.cell(row, 1, label)
|
||||||
|
a.alignment = WRAP_TOP
|
||||||
|
a.border = BORDER
|
||||||
|
for j, r in enumerate(resp):
|
||||||
|
ans = (r.get("answers") or {})
|
||||||
|
supp = (r.get("answers_supplement") or {})
|
||||||
|
c = ws.cell(row, 2 + j)
|
||||||
|
c.alignment = WRAP_TOP
|
||||||
|
c.border = BORDER
|
||||||
|
if key in supp:
|
||||||
|
val = (supp[key] or {}).get("value")
|
||||||
|
c.value = val
|
||||||
|
c.fill = C_SUPP
|
||||||
|
c.font = Font(italic=True)
|
||||||
|
src = (supp[key] or {}).get("answer_source") or "doplněno"
|
||||||
|
c.comment = Comment(f"Doplněno mimo SIPIQ: {src}", "sipiq_report")
|
||||||
|
else:
|
||||||
|
val = ans.get(key)
|
||||||
|
c.value = val
|
||||||
|
if val == "Yes":
|
||||||
|
c.fill = C_YES
|
||||||
|
elif val == "No":
|
||||||
|
c.fill = C_NO
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# sipiq_report_v1.1 — přehledný Excel report SIPIQ (centra × otázky)
|
||||||
|
|
||||||
|
**Verze:** 1.1 · **Datum:** 2026-06-17 · **Studie:** 77242113UCO3002 (ICONIC / DAWN)
|
||||||
|
|
||||||
|
## Co dělá
|
||||||
|
Excel report: **otázky ve sloupci A** (v pořadí SIPIQ, seskupené dle sekcí), **centra ve sloupcích**
|
||||||
|
(B+). Buňka = odpověď centra na otázku → vyplněné SIPIQ všech center vedle sebe.
|
||||||
|
Čte z MongoDB `feasibility`: `sipiq_questions` (slovník+pořadí+sekce+items) + `sipiq_responses`
|
||||||
|
(answers + `answers_supplement`).
|
||||||
|
|
||||||
|
## Layout
|
||||||
|
- Hlavička 2 řádky: **příjmení PI** + **název centra [země]**; centra seřazena CZ→SK, pak příjmení.
|
||||||
|
- Blok **IDENTITA** (země/město/PI/e-mail/sdl_site_id/datum vyplnění).
|
||||||
|
- Sekce SIPIQ jako barevné mezititulky; jednotlivé otázky a položky matic v řádcích.
|
||||||
|
- **Yes=zelená, No=červená**; doplněné odpovědi (`answers_supplement`) = oranžová + kurzíva + komentář se zdrojem.
|
||||||
|
- **Freeze panes B3** (sloupec A + hlavička), ohraničení, šířky (A=58, centra=20).
|
||||||
|
|
||||||
|
## Změny proti v1.0
|
||||||
|
- **Contact Information SBALENA** do 5 řádků (má koordinátora? / koordinátor jméno+e-mail /
|
||||||
|
CDA vyjednává / dotazník vyplnil) místo ~44; PI je už v IDENTITA. → `COMPACT_CONTACT`.
|
||||||
|
- **Q54** (doporučení dalších center) = JEDEN zřetězený řádek místo rozbitých „PI Name: - No Name" řádků.
|
||||||
|
- Confidentiality Statement (Q14–Q21, právní souhlasy) se vynechává (`SKIP_SECTIONS`).
|
||||||
|
- Řádků: 179 → 126.
|
||||||
|
|
||||||
|
## Použití
|
||||||
|
```
|
||||||
|
.venv\Scripts\python.exe Feasibility\sipiq_report_v1.1.py
|
||||||
|
```
|
||||||
|
Výstup: `u:\Dropbox\!!!Days\Downloads Z230\SIPIQ_prehled_center_<ts>.xlsx`.
|
||||||
|
Bez argumentů — vždy aktuální stav DB (vč. doplněných odpovědí). pymongo + openpyxl.
|
||||||
|
|
||||||
|
## Stav 17JUN2026
|
||||||
|
56 otázek × 15 center, 126 řádků. v1.0 v `Feasibility\TRASH`.
|
||||||
@@ -0,0 +1,220 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
sipiq_report_v1.1.py
|
||||||
|
====================
|
||||||
|
Verze: 1.1
|
||||||
|
Datum: 2026-06-17
|
||||||
|
Autor: Claude Code (pro MUDr. Vladimíra Buzalku)
|
||||||
|
|
||||||
|
Změny proti v1.0
|
||||||
|
----------------
|
||||||
|
- Contact Information SBALENA do kompaktního bloku (koordinátor jméno+e-mail, má koordinátora?,
|
||||||
|
kdo vyjednává CDA, kdo vyplnil dotazník) místo ~44 řádků; PI je už v bloku IDENTITA.
|
||||||
|
- Q54 (doporučení dalších center) sloučeno do JEDNOHO řádku (zřetězené neprázdné hodnoty)
|
||||||
|
místo rozbitých „PI Name: - No Name - -" řádků. Stará v1.0 v TRASH.
|
||||||
|
|
||||||
|
Popis
|
||||||
|
-----
|
||||||
|
Přehledný Excel report SIPIQ: OTÁZKY ve sloupci A (pořadí + sekce), CENTRA ve sloupcích (B+).
|
||||||
|
Zdroj MongoDB feasibility (sipiq_questions + sipiq_responses + answers_supplement).
|
||||||
|
Yes=zelená, No=červená; doplněné odpovědi oranžová+kurzíva+komentář; freeze panes B3.
|
||||||
|
|
||||||
|
Výstup: u:\\Dropbox\\!!!Days\\Downloads Z230\\SIPIQ_prehled_center_<ts>.xlsx
|
||||||
|
Závislosti: pymongo, openpyxl (.venv). Mongo 192.168.1.76:27017, bez auth.
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from pymongo import MongoClient
|
||||||
|
from openpyxl import Workbook
|
||||||
|
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
|
||||||
|
from openpyxl.comments import Comment
|
||||||
|
from openpyxl.utils import get_column_letter
|
||||||
|
|
||||||
|
MONGO_URI = "mongodb://192.168.1.76:27017"
|
||||||
|
DB = "feasibility"
|
||||||
|
OUT_DIR = r"u:\Dropbox\!!!Days\Downloads Z230"
|
||||||
|
SKIP_SECTIONS = {"Confidentiality Statement"}
|
||||||
|
COUNTRY_ABBR = {"Czech Republic": "CZ", "Slovakia": "SK"}
|
||||||
|
|
||||||
|
# kompaktní Contact Information: (label, [answer keys], spojovník)
|
||||||
|
COMPACT_CONTACT = [
|
||||||
|
("Má centrum koordinátora?", ["Q7"], ""),
|
||||||
|
("Koordinátor (jméno)", ["Q8_2", "Q8_1"], " "),
|
||||||
|
("Koordinátor (e-mail)", ["Q8_8"], ""),
|
||||||
|
("CDA vyjednává", ["Q10"], ""),
|
||||||
|
("Dotazník vyplnil", ["Q12"], ""),
|
||||||
|
]
|
||||||
|
|
||||||
|
C_SECTION = PatternFill("solid", fgColor="1F4E78")
|
||||||
|
C_STEM = PatternFill("solid", fgColor="DDEBF7")
|
||||||
|
C_IDENT = PatternFill("solid", fgColor="F2F2F2")
|
||||||
|
C_HEAD = PatternFill("solid", fgColor="305496")
|
||||||
|
C_YES = PatternFill("solid", fgColor="C6EFCE")
|
||||||
|
C_NO = PatternFill("solid", fgColor="FFC7CE")
|
||||||
|
C_SUPP = PatternFill("solid", fgColor="FFE699")
|
||||||
|
THIN = Side(style="thin", color="BFBFBF")
|
||||||
|
BORDER = Border(left=THIN, right=THIN, top=THIN, bottom=THIN)
|
||||||
|
WRAP_TOP = Alignment(wrap_text=True, vertical="top")
|
||||||
|
WRAP_CTR = Alignment(wrap_text=True, vertical="center", horizontal="center")
|
||||||
|
|
||||||
|
|
||||||
|
def fmt_date(s):
|
||||||
|
if not s:
|
||||||
|
return ""
|
||||||
|
try:
|
||||||
|
return datetime.strptime(str(s)[:10], "%Y-%m-%d").strftime("%d%b%Y").upper()
|
||||||
|
except Exception:
|
||||||
|
return str(s)[:10]
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
client = MongoClient(MONGO_URI, serverSelectionTimeoutMS=8000)
|
||||||
|
db = client[DB]
|
||||||
|
client.admin.command("ping")
|
||||||
|
|
||||||
|
questions = list(db.sipiq_questions.find().sort("order", 1))
|
||||||
|
resp = list(db.sipiq_responses.find())
|
||||||
|
resp.sort(key=lambda r: (COUNTRY_ABBR.get(r.get("site_country"), "ZZ"),
|
||||||
|
(r.get("pi_last_name") or "").lower()))
|
||||||
|
n = len(resp)
|
||||||
|
print(f"Otázek: {len(questions)} | center: {n}")
|
||||||
|
|
||||||
|
wb = Workbook()
|
||||||
|
ws = wb.active
|
||||||
|
ws.title = "SIPIQ přehled"
|
||||||
|
state = {"row": 1}
|
||||||
|
|
||||||
|
# --- hlavička center ---
|
||||||
|
h = ws.cell(1, 1, "OTÁZKA \\ CENTRUM")
|
||||||
|
h.font = Font(bold=True, color="FFFFFF"); h.fill = C_HEAD; h.alignment = WRAP_CTR
|
||||||
|
ws.cell(2, 1, "")
|
||||||
|
ws.merge_cells("A1:A2")
|
||||||
|
for j, r in enumerate(resp):
|
||||||
|
cc = COUNTRY_ABBR.get(r.get("site_country"), "?")
|
||||||
|
for rr, val in ((1, r.get("pi_last_name") or "?"), (2, f"{r.get('site_name') or ''} [{cc}]")):
|
||||||
|
c = ws.cell(rr, 2 + j, val)
|
||||||
|
c.font = Font(bold=True, color="FFFFFF", size=9); c.fill = C_HEAD
|
||||||
|
c.alignment = WRAP_CTR; c.border = BORDER
|
||||||
|
state["row"] = 3
|
||||||
|
|
||||||
|
def section_header(title):
|
||||||
|
row = state["row"]
|
||||||
|
sc = ws.cell(row, 1, f"■ {title.upper()}")
|
||||||
|
sc.font = Font(bold=True, color="FFFFFF"); sc.fill = C_SECTION
|
||||||
|
sc.alignment = WRAP_TOP; sc.border = BORDER
|
||||||
|
for j in range(n):
|
||||||
|
c = ws.cell(row, 2 + j); c.fill = C_SECTION; c.border = BORDER
|
||||||
|
state["row"] += 1
|
||||||
|
|
||||||
|
def combo_row(label, keys, sep, fill=None):
|
||||||
|
row = state["row"]
|
||||||
|
a = ws.cell(row, 1, label)
|
||||||
|
a.alignment = WRAP_TOP; a.border = BORDER
|
||||||
|
if fill:
|
||||||
|
a.fill = fill; a.font = Font(bold=True)
|
||||||
|
for j, r in enumerate(resp):
|
||||||
|
ans = r.get("answers") or {}
|
||||||
|
supp = r.get("answers_supplement") or {}
|
||||||
|
c = ws.cell(row, 2 + j); c.alignment = WRAP_TOP; c.border = BORDER
|
||||||
|
if fill:
|
||||||
|
c.fill = fill
|
||||||
|
vals, supp_hit, supp_src = [], False, None
|
||||||
|
for k in keys:
|
||||||
|
if k in supp:
|
||||||
|
vals.append((supp[k] or {}).get("value")); supp_hit = True
|
||||||
|
supp_src = (supp[k] or {}).get("answer_source") or "doplněno"
|
||||||
|
elif ans.get(k):
|
||||||
|
vals.append(ans.get(k))
|
||||||
|
val = sep.join(x for x in vals if x)
|
||||||
|
c.value = val or None
|
||||||
|
if supp_hit:
|
||||||
|
c.fill = C_SUPP; c.font = Font(italic=True)
|
||||||
|
c.comment = Comment(f"Doplněno mimo SIPIQ: {supp_src}", "sipiq_report")
|
||||||
|
elif val == "Yes":
|
||||||
|
c.fill = C_YES
|
||||||
|
elif val == "No":
|
||||||
|
c.fill = C_NO
|
||||||
|
state["row"] += 1
|
||||||
|
|
||||||
|
# --- IDENTITA ---
|
||||||
|
section_header("Identita centra")
|
||||||
|
for label, getter in [
|
||||||
|
("Země", lambda r: COUNTRY_ABBR.get(r.get("site_country"), r.get("site_country") or "")),
|
||||||
|
("Město", lambda r: r.get("site_city") or ""),
|
||||||
|
("PI", lambda r: f"{r.get('pi_first_name') or ''} {r.get('pi_last_name') or ''}".strip()),
|
||||||
|
("E-mail PI", lambda r: r.get("pi_email") or ""),
|
||||||
|
("sdl_site_id", lambda r: r.get("sdl_site_id") or ""),
|
||||||
|
("Datum vyplnění", lambda r: fmt_date((r.get("meta") or {}).get("recorded_date"))),
|
||||||
|
]:
|
||||||
|
row = state["row"]
|
||||||
|
a = ws.cell(row, 1, label); a.font = Font(bold=True); a.fill = C_IDENT
|
||||||
|
a.alignment = WRAP_TOP; a.border = BORDER
|
||||||
|
for j, r in enumerate(resp):
|
||||||
|
c = ws.cell(row, 2 + j, getter(r)); c.fill = C_IDENT
|
||||||
|
c.alignment = WRAP_TOP; c.border = BORDER
|
||||||
|
state["row"] += 1
|
||||||
|
|
||||||
|
# --- OTÁZKY po sekcích ---
|
||||||
|
cur_section = None
|
||||||
|
contact_done = False
|
||||||
|
for q in questions:
|
||||||
|
section = q.get("section") or "Other"
|
||||||
|
if section in SKIP_SECTIONS:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if section == "Contact Information":
|
||||||
|
if contact_done:
|
||||||
|
continue
|
||||||
|
section_header("Kontakty (koordinátor / CDA / vyplnil)")
|
||||||
|
for label, keys, sep in COMPACT_CONTACT:
|
||||||
|
combo_row(label, keys, sep)
|
||||||
|
contact_done = True
|
||||||
|
cur_section = section
|
||||||
|
continue
|
||||||
|
|
||||||
|
if section != cur_section:
|
||||||
|
section_header(section)
|
||||||
|
cur_section = section
|
||||||
|
|
||||||
|
base = q["_id"]
|
||||||
|
# Q54 = doporučení dalších center -> jeden zřetězený řádek
|
||||||
|
if base == "Q54":
|
||||||
|
keys = [it["key"] for it in (q.get("items") or [])] or [base]
|
||||||
|
combo_row("Q54 — Doporučená další centra/investigátoři", keys, "; ")
|
||||||
|
continue
|
||||||
|
|
||||||
|
items = q.get("items") or []
|
||||||
|
if not items:
|
||||||
|
combo_row(f'{base} — {q.get("text") or ""}', [base], "")
|
||||||
|
else:
|
||||||
|
row = state["row"]
|
||||||
|
sc = ws.cell(row, 1, f'{base} — {q.get("text") or ""}')
|
||||||
|
sc.font = Font(bold=True); sc.fill = C_STEM
|
||||||
|
sc.alignment = WRAP_TOP; sc.border = BORDER
|
||||||
|
for j in range(n):
|
||||||
|
c = ws.cell(row, 2 + j); c.fill = C_STEM; c.border = BORDER
|
||||||
|
state["row"] += 1
|
||||||
|
for it in items:
|
||||||
|
lbl = it.get("label") or "(odpověď)"
|
||||||
|
combo_row(f" – {lbl}", [it["key"]], "")
|
||||||
|
|
||||||
|
# --- formátování ---
|
||||||
|
ws.freeze_panes = "B3"
|
||||||
|
ws.column_dimensions["A"].width = 58
|
||||||
|
for j in range(n):
|
||||||
|
ws.column_dimensions[get_column_letter(2 + j)].width = 20
|
||||||
|
ws.row_dimensions[1].height = 28
|
||||||
|
ws.row_dimensions[2].height = 40
|
||||||
|
|
||||||
|
os.makedirs(OUT_DIR, exist_ok=True)
|
||||||
|
ts = datetime.now().strftime("%Y%m%d_%H%M")
|
||||||
|
out = os.path.join(OUT_DIR, f"SIPIQ_prehled_center_{ts}.xlsx")
|
||||||
|
wb.save(out)
|
||||||
|
print(f"Uloženo: {out}")
|
||||||
|
client.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
"""_list_cats_v0.py — DOČASNÉ: vypíše nedávné e-maily mailboxu (argv[1]) + jejich kategorie."""
|
||||||
|
import sys
|
||||||
|
sys.stdout.reconfigure(encoding="utf-8")
|
||||||
|
import msal, requests
|
||||||
|
|
||||||
|
TENANT_ID="7d269944-37a4-43a1-8140-c7517dc426e9"
|
||||||
|
CLIENT_ID="4b222bfd-78c9-4239-a53f-43006b3ed07f"
|
||||||
|
CLIENT_SECRET="Txg8Q~MjhocuopxsJyJBhPmDfMxZ2r5WpTFj1dfk"
|
||||||
|
AUTHORITY=f"https://login.microsoftonline.com/{TENANT_ID}"
|
||||||
|
mailbox = sys.argv[1] if len(sys.argv) > 1 else "vladimir.buzalka@buzalka.cz"
|
||||||
|
BASE=f"https://graph.microsoft.com/v1.0/users/{mailbox}"
|
||||||
|
|
||||||
|
app=msal.ConfidentialClientApplication(CLIENT_ID,authority=AUTHORITY,client_credential=CLIENT_SECRET)
|
||||||
|
tok=app.acquire_token_for_client(scopes=["https://graph.microsoft.com/.default"])
|
||||||
|
H={"Authorization":f"Bearer {tok['access_token']}"}
|
||||||
|
|
||||||
|
# 1) nejnovějších 20 e-mailů (napříč schránkou)
|
||||||
|
r=requests.get(f"{BASE}/messages",headers=H,params={
|
||||||
|
"$top":20,"$orderby":"receivedDateTime desc",
|
||||||
|
"$select":"subject,from,receivedDateTime,categories"},timeout=30)
|
||||||
|
print(f"[{mailbox}] nejnovějších {len(r.json().get('value',[]))}:")
|
||||||
|
for m in r.json().get("value",[]):
|
||||||
|
cats=m.get("categories") or []
|
||||||
|
frm=m.get("from",{}).get("emailAddress",{}).get("address","")
|
||||||
|
flag=" <<< KATEGORIE" if cats else ""
|
||||||
|
print(f" {m.get('receivedDateTime','')[:10]} | {frm:28.28} | {str(cats):28} | {m.get('subject','')[:50]}{flag}")
|
||||||
|
|
||||||
|
# 2) cokoliv s kategorií obsahující KPC/Claude (zkus víc názvů)
|
||||||
|
print("\nHledám kategorie ~ KPC/Claude:")
|
||||||
|
for cat in ["ForKPCGeneration","KPCzpracovánoClaudem","KPC","ForKPC","Claude","Pro Tebe","ProClaude"]:
|
||||||
|
rr=requests.get(f"{BASE}/messages",headers=H,params={
|
||||||
|
"$filter":f"categories/any(c:c eq '{cat}')","$select":"subject","$top":5},timeout=30)
|
||||||
|
n=len(rr.json().get("value",[])) if rr.ok else f"ERR{rr.status_code}"
|
||||||
|
print(f" '{cat}': {n}")
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
"""_process_clk_v0.py — DOČASNÉ: zpracuje manželčin ČLK příspěvek (mini-agent).
|
||||||
|
Telegram potvrzení → ano → KPC (plátce ordinace) → Dropbox → přehození kategorie.
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
from datetime import date
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
sys.stdout.reconfigure(encoding="utf-8")
|
||||||
|
sys.path.insert(0, r"U:\ordinaceprojekt")
|
||||||
|
import dropbox, msal, requests
|
||||||
|
from Knihovny.telegram_notify import zeptej_se_telegram, posli_telegram
|
||||||
|
|
||||||
|
# ── data platby ───────────────────────────────────────────────────────────
|
||||||
|
today = date.today()
|
||||||
|
ddmmyy = today.strftime("%d%m%y")
|
||||||
|
fdate = today.strftime("%Y-%m-%d")
|
||||||
|
|
||||||
|
DEBIT_NUM = "2800046620" # ordinace (plátce)
|
||||||
|
CREDIT_NUM = "244484339" # ČLK, bez předčíslí → holé číslo
|
||||||
|
CREDIT_BANK = "0800"
|
||||||
|
VS = "5141811171"; KS = "0000"
|
||||||
|
AMOUNT = 400000 # 4000,00 Kč v haléřích
|
||||||
|
AV = "clensky prispevek OS CLK 2026 Buzalkova"
|
||||||
|
SUMMARY = "ČLK příspěvek Buzalková 2026"
|
||||||
|
|
||||||
|
# e-mail ke zpracování (manželčina schránka)
|
||||||
|
MAILBOX = "michaela.buzalkova@buzalka.cz"
|
||||||
|
MSG_ID = "AAMkADFkMzE3NDlmLTg3NjQtNDQwNy05Nzc2LWJjYWJkNmNjMjIxZABGAAAAAABEaZtIoWAPQbVWi-ATA8XVBwB8gYSLibM0Q7crRJXlxBlcAAAAAAEMAAB8gYSLibM0Q7crRJXlxBlcAALrbxJcAAA="
|
||||||
|
|
||||||
|
# ── KPC ───────────────────────────────────────────────────────────────────
|
||||||
|
CRLF = "\r\n"
|
||||||
|
lines = [
|
||||||
|
"UHL1" + ddmmyy + " " * 20 + "0" * 28,
|
||||||
|
"1 1501 000000 2010",
|
||||||
|
f"2 000000-{DEBIT_NUM} {str(AMOUNT).zfill(14)} {ddmmyy}",
|
||||||
|
f"{CREDIT_NUM} {str(AMOUNT).zfill(12)} {VS} {CREDIT_BANK}{KS} AV:{AV}",
|
||||||
|
"3 +",
|
||||||
|
"5 +",
|
||||||
|
]
|
||||||
|
content = CRLF.join(lines) + CRLF
|
||||||
|
data = content.encode("ascii")
|
||||||
|
fname = f"{fdate} KPC k platbě [{SUMMARY}].kpc"
|
||||||
|
|
||||||
|
print("=== NÁHLED KPC ===")
|
||||||
|
print(content.replace("\r\n", "\\r\\n\n"), end="")
|
||||||
|
print(f"=== {fname} ({len(data)} B) ===\n")
|
||||||
|
|
||||||
|
# ── Telegram potvrzení ────────────────────────────────────────────────────
|
||||||
|
msg = (
|
||||||
|
"💳 Návrh platby (KPC agent)\n\n"
|
||||||
|
"ČLK členský příspěvek 2026 — MUDr. Buzalková\n"
|
||||||
|
"4 000 Kč → 244484339/0800, VS 5141811171\n"
|
||||||
|
"Z účtu: ordinace 2800046620\n"
|
||||||
|
"Splatnost: dnes (17.06.2026)\n\n"
|
||||||
|
"FIO: 2026 zatím nezaplaceno (poslední 2025-04-09) ✅\n\n"
|
||||||
|
"Vytvořit KPC a nahrát do Dropboxu? Odpověz: ano / ne"
|
||||||
|
)
|
||||||
|
odp = zeptej_se_telegram(msg, timeout=150)
|
||||||
|
print("TELEGRAM odpověď:", repr(odp))
|
||||||
|
|
||||||
|
if not odp or odp.strip().lower() not in ("ano", "ano.", "yes", "ok"):
|
||||||
|
print(">> NEPOTVRZENO (ano nepřišlo) — nic nevytvářím.")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
# ── lokální archiv + Dropbox upload ───────────────────────────────────────
|
||||||
|
out_dir = Path(__file__).parent / "output"; out_dir.mkdir(exist_ok=True)
|
||||||
|
(out_dir / fname).write_bytes(data)
|
||||||
|
print("lokální kopie:", out_dir / fname)
|
||||||
|
|
||||||
|
env = {}
|
||||||
|
for line in Path(r"U:\PythonProject\Janssen\EmailsImport\.env").read_text(encoding="utf-8").splitlines():
|
||||||
|
line = line.strip()
|
||||||
|
if "=" in line and not line.startswith("#"):
|
||||||
|
k, v = line.split("=", 1); env[k.strip()] = v.strip()
|
||||||
|
dbx = dropbox.Dropbox(app_key=env["DROPBOX_APP_KEY"], app_secret=env["DROPBOX_APP_SECRET"],
|
||||||
|
oauth2_refresh_token=env["DROPBOX_APP_REFRESH_TOKEN"])
|
||||||
|
res = dbx.files_upload(data, f"/!!!Days/Downloads Z230/{fname}",
|
||||||
|
mode=dropbox.files.WriteMode.add, autorename=True)
|
||||||
|
print("NAHRÁNO →", res.path_display)
|
||||||
|
|
||||||
|
# ── přehození kategorie na manželčině e-mailu ─────────────────────────────
|
||||||
|
TENANT="7d269944-37a4-43a1-8140-c7517dc426e9"; CID="4b222bfd-78c9-4239-a53f-43006b3ed07f"
|
||||||
|
SECRET="Txg8Q~MjhocuopxsJyJBhPmDfMxZ2r5WpTFj1dfk"
|
||||||
|
app=msal.ConfidentialClientApplication(CID,authority=f"https://login.microsoftonline.com/{TENANT}",client_credential=SECRET)
|
||||||
|
tok=app.acquire_token_for_client(scopes=["https://graph.microsoft.com/.default"])
|
||||||
|
H={"Authorization":f"Bearer {tok['access_token']}","Content-Type":"application/json"}
|
||||||
|
BASE=f"https://graph.microsoft.com/v1.0/users/{MAILBOX}"
|
||||||
|
rp=requests.patch(f"{BASE}/messages/{MSG_ID}",headers=H,json={"categories":["KPCzpracovánoClaudem"]},timeout=30)
|
||||||
|
print("PATCH kategorie:", rp.status_code, "" if rp.ok else rp.text[:300])
|
||||||
|
|
||||||
|
posli_telegram(f"✅ KPC vytvořeno a nahráno:\n{fname}\n4 000 Kč → 244484339/0800 (ČLK Buzalková), z ordinace.\nV bankingu stačí podepsat.")
|
||||||
|
print(">> HOTOVO.")
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
"""_read_v0.py — DOČASNÉ: přečte ForKPCGeneration e-maily z mailboxu (argv[1])."""
|
||||||
|
import sys
|
||||||
|
sys.stdout.reconfigure(encoding="utf-8")
|
||||||
|
import msal, requests
|
||||||
|
|
||||||
|
TENANT_ID = "7d269944-37a4-43a1-8140-c7517dc426e9"
|
||||||
|
CLIENT_ID = "4b222bfd-78c9-4239-a53f-43006b3ed07f"
|
||||||
|
CLIENT_SECRET = "Txg8Q~MjhocuopxsJyJBhPmDfMxZ2r5WpTFj1dfk"
|
||||||
|
AUTHORITY = f"https://login.microsoftonline.com/{TENANT_ID}"
|
||||||
|
SCOPE = ["https://graph.microsoft.com/.default"]
|
||||||
|
CATEGORY = "ForKPCGeneration"
|
||||||
|
|
||||||
|
mailbox = sys.argv[1] if len(sys.argv) > 1 else "vladimir.buzalka@buzalka.cz"
|
||||||
|
CATEGORY = sys.argv[2] if len(sys.argv) > 2 else CATEGORY
|
||||||
|
BASE = f"https://graph.microsoft.com/v1.0/users/{mailbox}"
|
||||||
|
|
||||||
|
app = msal.ConfidentialClientApplication(CLIENT_ID, authority=AUTHORITY, client_credential=CLIENT_SECRET)
|
||||||
|
tok = app.acquire_token_for_client(scopes=SCOPE)
|
||||||
|
assert "access_token" in tok, tok
|
||||||
|
H = {"Authorization": f"Bearer {tok['access_token']}"}
|
||||||
|
|
||||||
|
params = {
|
||||||
|
"$filter": f"categories/any(c:c eq '{CATEGORY}')",
|
||||||
|
"$select": "id,subject,from,receivedDateTime,categories,hasAttachments,body,bodyPreview",
|
||||||
|
"$top": 25,
|
||||||
|
}
|
||||||
|
r = requests.get(f"{BASE}/messages", headers=H, params=params, timeout=30)
|
||||||
|
if not r.ok:
|
||||||
|
print(f"CHYBA [{r.status_code}] pro {mailbox}: {r.text[:400]}")
|
||||||
|
sys.exit(1)
|
||||||
|
msgs = r.json().get("value", [])
|
||||||
|
print(f"[{mailbox}] e-mailů s kategorií '{CATEGORY}': {len(msgs)}\n")
|
||||||
|
|
||||||
|
for i, m in enumerate(msgs, 1):
|
||||||
|
frm = m.get("from", {}).get("emailAddress", {})
|
||||||
|
print("=" * 78)
|
||||||
|
print(f"[{i}] {m.get('subject')}")
|
||||||
|
print(f" od: {frm.get('name')} <{frm.get('address')}>")
|
||||||
|
print(f" datum: {m.get('receivedDateTime')}")
|
||||||
|
print(f" přílohy: {m.get('hasAttachments')}")
|
||||||
|
print(f" id: {m.get('id')}")
|
||||||
|
body = m.get("body", {})
|
||||||
|
print(f" --- TĚLO ({body.get('contentType')}) ---")
|
||||||
|
print(body.get("content", ""))
|
||||||
|
if m.get("hasAttachments"):
|
||||||
|
ra = requests.get(f"{BASE}/messages/{m['id']}/attachments",
|
||||||
|
headers=H, params={"$select": "id,name,size,contentType,isInline"}, timeout=30)
|
||||||
|
if ra.ok:
|
||||||
|
print(" --- PŘÍLOHY ---")
|
||||||
|
for a in ra.json().get("value", []):
|
||||||
|
print(f" • {a.get('name')} ({a.get('size')} B, {a.get('contentType')}, inline={a.get('isInline')})")
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
UHL1170626 0000000000000000000000000000
|
||||||
|
1 1501 000000 2010
|
||||||
|
2 000000-2800046620 00000000400000 170626
|
||||||
|
244484339 000000400000 5141811171 08000000 AV:clensky prispevek OS CLK 2026 Buzalkova
|
||||||
|
3 +
|
||||||
|
5 +
|
||||||
Reference in New Issue
Block a user