fix(prezentacja): podsumowanie bierze WSZYSTKIE policzone opcje (stacje, tabele, domy)
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 10m30s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 9m28s
Testy / Build obrazu silnika B (swisseph) (pull_request) Successful in 15s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 10s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 11m5s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m31s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 15s
Testy / Kontrola składni wszystkich warstw (push) Successful in 10s
build / build (push) Successful in 2m49s
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 10m30s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 9m28s
Testy / Build obrazu silnika B (swisseph) (pull_request) Successful in 15s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 10s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 11m5s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m31s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 15s
Testy / Kontrola składni wszystkich warstw (push) Successful in 10s
build / build (push) Successful in 2m49s
Regresja: „Skompiluj" przeliczało horoskop od nowa z OKROJONYM zestawem opcji (tylko system domów + zodiak), więc jeśli przy horoskopie policzyłeś stacje, tabele (żywioły, faza Księżyca…) albo porównanie domów — w podsumowaniu ich NIE było. Zamiast pokazać to, co policzono, liczyło uboższą wersję. Zostaje czysty przelicz (bez trzymania dużego wyniku w przeglądarce), ale z TYMI SAMYMI opcjami co przy horoskopie: - `formsync`: synchronizuje między zakładkami też opcje — checkboxy (stacje, tabele) i wielo-checkbox (porównanie domów). Wcześniej umiał tylko `.value`. - „Skompiluj" (formularz): dostaje te same opcje; `compile_build` i `compile_pdf` przekazują je do logiki (stations/tables/house_systems). `compile.js` wysyła je w payloadzie PDF-a. - Wspólny plik `_result_tables.html`: podsumowanie renderuje DOKŁADNIE te same tabele co „Horoskop" (porównanie domów, stacje, aspekty, paralele, antyscja, żywioły/faza/godziny, Lots…). Każda sekcja pokazuje się tylko, gdy jej dane są w wyniku — opcja niepoliczona → sekcji nie ma (zgodnie z prośbą). (TODO: przełączyć też chart.html na ten include, by widoki nie mogły się rozjechać — na razie zgodność pilnowana ręcznie, jest komentarz w pliku.) Weryfikacja: render wspólnego pliku na bogatym wyniku pokazuje wszystkie sekcje. Testy: +8 (opcje niesione w /compile i /compile/pdf, sync checkboxów/multi, wspólny include). Prezentacja 202. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit was merged in pull request #46.
This commit is contained in:
@@ -128,24 +128,32 @@ def compile_build(
|
|||||||
lat: float = Form(0.0),
|
lat: float = Form(0.0),
|
||||||
lon: float = Form(0.0),
|
lon: float = Form(0.0),
|
||||||
house_system: str = Form("whole_sign"),
|
house_system: str = Form("whole_sign"),
|
||||||
|
house_systems: list[str] = Form([]), # PRE-05 — porównanie domów w podsumowaniu
|
||||||
|
stations: bool = Form(False), # LOG-03 — stacje też w podsumowaniu
|
||||||
zodiac: str = Form("tropical"),
|
zodiac: str = Form("tropical"),
|
||||||
|
tables: bool = Form(False), # LOG-23 — żywioły/faza/godziny w podsumowaniu
|
||||||
):
|
):
|
||||||
"""Składa raport: horoskop liczymy TU NA NOWO, a części od AI (interpretacja
|
"""Składa raport: horoskop liczymy TU NA NOWO, a części od AI (interpretacja
|
||||||
natalna i predykcje okresowe) dokłada przeglądarka z magazynu (PRE-22/23).
|
natalna i predykcje okresowe) dokłada przeglądarka z magazynu (PRE-22/23).
|
||||||
|
|
||||||
Dlaczego horoskop liczymy ponownie, zamiast go zapamiętywać: to czysta funkcja
|
Horoskop liczymy ponownie (czysta funkcja wejścia — tanio powtórzyć, bez
|
||||||
danych wejściowych — tanio ją powtórzyć, a odpada trzymanie w przeglądarce
|
trzymania dużego wyniku w przeglądarce), ale z TYMI SAMYMI opcjami, które
|
||||||
dużego wyniku, który mógłby się rozjechać z aktualnym formularzem.
|
wybrano przy horoskopie (stacje, tabele, porównanie domów) — inaczej
|
||||||
|
podsumowanie miałoby braki względem tego, co policzono. Opcje wędrują między
|
||||||
|
zakładkami przez formsync.
|
||||||
"""
|
"""
|
||||||
form = {"person": person, "date": date, "time": time, "tz_offset": tz_offset,
|
form = {"person": person, "date": date, "time": time, "tz_offset": tz_offset,
|
||||||
"lat": lat, "lon": lon, "house_system": house_system, "zodiac": zodiac}
|
"lat": lat, "lon": lon, "house_system": house_system,
|
||||||
|
"house_systems": house_systems, "stations": stations,
|
||||||
|
"zodiac": zodiac, "tables": tables}
|
||||||
ctx: dict = {"form": form, "result": None, "error": None, "moment": None}
|
ctx: dict = {"form": form, "result": None, "error": None, "moment": None}
|
||||||
try:
|
try:
|
||||||
iso_utc, label = _build_utc(date, time, tz_offset)
|
iso_utc, label = _build_utc(date, time, tz_offset)
|
||||||
ctx["moment"] = label
|
ctx["moment"] = label
|
||||||
ctx["result"] = logic.positions(
|
ctx["result"] = logic.positions(
|
||||||
when_utc_iso=iso_utc, lat=lat, lon=lon,
|
when_utc_iso=iso_utc, lat=lat, lon=lon,
|
||||||
house_system=house_system, zodiac=zodiac,
|
house_system=house_system, house_systems=house_systems,
|
||||||
|
stations=stations, zodiac=zodiac, tables=tables,
|
||||||
)
|
)
|
||||||
from app import chartwheel
|
from app import chartwheel
|
||||||
ctx["wheel_svg"] = chartwheel.render(ctx["result"])
|
ctx["wheel_svg"] = chartwheel.render(ctx["result"])
|
||||||
@@ -189,6 +197,8 @@ def compile_pdf(payload: dict):
|
|||||||
when_utc_iso=iso_utc,
|
when_utc_iso=iso_utc,
|
||||||
lat=float(data.get("lat") or 0.0), lon=float(data.get("lon") or 0.0),
|
lat=float(data.get("lat") or 0.0), lon=float(data.get("lon") or 0.0),
|
||||||
house_system=str(data.get("house_system") or "whole_sign"),
|
house_system=str(data.get("house_system") or "whole_sign"),
|
||||||
|
house_systems=[s for s in (data.get("house_systems") or []) if s],
|
||||||
|
stations=bool(data.get("stations")), tables=bool(data.get("tables")),
|
||||||
zodiac=str(data.get("zodiac") or "tropical"),
|
zodiac=str(data.get("zodiac") or "tropical"),
|
||||||
)
|
)
|
||||||
from app import chartwheel
|
from app import chartwheel
|
||||||
|
|||||||
@@ -93,10 +93,23 @@
|
|||||||
var el = document.querySelector('[name=' + name + ']');
|
var el = document.querySelector('[name=' + name + ']');
|
||||||
return el ? el.value : '';
|
return el ? el.value : '';
|
||||||
}
|
}
|
||||||
|
function checked(name) {
|
||||||
|
var el = document.querySelector('[name=' + name + ']');
|
||||||
|
return !!(el && el.checked);
|
||||||
|
}
|
||||||
|
function multi(name) {
|
||||||
|
var out = [];
|
||||||
|
document.querySelectorAll('[name=' + name + ']:checked').forEach(function (el) { out.push(el.value); });
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
// Opcje (stacje, tabele, porównanie domów) MUSZĄ jechać do PDF-a — inaczej
|
||||||
|
// PDF liczyłby okrojony horoskop i miałby braki względem podsumowania.
|
||||||
return {
|
return {
|
||||||
date: v('date'), time: v('time'), tz_offset: v('tz_offset'),
|
date: v('date'), time: v('time'), tz_offset: v('tz_offset'),
|
||||||
lat: v('lat'), lon: v('lon'),
|
lat: v('lat'), lon: v('lon'),
|
||||||
house_system: v('house_system'), zodiac: v('zodiac'),
|
house_system: v('house_system'), zodiac: v('zodiac'),
|
||||||
|
house_systems: multi('house_systems'),
|
||||||
|
stations: checked('stations'), tables: checked('tables'),
|
||||||
place: (document.querySelector('#geoSearch') || {}).value || ''
|
place: (document.querySelector('#geoSearch') || {}).value || ''
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,18 +20,31 @@
|
|||||||
|
|
||||||
// Pola wspólne dla zakładek. Nazwa pola formularza -> jak je znaleźć.
|
// Pola wspólne dla zakładek. Nazwa pola formularza -> jak je znaleźć.
|
||||||
// `geoSearch` (nazwa miejsca) nie ma atrybutu name — szukamy po id.
|
// `geoSearch` (nazwa miejsca) nie ma atrybutu name — szukamy po id.
|
||||||
|
// type: brak = zwykła wartość (.value); 'check' = pojedynczy checkbox (.checked);
|
||||||
|
// 'multi' = kilka checkboxów o tej samej nazwie (lista zaznaczonych wartości).
|
||||||
|
// Opcje (stacje, tabele, porównanie domów) są tu, żeby wybór z „Horoskopu"
|
||||||
|
// wędrował na „Skompiluj" — inaczej podsumowanie liczyłoby się bez nich.
|
||||||
var FIELDS = [
|
var FIELDS = [
|
||||||
{ key: 'person', sel: 'input[name=person]' },
|
{ key: 'person', sel: 'input[name=person]' },
|
||||||
{ key: 'date', sel: 'input[name=date]' },
|
{ key: 'date', sel: 'input[name=date]' },
|
||||||
{ key: 'time', sel: 'input[name=time]' },
|
{ key: 'time', sel: 'input[name=time]' },
|
||||||
{ key: 'tz_offset', sel: 'input[name=tz_offset]' },
|
{ key: 'tz_offset', sel: 'input[name=tz_offset]' },
|
||||||
{ key: 'lat', sel: 'input[name=lat]' },
|
{ key: 'lat', sel: 'input[name=lat]' },
|
||||||
{ key: 'lon', sel: 'input[name=lon]' },
|
{ key: 'lon', sel: 'input[name=lon]' },
|
||||||
{ key: 'house_system', sel: 'select[name=house_system]' },
|
{ key: 'house_system', sel: 'select[name=house_system]' },
|
||||||
{ key: 'zodiac', sel: 'select[name=zodiac]' },
|
{ key: 'zodiac', sel: 'select[name=zodiac]' },
|
||||||
{ key: 'place', sel: '#geoSearch' }
|
{ key: 'place', sel: '#geoSearch' },
|
||||||
|
{ key: 'stations', sel: 'input[name=stations]', type: 'check' },
|
||||||
|
{ key: 'tables', sel: 'input[name=tables]', type: 'check' },
|
||||||
|
{ key: 'house_systems', name: 'house_systems', type: 'multi' }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
function elementsOf(f) {
|
||||||
|
if (f.type === 'multi') return Array.prototype.slice.call(document.querySelectorAll('[name=' + f.name + ']'));
|
||||||
|
var el = document.querySelector(f.sel);
|
||||||
|
return el ? [el] : [];
|
||||||
|
}
|
||||||
|
|
||||||
function load() {
|
function load() {
|
||||||
try {
|
try {
|
||||||
return JSON.parse(localStorage.getItem(KEY) || '{}') || {};
|
return JSON.parse(localStorage.getItem(KEY) || '{}') || {};
|
||||||
@@ -50,21 +63,35 @@
|
|||||||
|
|
||||||
function apply(state) {
|
function apply(state) {
|
||||||
FIELDS.forEach(function (f) {
|
FIELDS.forEach(function (f) {
|
||||||
var el = document.querySelector(f.sel);
|
var els = elementsOf(f);
|
||||||
if (!el) return;
|
if (!els.length) return;
|
||||||
var v = state[f.key];
|
var v = state[f.key];
|
||||||
if (v === undefined || v === null || v === '') return;
|
if (v === undefined || v === null) return;
|
||||||
if (el.value !== v) el.value = v;
|
if (f.type === 'multi') {
|
||||||
|
if (!Array.isArray(v)) return;
|
||||||
|
els.forEach(function (el) { el.checked = v.indexOf(el.value) !== -1; });
|
||||||
|
} else if (f.type === 'check') {
|
||||||
|
els[0].checked = !!v;
|
||||||
|
} else {
|
||||||
|
if (v === '') return;
|
||||||
|
if (els[0].value !== v) els[0].value = v;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function collect() {
|
function collect() {
|
||||||
var state = load();
|
var state = load();
|
||||||
FIELDS.forEach(function (f) {
|
FIELDS.forEach(function (f) {
|
||||||
var el = document.querySelector(f.sel);
|
var els = elementsOf(f);
|
||||||
if (!el) return; // pola nieobecnego na tej zakładce nie kasujemy
|
if (!els.length) return; // pola nieobecnego na tej zakładce nie kasujemy
|
||||||
if (el.value === '' || el.value === null) return;
|
if (f.type === 'multi') {
|
||||||
state[f.key] = el.value;
|
state[f.key] = els.filter(function (el) { return el.checked; }).map(function (el) { return el.value; });
|
||||||
|
} else if (f.type === 'check') {
|
||||||
|
state[f.key] = els[0].checked;
|
||||||
|
} else {
|
||||||
|
if (els[0].value === '' || els[0].value === null) return;
|
||||||
|
state[f.key] = els[0].value;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
@@ -72,12 +99,12 @@
|
|||||||
// 1) odtwórz to, co użytkownik wpisał gdzie indziej
|
// 1) odtwórz to, co użytkownik wpisał gdzie indziej
|
||||||
apply(load());
|
apply(load());
|
||||||
|
|
||||||
// 2) zapisuj każdą zmianę (input dla wpisywania, change dla list i kalendarzy)
|
// 2) zapisuj każdą zmianę (input dla wpisywania, change dla list, checkboxów, kalendarzy)
|
||||||
FIELDS.forEach(function (f) {
|
FIELDS.forEach(function (f) {
|
||||||
var el = document.querySelector(f.sel);
|
elementsOf(f).forEach(function (el) {
|
||||||
if (!el) return;
|
['input', 'change'].forEach(function (ev) {
|
||||||
['input', 'change'].forEach(function (ev) {
|
el.addEventListener(ev, function () { save(collect()); });
|
||||||
el.addEventListener(ev, function () { save(collect()); });
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,232 @@
|
|||||||
|
{# Tabele wyniku horoskopu — używane przez „Skompiluj" (/compile), żeby
|
||||||
|
podsumowanie pokazywało dokładnie to, co policzono (regresja 2026-07-28:
|
||||||
|
podsumowanie miało braki). Skopiowane 1:1 z sekcji tabel na „Horoskop" (/).
|
||||||
|
TODO: przełączyć też chart.html na ten include, żeby oba widoki nie mogły się
|
||||||
|
rozjechać — na razie pilnować ręcznie, że są zgodne. Rysunki (koło/aspektarian/
|
||||||
|
deklinacja/antyscja) renderuje rodzic; tu są same tabele. Każda sekcja pokazuje
|
||||||
|
się tylko, gdy jej dane są w wyniku (opcja policzona → sekcja jest). #}
|
||||||
|
|
||||||
|
{% if result.angles %}
|
||||||
|
<table class="angles">
|
||||||
|
<thead><tr><th>Oś</th><th>Znak</th><th>W znaku</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
{% for key in ["Asc", "MC", "Dsc", "IC"] %}
|
||||||
|
{% set a = result.angles[key] %}
|
||||||
|
<tr><td>{{ a.name }}</td><td>{{ a.sign }}</td><td class="mono">{{ a.in_sign }}</td></tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# Porównanie systemów domów (PRE-05) — te same osie, inny podział na domy #}
|
||||||
|
{% if result.house_systems and result.house_systems | length > 1 %}
|
||||||
|
{% set HSN = {'whole_sign': 'Whole Sign', 'equal': 'Equal', 'porphyry': 'Porphyry'} %}
|
||||||
|
<div class="meta" title="Osie (Asc/MC) są wspólne — różni się podział na domy. Kosmogram rysuje pierwszy z listy.">Porównanie systemów domów ({{ result.house_systems | length }})</div>
|
||||||
|
<table class="angles">
|
||||||
|
<thead><tr><th>Dom</th>{% for hs in result.house_systems %}<th>{{ HSN.get(hs.system, hs.system) }}</th>{% endfor %}</tr></thead>
|
||||||
|
<tbody>
|
||||||
|
{% for i in range(12) %}
|
||||||
|
<tr><td>{{ i + 1 }}</td>
|
||||||
|
{% for hs in result.house_systems %}
|
||||||
|
<td class="mono"><span class="glyph">{{ hs.cusps[i].sign_glyph }}</span> {{ hs.cusps[i].in_sign }}</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th title="Symbol astrologiczny (LOG-22)">Sym.</th>
|
||||||
|
<th>Obiekt</th><th>Znak</th><th>W znaku</th><th>Dom</th><th>Kier.</th><th>Prędkość °/d</th>
|
||||||
|
<th title="Deklinacja — odległość od równika niebieskiego. OOB = poza zakresem Słońca">Dekl.</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for p in result.positions %}
|
||||||
|
<tr>
|
||||||
|
<td class="glyph">{{ p.glyph or '' }}{% if p.sign_glyph %} {{ p.sign_glyph }}{% endif %}</td>
|
||||||
|
<td>{{ p.name }}</td>
|
||||||
|
<td>{{ p.sign }}</td>
|
||||||
|
<td class="mono">{{ p.in_sign }}</td>
|
||||||
|
<td>{{ p.house if p.house is defined else '—' }}</td>
|
||||||
|
<td class="{{ 'retro' if p.direction == 'Rx' else '' }}">{{ p.direction }}</td>
|
||||||
|
<td class="mono">{{ '%+.4f' | format(p.speed) }}</td>
|
||||||
|
<td class="mono">{% if p.declination is defined %}{{ '%+.2f°' | format(p.declination) }}{% if p.out_of_bounds %} <span class="badge" title="Out of bounds — deklinacja poza zakresem Słońca">OOB</span>{% endif %}{% else %}—{% endif %}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{% if result.lots %}
|
||||||
|
<div class="meta">Lots hermetyczne ({{ result.lots | length }}) · sekta: <strong>{{ result.sect }}</strong></div>
|
||||||
|
<table class="angles">
|
||||||
|
<thead><tr><th>Lot</th><th>Znak</th><th>W znaku</th><th>Dom</th><th>Formuła</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
{% for l in result.lots %}
|
||||||
|
<tr><td>{{ l.name }}</td><td>{{ l.sign }}</td><td class="mono">{{ l.in_sign }}</td><td>{{ l.house }}</td><td class="muted small">{{ l.formula }}</td></tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% set with_stations = result.positions | selectattr('stations', 'defined') | list %}
|
||||||
|
{% if with_stations %}
|
||||||
|
<div class="meta">Stacje planet (poprzednia / następna; <span class="badge">blisko</span> = mniej niż 7 dni)</div>
|
||||||
|
<table class="angles">
|
||||||
|
<thead><tr><th>Planeta</th><th>Poprzednia</th><th>Następna</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
{% for p in with_stations %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ p.name }}{% if p.stations.station_soon %} <span class="badge">blisko</span>{% endif %}</td>
|
||||||
|
<td class="mono">{% if p.stations.prev %}{{ p.stations.prev.type }} · {{ p.stations.prev.date }} · {{ p.stations.prev.degree }} ({{ p.stations.prev.days }} d){% else %}—{% endif %}</td>
|
||||||
|
<td class="mono">{% if p.stations.next %}{{ p.stations.next.type }} · {{ p.stations.next.date }} · {{ p.stations.next.degree }} (+{{ p.stations.next.days }} d){% else %}—{% endif %}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if result.aspects %}
|
||||||
|
<div class="meta">Aspekty główne ({{ result.aspects | length }})</div>
|
||||||
|
<table class="angles">
|
||||||
|
<thead><tr><th>Obiekt 1</th><th>Aspekt</th><th>Obiekt 2</th><th>Orb</th><th title="A = aplikacyjny (dokładność nastąpi), S = separacyjny (już minęła)">A/S</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
{% for a in result.aspects %}
|
||||||
|
<tr><td>{{ a.obj1 }}</td><td>{% if a.glyph %}<span class="glyph">{{ a.glyph }}</span> {% endif %}{{ a.aspect }}</td><td>{{ a.obj2 }}</td><td class="mono">{{ '%.2f'|format(a.orb) }}°</td><td>{{ a['as'] if a['as'] is defined else '—' }}</td></tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if result.parallels %}
|
||||||
|
<div class="meta" title="Ciała na tej samej (paralela) lub przeciwnej (kontrparalela) deklinacji — działają jak koniunkcja / opozycja poza ekliptyką">Paralele deklinacji ({{ result.parallels | length }})</div>
|
||||||
|
<table class="angles">
|
||||||
|
<thead><tr><th>Obiekt 1</th><th>Rodzaj</th><th>Obiekt 2</th><th>Orb</th><th>Dekl.</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
{% for r in result.parallels %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ r.obj1 }}</td>
|
||||||
|
<td>{{ 'paralela' if r.type == 'parallel' else 'kontrparalela' }}</td>
|
||||||
|
<td>{{ r.obj2 }}</td>
|
||||||
|
<td class="mono">{{ '%.2f'|format(r.orb) }}°</td>
|
||||||
|
<td class="mono">{{ '%+.2f'|format(r.dec1) }} / {{ '%+.2f'|format(r.dec2) }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if result.antiscia %}
|
||||||
|
<div class="meta" title="Odbicie względem osi przesileń (antyscja) lub równonocy (kontrantyscja) — punkty „dzielące” tę samą długość dnia">Antyscja ({{ result.antiscia | length }})</div>
|
||||||
|
<table class="angles">
|
||||||
|
<thead><tr><th>Obiekt 1</th><th>Rodzaj</th><th>Obiekt 2</th><th>Orb</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
{% for r in result.antiscia %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ r.obj1 }}</td>
|
||||||
|
<td>{{ 'antyscja' if r.type == 'antiscion' else 'kontrantyscja' }}</td>
|
||||||
|
<td>{{ r.obj2 }}</td>
|
||||||
|
<td class="mono">{{ '%.2f'|format(r.orb) }}°</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if result.tables %}
|
||||||
|
{% set tb = result.tables %}
|
||||||
|
<div class="meta">Tabele dodatkowe (LOG-23)</div>
|
||||||
|
|
||||||
|
{% set bal = tb.tally.with_modern_10_plus_asc %}
|
||||||
|
<table class="angles">
|
||||||
|
<thead><tr><th>Bilans (10 planet + Asc)</th><th colspan="4">Rozkład</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td>Żywioły</td>
|
||||||
|
{% for e, n in bal.elements.items() %}
|
||||||
|
<td>{{ tb.tally.labels.elements[e] }}: <strong>{{ n }}</strong></td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
<tr><td>Jakości</td>
|
||||||
|
{% for q, n in bal.qualities.items() %}
|
||||||
|
<td>{{ tb.tally.labels.qualities[q] }}: <strong>{{ n }}</strong></td>
|
||||||
|
{% endfor %}
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% if tb.tally.missing_elements or tb.tally.missing_qualities %}
|
||||||
|
<p class="muted small">Brak:
|
||||||
|
{% for e in tb.tally.missing_elements %}<strong>{{ tb.tally.labels.elements[e] }}</strong>{{ ", " if not loop.last }}{% endfor %}
|
||||||
|
{% for q in tb.tally.missing_qualities %}<strong>{{ tb.tally.labels.qualities[q] }}</strong>{{ ", " if not loop.last }}{% endfor %}
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if tb.moon_phase %}
|
||||||
|
<p class="muted small">
|
||||||
|
<strong>Faza Księżyca:</strong> {{ tb.moon_phase.phase_pl }} ·
|
||||||
|
elongacja {{ '%.2f'|format(tb.moon_phase.angle) }}° ·
|
||||||
|
oświetlenie {{ '%.1f'|format(tb.moon_phase.illumination * 100) }}% ·
|
||||||
|
{{ 'przybywa' if tb.moon_phase.waxing else 'ubywa' }}
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if tb.planetary_hours %}
|
||||||
|
{% set ph = tb.planetary_hours %}
|
||||||
|
<p class="muted small">
|
||||||
|
<strong>Godziny planetarne:</strong> władca dnia {{ ph.day_ruler }} ·
|
||||||
|
{{ ph.hour_number }}. godzina ({{ ph.period }}), władca {{ ph.hour_ruler }} ·
|
||||||
|
godzina trwa {{ ph.hour_length_minutes }} min
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if tb.prenatal_syzygy %}
|
||||||
|
{% set s = tb.prenatal_syzygy %}
|
||||||
|
<p class="muted small">
|
||||||
|
<strong>Syzygia prenatalna:</strong> {{ s.type_pl }} {{ s.when_utc }}
|
||||||
|
({{ s.days_before_birth }} dni przed) w {{ s.in_sign }}
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if tb.critical_degrees %}
|
||||||
|
<div class="meta">Stopnie krytyczne</div>
|
||||||
|
<table class="angles">
|
||||||
|
<thead><tr><th>Obiekt</th><th>Pozycja</th><th>Uwaga</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
{% for c in tb.critical_degrees %}
|
||||||
|
<tr><td>{{ c.name }}</td><td class="mono">{{ c.in_sign }}</td>
|
||||||
|
<td class="muted small">{{ c.flags | join('; ') }}</td></tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<details class="loc">
|
||||||
|
<summary>Podziały: dwunastniki (D12) i nawamsa (D9)</summary>
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>Obiekt</th><th>D12</th><th>D9</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
{% for d in tb.divisional %}
|
||||||
|
<tr><td>{{ d.name }}</td><td class="mono">{{ d.d12_in_sign }}</td>
|
||||||
|
<td class="mono">{{ d.d9_in_sign }}</td></tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</details>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if result.cusps %}
|
||||||
|
<details class="loc">
|
||||||
|
<summary>Cusps domów ({{ result.house_system }})</summary>
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>Dom</th><th>Znak</th><th>Cusp</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
{% for c in result.cusps %}
|
||||||
|
<tr><td>{{ c.house }}</td><td>{{ c.sign }}</td><td class="mono">{{ c.in_sign }}</td></tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</details>
|
||||||
|
{% endif %}
|
||||||
@@ -55,6 +55,21 @@ zapamiętane predykcje okresowe. Dane pobiera z pozostałych zakładek — nie t
|
|||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
{# Te same opcje co na „Horoskop" — synchronizowane między zakładkami (formsync),
|
||||||
|
żeby podsumowanie liczyło się z tym, co wybrano przy horoskopie (nie okrojone). #}
|
||||||
|
<div class="opts">
|
||||||
|
<label><input type="checkbox" name="stations" value="true" {{ 'checked' if form.stations else '' }}>
|
||||||
|
licz stacje planet (wolniejsze)</label>
|
||||||
|
<label><input type="checkbox" name="tables" value="true" {{ 'checked' if form.tables else '' }}>
|
||||||
|
tabele dodatkowe: żywioły, faza Księżyca, godziny planetarne (wolniejsze)</label>
|
||||||
|
</div>
|
||||||
|
<div class="opts">
|
||||||
|
{% set chosen = form.house_systems or [] %}
|
||||||
|
<span class="muted small">Porównaj systemy domów:</span>
|
||||||
|
<label><input type="checkbox" name="house_systems" value="whole_sign" {{ 'checked' if 'whole_sign' in chosen else '' }}> Whole Sign</label>
|
||||||
|
<label><input type="checkbox" name="house_systems" value="equal" {{ 'checked' if 'equal' in chosen else '' }}> Equal</label>
|
||||||
|
<label><input type="checkbox" name="house_systems" value="porphyry" {{ 'checked' if 'porphyry' in chosen else '' }}> Porphyry</label>
|
||||||
|
</div>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button type="submit">Złóż podsumowanie</button>
|
<button type="submit">Złóż podsumowanie</button>
|
||||||
<button type="button" id="pdfBtn" class="ghost"
|
<button type="button" id="pdfBtn" class="ghost"
|
||||||
@@ -106,34 +121,12 @@ zapamiętane predykcje okresowe. Dane pobiera z pozostałych zakładek — nie t
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{# ── 3. Dane policzone ────────────────────────────────────────────── #}
|
{# ── 3. Dane policzone ────────────────────────────────────────────── #}
|
||||||
|
{# WSPÓLNE tabele (ten sam plik co „Horoskop") — podsumowanie pokazuje dokładnie
|
||||||
|
to, co policzono: porównanie domów, stacje, żywioły itd., jeśli były wybrane. #}
|
||||||
<div class="meta">Horoskop · silnik {{ result.engine }}
|
<div class="meta">Horoskop · silnik {{ result.engine }}
|
||||||
{% if result.house_system %}· domy {{ result.house_system }}{% endif %}
|
{% if result.house_system %}· domy {{ result.house_system }}{% endif %}
|
||||||
{% if result.zodiac %}· zodiak {{ result.zodiac }}{% endif %}</div>
|
{% if result.zodiac %}· zodiak {{ result.zodiac }}{% endif %}</div>
|
||||||
<table>
|
{% include "_result_tables.html" %}
|
||||||
<thead><tr><th>Sym.</th><th>Obiekt</th><th>Znak</th><th>W znaku</th><th>Dom</th><th>Kier.</th></tr></thead>
|
|
||||||
<tbody>
|
|
||||||
{% for p in result.positions %}
|
|
||||||
<tr>
|
|
||||||
<td class="glyph">{{ p.glyph or '' }}{% if p.sign_glyph %} {{ p.sign_glyph }}{% endif %}</td>
|
|
||||||
<td>{{ p.name }}</td><td>{{ p.sign }}</td><td class="mono">{{ p.in_sign }}</td>
|
|
||||||
<td>{{ p.house if p.house is defined else '—' }}</td>
|
|
||||||
<td class="{{ 'retro' if p.direction == 'Rx' else '' }}">{{ p.direction }}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
{% if result.angles %}
|
|
||||||
<table class="angles">
|
|
||||||
<thead><tr><th>Oś</th><th>Znak</th><th>W znaku</th></tr></thead>
|
|
||||||
<tbody>
|
|
||||||
{% for key in ["Asc", "MC", "Dsc", "IC"] %}
|
|
||||||
{% set a = result.angles[key] %}
|
|
||||||
<tr><td>{{ a.name }}</td><td>{{ a.sign }}</td><td class="mono">{{ a.in_sign }}</td></tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{# ── 4. i 5. Części od AI — wstawia przeglądarka z magazynu ───────── #}
|
{# ── 4. i 5. Części od AI — wstawia przeglądarka z magazynu ───────── #}
|
||||||
<div id="reportNatal"></div>
|
<div id="reportNatal"></div>
|
||||||
|
|||||||
@@ -159,3 +159,36 @@ def test_ai_text_is_escaped_before_injection():
|
|||||||
|
|
||||||
def test_does_nothing_on_other_tabs():
|
def test_does_nothing_on_other_tabs():
|
||||||
assert "if (!document.getElementById('readiness')) return;" in JS
|
assert "if (!document.getElementById('readiness')) return;" in JS
|
||||||
|
|
||||||
|
|
||||||
|
# ─────── podsumowanie NIE gubi opcji policzonych przy horoskopie ──────────
|
||||||
|
# Regresja 2026-07-28: „Skompiluj" przeliczało horoskop bez stacji/tabel/
|
||||||
|
# porównania domów, więc podsumowanie miało braki względem tego, co policzono.
|
||||||
|
|
||||||
|
def test_compile_form_carries_the_options():
|
||||||
|
"""Te same opcje co „Horoskop" (synchronizowane) — inaczej nie da się ich przekazać."""
|
||||||
|
for f in ('name="stations"', 'name="tables"', 'name="house_systems"'):
|
||||||
|
assert f in TPL, f"brak opcji w formularzu podsumowania: {f}"
|
||||||
|
|
||||||
|
|
||||||
|
def test_compile_handler_recomputes_with_all_options():
|
||||||
|
assert "house_systems: list[str] = Form([])" in MAIN
|
||||||
|
assert "stations: bool = Form(False)" in MAIN
|
||||||
|
assert "house_systems=house_systems" in MAIN # przekazane do logiki
|
||||||
|
assert "stations=stations, zodiac=zodiac, tables=tables" in MAIN
|
||||||
|
|
||||||
|
|
||||||
|
def test_compile_pdf_recomputes_with_all_options():
|
||||||
|
assert 'stations=bool(data.get("stations"))' in MAIN
|
||||||
|
assert 'house_systems=[s for s in (data.get("house_systems")' in MAIN
|
||||||
|
|
||||||
|
|
||||||
|
def test_compile_pdf_payload_includes_options():
|
||||||
|
assert "house_systems: multi('house_systems')" in JS
|
||||||
|
assert "stations: checked('stations')" in JS
|
||||||
|
|
||||||
|
|
||||||
|
def test_summary_uses_shared_result_tables():
|
||||||
|
"""Podsumowanie pokazuje te same tabele co horoskop — jeden wspólny plik,
|
||||||
|
żeby nie mogły znowu się rozjechać."""
|
||||||
|
assert '{% include "_result_tables.html" %}' in TPL
|
||||||
|
|||||||
@@ -20,7 +20,15 @@ WITH_FORM = ("chart.html", "interpret.html", "timeline.html")
|
|||||||
# Pola, które mają wędrować między zakładkami. Gdy dojdzie nowe wspólne pole,
|
# Pola, które mają wędrować między zakładkami. Gdy dojdzie nowe wspólne pole,
|
||||||
# ten wykaz ma się nie rozjechać z formsync.js.
|
# ten wykaz ma się nie rozjechać z formsync.js.
|
||||||
SHARED = ("person", "date", "time", "tz_offset", "lat", "lon",
|
SHARED = ("person", "date", "time", "tz_offset", "lat", "lon",
|
||||||
"house_system", "zodiac", "place")
|
"house_system", "zodiac", "place",
|
||||||
|
"stations", "tables", "house_systems") # opcje też wędrują (regresja: podsumowanie liczyło się bez nich)
|
||||||
|
|
||||||
|
|
||||||
|
def test_option_checkboxes_are_synced_by_type():
|
||||||
|
"""Stacje/tabele to checkboxy (.checked), a porównanie domów to wiele checkboxów
|
||||||
|
tej samej nazwy (lista) — formsync musi umieć oba, nie tylko .value."""
|
||||||
|
assert "'check'" in SYNC_JS and "'multi'" in SYNC_JS
|
||||||
|
assert "el.checked" in SYNC_JS
|
||||||
|
|
||||||
|
|
||||||
# ─────────────────────────────── pole imienia ────────────────────────────
|
# ─────────────────────────────── pole imienia ────────────────────────────
|
||||||
@@ -90,5 +98,5 @@ def test_map_changes_are_reported_to_sync():
|
|||||||
|
|
||||||
def test_sync_does_not_wipe_fields_missing_on_a_tab():
|
def test_sync_does_not_wipe_fields_missing_on_a_tab():
|
||||||
"""Zakładka bez danego pola (np. Sygnifikatory) nie może wyczyścić wartości
|
"""Zakładka bez danego pola (np. Sygnifikatory) nie może wyczyścić wartości
|
||||||
zapamiętanej z innej zakładki."""
|
zapamiętanej z innej zakładki. `collect` pomija pole, którego nie ma na stronie."""
|
||||||
assert "if (!el) return;" in SYNC_JS
|
assert "if (!els.length) return;" in SYNC_JS
|
||||||
|
|||||||
Reference in New Issue
Block a user