Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 128c563974 | |||
| 91728ddea1 | |||
| 1c68ca04f6 | |||
| a7cd539d74 |
+10
@@ -20,6 +20,16 @@ services/data/data_files/*.xlsx
|
||||
services/data/data_files/.files-state.json
|
||||
!services/data/data_files/.gitkeep
|
||||
|
||||
# Materiały w docs/, które NIE trafiają do repo:
|
||||
# - kupione bazy interpretacji i eksport ich opisów — pułapki (DAN-26) działają
|
||||
# tylko, gdy każda kopia ma inne markery, a kopia w gicie trafia do każdego klona i CI;
|
||||
# - dane urodzeniowe prawdziwych osób (kosmogramy).
|
||||
docs/astro19 version2026.xlsm
|
||||
docs/The Encyclopaedia of Medical Astrology_FILENAMEtymcz-tab6 4parser.xlsx
|
||||
docs/astro-excel4_4parser2.xlsm
|
||||
docs/interpretacje.xlsx
|
||||
docs/kosmogramy z astro2019.xlsx
|
||||
|
||||
# Narzędzia
|
||||
.env
|
||||
.DS_Store
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,204 @@
|
||||
# Analiza materiałów od klienta — wrzesień 2026
|
||||
|
||||
Źródła:
|
||||
- `astro parser TO DO LIST 2026-09-05.xlsx` — dalej **TO DO**,
|
||||
- `astro parser_2026-09-13.docx` — dalej **specyfikacja**,
|
||||
- dosłane 2026-10-04: `wznoszenie znaków ascensional times.xlsx` i `fidaria calculator.xls`.
|
||||
|
||||
Architektura (trzy warstwy) zostaje bez zmian. Uwagi klienta o architekturze i stosie technologicznym są tu celowo pominięte.
|
||||
|
||||
Wnioski są naniesione do rejestru `astrololo_wymagania.xlsx` w tym samym PR. Każda zmiana — z wartością „było / jest", powodem i dowodem — jest w arkuszu **„Zmiany 2026-10"**.
|
||||
|
||||
Legenda pewności: **[kod]** sprawdzone w kodzie · **[hipoteza]** wniosek z objawów, niesprawdzony.
|
||||
|
||||
## TL;DR
|
||||
|
||||
1. **Interpretacje pokazują sygnifikatory, których w horoskopie nie ma.** Klient ma rację, mechanizm potwierdzony w kodzie, a ten sam błąd zasila prompt do LLM. Dodatkowo wyszukiwanie ucina rekordy limitem 5000, więc część bazy w ogóle nie jest sprawdzana.
|
||||
2. **Specyfikacja (docx) to w ~90% wynik AI, którego klient jeszcze nie przejrzał** (znacznik „{DOTĄD SPRAWDZONE}" w części 2 z 21). Służy jako skorowidz, nie jako wymagania.
|
||||
3. **Priorytety bierzemy z TO DO.** Astrologia wedyjska, chińska i majańska występuje tylko w specyfikacji — trafiła do pytań (Q-15), nie do wymagań.
|
||||
4. **Wygląd i interfejs zapisane jako opcje do wyboru:** język, motyw jasny obok obecnego ciemnego, kolorowanie, „symbol + skrót", styl koła.
|
||||
5. **Rejestr:** 56 nowych wymagań, 103 zmiany w istniejących, 34 nowe pytania (Q-15…Q-48), 21 haseł słownika.
|
||||
6. **17 statusów skorygowanych w dół.** Te wiersze już w pierwotnej treści obiecywały funkcje, których w kodzie nie ma.
|
||||
7. **Pięć plików z `docs/` celowo poza repozytorium** — kupione bazy i dane osobowe (sekcja 7).
|
||||
|
||||
## 1. Interpretacje działają źle (potwierdzone w kodzie)
|
||||
|
||||
**Zgłoszenie (TO DO, wiersz 11, \*\*\*).** Pod nagłówkiem „Sun w Taurus" pojawia się rekord „Sun in Gemini or Sagittarius, in square or opposition the Moon", choć żaden z warunków nie zachodzi. Fasetka „koniunkcja z Moon — 84 dopasowań" zbiera każdą koniunkcję czegokolwiek z Księżycem. Pokazują się też rekordy o dyrekcjach („by dir."), których nie liczymy, i o godnościach („well-dig. at B."), które w tym horoskopie nie są spełnione.
|
||||
|
||||
**Mechanizm [kod].** `_facet_samples` w [significators.py](../services/logic/app/significators.py#L70) przepuszcza rekord, jeśli tekst jego sygnifikatora *zawiera* wszystkie tokeny fasetki ([linia 82](../services/logic/app/significators.py#L82)). Sprawdza więc, czy rekord *wspomina* te tokeny, a nie czy *wszystkie jego warunki* są spełnione w horoskopie. Skutki:
|
||||
|
||||
- Nie rozróżnia, **która** planeta jest w aspekcie. Rekord `[Ve [conj [Mo … [Su in 7th H.` trafia do fasetki „Słońce w koniunkcji z Księżycem".
|
||||
- Ignoruje **dodatkowe warunki** rekordu: inny znak, inny aspekt, kwalifikatory techniki („by dir.", „tr."), godności („at B.").
|
||||
- **Limit 5000 [kod].** Wyszukiwanie po tokenie planety jest ucinane do 5000 rekordów ([main.py](../services/logic/app/main.py#L253), [excel_provider.py](../services/data/app/providers/excel_provider.py#L166)). Licznik „[Su: 5415 rekordów" jest liczony *przed* ucięciem, więc ok. 400 rekordów Słońca nigdy nie trafia do sprawdzania, a liczba w nagłówku nie odpowiada temu, co sprawdzono.
|
||||
|
||||
Ta sama funkcja obsługuje Kalendarz ([`interpret_events`](../services/logic/app/significators.py#L265)), a generator promptów (LOG-29/30) bierze dopasowania z tego samego `build_report` ([main.py](../services/logic/app/main.py#L338), [prompt.py](../services/logic/app/prompt.py#L66)). Model z przekonaniem cytuje więc świadectwa, których w horoskopie nie ma.
|
||||
|
||||
**W rejestrze:**
|
||||
- LOG-15 (parser) → „Do zrobienia";
|
||||
- LOG-16 (atomy) i LOG-19 (wypis) → „W trakcie";
|
||||
- kryteria odbioru naprawy, z przykładami klienta jako testami, są w LOG-19;
|
||||
- ekran opisuje PRE-36;
|
||||
- przypadek „rekord nie do oceny" — pytanie Q-35.
|
||||
|
||||
## 2. Czym są te dokumenty
|
||||
|
||||
**TO DO (xlsx)** to właściwa lista robocza: arkusz „LISTA TO DO" z priorytetami (\*\*\* / \*\* / \* / brak = na tym etapie niekonieczne) oraz 16 arkuszy-wzorców tabel. Arkusz ma też **legendę kolorów [kod — odczyt stylów]**:
|
||||
- **pomarańczowy** = „muszę uzupełnić ten opis — lepiej poczekać": wiersze 24, 32–34, 37, 40, 42–45, 73–76, 79, 87, 89, 91, 92, 94, 96, 98, 100;
|
||||
- **żółty** = „jeszcze w ogóle nie ugryzione — poczekać": wiersze 27 i 38.
|
||||
|
||||
Wymagania z tych wierszy mają w rejestrze dopisek „opis u klienta do uzupełnienia".
|
||||
|
||||
**Specyfikacja (docx)** to „Kompletna specyfikacja techniczna" w 21 częściach. Według klienta to efekt ponad 20 godzin pracy z agentem AI, wciąż niedokończony. Ustalenia z formatowania [kod — odczyt XML]:
|
||||
|
||||
- Legenda cieniowania akapitów: pomarańczowy = „to jeszcze uzupełnię", żółty = „kompletnie do poprawki", łososiowy = „chyba out".
|
||||
- Flagi występują **tylko w częściach 1–2**, a w części 2 — zaraz po „Accidental dignities" — stoi znacznik **„{DOTĄD SPRAWDZONE}"**. Części 3–21 to **nieprzejrzany wynik AI**.
|
||||
- Kolor „chyba out" nie występuje ani razu.
|
||||
- Czerwona czcionka w częściach 1–2 to specyfikacja kolorów UI (osłabienia, domy kadentne, ujemne punkty), a nie znacznik „bzdura".
|
||||
- Dokument jest urwany („TU WYWALIŁ BŁĄD" w części 11, „TU SIĘ SKOŃCZYŁ LIMIT WĄTKU" w części 21).
|
||||
- Zawiera błędy merytoryczne, np. „0.5° Vigintile (1/40)" zamiast 18° (1/20). Arkusz „ASPECTS" w TO DO ma to poprawnie. **Przy konflikcie źródłem prawdy jest xlsx.**
|
||||
|
||||
## 3. Błędy w działającej aplikacji
|
||||
|
||||
| TO DO | Prio | Zgłoszenie | Ocena | Rejestr |
|
||||
|---|---|---|---|---|
|
||||
| 11 | \*\*\* | Interpretacje pokazują nieobecne sygnifikatory | **[kod]** potwierdzone, sekcja 1 | LOG-15/16/19, PRE-36 |
|
||||
| 9 | \*\*\* | Pliki 5 MB i 7,75 MB kończą się „Gateway Timeout" | **[hipoteza]** 504 zwraca proxy/ingress — w aplikacji nie ma limitu rozmiaru. Rozmiary pasują do `astro19 version2026.xlsm` (7,76 MiB) i `Encyclopaedia of Medical Astrology` (4,9 MiB). **[kod]** Nawet po usunięciu przekroczenia czasu domyślna reguła wgrywania przyjmuje tylko `.xlsx` ([files.py](../services/data/app/files.py#L64)), więc `.xlsm` zostanie odrzucony. | DAN-29, DAN-16 (→ Must) |
|
||||
| 9 | \*\*\* | Komunikat „nie nadaje się do użycia" dopiero przy ponownym wgraniu | Walidacja od razu po wgraniu + stały opis wymaganego układu | DAN-30, PRE-34 |
|
||||
| 10 | \*\*\* | Brak listy wgranych plików | **[hipoteza]** Lista istnieje w astrololo (DAN-27). Klient pracuje najpewniej na astrokliencie, który z założenia jest „bez administracji". | PRE-35, Q-19, Q-21 |
|
||||
| 12 | \*\*\* | Ekran „Sygnifikatory" — nie wiadomo, do czego służy | Wymaga wpisywania tokenów w składni bazy | PRE-37 |
|
||||
|
||||
## 4. Wygląd i interfejs — jako opcje do wyboru
|
||||
|
||||
Zgodnie z decyzją: zmiany wyglądu są **opcjami**, nie zamiennikami — obecny wygląd zostaje dostępny.
|
||||
|
||||
| Wymaganie | Opcja |
|
||||
|---|---|
|
||||
| PRE-30 | język interfejsu: angielski lub polski |
|
||||
| PRE-31 | motyw jasny obok obecnego ciemnego |
|
||||
| PRE-32 | kolorowanie komórek wg planet, znaków i aspektów |
|
||||
| PRE-33 | zapis „symbol + skrót" (`☍ opp`, `☉ Su`, `♐ Sag`) |
|
||||
| PRE-49 | zakotwiczenie koła: Asc / whole sign / Baran / Rak / Słońce / Księżyc |
|
||||
| PRE-50 | granice terminów i dekanatów na kole |
|
||||
| PRE-51 | styl linii aspektów |
|
||||
|
||||
Wartości domyślne motywu i języka oraz zasięg przełącznika języka (wszystkie warianty? raporty LLM?) — pytania Q-17 i Q-18.
|
||||
|
||||
## 5. Co zostało naniesione do rejestru
|
||||
|
||||
| Warstwa | Wymagania przed → po | Zrobione / W trakcie / Do zrobienia — przed | po |
|
||||
|---|---|---|---|
|
||||
| Dane | 28 → 37 | 14 / 3 / 11 | 13 / 5 / 19 |
|
||||
| Logika | 34 → 51 | 29 / 3 / 2 | 18 / 12 / 21 |
|
||||
| Prezentacja | 29 → 59 | 27 / 1 / 1 | 23 / 5 / 31 |
|
||||
| Pytania otwarte | 14 → 48 | | |
|
||||
|
||||
### Korekty statusów (17)
|
||||
|
||||
Zasada: status opisuje to, co wiersz obiecuje **w swojej pierwotnej treści**. Lipcowy audyt czytał te wiersze łagodnie („rdzeń działa"). Obecny czyta je ściśle, z dowodem w kodzie dla każdej korekty.
|
||||
|
||||
- **LOG-15** → Do zrobienia: parsera nie ma (moduł sam nazywa się zalążkiem).
|
||||
- **LOG-17** → Do zrobienia: brak obsługi „asp" / „asp+" / „asp-".
|
||||
- **LOG-16, LOG-19** → W trakcie: sekcja 1.
|
||||
- **LOG-20** → W trakcie: brak „as-is-in-chart" i liczby trafień.
|
||||
- **LOG-02** → W trakcie: brak gwiazd stałych oraz węzła i Lilith w wersji true.
|
||||
- **DAN-09** → W trakcie: efemerydy tylko głównych ciał, bez asteroid i gwiazd.
|
||||
- **LOG-03** → W trakcie: brak prędkości w % ruchu i konfigurowalnej stacji umownej.
|
||||
- **LOG-04, PRE-07** → W trakcie: brak zodiaku RA.
|
||||
- **LOG-06, PRE-06** → W trakcie: z aspektów pobocznych tylko 30/45/150°, jedna orba dla wszystkich, brak harmonik, poziomów dokładności i aspektu przez znak.
|
||||
- **LOG-07** → W trakcie: orb paraleli stały (1°), niekonfigurowalny.
|
||||
- **LOG-10** → W trakcie: tylko profekcje roczne; brak miesięcznych, dziennych, ciągłych i Lord of the Orb.
|
||||
- **LOG-12** → W trakcie: brak monthly revolutions.
|
||||
- **PRE-01** → W trakcie: brak listy lokalizacji domyślnych i domyślnej daty/godziny.
|
||||
- **PRE-04** → W trakcie: z technik relacyjnych tylko synastria; solariusz i lunariusz nie mają ekranu.
|
||||
|
||||
**LOG-08 i LOG-14 zostają „Zrobione"** — ich pierwotna treść jest spełniona: By Degree / By Sign, odwracanie i 7 Lots hermetycznych w [lots.py](../services/logic/app/engine/lots.py#L29); tabela technique | significator | start | exact | end w [timeline.py](../services/logic/app/engine/timeline.py#L30). Nowy zakres klienta wydzielony do **LOG-50** (Lots z listy klienta) i **LOG-51** (docelowy format tabeli predykcji).
|
||||
|
||||
### Priorytety podniesione (13, nigdy nie obniżane)
|
||||
|
||||
- **Dane:** DAN-06 (orby), DAN-07 (czasy wznoszenia), DAN-08 (Lots), DAN-10 (katalog aspektów), DAN-16 (`.xlsm`).
|
||||
- **Logika:** LOG-07, LOG-10, LOG-11, LOG-12, LOG-13, LOG-17.
|
||||
- **Prezentacja:** PRE-06, PRE-08.
|
||||
|
||||
Wszystkie na Must, zgodnie z \*\*\* u klienta albo zależnością od naprawy z w.11.
|
||||
|
||||
### Nowe wymagania według tematów
|
||||
|
||||
- **Wgrywanie i bazy:** DAN-29 (duże pliki), DAN-30 (walidacja od razu), PRE-34, PRE-35, PRE-37.
|
||||
- **Horoskopy i dane wejściowe:**
|
||||
- DAN-31 i PRE-40: kopiuj/wklej;
|
||||
- DAN-32: format Zet;
|
||||
- DAN-33 i PRE-41: lokalizacje domyślne;
|
||||
- LOG-38 i PRE-38: strefa z DST, UT, czas gwiazdowy;
|
||||
- PRE-39: zapisane horoskopy.
|
||||
- **Aspekty i orby:** LOG-37 (daty dokładności, tabela ASPECT LIST), PRE-43, PRE-44, PRE-45 i macierz orbów w zmianach DAN-06, LOG-06, PRE-06.
|
||||
- **Synastria:** LOG-42 (obliczenia z pełnymi ustawieniami), PRE-46–48 (dane obu osób, tabela A–aspekt–B, koło podwójne, pełny kwadrat aspektów).
|
||||
- **Tabele:**
|
||||
- LOG-36: godności i faza;
|
||||
- LOG-39 i PRE-53: dane obiektów;
|
||||
- LOG-40 i PRE-52: tabela stopni;
|
||||
- LOG-41: midpointy;
|
||||
- PRE-59: godziny planetarne, syzygia, żywioły, asteroidy.
|
||||
- **Predykcja:**
|
||||
- LOG-35: Firdaria — wariant i pełny cykl, sekcja 6;
|
||||
- LOG-43: tranzyty;
|
||||
- LOG-44: ingresy, okresy cienia, OOB, lunacje;
|
||||
- LOG-45: progresje sekundarne;
|
||||
- LOG-46: Days of the Moon;
|
||||
- LOG-51 i PRE-54: zunifikowana tabela;
|
||||
- PRE-55: tabele technik okresowych.
|
||||
- **Poza astrologią zachodnią (niski priorytet):** numerologia (LOG-47, DAN-35, PRE-56), chiromancja i fizjonomika (LOG-48, PRE-57), Astro\*Carto\*Graphy (LOG-49), atlasy medyczny i historyczny (DAN-36, PRE-58).
|
||||
- **Luka w rejestrze uzupełniona:** DAN-28 (lustro baz w SQL). Numer był już używany w kodzie (`services/data/requirements.txt`, commit 003deb9) i w repo deploy, ale w rejestrze go brakowało.
|
||||
|
||||
## 6. Dosłane materiały
|
||||
|
||||
### Czasy wznoszenia znaków (`wznoszenie znaków ascensional times.xlsx`)
|
||||
|
||||
- Tabela © 2004 The Golden Hind Press: szerokości 0–66° N, wartości w stopniach i minutach (1° = 1 rok życia), pary znaków o równym czasie.
|
||||
- **Sprawdzone wzorem [kod]** (wznoszenie skośne): przy nachyleniu ekliptyki ok. 23,4465° (wartość z ok. 1945 r.) zgadza się do 0,6′ w 305 z 306 komórek.
|
||||
- **Jedna literówka:** 40°, Lew/Skorpion — w tabeli 38°20′, powinno być 38°02′ (tylko ten wiersz nie sumuje się do 180°).
|
||||
- **Tabela nie wystarcza do algorytmu z TO DO w.57–65:** brak szerokości ułamkowych (dla Warszawy odczyt z wiersza 52° myli się o ok. 7 tygodni), półkuli południowej i pasa 66,6°–78,5°, gdzie część znaków ma czasy ujemne.
|
||||
- **Rekomendacja:** liczyć wzorem, a tabelę trzymać jako wyrocznię testów (DAN-07, LOG-13). Decyzja i rozbieżności spec ↔ TO DO — pytania Q-37…Q-41.
|
||||
- Gotowe elementy w kodzie: `_ascensional_difference` i `mean_obliquity` w `houses.py`.
|
||||
|
||||
### Kalkulator firdarii (`fidaria calculator.xls`)
|
||||
|
||||
- Arkusz liczy okresy z samej daty urodzenia, w trzech wariantach obok siebie: dzienny, nocny Zollera (węzły zaraz po Marsie — poleca go autor arkusza) i nocny Abu Ma'shara (węzły na końcu).
|
||||
- **Nasz `firdaria.py` zgadza się z arkuszem [kod]** w wariancie dziennym i nocnym Abu Ma'shara: 51/51 władców okresów na przykładzie z arkusza. Daty różnią się w 2 z 51 wierszy — inna długość roku: 365,2422 u nas, 365,2425 w arkuszu.
|
||||
- **Braki:**
|
||||
- wariant Zollera — dla przykładowego urodzenia nocnego z 1984 r. daje dziś inny okres (Węzeł Południowy zamiast Słońca);
|
||||
- liczenie po 75. roku życia — arkusz zaczyna cykl od nowa, u nas wynik się kończy;
|
||||
- ręczny wybór sekty.
|
||||
- W rejestrze: LOG-35 (wariant do wyboru, pełny cykl, test referencyjny na danych arkusza), Q-42 (wariant domyślny).
|
||||
|
||||
## 7. Pliki poza repozytorium
|
||||
|
||||
Do repo trafiła cała zawartość `docs/` poza pięcioma plikami, dopisanymi do `.gitignore`:
|
||||
|
||||
- **Kupione bazy interpretacji:** `astro19 version2026.xlsm` (ok. 50 tys. rekordów), `The Encyclopaedia of Medical Astrology…xlsx` (ok. 54 tys., na bazie książki Cornella), `astro-excel4_4parser2.xlsm`.
|
||||
- **Eksport opisów z baz:** `interpretacje.xlsx` (444 opisy).
|
||||
- **Dane osobowe:** `kosmogramy z astro2019.xlsx` — dane urodzeniowe ok. 49 prawdziwych osób.
|
||||
|
||||
Powód: pułapki (DAN-26) działają tylko wtedy, gdy każda kopia baz ma inne markery, a kopia w gicie trafiłaby do każdego klona i runnera CI. Historii gita praktycznie nie da się wyczyścić. `lots astroseek.xlsx` i `lots astroseek 2.xlsx` są identyczne — oba w repo, bo leżały w `docs/`.
|
||||
|
||||
## 8. Pytania do klienta
|
||||
|
||||
Pełna lista w arkuszu „Pytania otwarte" (Q-15…Q-48). Najważniejsze:
|
||||
|
||||
- **Q-15** — czy astrologia wedyjska, chińska i majańska to zakres;
|
||||
- **Q-16** — kiedy będą przejrzane części 3–21 specyfikacji;
|
||||
- **Q-17, Q-18** — domyślny motyw i język, zasięg przełącznika języka;
|
||||
- **Q-19** — na którym wariancie i koncie widać problemy z wgrywaniem;
|
||||
- **Q-26** — reguły łączenia orbów i bonus świateł;
|
||||
- **Q-35** — co pokazywać przy rekordach nie do oceny;
|
||||
- **Q-37…Q-41** — czasy wznoszenia: tabela czy wzór, zamiana na datę, skrajne szerokości, małe lata Wenus;
|
||||
- **Q-42** — wariant firdarii nocnej;
|
||||
- **Q-43…Q-46** — techniki z opisem „do uzupełnienia": ZR, Annual Divisions, Days of the Moon, Solar Monthly Revolutions.
|
||||
|
||||
## 9. Proponowana kolejność
|
||||
|
||||
1. **Naprawa dopasowania sygnifikatorów** (LOG-15/16/19, PRE-36). Psuje Interpretacje, Kalendarz i prompt do LLM; dopóki działa źle, każda funkcja na nim dziedziczy błąd.
|
||||
2. **Wgrywanie plików** (DAN-29/30, DAN-16, PRE-34/35) — klient nie może wgrać własnych baz.
|
||||
3. **Horoskopy, kopiuj/wklej, lokalizacje domyślne** (DAN-31–33, PRE-39–41) — codzienna wygoda, \*\*\*.
|
||||
4. **Orby i aspekty** (DAN-06, LOG-06, PRE-43/45), potem **synastria** (LOG-42, PRE-46–48) — synastria korzysta z tych samych orbów.
|
||||
5. **Opcje wyglądu** (PRE-30–33, 49–51) razem z porządkowaniem wariantów astrololo / astroklient / astrodemo, żeby nie robić tego trzy razy.
|
||||
6. Dalej techniki z \*\*\* według TO DO. Wiersze oznaczone u klienta kolorem „do uzupełnienia" — po uzupełnieniu opisu.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -74,12 +74,7 @@ class LogicClient:
|
||||
"""Strumień NDJSON w dół — przekazywany dalej ramka po ramce.
|
||||
|
||||
Idzie przez szyfrowane łącze jak reszta ruchu (PRE-16): bez tego przy
|
||||
włączonym LINK_ENCRYPTION_REQUIRED serwer odrzuciłby żądanie.
|
||||
|
||||
Nie przechodzi przez `_post`, więc pulę konta dokłada sam — tak samo,
|
||||
z kontekstu i z pierwszeństwem przed polem przyniesionym w payloadzie.
|
||||
Bez tego horoskop strumieniowany szukałby w całym udziale."""
|
||||
payload = {**payload, "tenant": KONTO.get("")}
|
||||
włączonym LINK_ENCRYPTION_REQUIRED serwer odrzuciłby żądanie."""
|
||||
with httpx.Client(timeout=httpx.Timeout(None, connect=15.0)) as client:
|
||||
yield from link_crypto.stream_lines(
|
||||
client, f"{self.base_url}{sciezka}",
|
||||
|
||||
@@ -80,66 +80,15 @@ print("WYNIK:" + json.dumps({
|
||||
'''
|
||||
|
||||
|
||||
# Generowania tekstu w tym obrazie nie ma, ale most modułów zostaje — więc jego
|
||||
# transport musi nieść pulę z sesji tak samo jak wywołania ekranów. Osobna sonda,
|
||||
# bo dokłada trasę, której w produkcie nie ma: w sondzie produktu zafałszowałaby
|
||||
# każde pytanie o to, co produkt zawiera.
|
||||
SONDA_MODULU = '''
|
||||
import json, os, hashlib, secrets
|
||||
sol = secrets.token_bytes(16)
|
||||
dk = hashlib.scrypt(b"tajne", salt=sol, n=2**14, r=8, p=1, dklen=32)
|
||||
os.environ.update(ASTROKLIENT_USERS="klient:scrypt$%s$%s" % (sol.hex(), dk.hex()),
|
||||
SESSION_SECRET="t"*64, COOKIES_INSECURE="1", RATE_LIMIT_PER_MIN="0")
|
||||
from fastapi.responses import StreamingResponse
|
||||
from starlette.testclient import TestClient
|
||||
from app.main import app, logic
|
||||
from app import security, session, features
|
||||
from app.clients import logic_client
|
||||
wyslane = {}
|
||||
def call_json(client, method, url, payload=None, **kw):
|
||||
wyslane[url.split("/chart/", 1)[1]] = (payload or {}).get("tenant")
|
||||
return {}
|
||||
def stream_lines(client, url, payload=None, **kw):
|
||||
wyslane[url.split("/chart/", 1)[1]] = (payload or {}).get("tenant")
|
||||
yield '{"type": "result", "result": {}}'
|
||||
logic_client.link_crypto.call_json = call_json
|
||||
logic_client.link_crypto.stream_lines = stream_lines
|
||||
# Trasa w miejscu modułu: woła ten sam transport co moduł generowania, a strumień
|
||||
# konsumuje równie leniwie — dopiero przy wysyłaniu odpowiedzi, nie w obsłudze trasy.
|
||||
features.ROUTES[("POST", "/sonda/modul")] = None
|
||||
@app.post("/sonda/modul")
|
||||
def sonda_modul():
|
||||
podrobione = {"profile": "natal", "tenant": "ktos-inny"}
|
||||
logic.wywolaj("/chart/prompt", podrobione, 5)
|
||||
logic.wywolaj("/chart/horoscope", podrobione, 5)
|
||||
def relay():
|
||||
yield from logic.strumien("/chart/horoscope/stream", podrobione)
|
||||
return StreamingResponse(relay(), media_type="application/x-ndjson")
|
||||
c = TestClient(app, follow_redirects=False)
|
||||
H = {"Cookie": f"{session.COOKIE}={security.issue_session('klient')}"}
|
||||
r = c.post("/sonda/modul", headers=H)
|
||||
print("WYNIK:" + json.dumps({"status": r.status_code, "pule": wyslane}))
|
||||
'''
|
||||
|
||||
|
||||
def _uruchom(drzewo: pathlib.Path, sonda: str) -> dict:
|
||||
@pytest.fixture(scope="module")
|
||||
def wynik(drzewo):
|
||||
"""Aplikacja uruchomiona w OSOBNYM PROCESIE.
|
||||
|
||||
Importy Pythona są zapamiętywane w procesie, więc sprawdzanie „produktu bez
|
||||
modułu" tam, gdzie moduł już się zaimportował, dałoby wynik fałszywie
|
||||
pozytywny."""
|
||||
r = subprocess.run([sys.executable, "-c", sonda], cwd=drzewo, capture_output=True,
|
||||
r = subprocess.run([sys.executable, "-c", SONDA], cwd=drzewo, capture_output=True,
|
||||
text=True, env={"PYTHONPATH": str(drzewo), "PATH": "/usr/bin:/bin"})
|
||||
linia = next((l for l in r.stdout.splitlines() if l.startswith("WYNIK:")), None)
|
||||
assert linia, f"sonda nie wystartowała:\n{r.stdout[-2000:]}\n{r.stderr[-2000:]}"
|
||||
return json.loads(linia[len("WYNIK:"):])
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def wynik(drzewo):
|
||||
return _uruchom(drzewo, SONDA)
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def wynik_modulu(drzewo):
|
||||
return _uruchom(drzewo, SONDA_MODULU)
|
||||
|
||||
@@ -44,12 +44,3 @@ def test_the_pool_comes_from_the_session_not_from_the_form(wynik):
|
||||
"""Pula jedzie z zalogowanego konta. Gdyby brała się z żądania, wystarczyłoby
|
||||
podstawić cudzy login, żeby czytać cudze pliki — sonda właśnie to próbuje."""
|
||||
assert wynik["pula"] == "klient"
|
||||
|
||||
|
||||
def test_module_transport_carries_the_session_pool_too(wynik_modulu):
|
||||
"""Prompt i horoskop idą mostem modułów, a strumień horoskopu obok `_post` —
|
||||
i właśnie tam pula ginęła, więc horoskop szukałby w całym udziale. Sonda
|
||||
podsuwa cudzy login w payloadzie; ma wygrać konto z sesji."""
|
||||
assert wynik_modulu["status"] == 200
|
||||
assert wynik_modulu["pule"] == {"prompt": "klient", "horoscope": "klient",
|
||||
"horoscope/stream": "klient"}
|
||||
|
||||
@@ -294,10 +294,6 @@ class PromptRequest(BaseModel):
|
||||
from_date: str | None = None
|
||||
to_date: str | None = None
|
||||
techniques: list[str] | None = None
|
||||
# Pula konta (PRE-29) — puste = cały udział. Warstwa logiczna niczego tu nie
|
||||
# rozstrzyga, tylko przenosi: kto do jakiej puli ma prawo, wie warstwa
|
||||
# prezentacji, bo to ona zna konta. HoroscopeRequest dziedziczy to pole.
|
||||
tenant: str = ""
|
||||
|
||||
|
||||
@app.post("/chart/prompt")
|
||||
@@ -340,7 +336,7 @@ def chart_prompt(req: PromptRequest) -> dict:
|
||||
report: dict = {"objects": []}
|
||||
try:
|
||||
report = build_report(
|
||||
chart["positions"], DataClient(tenant=req.tenant),
|
||||
chart["positions"], DataClient(),
|
||||
aspects=chart.get("aspects"), parallels=chart.get("parallels"),
|
||||
per_object_limit=req.limit,
|
||||
)
|
||||
@@ -362,7 +358,7 @@ def chart_prompt(req: PromptRequest) -> dict:
|
||||
events = build_timeline(engine, moment, points, req.from_date, req.to_date,
|
||||
req.techniques)
|
||||
try:
|
||||
interpret_events(events, DataClient(tenant=req.tenant))
|
||||
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,
|
||||
@@ -631,7 +627,7 @@ def chart_timeline(req: TimelineRequest) -> dict:
|
||||
if req.interpret:
|
||||
from app.significators import interpret_events
|
||||
try:
|
||||
interpret_events(events, DataClient(tenant=req.tenant))
|
||||
interpret_events(events, DataClient())
|
||||
except httpx.HTTPError as e:
|
||||
out["data_error"] = f"Warstwa danych niedostępna: {e}"
|
||||
out.update(count=len(events), events=events)
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
"""Pula konta (PRE-29) dociera do warstwy danych z KAŻDEJ trasy, która w niej szuka.
|
||||
|
||||
Trasy są wołane przez HTTP, a DataClient podmieniony na szpiega, który zapisuje,
|
||||
z jaką pulą go zbudowano i czy naprawdę o coś zapytał. Celowo nie czytamy źródła:
|
||||
napis `tenant=` w kodzie nie mówi, czy pula przeżyła drogę od żądania do bazy.
|
||||
A pominięta pula nie daje błędu — daje cały udział, czyli cudze pliki.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
import pytest
|
||||
from starlette.testclient import TestClient
|
||||
|
||||
from app import link_crypto, main
|
||||
|
||||
POOL = "konto-a"
|
||||
PERIOD = {"from_date": "2024-01-01", "to_date": "2024-06-30"}
|
||||
# Nieznany dostawca kończy horoskop na LLMError zaraz po zbudowaniu promptu —
|
||||
# bez sieci i bez klucza API, a warstwa danych jest już wtedy przepytana.
|
||||
NO_PROVIDER = {"provider": "brak-dostawcy"}
|
||||
|
||||
ROUTES = [
|
||||
pytest.param("/chart/report", {}, id="report"),
|
||||
pytest.param("/chart/timeline", {**PERIOD, "interpret": True}, id="timeline"),
|
||||
pytest.param("/chart/prompt", {"profile": "natal"}, id="prompt-natal"),
|
||||
pytest.param("/chart/prompt", {"profile": "period", **PERIOD}, id="prompt-period"),
|
||||
pytest.param("/chart/horoscope", {"profile": "natal", **NO_PROVIDER}, id="horoscope-natal"),
|
||||
pytest.param("/chart/horoscope", {"profile": "period", **PERIOD, **NO_PROVIDER},
|
||||
id="horoscope-period"),
|
||||
pytest.param("/chart/horoscope/stream", {"profile": "natal", **NO_PROVIDER},
|
||||
id="stream-natal"),
|
||||
pytest.param("/chart/horoscope/stream", {"profile": "period", **PERIOD, **NO_PROVIDER},
|
||||
id="stream-period"),
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def clients(monkeypatch, own_engine) -> list:
|
||||
"""Każdy DataClient zbudowany w trakcie żądania, w kolejności tworzenia."""
|
||||
built: list = []
|
||||
|
||||
class SpyDataClient:
|
||||
def __init__(self, base_url: str | None = None, tenant: str = "") -> None:
|
||||
self.tenant = tenant
|
||||
self.searches = 0
|
||||
built.append(self)
|
||||
|
||||
def search(self, key, value, exact, limit, fields=None):
|
||||
self.searches += 1
|
||||
return {"provider": "szpieg", "total": 0, "rows": []}
|
||||
|
||||
monkeypatch.setattr(main, "DataClient", SpyDataClient)
|
||||
monkeypatch.setattr(main, "get_engine", lambda: own_engine)
|
||||
for env in ("INTERNAL_TOKEN", link_crypto.ENV_REQUIRED, link_crypto.ENV_PRESENTATION_LOGIC):
|
||||
monkeypatch.delenv(env, raising=False)
|
||||
return built
|
||||
|
||||
|
||||
def _call(path: str, body: dict, reference_moment) -> None:
|
||||
payload = {"when_utc": reference_moment.when_utc.isoformat(),
|
||||
"lat": reference_moment.lat, "lon": reference_moment.lon, **body}
|
||||
r = TestClient(main.app).post(path, json=payload)
|
||||
assert r.status_code == 200, r.text
|
||||
if path.endswith("/stream"):
|
||||
# strumień zawsze odpowiada 200 — o porażce mówi dopiero ostatnia ramka
|
||||
last = json.loads(r.text.strip().splitlines()[-1])
|
||||
assert last["type"] == "result", last
|
||||
|
||||
|
||||
@pytest.mark.parametrize("path, body", ROUTES)
|
||||
def test_account_pool_reaches_the_data_layer(clients, reference_moment, path, body):
|
||||
_call(path, {**body, "tenant": POOL}, reference_moment)
|
||||
|
||||
# Bez choćby jednego wyszukiwania test przechodziłby na pusto.
|
||||
assert any(c.searches for c in clients), "trasa nie zapytała warstwy danych"
|
||||
assert {c.tenant for c in clients} == {POOL}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("path, body", ROUTES)
|
||||
def test_without_a_pool_the_route_searches_the_whole_share(clients, reference_moment, path, body):
|
||||
"""Pełna aplikacja nie zna kont i nie wysyła puli — dla niej nic się nie zmienia."""
|
||||
_call(path, body, reference_moment)
|
||||
|
||||
assert any(c.searches for c in clients)
|
||||
assert {c.tenant for c in clients} == {""}
|
||||
Reference in New Issue
Block a user