Lots / punkty arabskie — 7 Lots hermetycznych (LOG-08)

- engine/lots.py: formuła Lot = C + A − B z odwracaniem w horoskopach nocnych
  (zamiana A<->B); 7 Lots hermetycznych (Fortuna, Duch, Eros, Konieczność,
  Odwaga, Zwycięstwo, Nemezis) — Fortuna i Duch liczone pierwsze, bo pozostałe
  się do nich odwołują. Dwa warianty: degree (domyślny) i sign (całe znaki).
- build_chart: liczy sektę (reużyta is_day_birth z Firdarii) i zwraca lots
  ze znakiem, pozycją i domem; parametr lots_method.
- wyszukiwarka: token [PF (tak Fortuna występuje w realnej bazie) + rozwinięcie
  skrótu do "Part of Fortune".
- widok Horoskop: tabela Lots z formułami i sektą.

Walidacja (dwie niezależne wyrocznie z notes3):
- Fortuna = Can 12°35'27" vs astro-seek Can 12°35'24" (3 sekundy różnicy), dom 1;
- Duch potwierdzony przez swoją antyscję (Taurus 28°14' z tabeli antyscji);
- odwracanie nocne: Fortuna nocna == Duch dzienny i odwrotnie;
- Lots pochodne faktycznie używają Fortuny/Ducha; wariant sign trafia w 0° znaku.
85 testów przechodzi (nowy test_lots).

Odblokowuje Zodiacal Releasing (LOG-11), które startuje z Fortuny/Ducha.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-20 20:02:02 +02:00
parent c75f8377bf
commit 4c0f5f2b26
7 changed files with 166 additions and 2 deletions
+2
View File
@@ -24,6 +24,8 @@ ABBREVIATIONS: dict[str, str] = {
"NN": "North Node", "SN": "South Node", "Lilith": "Lilith", "Chiron": "Chiron",
# osie
"Asc": "Ascendant", "Dsc": "Descendant", "MC": "Midheaven", "IC": "Imum Coeli",
# Lots (punkty arabskie)
"PF": "Part of Fortune", "Fortune": "Part of Fortune", "Spirit": "Lot of Spirit",
# aspekty
"conj": "conjunction", "sex": "sextile", "sq": "square", "tri": "trine",
"opp": "opposition", "semisex": "semisextile", "semisq": "semisquare",
+18 -1
View File
@@ -21,7 +21,8 @@ def _fmt(name: str, lon: float) -> dict:
}
def build_chart(engine: EphemerisEngine, moment: ChartMoment, house_system: str = H.WHOLE_SIGN) -> dict:
def build_chart(engine: EphemerisEngine, moment: ChartMoment, house_system: str = H.WHOLE_SIGN,
lots_method: str = "degree") -> dict:
from app.engine.aspects import find_aspects
positions = engine.positions(moment)
@@ -50,4 +51,20 @@ def build_chart(engine: EphemerisEngine, moment: ChartMoment, house_system: str
]
for pdict, obj in zip(result["positions"], positions):
pdict["house"] = H.assign_house(obj.longitude, cusp_list)
# Lots (LOG-08) — wymagają Asc i sekty (dzień/noc)
from app.engine.firdaria import is_day_birth
from app.engine.lots import compute_lots
pts = {p.name: p.longitude for p in positions}
pts["Asc"] = asc
day = is_day_birth(pts["Sun"], asc, mc) if "Sun" in pts else True
result["sect"] = "day" if day else "night"
result["lots"] = [
{**lot,
"sign": SIGNS[sign_index(lot["longitude"])],
"in_sign": in_sign(lot["longitude"]),
"house": H.assign_house(lot["longitude"], cusp_list)}
for lot in compute_lots(pts, day, lots_method)
]
return result
+60
View File
@@ -0,0 +1,60 @@
"""Lots / punkty arabskie (LOG-08) — 7 Lots hermetycznych.
Formuła: Lot = C + A B (od punktu C odmierzamy odległość między A i B).
Większość Lots **odwraca się w horoskopach nocnych** (zamiana A↔B) — np.
Fortuna: dzień Asc + Mo Su, noc Asc + Su Mo.
Dwa warianty liczenia (notes3):
- `degree` (domyślny) — dokładny stopień,
- `sign` — liczone całymi znakami (Lot wypada na 0° wyliczonego znaku).
Kolejność ma znaczenie: Fortuna i Duch liczone są pierwsze, bo pozostałe Lots
odwołują się do nich.
"""
from __future__ import annotations
from app.engine.formats import norm360, sign_index
# (nazwa, C, A, B, odwracalny w nocy)
LOT_DEFS: list[tuple[str, str, str, str, bool]] = [
("Fortune", "Asc", "Moon", "Sun", True),
("Spirit", "Asc", "Sun", "Moon", True),
("Eros", "Asc", "Venus", "Spirit", True),
("Necessity", "Asc", "Fortune", "Mercury", True),
("Courage", "Asc", "Fortune", "Mars", True),
("Victory", "Asc", "Jupiter", "Spirit", True),
("Nemesis", "Asc", "Fortune", "Saturn", True),
]
METHODS = ("degree", "sign")
def compute_lots(
points: dict[str, float], is_day: bool, method: str = "degree"
) -> list[dict]:
"""points: nazwa → długość natalna (wymagane Asc + planety formuł).
Zwraca listę {name, longitude, formula} w kolejności definicji.
"""
if method not in METHODS:
raise ValueError(f"nieznana metoda liczenia Lots: {method}")
vals = dict(points)
out: list[dict] = []
for name, c, a, b, reversible in LOT_DEFS:
first, second = (a, b) if (is_day or not reversible) else (b, a)
if any(k not in vals for k in (c, first, second)):
continue # brak składnika — pomijamy
if method == "sign":
idx = (sign_index(vals[c]) + sign_index(vals[first])
- sign_index(vals[second])) % 12
lon = idx * 30.0
else:
lon = norm360(vals[c] + vals[first] - vals[second])
vals[name] = lon # dostępny dla kolejnych Lots
out.append({
"name": name,
"longitude": lon,
"formula": f"{c} + {first} {second}",
})
return out
+2
View File
@@ -22,6 +22,8 @@ PLANET_ABBR = {
"Jupiter": "Ju", "Saturn": "Sa", "Uranus": "Ur", "Neptune": "Ne", "Pluto": "Pl",
# punkty wirtualne — tokeny wg SIGNIFICATORS KEY ([NN, [SN, [Lilith)
"North Node": "NN", "South Node": "SN", "Lilith": "Lilith",
# Lots — w bazie Fortuna występuje jako [PF (Part of Fortune)
"Fortune": "PF", "Spirit": "Spirit",
}
SIGN_TO_ABBR = dict(zip(SIGNS, SIGN_ABBR))