feat(logic): generator promptow do LLM + budzetowanie (LOG-29, LOG-30)
build / build (push) Successful in 1m4s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m47s
Testy / Build obrazu silnika B (swisseph) (push) Failing after 26s
Testy / Kontrola składni wszystkich warstw (push) Successful in 25s

Pierwszy krok ustalonej kolejnosci: prompt z podgladem, uzyteczny od razu
BEZ integracji API (ta przyjdzie w LOG-31).

LOG-29 — app/prompt.py:
- profil natal (ekran Interpretacje) i period (ekran Kalendarz),
- prompt po polsku: zadanie -> dane horoskopu (pozycje, osie, Lots, aspekty,
  sekta, zodiak) -> wskazania z baz wg wagi -> instrukcje -> zastrzezenie,
- twarde reguly: kazda teza musi cytowac konkretny sygnifikator, zakaz
  wychodzenia poza dostarczone dane, jawne wskazanie sprzecznosci,
- deterministyczny: ten sam horoskop + budzet = ten sam prompt.

LOG-30 — redukcja do budzetu (concise/medium/extensive):
dedup -> grupowanie z licznikiem -> sortowanie wg punktacji sily (LOG-21) ->
obciecie ogona (jednostka = CALE wskazanie) -> skracanie dlugich opisow.
Statystyki zwracaja ile weszlo/pominieto i jaki byl prog — takze w tresci
promptu, zeby model wiedzial, ze widzi wybor.

POST /chart/prompt — zwraca sam prompt + statystyki, bez wolania modelu.

Prezentacja: przycisk „Generuj prompt (AI)" na obu ekranach, wybor budzetu,
pole z promptem + kopiowanie (z fallbackiem dla http bez secure context).

WAZNE (znalezione przy tescie e2e): padnieta warstwa danych zabiera tylko
wskazania — horoskop i OS CZASU zostaja, bo sa czysto obliczeniowe. Wczesniej
blad bazy gubil cala osie czasu, czyniac prognoze okresowa bezuzyteczna.
Zabezpieczone testem.

Testy: 19 nowych, calosc 118 passed / 1 skipped. Zweryfikowane e2e w
przegladarce: profil natal (3340 znakow) i period (15 zdarzen, 4728 znakow).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit was merged in pull request #9.
This commit is contained in:
2026-07-21 17:31:51 +02:00
committed by gitea
parent 2892b71be3
commit 04b26afa6d
10 changed files with 761 additions and 9 deletions
+80
View File
@@ -121,6 +121,86 @@ def chart_report(req: ReportRequest) -> dict:
return {"engine": engine.name, **report}
class PromptRequest(BaseModel):
"""Wejście generatora promptu (LOG-29/30)."""
profile: str = "natal" # natal | period
when_utc: datetime
lat: float = 0.0
lon: float = 0.0
budget: str = "medium" # concise | medium | extensive
limit: int = 5000
# tylko dla profilu period:
from_date: str | None = None
to_date: str | None = None
techniques: list[str] | None = None
@app.post("/chart/prompt")
def chart_prompt(req: PromptRequest) -> dict:
"""Gotowy prompt do LLM z naszych wyliczeń (LOG-29) z budżetowaniem (LOG-30).
profile=natal → horoskop urodzeniowy (ekran Interpretacje)
profile=period → horoskop na wybrany okres (ekran Kalendarz)
Nie woła żadnego modelu — zwraca sam prompt i statystyki redukcji, żeby dało się
go obejrzeć i skopiować. Wysyłkę do modelu doda LOG-31.
"""
from app.engine.chart import build_chart
from app.engine.models import ChartMoment
from app.prompt import build_natal_prompt, build_period_prompt
engine = get_engine()
moment = ChartMoment(when_utc=req.when_utc, lat=req.lat, lon=req.lon)
chart = build_chart(engine, moment)
label = req.when_utc.strftime("%Y-%m-%d %H:%M UTC")
data_error = None
# Warstwa danych dokłada wyłącznie WSKAZANIA. Wyliczenia (horoskop, oś czasu) są
# od niej niezależne — gdy padnie, prompt musi zachować wszystko, co policzyliśmy.
try:
if req.profile == "natal":
from app.significators import build_report
report: dict = {"objects": []}
try:
report = build_report(
chart["positions"], DataClient(),
aspects=chart.get("aspects"), per_object_limit=req.limit,
)
except httpx.HTTPError as e:
data_error = f"Warstwa danych niedostępna: {e}"
out = build_natal_prompt(chart, report, req.budget, label)
elif req.profile == "period":
if not (req.from_date and req.to_date):
raise HTTPException(422, "profile=period wymaga from_date i to_date")
from app.engine import houses as H
from app.engine.timeline import build_timeline
from app.significators import interpret_events
ramc, eps = engine.sidereal(moment)
points = {"Asc": H.compute_asc(ramc, eps, moment.lat), "MC": H.compute_mc(ramc, eps)}
for p in engine.positions(moment):
points[p.name] = p.longitude
events = build_timeline(engine, moment, points, req.from_date, req.to_date,
req.techniques)
try:
interpret_events(events, DataClient())
except httpx.HTTPError as e:
data_error = f"Warstwa danych niedostępna: {e}" # oś czasu zostaje
out = build_period_prompt(chart, events, req.from_date, req.to_date,
req.budget, label)
else:
raise HTTPException(422, f"Nieznany profil: {req.profile!r} (natal | period)")
except ValueError as e: # nieznany budżet
raise HTTPException(422, str(e))
out["engine"] = engine.name
if data_error:
out["data_error"] = data_error
return out
class ProfectionsRequest(BaseModel):
when_utc: datetime # moment urodzenia (UTC)
lat: float = 0.0