mirror of
https://github.com/migatu/astrololo.git
synced 2026-07-14 13:34:38 +00:00
166b438f83
Aspekty: - engine/aspects.py: aspekty główne (conj/sex/sq/tri/opp) z orbami (bonus dla luminarzy), separacja z obsługą zawinięcia. Applying/sep na później. - build_chart zwraca listę aspektów; /chart/positions je udostępnia; widok Horoskop pokazuje tabelę aspektów. Bogatsze sygnifikatory: - trzecia faseta "w aspekcie": dla każdego aspektu głównego obiektu filtruje rekordy po tokenie aspektu + drugiej planety ([conj + [Mo). Cookbook komplet: znak + dom + aspekt. Dopieszczenie wyników: - ODSIEWANIE DUPLIKATÓW: duplikat = ten sam sygnifikator ORAZ ten sam opis (po normalizacji). Dedup wewnątrz fasety, działa też na wynikach z wielu baz. - _facet_samples przyjmuje wiele tokenów (AND); dedup + istniejące odsiewanie szumu. Zweryfikowano na realnym main_base.xlsx (30.04.1984): 16 aspektów zgodnych z astro.com (Sun conj Moon 9.59°, Sun opp Saturn 3.17°); faseta aspektu daje bogate trafienia (Sun koniunkcja z Moon 84, opozycja z Saturn 43); dedup obniżył duplikaty (Sun w znaku 46->44). 36 testów przechodzi. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
117 lines
3.8 KiB
Python
117 lines
3.8 KiB
Python
"""Warstwa LOGICZNA — usługa HTTP.
|
|
|
|
W górę: udostępnia API dla warstwy prezentacji.
|
|
W dół: woła warstwę bazodanową (DataClient).
|
|
Nie serwuje HTML, nie czyta plików/baz — tylko reguły i pośrednictwo.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
|
|
import httpx
|
|
from fastapi import FastAPI, HTTPException
|
|
from pydantic import BaseModel
|
|
|
|
from app.clients.data_client import DataClient
|
|
from app.models import QueryRequest, QueryResponse
|
|
from app.service import QueryService
|
|
|
|
app = FastAPI(title="astrololo · warstwa logiczna")
|
|
service = QueryService()
|
|
|
|
# --- silnik efemeryd (LOG-24): budowany leniwie, by nie wymagać Skyfielda do startu ---
|
|
_engine = None
|
|
|
|
|
|
def get_engine():
|
|
global _engine
|
|
if _engine is None:
|
|
from app.engine.factory import build_engine
|
|
|
|
_engine = build_engine()
|
|
return _engine
|
|
|
|
|
|
class PositionsRequest(BaseModel):
|
|
when_utc: datetime # moment w UTC (świadomy strefy)
|
|
lat: float = 0.0
|
|
lon: float = 0.0
|
|
objects: list[str] | None = None
|
|
house_system: str = "whole_sign" # whole_sign | equal | porphyry
|
|
|
|
|
|
@app.post("/api/query", response_model=QueryResponse)
|
|
def query(req: QueryRequest) -> QueryResponse:
|
|
try:
|
|
return service.handle(req)
|
|
except httpx.HTTPError as e:
|
|
raise HTTPException(status_code=502, detail=f"Warstwa bazodanowa niedostępna: {e}")
|
|
|
|
|
|
@app.post("/chart/positions")
|
|
def chart_positions(req: PositionsRequest) -> dict:
|
|
"""Pełny horoskop: pozycje (LOG-01) + osie i domy (LOG-05), aktywnym silnikiem."""
|
|
from app.engine.chart import build_chart
|
|
from app.engine.models import ChartMoment
|
|
|
|
engine = get_engine()
|
|
moment = ChartMoment(when_utc=req.when_utc, lat=req.lat, lon=req.lon)
|
|
return build_chart(engine, moment, req.house_system)
|
|
|
|
|
|
@app.post("/chart/compare")
|
|
def chart_compare(req: PositionsRequest) -> dict:
|
|
"""Tryb dwu-silnikowy (LOG-26): policz oboma silnikami i zwróć raport różnic.
|
|
|
|
Wymaga skonfigurowanego ENGINE_SWISSEPH_URL (silnik B). W przeciwnym razie
|
|
zwraca informację, że porównanie jest niedostępne.
|
|
"""
|
|
from app.engine.compare import compare_engines
|
|
from app.engine.factory import build_engine
|
|
from app.engine.models import ChartMoment
|
|
|
|
moment = ChartMoment(when_utc=req.when_utc, lat=req.lat, lon=req.lon)
|
|
try:
|
|
report = compare_engines(build_engine("own"), build_engine("swisseph"), moment)
|
|
except (RuntimeError, httpx.HTTPError) as e:
|
|
raise HTTPException(status_code=503, detail=f"Silnik B niedostępny: {e}")
|
|
return report.summary()
|
|
|
|
|
|
class ReportRequest(BaseModel):
|
|
when_utc: datetime
|
|
lat: float = 0.0
|
|
lon: float = 0.0
|
|
limit: int = 5000
|
|
|
|
|
|
@app.post("/chart/report")
|
|
def chart_report(req: ReportRequest) -> dict:
|
|
"""Wynik obliczeń szukany w bazie: z pozycji + domów + aspektów generuje
|
|
sygnifikatory (fasety znak/dom/aspekt) i pyta warstwę danych o interpretacje."""
|
|
from app.engine.chart import build_chart
|
|
from app.engine.models import ChartMoment
|
|
from app.significators import build_report
|
|
|
|
engine = get_engine()
|
|
moment = ChartMoment(when_utc=req.when_utc, lat=req.lat, lon=req.lon)
|
|
chart = build_chart(engine, moment) # pozycje z domami + aspekty
|
|
try:
|
|
report = build_report(
|
|
chart["positions"], DataClient(),
|
|
aspects=chart.get("aspects"), per_object_limit=req.limit,
|
|
)
|
|
except httpx.HTTPError as e:
|
|
return {"engine": engine.name, "objects": [], "data_error": f"Warstwa danych niedostępna: {e}"}
|
|
return {"engine": engine.name, **report}
|
|
|
|
|
|
@app.get("/health")
|
|
def health() -> dict:
|
|
info = {"status": "ok", "layer": "logic"}
|
|
try:
|
|
info["data_layer"] = DataClient().health()
|
|
except httpx.HTTPError as e:
|
|
info["data_layer"] = {"status": "down", "error": str(e)}
|
|
return info
|