Files
astrololo/services/presentation/app/clients/logic_client.py
T
gitea 04b26afa6d
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
feat(logic): generator promptow do LLM + budzetowanie (LOG-29, LOG-30)
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>
2026-07-21 16:44:17 +00:00

91 lines
3.6 KiB
Python

"""Klient HTTP do warstwy logicznej.
Jedyny punkt styku prezentacji w dół. Przekazuje dane z formularza i odbiera
opracowane wyniki. Prezentacja nie sięga bezpośrednio do bazy ani do silnika.
"""
from __future__ import annotations
from typing import Any
import httpx
from app.config import settings
class LogicClient:
def __init__(self, base_url: str | None = None) -> None:
self.base_url = (base_url or settings.logic_url).rstrip("/")
def query(self, query: str, field: str, exact: bool, limit: int) -> dict[str, Any]:
payload = {"query": query, "field": field, "exact": exact, "limit": limit}
with httpx.Client(timeout=settings.http_timeout) as client:
r = client.post(f"{self.base_url}/api/query", json=payload)
r.raise_for_status()
return r.json()
def positions(
self,
when_utc_iso: str,
lat: float,
lon: float,
objects: list[str] | None = None,
house_system: str = "whole_sign",
stations: bool = False,
zodiac: str = "tropical",
) -> dict[str, Any]:
"""Pełny horoskop dla danego momentu — woła logic /chart/positions."""
payload = {
"when_utc": when_utc_iso,
"lat": lat,
"lon": lon,
"objects": objects,
"house_system": house_system,
"stations": stations,
"zodiac": zodiac,
}
# stacje wymagają root-findów — dłuższy timeout
with httpx.Client(timeout=max(settings.http_timeout, 60.0) if stations else settings.http_timeout) as client:
r = client.post(f"{self.base_url}/chart/positions", json=payload)
r.raise_for_status()
return r.json()
def report(
self, when_utc_iso: str, lat: float, lon: float, limit: int = 5000, group: bool = False
) -> dict[str, Any]:
"""Sygnifikatory z obliczeń szukane w bazie — woła logic /chart/report."""
payload = {"when_utc": when_utc_iso, "lat": lat, "lon": lon, "limit": limit, "group": group}
with httpx.Client(timeout=max(settings.http_timeout, 30.0)) as client:
r = client.post(f"{self.base_url}/chart/report", json=payload)
r.raise_for_status()
return r.json()
def prompt(
self, profile: str, when_utc_iso: str, lat: float, lon: float,
budget: str = "medium", from_date: str | None = None, to_date: str | None = None,
) -> dict[str, Any]:
"""Gotowy prompt do LLM z wyliczeń (LOG-29/30) — woła logic /chart/prompt."""
payload: dict[str, Any] = {
"profile": profile, "when_utc": when_utc_iso,
"lat": lat, "lon": lon, "budget": budget,
}
if from_date and to_date:
payload["from_date"], payload["to_date"] = from_date, to_date
with httpx.Client(timeout=max(settings.http_timeout, 60.0)) as client:
r = client.post(f"{self.base_url}/chart/prompt", json=payload)
r.raise_for_status()
return r.json()
def timeline(
self, when_utc_iso: str, lat: float, lon: float,
from_date: str, to_date: str, interpret: bool = True,
) -> dict[str, Any]:
"""Oś czasu z technik (+interpretacje z bazy) — woła logic /chart/timeline."""
payload = {
"when_utc": when_utc_iso, "lat": lat, "lon": lon,
"from_date": from_date, "to_date": to_date, "interpret": interpret,
}
with httpx.Client(timeout=max(settings.http_timeout, 60.0)) as client:
r = client.post(f"{self.base_url}/chart/timeline", json=payload)
r.raise_for_status()
return r.json()