From 413c46b5ddb61d7a62c9c9fe9beaa90575b1303f Mon Sep 17 00:00:00 2001 From: migatu Date: Wed, 1 Jul 2026 10:55:04 +0200 Subject: [PATCH] =?UTF-8?q?Napraw=20lokalne=20=C5=9Brodowisko=20dev:=20Par?= =?UTF-8?q?quet=20mixed-types,=20brak=20deps,=20compose?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Trzy usterki uniemożliwiające uruchomienie stosu lokalnie: 1. Warstwa danych wykładała się na starcie przy zapisie Parquet dla realnych plików (np. Encyclopaedia of Medical Astrology) — kolumny o mieszanych typach (int+str+NaN). frame_cache.put() zapisuje teraz ramkę jako string (warstwa i tak wyszukuje po tekście). Dodatkowo warmup() jest odporny: pojedynczy uszkodzony plik nie blokuje startu usługi. 2. Brakowało kroku instalacji zależności — dev-logic/dev-presentation padały na 'No module named httpx'. Nowy cel `make install` instaluje zależności WSZYSTKICH warstw do aktywnego venv. README zaktualizowane (instalowało wcześniej tylko warstwę danych). 3. `make up` zakładał `docker compose`, którego użytkownik nie ma. Makefile wykrywa `docker compose` lub `docker-compose`, a przy braku obu podaje czytelną instrukcję trybu lokalnego. Dodano `make test` i `make clean-cache`. Zweryfikowane end-to-end na realnym środowisku (.env, Python 3.14) i danych: warmup przechodzi (3 pliki), cały stos wstaje, formularz → logika → silnik Skyfield zwraca poprawne pozycje. Co-Authored-By: Claude Opus 4.8 --- Makefile | 78 ++++++++++++++----- README.md | 13 +++- services/data/app/cache/frame_cache.py | 9 ++- services/data/app/providers/excel_provider.py | 5 +- 4 files changed, 81 insertions(+), 24 deletions(-) diff --git a/Makefile b/Makefile index cea3734..00231e9 100644 --- a/Makefile +++ b/Makefile @@ -1,31 +1,56 @@ -.PHONY: help up down sample reindex migrate sql dev-data dev-logic dev-presentation +.PHONY: help install up down sample reindex migrate sql test clean-cache \ + dev-data dev-logic dev-presentation + +# Autodetekcja narzędzia compose: nowe "docker compose" albo stare "docker-compose". +COMPOSE := $(shell if docker compose version >/dev/null 2>&1; then echo "docker compose"; \ + elif command -v docker-compose >/dev/null 2>&1; then echo "docker-compose"; fi) + +# Python aktywnego środowiska (venv). Nadpisz: make install PY=python3.12 +PY ?= python help: - @echo "up - uruchom wszystkie 3 warstwy (docker compose)" - @echo "down - zatrzymaj" - @echo "sample - wygeneruj przykładowe pliki .xlsx" - @echo "reindex - zbuduj cache + indeks warstwy bazodanowej" - @echo "migrate - ETL: Excel -> SQL" - @echo "sql - uruchom z warstwą SQL (DATA_PROVIDER=sql)" - @echo "dev-* - uruchom pojedynczą warstwę lokalnie (bez dockera)" + @echo "install - zainstaluj zależności WSZYSTKICH warstw do aktywnego venv" + @echo "sample - wygeneruj przykładowe pliki .xlsx" + @echo "dev-data - uruchom warstwę danych lokalnie (:8002)" + @echo "dev-logic - uruchom warstwę logiczną lokalnie (:8001)" + @echo "dev-presentation - uruchom warstwę prezentacji lokalnie (:8000)" + @echo "test - testy warstwy logicznej (silnik efemeryd)" + @echo "clean-cache - wyczyść regenerowalny cache warstwy danych" + @echo "reindex - zbuduj cache + indeks warstwy bazodanowej" + @echo "migrate - ETL: Excel -> SQL" + @echo "up / down - uruchom / zatrzymaj cały stos (Docker; wykryto: $(COMPOSE))" + @echo "sql - Docker z warstwą SQL (DATA_PROVIDER=sql)" + @echo "" + @echo "Szybki start lokalny (bez Dockera), w 3 terminalach:" + @echo " make install" + @echo " make sample # opcjonalnie: dane przykładowe" + @echo " make dev-data # terminal 1" + @echo " make dev-logic # terminal 2" + @echo " make dev-presentation # terminal 3 -> http://localhost:8000" -up: - docker compose up --build - -down: - docker compose down +# --- instalacja zależności do aktywnego środowiska --- +install: + $(PY) -m pip install \ + -r services/data/requirements.txt \ + -r services/logic/requirements.txt \ + -r services/presentation/requirements.txt + @echo "OK. Zależności warstw zainstalowane. (Silnik AGPL 'engine-swisseph' instaluje się osobno.)" sample: - cd services/data && python scripts/make_sample_data.py + cd services/data && $(PY) scripts/make_sample_data.py reindex: - cd services/data && python -m app.ingest.build_index + cd services/data && $(PY) -m app.ingest.build_index migrate: - cd services/data && python -m app.ingest.to_sql + cd services/data && $(PY) -m app.ingest.to_sql -sql: - DATA_PROVIDER=sql docker compose up --build +test: + cd services/logic && PYTHONPATH=. $(PY) -m pytest tests -q + +clean-cache: + rm -rf services/data/.cache/* services/logic/.ephemeris/* 2>/dev/null || true + @echo "Cache wyczyszczony." # --- lokalny dev (3 osobne terminale) --- dev-data: @@ -36,3 +61,20 @@ dev-logic: dev-presentation: cd services/presentation && LOGIC_URL=http://localhost:8001 uvicorn app.main:app --reload --port 8000 + +# --- Docker (opcjonalnie) --- +up: + @if [ -z "$(COMPOSE)" ]; then \ + echo "Nie znaleziono Docker Compose (ani 'docker compose', ani 'docker-compose')."; \ + echo "Użyj trybu lokalnego: make install, potem dev-data / dev-logic / dev-presentation."; \ + exit 1; \ + fi + $(COMPOSE) up --build + +down: + @if [ -z "$(COMPOSE)" ]; then echo "Brak Docker Compose."; exit 1; fi + $(COMPOSE) down + +sql: + @if [ -z "$(COMPOSE)" ]; then echo "Brak Docker Compose."; exit 1; fi + DATA_PROVIDER=sql $(COMPOSE) up --build diff --git a/README.md b/README.md index 64cc77d..7b6969e 100644 --- a/README.md +++ b/README.md @@ -28,13 +28,20 @@ make up # zbuduj i uruchom 3 warstwy # otwórz http://localhost:8000 ``` -## Szybki start (lokalnie, 3 terminale) +## Szybki start (lokalnie, bez Dockera — zalecane) +```bash +python -m venv .env && source .env/bin/activate # venv (jednorazowo) +make install # zależności WSZYSTKICH warstw +make sample # opcjonalnie: dane przykładowe +``` +Potem w 3 osobnych terminalach (w każdym `source .env/bin/activate`): ```bash -cd services/data && pip install -r requirements.txt && python scripts/make_sample_data.py make dev-data # terminal 1 -> :8002 make dev-logic # terminal 2 -> :8001 -make dev-presentation # terminal 3 -> :8000 +make dev-presentation # terminal 3 -> :8000 -> http://localhost:8000 ``` +> `make install` instaluje zależności wszystkich trzech warstw do aktywnego venv. +> Testy silnika: `make test`. Wyczyszczenie cache: `make clean-cache`. ## Modułowość — dowód - Wymień prezentację (np. na SPA/React) → reszta bez zmian, kontrakt `/api/query` stały. diff --git a/services/data/app/cache/frame_cache.py b/services/data/app/cache/frame_cache.py index c110c14..b699151 100644 --- a/services/data/app/cache/frame_cache.py +++ b/services/data/app/cache/frame_cache.py @@ -31,5 +31,10 @@ class FrameCache: return None def put(self, fp: str, sheet: str, frame: pd.DataFrame) -> None: - # astype(str) na kolumnach object zapewnia stabilny zapis Parquet - frame.to_parquet(self._path(fp, sheet), index=False) + # Kolumny z heterogenicznych plików Excela bywają mieszane (int + str + + # NaN w jednej kolumnie) — pyarrow tego nie zapisze. Warstwa danych i tak + # operuje na tekście (wyszukiwanie po .astype(str)), więc zapisujemy ramkę + # jako string (brak wartości -> pusty tekst). Gwarantuje to stabilny zapis + # Parquet niezależnie od zawartości pliku źródłowego. + safe = frame.astype("string").fillna("") + safe.to_parquet(self._path(fp, sheet), index=False) diff --git a/services/data/app/providers/excel_provider.py b/services/data/app/providers/excel_provider.py index a136ea2..d564010 100644 --- a/services/data/app/providers/excel_provider.py +++ b/services/data/app/providers/excel_provider.py @@ -82,7 +82,10 @@ class ExcelDataProvider(DataProvider): def warmup(self) -> None: for path in self._excel_files(): - self._ensure_indexed(path) + try: + self._ensure_indexed(path) + except Exception as e: # jeden uszkodzony plik nie może zablokować startu + print(f"[data] pominięto plik przy indeksowaniu: {path} — {e}") def _excel_files(self) -> list[str]: base = Path(self.s.excel_dir)