T9: Person title/pwzNumber fields [checkpoint]

This commit is contained in:
Artur Kruszewski
2026-07-08 23:04:40 +02:00
parent 79fdf6a715
commit 387e2617b8
32 changed files with 348 additions and 72 deletions
+192 -13
View File
@@ -8,6 +8,8 @@
**Historia (poprzedni plan, superseded)** **Historia (poprzedni plan, superseded)**
- Pierwsza wersja planu była jednoskryptowa (Python: `requests` + BeautifulSoup, ładowanie przez `neo4j` driver) i ograniczona do województwa podlaskiego. Zastąpiona przez ten plan: **wielowojewódzki**, **warstwowy**, jako aplikacja **Java 21 + Maven + Spring Shell**, gdzie każda warstwa to osobna komenda CLI. - Pierwsza wersja planu była jednoskryptowa (Python: `requests` + BeautifulSoup, ładowanie przez `neo4j` driver) i ograniczona do województwa podlaskiego. Zastąpiona przez ten plan: **wielowojewódzki**, **warstwowy**, jako aplikacja **Java 21 + Maven + Spring Shell**, gdzie każda warstwa to osobna komenda CLI.
- Wkład starego planu wchłonięty tutaj: tabela źródeł/provenance (sekcja 3→ obecnie 4.6 + 7), zapytania walidacyjne i raport nakładek (→ 5.6), obsługa przypadków brzegowych i rate-limitu (→ 7), lista produktów i checklista wykonawcza (→ 9, 10). Pseudokod Pythona porzucony — logikę realizuje kod Javy warstw `ingest`/`load`. - Wkład starego planu wchłonięty tutaj: tabela źródeł/provenance (sekcja 3→ obecnie 4.6 + 7), zapytania walidacyjne i raport nakładek (→ 5.6), obsługa przypadków brzegowych i rate-limitu (→ 7), lista produktów i checklista wykonawcza (→ 9, 10). Pseudokod Pythona porzucony — logikę realizuje kod Javy warstw `ingest`/`load`.
- `PLAN2.md` (delta) wchłonięty tutaj: rozszerzenie `Person` o `title`/`pwzNumber` (→ 2, 4.7), nowe encje `Organization`/`Company` z powiązaniami po KRS/NIP/CEIDG (→ 2, 4.8, 5.2), nowe zadania kolejki agenta (→ T9T13). Plik `PLAN2.md` usunięty po scaleniu.
- Kolejka §11 przepisana pod konkretny model wykonawczy: **Qwen-3.6-35B-A3B** (MoE, ~3B aktywnych parametrów na token, mały kontekst efektywny). Reguły atomowości zadań i zakaz planowania (już obecne w poprzedniej wersji dla „małego lokalnego modelu") pozostają — dopisano jawne odniesienie do tego modelu, by przyszłe zadania kalibrować pod jego ograniczenia kontekstu i skłonność do zmyślania API.
--- ---
@@ -89,9 +91,57 @@ Jeden schemat dla **wszystkich** województw. Plik `canonical/<voivodeship>.json
"fullName": "Jan Kochanowicz", "fullName": "Jan Kochanowicz",
"displayName": "prof. dr hab. n. med. Jan Kochanowicz", // z tytułami, jak w źródle "displayName": "prof. dr hab. n. med. Jan Kochanowicz", // z tytułami, jak w źródle
"titles": ["prof.", "dr hab.", "n. med."], "titles": ["prof.", "dr hab.", "n. med."],
"title": "prof. dr hab. n. med.", // tytuł skondensowany (jedno pole, do wyświetlania obok fullName)
"pwzNumber": "1234567", // Prawo Wykonywania Zawodu, z rejestr.nil.org.pl; null gdy nieznaleziony
"sourceUrls": ["https://uskwb.pl/dyrekcja-szpitala/"] "sourceUrls": ["https://uskwb.pl/dyrekcja-szpitala/"]
} }
], ],
"organizations": [ // NGO/fundacje/stowarzyszenia powiązane z osobami przez KRS
{
"id": "org:fundacja-xyz",
"name": "Fundacja XYZ",
"krs": "0000123456",
"nip": null,
"sourceUrls": ["https://ems.ms.gov.pl/..."]
}
],
"companies": [ // spółki/działalności gospodarcze powiązane z osobami
{
"id": "company:abc-sp-zoo",
"name": "ABC sp. z o.o.",
"krs": "0000654321",
"nip": "1234567890",
"ceidgId": null,
"sourceUrls": ["https://wyszukiwarka-krs.ms.gov.pl/..."]
}
],
"personOrganizationLinks": [ // osoba ↔ organizacja, dopasowanie po KRS
{
"personId": "person:jan-kochanowicz",
"organizationId": "org:fundacja-xyz",
"roleLabel": "Prezes Zarządu",
"basis": "KRS", // klucz, po którym dopasowano powiązanie
"sourceUrls": ["https://ems.ms.gov.pl/..."]
}
],
"organizationLinks": [ // organizacja ↔ organizacja, po KRS/NIP (np. wspólny adres/zarząd)
{
"fromOrganizationId": "org:fundacja-xyz",
"toOrganizationId": "org:inna-fundacja",
"basis": "NIP", // KRS | NIP
"note": "wspólny adres siedziby",
"sourceUrls": ["https://ems.ms.gov.pl/..."]
}
],
"personCompanyLinks": [ // osoba ↔ firma, po KRS/CEIDG/NIP
{
"personId": "person:jan-kochanowicz",
"companyId": "company:abc-sp-zoo",
"roleLabel": "Wspólnik",
"basis": "KRS", // KRS | CEIDG | NIP
"sourceUrls": ["https://wyszukiwarka-krs.ms.gov.pl/..."]
}
],
"affiliations": [ // przynależność partyjna/komitetowa osoby "affiliations": [ // przynależność partyjna/komitetowa osoby
{ {
"personId": "person:karol-pilecki", "personId": "person:karol-pilecki",
@@ -133,8 +183,10 @@ Jeden schemat dla **wszystkich** województw. Plik `canonical/<voivodeship>.json
**Uwagi projektowe:** **Uwagi projektowe:**
- `id` osoby jest generowane deterministycznie z znormalizowanego imienia i nazwiska (patrz normalizacja) — to podstawa deduplikacji **między szpitalami i województwami** (np. osoba zasiadająca w radach kilku szpitali). - `id` osoby jest generowane deterministycznie z znormalizowanego imienia i nazwiska (patrz normalizacja) — to podstawa deduplikacji **między szpitalami i województwami** (np. osoba zasiadająca w radach kilku szpitali).
- Enumy (`legalForm`, `roleType`, `mandateType`, `confidence`, `status`) są zamknięte i wspólne dla wszystkich województw — to gwarantuje uwspólniony format. - Enumy (`legalForm`, `roleType`, `mandateType`, `confidence`, `status`) są zamknięte i wspólne dla wszystkich województw — to gwarantuje uwspólniony format. Nowy zamknięty enum `LinkBasis` (`KRS`, `NIP`, `CEIDG`) opisuje klucz dopasowania powiązań organizacja/firma.
- Pola `note` i `confidence` przenoszą metadane jakościowe z materiału roboczego (kluczowe dla kontekstu dziennikarskiego). - Pola `note` i `confidence` przenoszą metadane jakościowe z materiału roboczego (kluczowe dla kontekstu dziennikarskiego).
- `id` organizacji/firmy jest slugiem z nazwy + (gdy dostępny) KRS w nawiasie logiki dedupu — dwie różne organizacje o tej samej nazwie, ale różnym KRS, **nie są scalane** (analogicznie do zasady dla `Person` w 4.3 pkt 4).
- `pwzNumber` bywa `null` — brak trafienia w rejestr.nil.org.pl to nie błąd, tylko luka odnotowywana w `ingest-report.json` (patrz 4.7).
--- ---
@@ -152,6 +204,7 @@ Jeden schemat dla **wszystkich** województw. Plik `canonical/<voivodeship>.json
| CSV (raporty) | `commons-csv` | | CSV (raporty) | `commons-csv` |
| Testy | JUnit 5, Testcontainers (`neo4j` container) do testów warstwy ładowania | | Testy | JUnit 5, Testcontainers (`neo4j` container) do testów warstwy ładowania |
| Konteneryzacja Neo4j | Docker (`neo4j:5`), Bolt na `7687`, UI na `7474` | | Konteneryzacja Neo4j | Docker (`neo4j:5`), Bolt na `7687`, UI na `7474` |
| Rejestry zewnętrzne | rejestr.nil.org.pl (PWZ, formularz HTML — brak API), wyszukiwarka-krs.ms.gov.pl / api-krs (KRS), CEIDG API (dane.biznes.gov.pl) |
### Szkielet projektu Maven ### Szkielet projektu Maven
``` ```
@@ -163,14 +216,18 @@ szpitale-graph/
│ ├── model/ # rekordy modelu kanonicznego │ ├── model/ # rekordy modelu kanonicznego
│ │ ├── CanonicalDataset.java │ │ ├── CanonicalDataset.java
│ │ ├── Hospital.java Person.java Affiliation.java Role.java Mandate.java │ │ ├── Hospital.java Person.java Affiliation.java Role.java Mandate.java
│ │ ── enums/ (LegalForm, RoleType, MandateType, Confidence, ...) │ │ ── Organization.java Company.java PersonOrganizationLink.java OrganizationLink.java PersonCompanyLink.java
│ │ └── enums/ (LegalForm, RoleType, MandateType, Confidence, LinkBasis, ...)
│ ├── ingest/ # WARSTWA 1 │ ├── ingest/ # WARSTWA 1
│ │ ├── IngestCommand.java # @ShellComponent — komenda `ingest` │ │ ├── IngestCommand.java # @ShellComponent — komenda `ingest`
│ │ ├── source/ # źródła per-województwo │ │ ├── source/ # źródła per-województwo
│ │ │ ├── VoivodeshipSource.java # interfejs: List<RawRecord> fetch() │ │ │ ├── VoivodeshipSource.java # interfejs: List<RawRecord> fetch()
│ │ │ ├── MarkdownRegistrySource.java# parser materiałów typu research-*.md │ │ │ ├── MarkdownRegistrySource.java# parser materiałów typu research-*.md
│ │ │ ├── HtmlScraperSource.java # jsoup: strony szpitali/BIP │ │ │ ├── HtmlScraperSource.java # jsoup: strony szpitali/BIP
│ │ │ ── PkwSource.java # weryfikacja afiliacji w PKW 2024 │ │ │ ── PkwSource.java # weryfikacja afiliacji w PKW 2024
│ │ │ ├── PwzRegistrySource.java # uzupełnienie pwzNumber z rejestr.nil.org.pl (4.7)
│ │ │ ├── KrsSource.java # organizacje/firmy + powiązania po KRS (4.8)
│ │ │ └── CeidgSource.java # powiązania osoba↔firma po CEIDG/NIP (4.8)
│ │ ├── normalize/ │ │ ├── normalize/
│ │ │ ├── NameNormalizer.java # diakrytyki, tytuły, wielkość liter, slug │ │ │ ├── NameNormalizer.java # diakrytyki, tytuły, wielkość liter, slug
│ │ │ ├── PartyNormalizer.java # mapowanie nazw partii/komitetów │ │ │ ├── PartyNormalizer.java # mapowanie nazw partii/komitetów
@@ -203,6 +260,9 @@ Implementacje (wybierane po fladze/konfiguracji):
- **`MarkdownRegistrySource`** — parsuje istniejące materiały robocze w formacie tabel Markdown (jak `research-podlaskie.md`). To ścieżka startowa: podlaskie już mamy. Kolejne województwa dokładamy jako `research-<voiv>.md` (patrz sekcja 4.6 — generowane skillem deep-research). - **`MarkdownRegistrySource`** — parsuje istniejące materiały robocze w formacie tabel Markdown (jak `research-podlaskie.md`). To ścieżka startowa: podlaskie już mamy. Kolejne województwa dokładamy jako `research-<voiv>.md` (patrz sekcja 4.6 — generowane skillem deep-research).
- **`HtmlScraperSource`** — `HttpClient` + jsoup do stron szpitali (dyrekcja) i BIP (rady). Rate-limiting + retry z backoffem, `User-Agent`, poszanowanie `robots.txt`. - **`HtmlScraperSource`** — `HttpClient` + jsoup do stron szpitali (dyrekcja) i BIP (rady). Rate-limiting + retry z backoffem, `User-Agent`, poszanowanie `robots.txt`.
- **`PkwSource`** — kontrola krzyżowa afiliacji radnych w `samorzad2024.pkw.gov.pl` (podnosi `confidence`). - **`PkwSource`** — kontrola krzyżowa afiliacji radnych w `samorzad2024.pkw.gov.pl` (podnosi `confidence`).
- **`PwzRegistrySource`** — uzupełnia `pwzNumber` osoby z `rejestr.nil.org.pl` (formularz imię+nazwisko, patrz 4.7).
- **`KrsSource`** — wyszukuje organizacje/firmy powiązane z osobą po KRS (patrz 4.8).
- **`CeidgSource`** — kontrola krzyżowa działalności gospodarczych osoby po CEIDG/NIP (patrz 4.8).
Rejestr źródeł spina wszystkie województwa; brak konfiguracji dla danego województwa = pomijane z ostrzeżeniem w logu (żadnych cichych luk). Rejestr źródeł spina wszystkie województwa; brak konfiguracji dla danego województwa = pomijane z ostrzeżeniem w logu (żadnych cichych luk).
@@ -238,6 +298,27 @@ Wynik zapisujemy jako `research-mazowieckie.md`. Skalowanie na wszystkie wojewó
**Granica odpowiedzialności:** deep-research **znajduje i weryfikuje źródła** (etap dziennikarsko-badawczy, wymaga oceny człowieka przed publikacją, zwłaszcza dla oznaczeń `NIEZWERYFIKOWANA`). Kod Javy (warstwa 1) **nie prowadzi researchu** — jedynie deterministycznie normalizuje gotowy materiał `.md` do modelu kanonicznego. Ten podział utrzymuje warstwę 1 testowalną i powtarzalną, a odkrywanie źródeł — audytowalne i cytowane. **Granica odpowiedzialności:** deep-research **znajduje i weryfikuje źródła** (etap dziennikarsko-badawczy, wymaga oceny człowieka przed publikacją, zwłaszcza dla oznaczeń `NIEZWERYFIKOWANA`). Kod Javy (warstwa 1) **nie prowadzi researchu** — jedynie deterministycznie normalizuje gotowy materiał `.md` do modelu kanonicznego. Ten podział utrzymuje warstwę 1 testowalną i powtarzalną, a odkrywanie źródeł — audytowalne i cytowane.
### 4.7. Uzupełnienie numeru PWZ — `PwzRegistrySource`
`rejestr.nil.org.pl` (Naczelna Izba Lekarska) udostępnia wyszukiwarkę lekarzy **wyłącznie jako formularz HTML** (imię + nazwisko), bez publicznego API. Wzbogacenie działa jako osobny krok w `ingest`, **po** normalizacji osób (potrzebuje `fullName`):
1. Dla każdej osoby o `titles` sugerujących zawód medyczny (`dr`, `dr n. med.`, `prof.`, `lek.`, itp. — heurystyka w `PwzRegistrySource`, nie każda osoba w radzie jest lekarzem) wyślij zapytanie z `fullName`.
2. **Wiele trafień** (częste nazwiska) → `pwzNumber = null`, `note = "wieloznaczne trafienie NIL, wymaga ręcznej weryfikacji"`, wpis w `ingest-report.json`.
3. **Brak trafienia**`pwzNumber = null`, brak `note` (osoba może nie być lekarzem — to nie błąd).
4. Rate-limiting jak dla `HtmlScraperSource` (4.2/7) — to ten sam serwis żywy, nie plik `.md`.
**Granica odpowiedzialności:** to wzbogacenie jest **deterministyczne i automatyczne** (1:1 zapytanie→wynik po nazwisku), inaczej niż `deep-research` (4.6) — nie wymaga oceny dziennikarskiej, tylko technicznej weryfikacji przy wieloznaczności.
### 4.8. Organizacje i firmy — `KrsSource` / `CeidgSource`
Rozszerzenie modelu o encje `Organization` (fundacje, stowarzyszenia) i `Company` (spółki, działalności gospodarcze) powiązane z osobami z organów szpitali — cel: ujawnić potencjalne konflikty interesów.
1. **`KrsSource`** — dla każdej osoby: wyszukanie w KRS (`wyszukiwarka-krs.ms.gov.pl` / `api-krs`) wpisów, gdzie figuruje jako zarząd/wspólnik/fundator. Zwraca `Organization`/`Company` + `personOrganizationLinks`/`personCompanyLinks` z `basis=KRS`.
2. Powiązania **organizacja↔organizacja** (`organizationLinks`) — wspólny KRS-owy adres, zarząd lub `nip` między dwiema organizacjami; `basis=KRS` lub `basis=NIP`.
3. **`CeidgSource`** — kontrola krzyżowa jednoosobowych działalności gospodarczych osoby po CEIDG (`dane.biznes.gov.pl`, API publiczne) i po `nip`; `basis=CEIDG` lub `basis=NIP`.
4. Deduplikacja: `Organization`/`Company` scalane po `krs` (klucz twardy, jednoznaczny) — w przeciwieństwie do `Person`, gdzie deduplikacja jest fuzzy i ostrożna (4.3 pkt 4), bo KRS/NIP są identyfikatorami unikalnymi z rejestru państwowego.
5. Brak dopasowania po KRS/CEIDG/NIP dla danej osoby = brak wpisu (nie każda osoba w radzie ma powiązania biznesowe) — nie jest to luka do raportowania.
### 4.3. Normalizacja do formatu uwspólnionego ### 4.3. Normalizacja do formatu uwspólnionego
1. **Nazwiska** (`NameNormalizer`): usunięcie tytułów do `titles[]`, `displayName` zachowuje oryginał, `fullName` = imię+nazwisko, `id` = slug z NFKD bez diakrytyków, lower-case, `-`. Uwaga na warianty (np. `Wojciech Łuczaj` występuje w kilku szpitalach). 1. **Nazwiska** (`NameNormalizer`): usunięcie tytułów do `titles[]`, `displayName` zachowuje oryginał, `fullName` = imię+nazwisko, `id` = slug z NFKD bez diakrytyków, lower-case, `-`. Uwaga na warianty (np. `Wojciech Łuczaj` występuje w kilku szpitalach).
2. **Partie/komitety** (`PartyNormalizer`): mapowanie na kanoniczne nazwy (`Koalicja Obywatelska`, `PiS`, `PSL`, `Polska 2050`, `Trzecia Droga`, ...) + typ `PARTIA`/`KOMITET_WYBORCZY`/`KWW_LOKALNY`. `confidence` z markerów źródła (`NIEZWERYFIKOWANE``NIEZWERYFIKOWANA`, `brak danych``BRAK_DANYCH`, `brak/niepotwierdzona``NIEPOTWIERDZONA`). 2. **Partie/komitety** (`PartyNormalizer`): mapowanie na kanoniczne nazwy (`Koalicja Obywatelska`, `PiS`, `PSL`, `Polska 2050`, `Trzecia Droga`, ...) + typ `PARTIA`/`KOMITET_WYBORCZY`/`KWW_LOKALNY`. `confidence` z markerów źródła (`NIEZWERYFIKOWANE``NIEZWERYFIKOWANA`, `brak danych``BRAK_DANYCH`, `brak/niepotwierdzona``NIEPOTWIERDZONA`).
@@ -285,10 +366,12 @@ Odczytać plik(i) kanoniczne i zmaterializować graf w Neo4j: węzły, relacje i
### 5.2. Model grafu ### 5.2. Model grafu
**Węzły (labels):** **Węzły (labels):**
- `:Hospital {id, name, shortName, city, voivodeship, legalForm, foundingBody, supervisoryBodyType, nip, krs, website, sourceUrls}` - `:Hospital {id, name, shortName, city, voivodeship, legalForm, foundingBody, supervisoryBodyType, nip, krs, website, sourceUrls}`
- `:Person {id, fullName, displayName, titles, sourceUrls}` - `:Person {id, fullName, displayName, titles, title, pwzNumber, sourceUrls}`
- `:Party {name, type}` (partia/komitet) - `:Party {name, type}` (partia/komitet)
- `:GovBody {name, type, voivodeship, term}` (sejmik, rada powiatu, urząd gminy — dla mandatów) - `:GovBody {name, type, voivodeship, term}` (sejmik, rada powiatu, urząd gminy — dla mandatów)
- `:Voivodeship {slug, name}` (grupowanie i zapytania regionalne) - `:Voivodeship {slug, name}` (grupowanie i zapytania regionalne)
- `:Organization {id, name, krs, nip, sourceUrls}` (fundacje, stowarzyszenia)
- `:Company {id, name, krs, nip, ceidgId, sourceUrls}` (spółki, działalności gospodarcze)
**Relacje:** **Relacje:**
| Relacja | Od → Do | Właściwości | | Relacja | Od → Do | Właściwości |
@@ -300,6 +383,9 @@ Odczytać plik(i) kanoniczne i zmaterializować graf w Neo4j: węzły, relacje i
| `[:CZLONEK_PARTII]` | Person → Party | `confidence, note, sourceUrls` | | `[:CZLONEK_PARTII]` | Person → Party | `confidence, note, sourceUrls` |
| `[:PELNI_MANDAT]` | Person → GovBody | `mandateType, term, sourceUrls` | | `[:PELNI_MANDAT]` | Person → GovBody | `mandateType, term, sourceUrls` |
| `[:W_WOJEWODZTWIE]` | Hospital → Voivodeship | — | | `[:W_WOJEWODZTWIE]` | Hospital → Voivodeship | — |
| `[:CZLONEK_ORGANIZACJI]` | Person → Organization | `roleLabel, basis (KRS), sourceUrls` |
| `[:POWIAZANA_Z]` | Organization → Organization | `basis (KRS/NIP), note, sourceUrls` |
| `[:POWIAZANY_Z_FIRMA]` | Person → Company | `roleLabel, basis (KRS/CEIDG/NIP), sourceUrls` |
> Uwaga: zamiast osobnych typów relacji dla każdej funkcji można użyć jednej `[:PELNI_FUNKCJE {roleType, organ, ...}]`. Wybór: **typowane relacje** dla czytelności zapytań Cypher + `roleType` jako property dla filtrowania. > Uwaga: zamiast osobnych typów relacji dla każdej funkcji można użyć jednej `[:PELNI_FUNKCJE {roleType, organ, ...}]`. Wybór: **typowane relacje** dla czytelności zapytań Cypher + `roleType` jako property dla filtrowania.
@@ -309,7 +395,10 @@ CREATE CONSTRAINT hospital_id IF NOT EXISTS FOR (h:Hospital) REQUIRE h.id IS UNI
CREATE CONSTRAINT person_id IF NOT EXISTS FOR (p:Person) REQUIRE p.id IS UNIQUE; CREATE CONSTRAINT person_id IF NOT EXISTS FOR (p:Person) REQUIRE p.id IS UNIQUE;
CREATE CONSTRAINT party_name IF NOT EXISTS FOR (x:Party) REQUIRE x.name IS UNIQUE; CREATE CONSTRAINT party_name IF NOT EXISTS FOR (x:Party) REQUIRE x.name IS UNIQUE;
CREATE CONSTRAINT voiv_slug IF NOT EXISTS FOR (v:Voivodeship) REQUIRE v.slug IS UNIQUE; CREATE CONSTRAINT voiv_slug IF NOT EXISTS FOR (v:Voivodeship) REQUIRE v.slug IS UNIQUE;
CREATE CONSTRAINT org_id IF NOT EXISTS FOR (o:Organization) REQUIRE o.id IS UNIQUE;
CREATE CONSTRAINT company_id IF NOT EXISTS FOR (c:Company) REQUIRE c.id IS UNIQUE;
CREATE INDEX person_name IF NOT EXISTS FOR (p:Person) ON (p.fullName); CREATE INDEX person_name IF NOT EXISTS FOR (p:Person) ON (p.fullName);
CREATE INDEX person_pwz IF NOT EXISTS FOR (p:Person) ON (p.pwzNumber);
``` ```
### 5.4. Ładowanie (parametryzowane, wsadowe) ### 5.4. Ładowanie (parametryzowane, wsadowe)
@@ -325,7 +414,30 @@ MERGE (hosp)-[:W_WOJEWODZTWIE]->(v);
UNWIND $people AS p UNWIND $people AS p
MERGE (person:Person {id: p.id}) MERGE (person:Person {id: p.id})
SET person += {fullName:p.fullName, displayName:p.displayName, SET person += {fullName:p.fullName, displayName:p.displayName,
titles:p.titles, sourceUrls:p.sourceUrls}; titles:p.titles, title:p.title, pwzNumber:p.pwzNumber, sourceUrls:p.sourceUrls};
UNWIND $organizations AS o
MERGE (org:Organization {id: o.id})
SET org += {name:o.name, krs:o.krs, nip:o.nip, sourceUrls:o.sourceUrls};
UNWIND $companies AS c
MERGE (comp:Company {id: c.id})
SET comp += {name:c.name, krs:c.krs, nip:c.nip, ceidgId:c.ceidgId, sourceUrls:c.sourceUrls};
UNWIND $personOrganizationLinks AS pol
MATCH (p:Person {id:pol.personId}), (org:Organization {id:pol.organizationId})
MERGE (p)-[m:CZLONEK_ORGANIZACJI]->(org)
SET m += {roleLabel:pol.roleLabel, basis:pol.basis, sourceUrls:pol.sourceUrls};
UNWIND $organizationLinks AS ol
MATCH (a:Organization {id:ol.fromOrganizationId}), (b:Organization {id:ol.toOrganizationId})
MERGE (a)-[m:POWIAZANA_Z]->(b)
SET m += {basis:ol.basis, note:ol.note, sourceUrls:ol.sourceUrls};
UNWIND $personCompanyLinks AS pcl
MATCH (p:Person {id:pcl.personId}), (comp:Company {id:pcl.companyId})
MERGE (p)-[m:POWIAZANY_Z_FIRMA]->(comp)
SET m += {roleLabel:pcl.roleLabel, basis:pcl.basis, sourceUrls:pcl.sourceUrls};
UNWIND $roles AS r UNWIND $roles AS r
MATCH (h:Hospital {id:r.hospitalId}), (p:Person {id:r.personId}) MATCH (h:Hospital {id:r.hospitalId}), (p:Person {id:r.personId})
@@ -393,8 +505,15 @@ RETURN p.fullName, collect(DISTINCT h.name) AS szpitale, collect(DISTINCT g.name
MATCH (h:Hospital)-[:CZLONEK_ORGANU]->(p:Person) MATCH (h:Hospital)-[:CZLONEK_ORGANU]->(p:Person)
WITH p, collect(DISTINCT h) AS hs WHERE size(hs) > 1 WITH p, collect(DISTINCT h) AS hs WHERE size(hs) > 1
RETURN p.fullName, [x IN hs | x.name] AS szpitale; RETURN p.fullName, [x IN hs | x.name] AS szpitale;
// Osoby w organie szpitala mające jednocześnie powiązanie biznesowe (konflikt interesów)
MATCH (h:Hospital)-[:CZLONEK_ORGANU]->(p:Person)
WHERE (p)-[:POWIAZANY_Z_FIRMA]->() OR (p)-[:CZLONEK_ORGANIZACJI]->()
OPTIONAL MATCH (p)-[:POWIAZANY_Z_FIRMA]->(comp:Company)
OPTIONAL MATCH (p)-[:CZLONEK_ORGANIZACJI]->(org:Organization)
RETURN p.fullName, h.name, collect(DISTINCT comp.name) AS firmy, collect(DISTINCT org.name) AS organizacje;
``` ```
Eksport `overlap_report.csv` (osoba, szpitale, funkcje, partia, mandaty, `confidence`, `sourceUrls`). Eksport `overlap_report.csv` (osoba, szpitale, funkcje, partia, mandaty, firmy/organizacje, `confidence`, `sourceUrls`).
--- ---
@@ -421,6 +540,9 @@ Model kanoniczny, schemat grafu i komendy pozostają niezmienne — nowe wojewó
| Dane dynamiczne (zmiany kadr) | `validFrom`/`validTo` + `status` na relacji funkcji (`AKTUALNY`/`PELNIACY_OBOWIAZKI`/`ELEKT`/`BYLY`) | | Dane dynamiczne (zmiany kadr) | `validFrom`/`validTo` + `status` na relacji funkcji (`AKTUALNY`/`PELNIACY_OBOWIAZKI`/`ELEKT`/`BYLY`) |
| Rate-limit / blokada scrapera | Exponential backoff; przełączenie na materiał `.md` jako cache; adnotacja źródła | | Rate-limit / blokada scrapera | Exponential backoff; przełączenie na materiał `.md` jako cache; adnotacja źródła |
| Spółka z o.o. (rada nadzorcza) vs SPZOZ (rada społeczna) | `supervisoryBodyType` + `organ` na relacji rozróżniają organy | | Spółka z o.o. (rada nadzorcza) vs SPZOZ (rada społeczna) | `supervisoryBodyType` + `organ` na relacji rozróżniają organy |
| Wieloznaczne trafienie w rejestr.nil.org.pl (popularne nazwisko) | `pwzNumber=null`, `note="wieloznaczne trafienie NIL, wymaga ręcznej weryfikacji"`, wpis w `ingest-report.json` |
| Brak trafienia w KRS/CEIDG dla osoby | Brak wpisu w `personOrganizationLinks`/`personCompanyLinks` — to nie luka (nie każdy ma powiązania biznesowe) |
| Ta sama nazwa organizacji, różny KRS | Osobne węzły `:Organization` (dedup po `krs`, nie po nazwie) |
--- ---
@@ -452,9 +574,10 @@ java -jar target/szpitale-graph.jar load --voivodeship podlaskie
1. **Aplikacja Java 21 / Maven / Spring Shell** z komendami `ingest` (warstwa 1) i `load` (warstwa 2). 1. **Aplikacja Java 21 / Maven / Spring Shell** z komendami `ingest` (warstwa 1) i `load` (warstwa 2).
2. **Model kanoniczny** (`canonical/*.json`) — wersjonowalny, wielowojewódzki snapshot danych. 2. **Model kanoniczny** (`canonical/*.json`) — wersjonowalny, wielowojewódzki snapshot danych.
3. **Schemat Neo4j** — udokumentowane labels, typy relacji i właściwości (sekcja 5.25.3). 3. **Schemat Neo4j** — udokumentowane labels, typy relacji i właściwości (sekcja 5.25.3).
4. **`overlap_report.csv`** — osoby łączące funkcje w organach szpitali z mandatami/partiami, z `sourceUrls` i `confidence`. 4. **`overlap_report.csv`** — osoby łączące funkcje w organach szpitali z mandatami/partiami/firmami/organizacjami, z `sourceUrls` i `confidence`.
5. **`ingest-report.json`** — statystyki i rejestr luk/rozbieżności per województwo. 5. **`ingest-report.json`** — statystyki i rejestr luk/rozbieżności per województwo (w tym wieloznaczne trafienia PWZ).
6. **Testy** — JUnit + Testcontainers (Neo4j) dla warstwy ładowania; testy jednostkowe normalizacji/deduplikacji. 6. **Testy** — JUnit + Testcontainers (Neo4j) dla warstwy ładowania; testy jednostkowe normalizacji/deduplikacji.
7. **Encje `Organization`/`Company`** w modelu kanonicznym i grafie, z powiązaniami do osób po KRS/CEIDG/NIP (sekcja 4.8, 5.2).
--- ---
@@ -471,12 +594,15 @@ java -jar target/szpitale-graph.jar load --voivodeship podlaskie
- [ ] Testy: parsowanie MD, normalizacja nazwisk, ładowanie do Testcontainers-Neo4j, zapytania walidacyjne. - [ ] Testy: parsowanie MD, normalizacja nazwisk, ładowanie do Testcontainers-Neo4j, zapytania walidacyjne.
- [ ] Weryfikacja end-to-end na podlaskim, następnie dołożenie kolejnego województwa: `deep-research``research-<voiv>.md``ingest``load`. - [ ] Weryfikacja end-to-end na podlaskim, następnie dołożenie kolejnego województwa: `deep-research``research-<voiv>.md``ingest``load`.
- [ ] Dla każdego nowego województwa: uruchomić skill `deep-research` (sekcja 4.6), zweryfikować oznaczenia `NIEZWERYFIKOWANA` przed użyciem, zapisać `research-<voiv>.md`. - [ ] Dla każdego nowego województwa: uruchomić skill `deep-research` (sekcja 4.6), zweryfikować oznaczenia `NIEZWERYFIKOWANA` przed użyciem, zapisać `research-<voiv>.md`.
- [ ] `Person` rozszerzony o `title`/`pwzNumber` (sekcja 2, 4.7) + `PwzRegistrySource`.
- [ ] Nowe rekordy modelu: `Organization`, `Company`, `PersonOrganizationLink`, `OrganizationLink`, `PersonCompanyLink` (sekcja 2) + `KrsSource`/`CeidgSource` (sekcja 4.8).
- [ ] Warstwa 2: węzły `:Organization`/`:Company` + relacje `CZLONEK_ORGANIZACJI`/`POWIAZANA_Z`/`POWIAZANY_Z_FIRMA` (sekcja 5.2, 5.4) + constrainty (5.3).
--- ---
## 11. Kolejka zadań dla lokalnego agenta (TDD, kroki atomowe) ## 11. Kolejka zadań dla lokalnego agenta (TDD, kroki atomowe)
Ta sekcja jest pisana pod **mały lokalny model** o ograniczonym kontekście. Każde zadanie jest samowystarczalne: jeden plik lub jedna metoda, dokładna ścieżka, dokładny test, jedna komenda weryfikacji, twarde kryterium ukończenia. Agent **nie planuje** — bierze pierwsze niezrobione zadanie i wykonuje je w całości. Ta sekcja jest pisana pod lokalny model wykonawczy **Qwen-3.6-35B-A3B** (MoE, ~3B aktywnych parametrów na token, mały efektywny kontekst, skłonność do zmyślania nieistniejących API przy niedoprecyzowanym zadaniu). Każde zadanie jest samowystarczalne: jeden plik lub jedna metoda, dokładna ścieżka, dokładny test, jedna komenda weryfikacji, twarde kryterium ukończenia. Agent **nie planuje** — bierze pierwsze niezrobione zadanie i wykonuje je w całości. Zasada 7 (poniżej) jest przy tym modelu szczególnie krytyczna: A3B chętnie "dopisuje" wygodne metody, których nie ma w `src/main/java` — zawsze czytaj plik przed wywołaniem metody.
### Reguły pracy (przeczytaj przed każdym zadaniem) ### Reguły pracy (przeczytaj przed każdym zadaniem)
@@ -555,6 +681,47 @@ Ta sekcja jest pisana pod **mały lokalny model** o ograniczonym kontekście. Ka
- Jeśli któreś [NEO4J] pominięte — odnotuj w tej checklistcie jako `[~]` z powodem. - Jeśli któreś [NEO4J] pominięte — odnotuj w tej checklistcie jako `[~]` z powodem.
- DoD: `./mvnw -q verify` przechodzi (pomijając wyłącznie zadania bez Dockera). - DoD: `./mvnw -q verify` przechodzi (pomijając wyłącznie zadania bez Dockera).
**T9 — Rozszerzyć `Person` o `title` i `pwzNumber`**
- Plik: `src/main/java/com/developx/szpitale/model/Person.java` (rekord z 5 polami: `id, fullName, displayName, titles, sourceUrls`).
- Zamiar: dodać dwa nowe pola rekordu: `@JsonProperty("title") String title` (może być `null`) i `@JsonProperty("pwzNumber") String pwzNumber` (może być `null`), jako **ostatnie** dwa parametry konstruktora rekordu.
- TDD:
1. Test `PersonTest.person_withTitleAndPwzNumber_fieldsAccessible` w `src/test/java/com/developx/szpitale/model/PersonTest.java`: zbuduj `Person` z niepustym `title` i `pwzNumber`, asercja że `person.title()` i `person.pwzNumber()` zwracają wpisane wartości. RED (kompilacja nie przejdzie, bo konstruktor ma 5 argumentów) → dodaj pola → GREEN.
2. Znajdź **wszystkie** miejsca konstruujące `new Person(...)` (`grep -rn "new Person(" src/`) i dopisz `null, null` (lub realną wartość jeśli już znana) na końcu wywołania — inaczej build nie skompiluje się.
- DoD: `./mvnw -q compile` przechodzi; `./mvnw -q test -Dtest=PersonTest` zielony.
**T10 — `PwzRegistrySource` (fixture, bez żywego HTTP w teście)**
- Nowy plik: `src/main/java/com/developx/szpitale/ingest/source/PwzRegistrySource.java`.
- Zamiar (sekcja 4.7): metoda `Optional<String> lookupPwz(String fullName)` przyjmuje wynik zapytania do `rejestr.nil.org.pl` jako **wstrzykniętą zależność** (np. interfejs `PwzLookupClient` z metodą `List<String> query(String fullName)`), żeby test nie robił żywego HTTP.
- Fixture: `src/test/resources/fixtures/nil-single-match.json` z jednym wynikiem (`["1234567"]`) i `nil-multiple-matches.json` z dwoma.
- TDD:
1. Test `PwzRegistrySourceTest.lookupPwz_singleMatch_returnsPwzNumber` → mock `PwzLookupClient` zwraca jeden wynik → `lookupPwz` zwraca `Optional.of("1234567")`.
2. Test `PwzRegistrySourceTest.lookupPwz_multipleMatches_returnsEmpty` → mock zwraca 2 wyniki → `lookupPwz` zwraca `Optional.empty()` (wieloznaczność, patrz 4.7 pkt 2 — **nie zgadywać**, który wynik jest właściwy).
3. Test `PwzRegistrySourceTest.lookupPwz_noMatch_returnsEmpty` → mock zwraca pustą listę → `Optional.empty()`.
- DoD: `./mvnw -q test -Dtest=PwzRegistrySourceTest` zielony. Nie implementuj żywego klienta HTTP w tym zadaniu — tylko interfejs `PwzLookupClient` + logika `PwzRegistrySource` na nim.
**T11 — Rekordy `Organization`/`Company` + linki (round-trip)**
- Nowe pliki: `src/main/java/com/developx/szpitale/model/Organization.java`, `Company.java`, `PersonOrganizationLink.java`, `OrganizationLink.java`, `PersonCompanyLink.java` — pola dokładnie jak w sekcji 2 PLAN.md (przykłady JSON), jako rekordy Java z `@JsonProperty` (wzoruj się na `Hospital.java`/`Person.java`).
- Zamiar: `CanonicalDataset` (`src/main/java/com/developx/szpitale/model/CanonicalDataset.java`) dostaje 5 nowych pól-list: `organizations, companies, personOrganizationLinks, organizationLinks, personCompanyLinks` (domyślnie puste listy, nie `null`).
- TDD:
1. Test `OrganizationCompanyRoundTripTest.write_thenRead_preservesOrganizationsAndCompanies` w `src/test/java/com/developx/szpitale/ingest/` (mirror `CanonicalRoundTripTest` z T5): zbuduj `CanonicalDataset` z 1 `Organization`, 1 `Company`, po 1 linku każdego typu; zapisz `CanonicalWriter`, odczytaj `CanonicalReader`, porównaj pola. RED najpierw.
2. Minimalny kod: dodaj rekordy + pola w `CanonicalDataset`, sprawdź że Jackson serializuje/deserializuje bez adnotacji dodatkowych (jak reszta modelu).
- DoD: `./mvnw -q test -Dtest=OrganizationCompanyRoundTripTest` zielony.
**T12 — [NEO4J] `GraphLoader` ładuje `:Organization`/`:Company`**
- Plik prod: `src/main/java/com/developx/szpitale/load/GraphLoader.java` (przeczytaj istniejącą metodę ładującą `Hospital`/`Person`, wzoruj się na jej strukturze).
- Zamiar (sekcja 5.4): dodać metodę analogiczną do istniejącego ładowania węzłów, wykonującą `UNWIND $organizations ... MERGE (:Organization {id})`, `UNWIND $companies ... MERGE (:Company {id})` oraz 3 `MERGE` relacji: `CZLONEK_ORGANIZACJI`, `POWIAZANA_Z`, `POWIAZANY_Z_FIRMA` (dokładne zapytania Cypher w sekcji 5.4 PLAN.md).
- Test integracyjny: `GraphLoaderOrganizationIT` z `@Testcontainers` (wzoruj się na istniejącym wzorcu z T6, jeśli `GraphLoaderIT` już istnieje — skopiuj setup kontenera).
- `load_organizationAndCompany_nodesCreatedWithLinks` → załaduj 1 osobę + 1 organizację + 1 firmę + odpowiednie linki, sprawdź że istnieją węzły `:Organization`, `:Company` i relacje do `:Person`.
- Wymaga Dockera. Bez Dockera → `[~]` + notatka (jak T6/T7).
- DoD: `./mvnw -q test -Dtest=GraphLoaderOrganizationIT` zielony (lub pominięte z notatką).
**T13 — [NEO4J] `GraphValidator` — zapytanie o konflikt interesów**
- Plik prod: `src/main/java/com/developx/szpitale/load/GraphValidator.java`.
- Zamiar (sekcja 5.6): dodać metodę zwracającą wynik zapytania „osoby w organie szpitala z powiązaniem biznesowym" (dokładny Cypher w sekcji 5.6 PLAN.md, blok „konflikt interesów").
- Test integracyjny `GraphValidatorOrganizationIT`: wgraj dataset z 1 osobą mającą `CZLONEK_ORGANU` (szpital) i `POWIAZANY_Z_FIRMA` (firma); asercja: wynik zawiera tę osobę z nazwą firmy.
- Wymaga Dockera. Bez Dockera → `[~]` + notatka.
- DoD: `./mvnw -q test -Dtest=GraphValidatorOrganizationIT` zielony (lub pominięte z notatką).
--- ---
## 12. Sekcja postępu (aktualizuje lokalny agent) ## 12. Sekcja postępu (aktualizuje lokalny agent)
@@ -563,15 +730,27 @@ Po ukończeniu zadania agent zmienia jego status w liście poniżej i dopisuje j
- [x] T1 Deduplicator compile fix - [x] T1 Deduplicator compile fix
- [x] T2 NameNormalizer testy - [x] T2 NameNormalizer testy
- [ ] T3 PartyNormalizer testy - [x] T3 PartyNormalizer testy
- [x] T4 MarkdownRegistrySource test - [x] T4 MarkdownRegistrySource test
- [x] T5 Canonical round-trip - [x] T5 Canonical round-trip
- [~] T6 GraphLoader idempotencja [NEO4J] (skipped: Docker API too old) - [~] T6 GraphLoader idempotencja [NEO4J] (skipped: Docker API too old)
- [ ] T7 GraphValidator + CSV [NEO4J] - [~] T7 GraphValidator + CSV [NEO4J] (skipped: Docker API too old, jak T6)
- [ ] T8 Zielony pełny build - [x] T8 Zielony pełny build
- [x] T9 Person: title/pwzNumber
- [ ] T10 PwzRegistrySource
- [ ] T11 Organization/Company rekordy + round-trip
- [ ] T12 GraphLoader: Organization/Company [NEO4J]
- [ ] T13 GraphValidator: konflikt interesów [NEO4J]
**Dziennik (data — zadanie — wynik):** **Dziennik (data — zadanie — wynik):**
- (pusto) - 2026-07-07 — T1 — Deduplicator compile fix + unit tests [checkpoint] (commit 38a93d0)
- 2026-07-07 — T2 — NameNormalizer testy + transliteratePolish, token-strip titles [checkpoint] (commit afe107c)
- 2026-07-07 — T3 — PartyNormalizer testy + KO alias [checkpoint] (commit bc4eaac)
- 2026-07-07 — T4 — MarkdownRegistrySource test + fix isPersonTable check [checkpoint] (commit 7f636a1)
- 2026-07-07 — T5 — CanonicalWriter/Reader round-trip test [checkpoint] (commit b072cb3)
- 2026-07-07 — T6/T8 — skip NEO4J tests (Docker API zbyt stary) + pełny build zielony [checkpoint] (commit 59d27b1)
- 2026-07-08 — PLAN.md scalony z PLAN2.md (Person.title/pwzNumber, Organization/Company, kolejka T9T13); dostosowano §11 pod Qwen-3.6-35B-A3B.
- 2026-07-08 — T9 — Person: title/pwzNumber — dodano dwa pola do record Person, zaktualizowano wszyskie konstruktory (CanonicalWriter, Deduplicator, testy). 7 testow zielonych.
--- ---
View File
+5 -5
View File
@@ -4,7 +4,7 @@
"description": "Stan wykonania kolejki zadań lokalnego agenta (PLAN2.md §11-13). Zrodlo prawdy przy konflikcie: git log.", "description": "Stan wykonania kolejki zadań lokalnego agenta (PLAN2.md §11-13). Zrodlo prawdy przy konflikcie: git log.",
"resumeHint": "Wczytaj ten plik, znajdz pierwszy task o statusie todo/in_progress. Jesli in_progress i drzewo git brudne -> git checkout -- . && git clean -fd, ustaw task na todo, zacznij od zera (PLAN2.md §13.1).", "resumeHint": "Wczytaj ten plik, znajdz pierwszy task o statusie todo/in_progress. Jesli in_progress i drzewo git brudne -> git checkout -- . && git clean -fd, ustaw task na todo, zacznij od zera (PLAN2.md §13.1).",
"createdAt": "2026-07-07", "createdAt": "2026-07-07",
"updatedAt": "2026-07-08", "updatedAt": "2026-07-08T23:03:00Z",
"tasks": [ "tasks": [
{ {
"id": "T1", "id": "T1",
@@ -82,10 +82,10 @@
"id": "T9", "id": "T9",
"title": "Person: title/pwzNumber", "title": "Person: title/pwzNumber",
"tag": null, "tag": null,
"status": "todo", "status": "done",
"startedAt": null, "startedAt": "2026-07-08T23:00:00Z",
"finishedAt": null, "finishedAt": "2026-07-08T23:03:00Z",
"note": "PLAN2.md delta: rozszerzenie Person o title i pwzNumber (PLAN.md sekcja 2, T9)." "note": "Dodano title i pwzNumber do Person record. Zaktualizowano wszystkie nowicje Person (CanonicalWriter, Deduplicator, testy). 7 testow zielonych."
}, },
{ {
"id": "T10", "id": "T10",
@@ -73,7 +73,7 @@ public class CanonicalWriter {
if (!peopleMap.containsKey(personId)) { if (!peopleMap.containsKey(personId)) {
peopleMap.put(personId, new Person( peopleMap.put(personId, new Person(
personId, extractedName, personEntry.displayTitle(), personId, extractedName, personEntry.displayTitle(),
titles, new ArrayList<>() titles, new ArrayList<>(), null, null
)); ));
personUrlsMap.put(personId, new LinkedHashSet<>()); personUrlsMap.put(personId, new LinkedHashSet<>());
} }
@@ -122,7 +122,7 @@ public class CanonicalWriter {
if (p != null) { if (p != null) {
updatedPeople.add(new Person( updatedPeople.add(new Person(
p.id(), p.fullName(), p.displayName(), p.id(), p.fullName(), p.displayName(),
p.titles(), new ArrayList<>(entry.getValue()) p.titles(), new ArrayList<>(entry.getValue()), null, null
)); ));
} }
} }
@@ -49,7 +49,8 @@ public class Deduplicator {
first.fullName(), first.fullName(),
first.displayName(), first.displayName(),
first.titles(), first.titles(),
new ArrayList<>(allUrls) new ArrayList<>(allUrls),
null, null
); );
} }
@@ -9,6 +9,8 @@ public record Person(
@JsonProperty("fullName") String fullName, @JsonProperty("fullName") String fullName,
@JsonProperty("displayName") String displayName, @JsonProperty("displayName") String displayName,
@JsonProperty("titles") List<String> titles, @JsonProperty("titles") List<String> titles,
@JsonProperty("sourceUrls") List<String> sourceUrls @JsonProperty("sourceUrls") List<String> sourceUrls,
@JsonProperty("title") String title,
@JsonProperty("pwzNumber") String pwzNumber
) { ) {
} }
@@ -24,14 +24,16 @@ class DeduplicatorTest {
"Jan Kowalski", "Jan Kowalski",
"Jan Kowalski", "Jan Kowalski",
List.of(), List.of(),
List.of("https://example.com/1") List.of("https://example.com/1"),
null, null
); );
Person janKowaski = new Person( Person janKowaski = new Person(
"person:jan-kowaski", "person:jan-kowaski",
"Jan Kowaski", "Jan Kowaski",
"Jan Kowaski", "Jan Kowaski",
List.of(), List.of(),
List.of("https://example.com/2") List.of("https://example.com/2"),
null, null
); );
List<Person> input = List.of(janKowalski, janKowaski); List<Person> input = List.of(janKowalski, janKowaski);
@@ -50,14 +52,16 @@ class DeduplicatorTest {
"Jan Kowalski", "Jan Kowalski",
"Jan Kowalski", "Jan Kowalski",
List.of(), List.of(),
List.of("https://example.com/1") List.of("https://example.com/1"),
null, null
); );
Person second = new Person( Person second = new Person(
"person:jan-kowalski", "person:jan-kowalski",
"Jan Kowalski", "Jan Kowalski",
"Jan Kowalski", "Jan Kowalski",
List.of(), List.of(),
List.of("https://example.com/2") List.of("https://example.com/2"),
null, null
); );
List<Person> result = deduplicator.deduplicate(List.of(first, second)); List<Person> result = deduplicator.deduplicate(List.of(first, second));
@@ -40,7 +40,8 @@ class CanonicalRoundTripTest {
"Jan Kowalski", "Jan Kowalski",
"Dyrektor Naczelny", "Dyrektor Naczelny",
List.of("prof."), List.of("prof."),
List.of("https://uskwb.pl/dyrekcja") List.of("https://uskwb.pl/dyrekcja"),
null, null
); );
Affiliation affiliation = new Affiliation( Affiliation affiliation = new Affiliation(
@@ -0,0 +1,26 @@
package com.developx.szpitale.model;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
class PersonTest {
@Test
void person_withTitleAndPwzNumber_fieldsAccessible() {
Person person = new Person(
"person:jan-kochanowicz",
"Jan Kochanowicz",
"prof. dr hab. n. med. Jan Kochanowicz",
List.of("prof.", "dr hab.", "n. med."),
List.of("https://uskwb.pl/dyrekcja-szpitala/"),
"prof. dr hab. n. med.",
"1234567"
);
assertEquals("prof. dr hab. n. med.", person.title());
assertEquals("1234567", person.pwzNumber());
}
}
@@ -1,6 +1,6 @@
com/developx/szpitale/ingest/normalize/NameNormalizerTest.class com/developx/szpitale/ingest/normalize/NameNormalizerTest.class
com/developx/szpitale/ingest/normalize/PartyNormalizerTest.class com/developx/szpitale/ingest/normalize/PartyNormalizerTest.class
com/developx/szpitale/model/PersonTest.class
com/developx/szpitale/ingest/source/MarkdownRegistrySourceTest.class com/developx/szpitale/ingest/source/MarkdownRegistrySourceTest.class
com/developx/szpitale/ingest/normalize/DeduplicatorTest.class com/developx/szpitale/ingest/normalize/DeduplicatorTest.class
com/developx/szpitale/load/GraphLoaderIT.class
com/developx/szpitale/load/CanonicalRoundTripTest.class com/developx/szpitale/load/CanonicalRoundTripTest.class
@@ -3,4 +3,4 @@
/home/kruszewskia/Workspace/Private/Szpitale-graph/szpitale-graph/src/test/java/com/developx/szpitale/ingest/normalize/PartyNormalizerTest.java /home/kruszewskia/Workspace/Private/Szpitale-graph/szpitale-graph/src/test/java/com/developx/szpitale/ingest/normalize/PartyNormalizerTest.java
/home/kruszewskia/Workspace/Private/Szpitale-graph/szpitale-graph/src/test/java/com/developx/szpitale/ingest/source/MarkdownRegistrySourceTest.java /home/kruszewskia/Workspace/Private/Szpitale-graph/szpitale-graph/src/test/java/com/developx/szpitale/ingest/source/MarkdownRegistrySourceTest.java
/home/kruszewskia/Workspace/Private/Szpitale-graph/szpitale-graph/src/test/java/com/developx/szpitale/load/CanonicalRoundTripTest.java /home/kruszewskia/Workspace/Private/Szpitale-graph/szpitale-graph/src/test/java/com/developx/szpitale/load/CanonicalRoundTripTest.java
/home/kruszewskia/Workspace/Private/Szpitale-graph/szpitale-graph/src/test/java/com/developx/szpitale/load/GraphLoaderIT.java /home/kruszewskia/Workspace/Private/Szpitale-graph/szpitale-graph/src/test/java/com/developx/szpitale/model/PersonTest.java
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,4 +1,4 @@
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
Test set: com.developx.szpitale.ingest.normalize.DeduplicatorTest Test set: com.developx.szpitale.ingest.normalize.DeduplicatorTest
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.006 s -- in com.developx.szpitale.ingest.normalize.DeduplicatorTest Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.008 s -- in com.developx.szpitale.ingest.normalize.DeduplicatorTest
@@ -1,4 +1,4 @@
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
Test set: com.developx.szpitale.ingest.normalize.NameNormalizerTest Test set: com.developx.szpitale.ingest.normalize.NameNormalizerTest
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
Tests run: 25, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.078 s -- in com.developx.szpitale.ingest.normalize.NameNormalizerTest Tests run: 25, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.081 s -- in com.developx.szpitale.ingest.normalize.NameNormalizerTest
@@ -1,4 +1,4 @@
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
Test set: com.developx.szpitale.ingest.normalize.PartyNormalizerTest Test set: com.developx.szpitale.ingest.normalize.PartyNormalizerTest
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
Tests run: 15, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.011 s -- in com.developx.szpitale.ingest.normalize.PartyNormalizerTest Tests run: 15, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.010 s -- in com.developx.szpitale.ingest.normalize.PartyNormalizerTest
@@ -1,4 +1,4 @@
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
Test set: com.developx.szpitale.ingest.source.MarkdownRegistrySourceTest Test set: com.developx.szpitale.ingest.source.MarkdownRegistrySourceTest
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.100 s -- in com.developx.szpitale.ingest.source.MarkdownRegistrySourceTest Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.101 s -- in com.developx.szpitale.ingest.source.MarkdownRegistrySourceTest
@@ -1,4 +1,4 @@
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
Test set: com.developx.szpitale.load.CanonicalRoundTripTest Test set: com.developx.szpitale.load.CanonicalRoundTripTest
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.187 s -- in com.developx.szpitale.load.CanonicalRoundTripTest Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.165 s -- in com.developx.szpitale.load.CanonicalRoundTripTest
@@ -0,0 +1,4 @@
-------------------------------------------------------------------------------
Test set: com.developx.szpitale.model.PersonTest
-------------------------------------------------------------------------------
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.002 s -- in com.developx.szpitale.model.PersonTest