diff --git a/services/presentation/app/main.py b/services/presentation/app/main.py index b77fbc0..88c821e 100644 --- a/services/presentation/app/main.py +++ b/services/presentation/app/main.py @@ -279,6 +279,18 @@ def interpret_run( try: iso_utc, label = _build_utc(date, time, tz_offset) ctx["moment"] = label + if action == "export": + # Tabela robocza do Excela (DAN-23/PRE-10) — pobranie pliku, nie strona. + from fastapi.responses import Response + + from app.report_export import report_to_xlsx + + report = logic.report(when_utc_iso=iso_utc, lat=lat, lon=lon, group=group) + return Response( + content=report_to_xlsx(report), + media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + headers={"Content-Disposition": 'attachment; filename="interpretacje.xlsx"'}, + ) if action == "prompt": ctx["prompt_result"] = logic.prompt( profile="natal", when_utc_iso=iso_utc, lat=lat, lon=lon, budget=prompt_budget, diff --git a/services/presentation/app/report_export.py b/services/presentation/app/report_export.py new file mode 100644 index 0000000..c5067a6 --- /dev/null +++ b/services/presentation/app/report_export.py @@ -0,0 +1,56 @@ +"""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() diff --git a/services/presentation/app/templates/interpret.html b/services/presentation/app/templates/interpret.html index 07506b6..f3772ca 100644 --- a/services/presentation/app/templates/interpret.html +++ b/services/presentation/app/templates/interpret.html @@ -40,6 +40,7 @@