Metodyka

Skąd bierzemy dane, co z nimi robimy i czego nie wiemy. Źródła, ograniczenia i cały pipeline krok po kroku.

Źródła danych

Żabka – główne źródło

Lokalizacje sklepów pochodzą z publicznego pliku JSON na zabka.pl – to ten sam plik, z którego korzysta sklepowa wyszukiwarka na ich stronie. Plik ma około 13 200 sklepów, każdy ze współrzędnymi GPS, adresem, godzinami otwarcia i flagami (piec Merrychef, otwarte w niedzielę, 24/7). Z pliku wyrzucamy dane osobowe dyrektorów, stałe pola (kraj, aktywność) i marketingowe URLe. Zostają same dane analityczne.

Podział administracyjny i mapowanie

Strukturę podziału terytorialnego Polski (trzy poziomy: województwa, powiaty i gminy) budujemy bezpośrednio z oficjalnych rejestrów GUS BDL oraz TERYT jako pierwszy krok procesu ETL. Sklepy oraz paczkomaty przypisujemy do tej hierarchii relacyjnie na podstawie ich nazw miejscowości, z wykorzystaniem oficjalnej Uniwersalnej Usługi Geokodowania GUGiK do jednoznacznego rozstrzygania przynależności mniejszych miejscowości i wsi, wraz z zapasowym dopasowaniem przestrzennym. Eliminuje to potrzebę stosowania uproszczonych plików GeoJSON i powolnych testów punkt-w-poligon.

Gospodarka – GUS BDL

Zarobki, bezrobocie i populacja na poziomie powiatów pochodzą z Banku Danych Lokalnych (zmienne 64428, 60270, 72305). Pobieramy je raz w roku. Nazwy powiatów trzeba normalizować – GUS lubi dodawać prefiksy (Powiat m.), przyrostki czasowe (od 2023) i zmieniać nazwy (jeleniogórski staje się karkonoski w 2021). Wszystko to czyścimy przed zmatchowaniem. Dokładność: dane pochodzą z roku, w którym GUS je opublikował, nie z bieżącego miesiąca.

Parki narodowe i krajobrazowe – GDOŚ

Granice parków i ich otulin pobieramy z WFS GDOŚ (warstwy ParkiNarodowe + ParkiKrajobrazowe). Mamy 259 obiektów: 46 parków narodowych z otulinami i 213 krajobrazowych. Każdy sklep sprawdzamy punkt-w-poligon – jeśli trafił do parku lub otuliny, dostaje flagę.

Wysokość terenu – GUGiK NMT

Wysokość n.p.m. z Numerycznego Modelu Terenu GUGiK (GetHByXY, współrzędne PL-1992). To jest opt-in (flaga --elevation), bo ponad 13 tysięcy zapytań HTTP. Wyniki trafiają do lokalnego cache (elevation_cache.json) – ponowne odpalenie ETL nie odpytuje punktów, które już mamy. Uwaga: serwis GUGiK przyjmuje tylko współrzędne PL-1992, nie WGS84, więc w ETL jest konwersja własnym kodem (bez pyproj).

Paczkomaty InPost

Punkty paczkowe z publicznego API InPost ShipX (typ parcel_locker). Każdy paczkomat przypisujemy do podziału administracyjnego za pomocą tego samego geokodera GUGiK i relacyjnego dopasowania co sklepy. To jest jednorazowe pobranie – sieć paczkomatów rośnie wolno, mamy około 31 800 punktów. Porównanie Żabek vs paczkomaty – z podziałem na województwa, powiaty i gminy – jest na dashboardzie.

Płazy – GBIF

Obserwacje płazów z Global Biodiversity Information Facility (klasa Amphibia, taxonKey=131) w Polsce. Mamy około 46 000 rekordów ze współrzędnymi. BallTree (haversine) zlicza obserwacje w promieniu 5 km od każdego sklepu. Najbardziej żabia Żabka ma 2 028 obserwacji – i to jest w Ursynowie, nie w żadnym parku narodowym.

Sąsiedztwo – obliczenia lokalne

Odległość do najbliższej innej Żabki obliczamy lokalnie przez BallTree (haversine, k=2). Najbardziej odosobniony sklep ma 27 km do najbliższego sąsiada (Michałowo, podlaskie). Punkt najdalszy od jakiejkolwiek Żabki – pusta przestrzeń w Bieszczadach – to około 46,5 km. Zero zapytań sieciowych, czysta geometria na danych już wczytanych.

Pogoda i niebo – Open-Meteo, OpenLightMap

Aktualne dane pogodowe z darmowego API Open-Meteo (temperatura, zachmurzenie) dla najzimniejszej i najcieplejszej lokalizacji Żabki. Podobnie najciemniejsze i najjaśniejsze niebo z OpenLightMap. Dane live, pobierane przy otwarciu strony. Nie wypiekamy ich do bazy – pogoda z wczoraj nikomu nie potrzebna.

Potok ETL

Mamy codzienny potok, który pobiera dane, czyści, wzbogaca i zapisuje do bazy. Wygląda tak:

  1. Pobranie. Pobieramy publiczny plik JSON z lokalizacjami sklepów. Jak API niedostępne, bierzemy z lokalnego pliku fallback z data/input/.
  2. Oczyszczenie. Deduplikacja po storeId (około 32 duplikaty w źródłach), normalizacja nazw miast (LEGNICA staje się Legnica), usuwanie <br> i kodów pocztowych z ulic, wyrzucenie danych osobowych i stałych pól.
  3. Wzbogacenie. Każde źródło niezależnie: regiony, sąsiedztwo, płazy, parki, elewacja, GUS, paczkomaty. Błąd jednego źródła nie przerywa potoku – kolumna zostaje NULL i ETL idzie dalej. Każde zapytanie sieciowe ma retry: 3 próby z odstępem 60 sekund.
  4. Diff. Porównujemy z poprzednim snapshotem: nowe store_id to sklep otwarty, zniknięte – zamknięty. Wynik zapisujemy jako historia zmian.
  5. Zapis do bazy. Zapis do DuckDB. Nowe kolumny dodajemy przez ALTER TABLE ADD COLUMN IF NOT EXISTS (bez DEFAULT, bo DuckDB nie obsługuje DEFAULT w ALTER). Baza przechowuje ostatnie 6 miesięcy snapshotów – starsze usuwamy.
  6. Czyszczenie cache. Po udanym ETL czyścimy cache Redis. Backend odbudowuje odpowiedzi na następnym zapytaniu. Redis jest opcjonalny – jak niedostępny, backend działa bez niego.

Czego nie gwarantujemy

Ograniczenia danych

  • Około 218 sklepów nie ma daty otwarcia w źródłach – nie pojawiają się na wykresie historii wzrostu.
  • Populacje powiatów to dane roczne z GUS, nie bieżące szacunki.
  • Sklepy na granicach uproszczonych poligonów mogą trafić do sąsiedniego województwa (dotyczy około 3 punktów).
  • Godziny otwarcia pochodzą ze źródła Żabki, nie z weryfikacji terenowej.
  • Dane pogodowe są live – mogą różnić się od off-line bazy.
  • Obserwacje GBIF zależą od aktywności obserwatorów – mniej zamieszkałe regiony mają mniej rekordów nie dlatego, że jest mniej płazów, tylko dlatego, że mniej osób tam patrzy.
  • Brakujące źródło wzbogacenia zostawia kolumnę NULL – tabela jest spójna, ale niekompletna.
  • Soft delete: sklepy, które zniknęły ze źródła, dostają deleted_at w bazie. Zapytania domyślnie filtrują deleted_at IS NULL, więc zamknięte sklepy nie psują statystyk.