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