astrololo: generowanie tekstu jako moduł odłączalny (2/5)
build / build (push) Failing after 2s
Testy / Testy warstwy logicznej (silnik) (push) Failing after 4s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Failing after 3s
Testy / Testy warstwy bazodanowej (ochrona baz) (push) Failing after 3s
Testy / Testy astrodemo (push) Failing after 3s
Testy / Build obrazu silnika B (swisseph) (push) Failing after 2s
Testy / Kontrola składni wszystkich warstw (push) Failing after 3s
build / build (push) Failing after 2s
Testy / Testy warstwy logicznej (silnik) (push) Failing after 4s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Failing after 3s
Testy / Testy warstwy bazodanowej (ochrona baz) (push) Failing after 3s
Testy / Testy astrodemo (push) Failing after 3s
Testy / Build obrazu silnika B (swisseph) (push) Failing after 2s
Testy / Kontrola składni wszystkich warstw (push) Failing after 3s
Drugi z pięciu kroków budowy trzech produktów. Czysty refaktor — zachowanie
aplikacji się nie zmienia, liczba testów rośnie tylko o nowe.
DLACZEGO WARUNEK W SZABLONIE NIE WYSTARCZA. Dotąd generowanie było chowane przez
`{% if can(request, 'ai') %}`. To sprawia, że funkcji nie WIDAĆ, ale nie że jej
NIE MA: plik szablonu dalej leży w obrazie i dalej zawiera jej nazwy, więc `grep`
po kontenerze pokazuje wszystko, czego warunek nie pokazał na ekranie. Astroklient
ma nie mieć śladu, nie mieć wyłączoną funkcję — więc granica musi być KATALOGIEM.
CO SIĘ PRZENIOSŁO do app/dodatki/: dwa szablony, pięć plików statycznych, trasa
strumienia, cztery metody klienta warstwy logicznej, wpis w katalogu funkcji,
wpisy tras i zasobów, akcje formularza i reguły CSS nazywające funkcję
(`textarea.prompt`).
JAK APLIKACJA O TO PYTA. Neutralny most `app/rozszerzenia.py` odpowiada wyłącznie
na pytanie „czy coś jest podpięte i co wnosi". Sam nie wymienia ani jednej nazwy —
pilnuje tego osobny test, bo most jest w KAŻDYM obrazie. Dlatego też katalog
nazywa się `dodatki`, a nie `ai`: nazwa w instrukcji importu byłaby dokładnie tym
śladem, którego wydzielanie ma się pozbyć.
TRZY MIEJSCA, KTÓRE OKAZAŁY SIĘ TRUDNE:
1. Pola formularza. FastAPI czyta je z SYGNATURY, a wspólny handler nie może
wymieniać `prompt_budget` ani `llm_provider`. Rozwiązane zależnością: moduł
deklaruje własne pola u siebie, handler wie tylko, że dostaje słownik.
2. Klient warstwy logicznej jest wspólny, więc metody `prompt`/`horoscope`/
`llm_models` musiały z niego wyjść. Wspólny klient daje teraz samą drogę
w dół (`wywolaj`, `pobierz`, `strumien`) — z szyfrowaniem łącza i tokenem
międzywarstwowym; co nią pojedzie, jest sprawą modułu.
3. Podtytuł ekranu Skompiluj miał wariant „z AI" i wariant bez. Zamiast warunku
jedno zdanie prawdziwe niezależnie od tego, jakie moduły są w obrazie.
DOCKERFILE. `COPY . .` wnosiło do obrazu także testy — a plik testowy nazywa
funkcje wprost. Teraz wchodzi wyłącznie `app/`.
TEST GRANICY. `test_ai_tylko_w_module.py` przechodzi po wszystkim poza modułem
i szuka siedemnastu słów. Znalazł dwanaście resztek, których nie widziałem:
komentarze w main.py i security.py, przykład `models.js` w komentarzu features.py,
komentarze w compile.js i w trzech arkuszach, oraz regułę `textarea.prompt`,
która przy rozbijaniu CSS trafiła do arkuszy ekranów zamiast do modułu.
Ma też kontrolę pozytywną (te słowa MAJĄ padać w module — inaczej test
przechodziłby, gdyby funkcję wydrążono) i test odłączalności mostu.
SPRAWDZONE NA KOPII BEZ MODUŁU: aplikacja startuje, wszystkie ekrany oddają 200,
katalog ma 13 funkcji zamiast 14, trasy strumienia nie ma, spreparowane
`action=prompt` wraca do akcji domyślnej bez śladu, a `grep` po drzewie nie
znajduje ani jednego ze słów.
Testy: presentation 362, logic 342, data 37, render 41.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit was merged in pull request #80.
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
// Zapamiętana interpretacja natalna (PRE-23).
|
||||
//
|
||||
// Odpowiednik predictions.js, tylko że natalna jest JEDNA — dotyczy momentu
|
||||
// urodzenia, nie wybranego okresu. Ponowne wygenerowanie podmienia poprzednią.
|
||||
//
|
||||
// Po co magazyn: zakładka „Skompiluj" składa raport z trzech kawałków liczonych
|
||||
// w różnych miejscach (horoskop, interpretacja natalna, predykcje okresowe).
|
||||
// Bez zapamiętania trzeba by je generować od nowa na jednej stronie.
|
||||
//
|
||||
// localStorage, tak jak reszta stanu (PRE-21/22) — serwer zostaje bezstanowy.
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var KEY = 'astrololo.natal.v1';
|
||||
|
||||
function read() {
|
||||
try {
|
||||
var v = JSON.parse(localStorage.getItem(KEY) || 'null');
|
||||
return (v && typeof v === 'object' && v.text) ? v : null;
|
||||
} catch (e) {
|
||||
return null; // uszkodzony wpis nie może zablokować strony
|
||||
}
|
||||
}
|
||||
|
||||
function clear() {
|
||||
try { localStorage.removeItem(KEY); } catch (e) { /* trudno */ }
|
||||
}
|
||||
|
||||
function val(sel) {
|
||||
var el = document.querySelector(sel);
|
||||
return el ? el.value.trim() : '';
|
||||
}
|
||||
|
||||
function save() {
|
||||
var text = val('#horoscopeText');
|
||||
if (!text) return false; // nie ma czego zapamiętać
|
||||
var entry = {
|
||||
text: text,
|
||||
person: val('input[name=person]'),
|
||||
date: val('input[name=date]'),
|
||||
time: val('input[name=time]'),
|
||||
tz_offset: val('input[name=tz_offset]'),
|
||||
lat: val('input[name=lat]'),
|
||||
lon: val('input[name=lon]'),
|
||||
saved_at: new Date().toISOString()
|
||||
};
|
||||
try {
|
||||
localStorage.setItem(KEY, JSON.stringify(entry));
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false; // przepełniony magazyn — zgłaszamy wyżej
|
||||
}
|
||||
}
|
||||
|
||||
function note(msg) {
|
||||
var el = document.getElementById('natalNote');
|
||||
if (el) el.textContent = msg;
|
||||
}
|
||||
|
||||
function saveCurrent() {
|
||||
var text = val('#horoscopeText');
|
||||
if (!text) return;
|
||||
if (save()) {
|
||||
note('Interpretacja natalna zapamiętana — trafi do raportu (zakładka Skompiluj).');
|
||||
} else {
|
||||
// Interpretacje bywają długie; cicha strata byłaby najgorsza.
|
||||
note('Nie udało się zapamiętać — magazyn przeglądarki pełny. '
|
||||
+ 'Usuń starsze predykcje i spróbuj ponownie.');
|
||||
}
|
||||
}
|
||||
|
||||
function ready(fn) {
|
||||
if (document.readyState !== 'loading') fn();
|
||||
else document.addEventListener('DOMContentLoaded', fn);
|
||||
}
|
||||
|
||||
ready(function () {
|
||||
if (!document.getElementById('natalNote')) return; // nie ta zakładka
|
||||
|
||||
// 1) interpretacja policzona przez okno postępu (strumień)
|
||||
document.addEventListener('astrololo:horoscope', function (e) {
|
||||
if (!e.detail || e.detail.profile === 'natal') saveCurrent();
|
||||
});
|
||||
|
||||
// 2) wariant bez strumienia: strona wróciła z gotowym wynikiem po zwykłym
|
||||
// POST. Zapis nadpisuje jeden slot, więc odświeżenie niczego nie mnoży.
|
||||
if (document.querySelector('#horoscopeText')) saveCurrent();
|
||||
|
||||
var status = read();
|
||||
if (status) {
|
||||
note('W raporcie jest zapamiętana interpretacja natalna z '
|
||||
+ String(status.saved_at).slice(0, 10) + '.');
|
||||
}
|
||||
});
|
||||
|
||||
window.astrololoNatal = { read: read, clear: clear, key: KEY };
|
||||
})();
|
||||
Reference in New Issue
Block a user