"""Eksport raportu interpretacji do Excela — „tabela robocza" (DAN-23 / PRE-10). Raport z logiki jest zagnieżdżony: obiekt → fasety → próbki (dopasowania z bazy). Astrolog chce z tym PRACOWAĆ w Excelu: filtrować, sortować, zaznaczać, usuwać — więc spłaszczamy go do JEDNEJ płaskiej tabeli (wiersz = jedno dopasowanie) z auto-filtrem i zamrożonym nagłówkiem. Bez wykresów i ozdób — to materiał roboczy. Świadomie budujemy plik TU, w prezentacji (jak PDF idzie przez usługę render): warstwa danych/logiki liczy, prezentacja formatuje wyjście. """ from __future__ import annotations import io from openpyxl import Workbook from openpyxl.styles import Alignment, Font HEADERS = ["Obiekt", "Faseta", "Typ", "Token", "Sygnifikator", "Rozwinięcie", "Opis / efekt"] _WIDTHS = [16, 24, 10, 20, 22, 28, 72] # „Opis" najszerszy — tam jest treść def _rows(report: dict): """Spłaszcza obiekt→fasety→próbki na wiersze płaskiej tabeli.""" for o in report.get("objects") or []: name = o.get("name") or "" for f in o.get("facets") or []: label, typ, token = f.get("label") or "", f.get("type") or "", f.get("token") or "" for s in (f.get("samples") or []): yield [name, label, typ, token, s.get("significator") or "", s.get("expanded") or "", s.get("effect") or ""] def report_to_xlsx(report: dict) -> bytes: """Raport (dict z /chart/report) → bajty pliku .xlsx z jedną tabelą roboczą.""" wb = Workbook() ws = wb.active ws.title = "Interpretacje" ws.append(HEADERS) for c in ws[1]: c.font = Font(bold=True) for row in _rows(report): ws.append(row) # Auto-filtr + zamrożony nagłówek = filtrowanie/sortowanie od razu w Excelu (PRE-10). ws.auto_filter.ref = f"A1:{chr(64 + len(HEADERS))}{ws.max_row}" ws.freeze_panes = "A2" for i, w in enumerate(_WIDTHS, start=1): ws.column_dimensions[chr(64 + i)].width = w for row in ws.iter_rows(min_row=2): # opis bywa długi — zawijamy row[6].alignment = Alignment(wrap_text=True, vertical="top") buf = io.BytesIO() wb.save(buf) return buf.getvalue()