Open Data polskich przetargów 2024–2025 — 1,4 mln ogłoszeń z BZP i TED na GitHubie
Pełna baza polskich zamówień publicznych z dwóch ostatnich lat jest teraz otwartym zbiorem danych. CSV i Parquet, licencja CC BY 4.0, anonimizacja JDG zgodna z RODO. 1,4 mln ogłoszeń, 23 tys. zamawiających, 82 tys. wykonawców — do pobrania w minutę, do cytowania przez CITATION.cff.
Zespół analityków danych specjalizujących się w zamówieniach publicznych. Dane pochodzą z oficjalnych źródeł: BZP i TED.
Od dzisiaj pełna baza polskich zamówień publicznych z lat 2024–2025 jest dostępna jako otwarty zbiór danych na GitHubie. 1,4 miliona ogłoszeń z BZP i TED, 23 tysiące profili zamawiających, 82 tysiące profili wykonawców. Format CSV i Parquet, licencja CC BY 4.0, kod źródłowy na MIT. Dane z mocy prawa są publiczne (PZP art. 269), jednak w publikowanym pakiecie dodatkowo hashujemy NIP-y wykonawców rozpoznanych jako osoby fizyczne — dobrowolny środek ostrożności przy zbiorczej redystrybucji.
Do tej pory ktokolwiek chcący analizować polski rynek zamówień publicznych musiał sam scraper'ować BZP, przetwarzać XML-e TED, łączyć ogłoszenia otwierające z wynikowymi, normalizować kody województw i numery NIP. Kilka dni pracy zanim dostanie się pierwszy realnie użyteczny arkusz. Po premierze tego datasetu wystarczy pandas.read_parquet(...) — i masz gotową bazę do analiz, ML, badań naukowych, publicystyki danowej.
2024 i 2025 razem — 1,4 miliona ogłoszeń. To pełna pokrywa obu źródeł: krajowego BZP (Biuletyn Zamówień Publicznych) i unijnego TED (Tenders Electronic Daily). Kolejne lata zostaną dołożone w następnych releasach.
Dane możesz używać komercyjnie, redystrybuować, przetwarzać — wymagane jest tylko wskazanie źródła. Kod eksportu i anonimizacji jest na MIT, więc jeśli chcesz zregenerować albo zbudować własną wersję — kod jest otwarty.
Dane z BZP i TED są z urzędu publiczne (PZP art. 269, EU Open Data Directive). W interfejsie Atlas pokazujemy je bez zmian. W publikowanym datasecie dodatkowo hashujemy stabilnym SHA-256 NIP-y wykonawców rozpoznanych jako osoby fizyczne (CEIDG, PESEL) — to dobrowolne zabezpieczenie przy zbiorczej redystrybucji na CC BY, nie wymóg prawny.
Parquet z kompresją zstd jest w repozytorium (76 + 89 MB dla dwóch lat tenderów). CSV-y są w release assets — skompresowane gzip-em, łącznie 263 MB. Do wyboru, do koloru.
Dla kogo ten dataset
Trzy typy odbiorców, którzy od lat robili podobne rzeczy własnymi siłami — i dla których ta publikacja oznacza konkretne godziny odzyskanego czasu:
- Badacze akademiccy i doktoranci — ekonomia zamówień publicznych, konkurencja w postępowaniach, analiza cen, detekcja zmów. Polski rynek zamówień jest wart ponad 300 mld zł rocznie, a dostępność surowych danych była dotąd fragmentaryczna. Dataset ma plik
CITATION.cff, więc Zotero, Mendeley i Google Scholar zaciągną cytowanie automatycznie. - Dziennikarze danowi i analitycy — Gazeta Wyborcza, Rzeczpospolita, Fundacja Stocznia, wykonawcy raportów dla Sejmu. Rynek publikuje teksty o wartościach kontraktów, nepotyzmie, koncentracji rynku — z tym datasetem nie trzeba negocjować z UZP o plik, wystarczy pobrać Parquet.
- Developerzy ML i NLP — polskojęzyczny korpus tytułów i ogłoszeń rzędu 1,4 mln rekordów to wdzięczna baza do klasyfikacji (predykcja kategorii CPV), regresji (estymacja wartości), NER (ekstrakcja podmiotów z tytułów). Format Parquet jest natywny dla pandas, polars, DuckDB i Apache Arrow — żadnego ETL-a po drodze.
Metodologia
Dataset nie jest prostym dumpem tabeli z bazy produkcyjnej. Między surowymi plikami z BZP a tym, co trafiło na GitHub, jest pięć kroków przetwarzania. Każdy z nich dokumentujemy — od źródeł do anonimizacji — bo transparentność metodologii jest tym, co odróżnia użyteczny dataset od trzeciorzędowego CSV-a.
1. Źródła
Dwa niezależne kanały, oba oficjalne i publiczne:
| Źródło | Rozwinięcie | Co zawiera |
|---|---|---|
bzp | Biuletyn Zamówień Publicznych, ezamowienia.gov.pl | Polski krajowy portal publikacyjny prowadzony przez UZP. Wszystkie ogłoszenia poniżej i powyżej progu unijnego wymagane prawem PZP. Format: OCDS (Open Contracting Data Standard), JSON-y po REST API. |
ted | Tenders Electronic Daily, ted.europa.eu | Unijna baza ogłoszeń powyżej progu UE, prowadzona przez Biuro Publikacji UE. Publikacje w nowym formacie eForms (XML) od 2023, wcześniejsze pod standardem TED eSender. Zbieramy polskie (country = PL). |
Oba źródła są publiczne, ale mają różne identyfikatory, różne struktury, różne cykle publikacji. Surowe pliki wymagają integracji — i właśnie tą integracją zajmuje się Atlas od 2021 roku.
2. Deduplikacja BZP ↔ TED
Duże postępowania (powyżej ~430 tys. EUR dla dostaw i usług, ~5,4 mln EUR dla robót) są publikowane równocześnie w BZP i w TED. Jedno ogłoszenie, dwa rekordy w surowych danych — to potencjalne 20–25% duplikatów w analizach branżowych. Żeby temu zapobiec, każdy rekord TED ma flagę is_duplicate ustawianą przez naszą logikę dopasowania (porównujemy numery referencyjne, NIP zamawiającego, tytuł po normalizacji, datę publikacji w oknie ±3 dni).
Do analiz zdedupliokowanych filtruj is_duplicate = false. Do analizy per-źródło (np. ile postępowań publikowane jest w TED) — zostaw flagę, użyj kolumny source.
3. Geokodowanie i normalizacja
Każde miasto występujące w bazie jest rozwiązywane do współrzędnych geograficznych (latitude, longitude) oraz kodu województwa w standardzie NUTS-2 (PL11, PL12, ..., PL63). Tabelka city_cache zawiera samą mapę nazwa miasta → współrzędne, licząca prawie 5 tysięcy wpisów. Użyteczne dla wizualizacji na mapach (Deck.gl, Folium, Kepler) bez potrzeby osobnego geocoding API.
Numery NIP są normalizowane do formatu 10 cyfr bez myślników i spacji (kolumna nip_normalized). Oryginalny zapis z BZP często ma formę 123-456-78-90 albo NIP: 1234567890 — do joinów używaj zawsze nip_normalized.
4. Agregacja do profili
Poza surowymi ogłoszeniami wyliczamy dwa zbiory agregatów:
buyers.csv/buyers.parquet— po jednym rekordzie na NIP zamawiającego. Liczby: łączna liczba postępowań, łączna wartość, średnia wartość, top 5 kategorii CPV, trendy per rok, top 5 wykonawców wygrywających od tego zamawiającego.contractors.csv/contractors.parquet— po jednym rekordzie na NIP wykonawcy. Analogicznie: liczba wygranych, łączna wartość, sezonowość, top zamawiający.
Agregaty są deterministyczne — regenerowalne tym samym export.py z dowolnego snapshotu bazy. Jeśli potrzebujesz własnego cięcia (np. per region, per kategoria CPV z odrzuceniem dostaw) — tabela tenders jest jedynym źródłem prawdy, reszta się z niej liczy.
5. Anonimizacja wykonawców
Zacznijmy od faktu prawnego: wyniki postępowań publicznych, w tym nazwa i NIP wykonawcy, są z urzędu publiczne. PZP art. 269 nakazuje ich publikację w BZP, a EU Open Data Directive 2019/1024 wprost zachęca do re-use. Atlas w interfejsie pokazuje te dane bez zmian — tak samo jak eGospodarka, Oferent, Bazhub i inne platformy agregujące BZP od lat. Żadna publiczna anonimizacja nie jest wymagana ani po stronie zamawiającego, ani wykonawcy.
Mimo to w publikowanym pakiecie open-data (GitHub, Zenodo, Kaggle) zdecydowaliśmy się dodatkowo zahashować NIP-y wykonawców rozpoznanych jako osoby fizyczne. Dlaczego? Bulk-redistribution 1,4 mln rekordów na licencji CC BY, pobieralnej przez dowolny bot, jest jakościowo inna od wyświetlania danych per zapytanie w wyszukiwarce. To ostrożność prewencyjna, nie wymóg RODO — gdyby kiedyś UODO albo sam wykonawca zgłosił zastrzeżenie co do zbiorczego datasetu, mamy już zaimplementowane rozwiązanie. Detekcję osoby fizycznej robimy trzema regułami, w kolejności pewności:
- PESEL (11 cyfr) w polu
contractor_national_id— PESEL jest identyfikatorem osoby fizycznej z definicji. Firmy używają NIP-ów 10-cyfrowych. Jeśli pole ma 11 cyfr, to z 100% pewnością osoba fizyczna. W całej bazie jest 4 458 takich rekordów. - Markery „osoba fizyczna", „jednoosobowa działalność", „prowadzący działalność", „CEIDG" w nazwie wykonawcy. 4 260 przypadków w całej bazie — oferent sam deklaruje formę prawną.
- Wzorzec „Imię Nazwisko" na końcu nazwy — klasyka CEIDG:
"PHUP DELTABUD Krzysztof Łakomiec","Firma Usługowa Danuta Frymark","ALU-CAR Gorzyce Krzysztof Drozd". Dwa słowa Title case (albo ALL CAPS), pierwsze z nich musi być rozpoznanym polskim imieniem — mamy listę 253 najpopularniejszych imion męskich i żeńskich. Dlaczego lista imion? Bo bez niej regex łapałby „Roche Diagnostics Polska" (Diagnostics + Polska = dwa słowa title case) jako osobę fizyczną — a to oczywiście firma. Lista polskich imion eliminuje tego typu false positives.
Gdy którykolwiek warunek się spełnia, rekord jest transformowany tak:
| Pole | Przed | Po anonimizacji |
|---|---|---|
contractor_name | "Firma Usługowa Danuta Frymark" | "[Osoba fizyczna]" |
contractor_national_id | "9876543210" | "anon-a1b2c3d4e5" (SHA-256 hash z solą) |
contractor_city, contractor_province | bez zmian | bez zmian — geografia to nie PII |
Hash jest stabilny między releasami (ta sama sól = ten sam hash), więc łączenia cross-year po zanonimizowanym identyfikatorze dalej działają. Ale odwrócić hashu bez soli nie da się — a sól nie jest publiczna. W 2024 roku zanonimizowaliśmy 59 472 rekordy przetargów (25% tych z contractor_name); w 2025 — 64 964 rekordy; w agregacie contractors.csv — 33 714 profili wykonawców (41%).
Verifikacja po stronie zamawiającego. Przed publikacją sprawdziliśmy rozkład długości pola buyer_nip: w całej bazie 2,8 mln rekordów — wszystkie mają dokładnie 10 cyfr. Zero PESEL-i, zero osób fizycznych jako zamawiających. To zgadza się z literą PZP — osoba fizyczna nieprowadząca działalności nie może być zamawiającym w sensie ustawy. Gdyby kiedyś pojawił się taki przypadek, rozszerzymy pii_utils.py o analogiczną detekcję strony zamawiającego.
Jak użyć — pierwsze pięć minut
Dwa scenariusze, w zależności od stacku:
Python + pandas
import pandas as pd
# Pobierz parquet prosto z GitHuba (CDN, bez logowania)
url = "https://github.com/atlasprzetargow/polish-tenders-dataset/raw/main/data/tenders_2025.parquet"
df = pd.read_parquet(url)
print(f"{len(df):,} ogłoszeń w 2025")
print(df.groupby("province")["estimated_value"].sum().sort_values(ascending=False).head())
Pakiet pyarrow albo fastparquet musi być zainstalowany. Parquet jest kolumnowy, więc jeśli czytasz tylko kilka kolumn — pandas ściąga tylko to:
cols = ["id", "title", "buyer", "city", "province", "estimated_value"]
df = pd.read_parquet(url, columns=cols) # pobiera znacznie mniej bajtów
DuckDB — SQL bez instalacji bazy
SELECT province, COUNT(*) AS n, SUM(estimated_value) AS total_pln
FROM 'https://github.com/atlasprzetargow/polish-tenders-dataset/raw/main/data/tenders_2025.parquet'
WHERE notice_type LIKE 'Contract%'
GROUP BY province
ORDER BY total_pln DESC;
DuckDB czyta Parquet bezpośrednio z HTTP — nie musisz niczego pobierać ręcznie, nie musisz mieć bazy. Poza pandas to najszybszy sposób na ad-hoc analizę — kilkadziesiąt milisekund na zapytanie, zero konfiguracji.
Schemat — najważniejsze kolumny
Tabela tenders ma 43 kolumny. Pełny opis jest w schema/tenders.md w repozytorium. Tu tylko najczęściej używane:
| Kolumna | Typ | Opis |
|---|---|---|
id | string | Klucz główny. Numer BZP (np. 2024/BZP 00123456/01) albo TED (123456-2024). |
title | string | Tytuł ogłoszenia dokładnie jak opublikowano. |
buyer + buyer_nip | string | Zamawiający: nazwa i numer NIP (10 cyfr). |
city + province | string | Lokalizacja zamawiającego. Województwo w kodzie NUTS-2 (PL12 itd.). |
cpv_code | string | Kody CPV oddzielone przecinkami. Pierwszy to główny. |
notice_type | string | Typ ogłoszenia: ContractNotice (otwarcie), TenderResultNotice (wynik) i wariantów w TED. |
order_type | string | Roboty budowlane / Dostawy / Usługi. |
date | date | Data publikacji. |
submitting_offers_date | timestamp | Termin składania ofert. |
estimated_value + currency | float, string | Wartość szacunkowa albo wygranej oferty. PLN dla BZP, EUR dla TED. |
contractor_name + contractor_national_id | string | Wykonawca (dla ogłoszeń wynikowych). Anonimizowane dla osób fizycznych — patrz sekcja metodologii. |
is_duplicate | bool | Ogłoszenie TED duplikujące wpis BZP. Filtruj false do analiz zdedupliokowanych. |
source | string | bzp lub ted. |
Przypadki użycia — z sufitu
- Ranking najbardziej aktywnych zamawiających —
GROUP BY buyer_nip ORDER BY count DESC. W 2025 na szczycie jest zazwyczaj GDDKiA, duże szpitale kliniczne, kilka miast wojewódzkich. - Mediany wartości per kategoria CPV — która branża ma największe kontrakty? Budownictwo drogowe (45233000) vs. aparatura medyczna (33100000) vs. oprogramowanie (72000000).
- Detekcja koncentracji rynku — jaki procent kontraktów w danym województwie wygrywa top 10% wykonawców? Klasyczny wskaźnik Herfindahla w analizach konkurencyjnych.
- Ranking unieważnień — w
procedure_resultmamy informacje o zakończeniu postępowania; filtruj „Unieważniono" i policz odsetek per zamawiający. Wysoki odsetek unieważnień bywa sygnałem problemów z planowaniem. - ML: predykcja CPV z tytułu — 1,4 mln par (tytuł, kod CPV) to fajny dataset treningowy dla klasyfikatora. Baseline na fine-tuningu herberta albo prostszym TF-IDF + logistic regression.
- Analiza sezonowości — kiedy w roku zamawiający publikują najwięcej? Czwarty kwartał zawsze dominuje — z racji budżetu rocznego.
Cytowanie i licencja
Jeśli używasz datasetu w pracy akademickiej, raporcie albo publikacji — cytuj nas. Plik CITATION.cff w repozytorium zawiera strukturyzowane metadane w formacie Citation File Format. Zenodo, Zotero, Mendeley, Papers, Hayagriva (referencer Typst-a) — wszystkie go czytają automatycznie.
Forma ludzka, którą można wkleić do stopki artykułu:
Atlas Przetargów (2026). Polish Public Tenders Dataset (BZP + TED), wersja 2026.Q2. https://github.com/atlasprzetargow/polish-tenders-dataset
Licencja na dane: CC BY 4.0 — dowolne użycie, w tym komercyjne, redystrybucja, modyfikacja. Wymagane jest tylko przypisanie autorstwa. Licencja na kod: MIT — używaj jak chcesz, bez obowiązku atrybucji.
Co dalej
Dataset w obecnej formie jest minimalnym użytecznym (MVP). Planowane rozszerzenia:
- Rozszerzenie zasięgu do 2018+ — pełna historia od momentu, kiedy BZP zaczął publikować w formacie OCDS. Kolejny release w tym kwartale.
- Zenodo + DOI — równoległa publikacja na Zenodo dla stałego identyfikatora cytowania. DOI to standard w cytowaniach naukowych, niezależny od tego, czy repozytorium GitHub kiedykolwiek zniknie.
- Kaggle — kopia na Kaggle jako zachęta do publikowania notebooków przez społeczność ML. Każdy publiczny notebook na Kaggle to backlink do naszego źródła.
- Oddzielna kolekcja dokumentów — w planach drugi dataset z SWZ (Specyfikacja Warunków Zamówienia) i załącznikami, tam gdzie są publicznie dostępne. Oddzielnie, bo rzędu dziesiątek gigabajtów i wymaga Git LFS.
Znalazłeś błąd, zaskakujący rekord, false positive w anonimizacji? Zgłoś issue na GitHubie: github.com/atlasprzetargow/polish-tenders-dataset/issues. Jeśli zbudowałeś coś ciekawego na bazie tych danych (artykuł, notebook, wizualizacja) — napisz do nas na contact@atlasprzetargow.pl, chętnie nagłośnimy. Dostęp do świeżych, surowych danych w czasie rzeczywistym — REST API Atlas lub MCP Server dla Claude Desktop.
Zasoby powiązane
- Repozytorium GitHub: github.com/atlasprzetargow/polish-tenders-dataset
- Release v2026.Q2 (CSV gzipped): releases/tag/v2026.Q2
- Dokumentacja schema: schema/*.md
- Kod anonimizacji: pii_utils.py
- MCP Server dla AI: premiera MCP Server
- REST API Atlas: atlasprzetargow.pl/api
- Słownik zamówień publicznych: atlasprzetargow.pl/slownik
Potrzebujesz głębszej analizy?
Atlas Przetargów to nie tylko blog. To narzędzie, które daje Ci przewagę informacyjną nad konkurencją.
Sprawdź demo narzędzia