Files
astrololo/services/data
gitea caf4fd80d1 feat(astroklient): pule plików per konto i izolacja od produkcji (PRE-29)
Demo ma być rozdawane szeroko i różnym osobom, więc pierwsza wersja — jedno konto
na produkcyjnej warstwie danych — nie nadawała się do użycia: każdy dostawałby
dostęp do oryginalnych baz, a wgrania jednego klienta widzieliby wszyscy.

IZOLACJA OD PRODUKCJI. Warstwa danych i logiczna demo są osobne (manifesty w repo
deploy). Osobna musi być TEŻ LOGICZNA, bo zna ona jeden adres warstwy danych —
demo korzystające z produkcyjnej logiki i tak trafiłoby na produkcyjne bazy.

PULE PER KONTO w warstwie danych. Zapytanie i lista plików niosą nazwę puli;
puste = cały udział, czyli produkcja działa dokładnie jak dotąd i o pulach nic
nie wie. Nazwa puli przechodzi przez sito dopuszczające wyłącznie znaki bezpieczne
w nazwie katalogu — „../..” albo ukośnik wyprowadziłyby zapytanie wprost do cudzych
baz, więc sito ZAMIENIA podejrzane znaki zamiast ufać, że nikt ich nie poda.

PULA MUSI BYĆ W KLUCZU CACHE ZAPYTAŃ. Bez tego wynik policzony dla jednego konta
trafiłby z cache do drugiego — cicha wymiana treści baz między klientami,
niewidoczna w logach i nie do wykrycia z zewnątrz. Osobny test tego pilnuje.

PULA WYNIKA Z LOGINU, nigdy z żądania. Klient warstwy logicznej jest budowany
per żądanie i związany z pulą zalogowanej osoby; gdyby nazwa przychodziła
z formularza, wystarczyłoby podstawić cudzy login. Test wysyła `tenant`, `user`
i `login` w polach formularza i sprawdza, że nie mają na nią wpływu.

Pulę wstrzykujemy w INSTANCJĘ klienta, nie w sygnatury metod. Argumentem trzeba
by ją przeprowadzić przez protokół DataSource i build_report — kod, który o kontach
nie ma prawa nic wiedzieć — a każde nowe wywołanie byłoby okazją, żeby o nią
zapomnieć i sięgnąć nie tam.

Konta demo to lista `login:sekret` (DEMO_USERS), bo jedno wspólne konto oznaczałoby
wspólną pulę. Format i skrypt haseł te same, co w głównej aplikacji.

Pula klienta to JEDEN KATALOG, więc przejście na pełną wersję nie oznacza utraty
wgrań — procedurę importu opisuje runbook w repo deploy.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 11:57:05 +02:00
..

Warstwa bazodanowa (data)

Niezależna usługa. Jedyne zadanie: wyszukać dane i podać je w górę. Nie zna warstwy logicznej ani prezentacji — komunikacja wyłącznie przez HTTP/JSON (models.py).

API

  • POST /searchSearchQuerySearchResult
  • GET /healthHealthInfo

Architektura wewnętrzna

providers/        wymienna implementacja (wzorzec Repository)
  base.py         interfejs DataProvider  ← kontrakt
  excel_provider  dziś: Excel + 4 poziomy cache
  sql_provider    jutro: SQL (ten sam interfejs)
  factory.py      DATA_PROVIDER=excel|sql
excel/            wykrywanie nagłówka + mapowanie układu kolumn (jedyne miejsce znające .xlsx)
cache/            fingerprint, schema(L1), frame/parquet(L2), query(L3), index(L4)
ingest/           build_index.py (warmup), to_sql.py (migracja ETL)

Cache — dlaczego szybko

Poziom Co cache'uje Zysk
L1 schema.db wykryty nagłówek + układ kolumn per plik brak ponownego skanu heurystyką
L2 Parquet znormalizowany arkusz 10100× szybciej niż parsowanie .xlsx
L3 QueryCache wynik zapytania (TTL/LRU) powtarzalne zapytania natychmiast
L4 index.db odwrócony indeks wartość→plik otwieramy tylko trafione pliki, nie setki

Unieważnianie: klucz = odcisk pliku (mtime+rozmiar, opcjonalnie sha256). Zmiana pliku → inny odcisk → automatyczny przebudowa.

Uruchomienie lokalne

pip install -r requirements.txt
python scripts/make_sample_data.py     # przykładowe .xlsx
python -m app.ingest.build_index       # (opcjonalnie) prebuild indeksu
uvicorn app.main:app --port 8002

Migracja do SQL (gdy nadejdzie czas)

python -m app.ingest.to_sql            # Excel -> tabela 'records' + indeksy
export DATA_PROVIDER=sql               # przełącz warstwę — reszta systemu bez zmian