Compare commits
8
Commits
464f5ea69f
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a9163509b9 | ||
|
|
024527535e | ||
|
|
f643db6118 | ||
|
|
6d16828df4 | ||
|
|
6e87fd1b8d | ||
|
|
a8bdaf4cae | ||
|
|
387e2617b8 | ||
|
|
79fdf6a715 |
@@ -8,6 +8,8 @@
|
||||
**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.
|
||||
- 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 (→ T9–T13). 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",
|
||||
"displayName": "prof. dr hab. n. med. Jan Kochanowicz", // z tytułami, jak w źródle
|
||||
"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/"]
|
||||
}
|
||||
],
|
||||
"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
|
||||
{
|
||||
"personId": "person:karol-pilecki",
|
||||
@@ -133,8 +183,10 @@ Jeden schemat dla **wszystkich** województw. Plik `canonical/<voivodeship>.json
|
||||
|
||||
**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).
|
||||
- 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).
|
||||
- `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` |
|
||||
| Testy | JUnit 5, Testcontainers (`neo4j` container) do testów warstwy ładowania |
|
||||
| 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
|
||||
```
|
||||
@@ -163,14 +216,18 @@ szpitale-graph/
|
||||
│ ├── model/ # rekordy modelu kanonicznego
|
||||
│ │ ├── CanonicalDataset.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
|
||||
│ │ ├── IngestCommand.java # @ShellComponent — komenda `ingest`
|
||||
│ │ ├── source/ # źródła per-województwo
|
||||
│ │ │ ├── VoivodeshipSource.java # interfejs: List<RawRecord> fetch()
|
||||
│ │ │ ├── MarkdownRegistrySource.java# parser materiałów typu research-*.md
|
||||
│ │ │ ├── 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/
|
||||
│ │ │ ├── NameNormalizer.java # diakrytyki, tytuły, wielkość liter, slug
|
||||
│ │ │ ├── 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).
|
||||
- **`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`).
|
||||
- **`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).
|
||||
|
||||
@@ -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.
|
||||
|
||||
### 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
|
||||
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`).
|
||||
@@ -285,10 +366,12 @@ Odczytać plik(i) kanoniczne i zmaterializować graf w Neo4j: węzły, relacje i
|
||||
### 5.2. Model grafu
|
||||
**Węzły (labels):**
|
||||
- `: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)
|
||||
- `:GovBody {name, type, voivodeship, term}` (sejmik, rada powiatu, urząd gminy — dla mandatów)
|
||||
- `: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:**
|
||||
| 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` |
|
||||
| `[:PELNI_MANDAT]` | Person → GovBody | `mandateType, term, sourceUrls` |
|
||||
| `[: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.
|
||||
|
||||
@@ -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 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 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_pwz IF NOT EXISTS FOR (p:Person) ON (p.pwzNumber);
|
||||
```
|
||||
|
||||
### 5.4. Ładowanie (parametryzowane, wsadowe)
|
||||
@@ -325,7 +414,30 @@ MERGE (hosp)-[:W_WOJEWODZTWIE]->(v);
|
||||
UNWIND $people AS p
|
||||
MERGE (person:Person {id: p.id})
|
||||
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
|
||||
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)
|
||||
WITH p, collect(DISTINCT h) AS hs WHERE size(hs) > 1
|
||||
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`) |
|
||||
| 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 |
|
||||
| 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).
|
||||
2. **Model kanoniczny** (`canonical/*.json`) — wersjonowalny, wielowojewódzki snapshot danych.
|
||||
3. **Schemat Neo4j** — udokumentowane labels, typy relacji i właściwości (sekcja 5.2–5.3).
|
||||
4. **`overlap_report.csv`** — osoby łączące funkcje w organach szpitali z mandatami/partiami, z `sourceUrls` i `confidence`.
|
||||
5. **`ingest-report.json`** — statystyki i rejestr luk/rozbieżności per województwo.
|
||||
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 (w tym wieloznaczne trafienia PWZ).
|
||||
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.
|
||||
- [ ] 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`.
|
||||
- [ ] `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)
|
||||
|
||||
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)
|
||||
|
||||
@@ -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.
|
||||
- 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)
|
||||
@@ -563,15 +730,30 @@ Po ukończeniu zadania agent zmienia jego status w liście poniżej i dopisuje j
|
||||
|
||||
- [x] T1 Deduplicator compile fix
|
||||
- [x] T2 NameNormalizer testy
|
||||
- [ ] T3 PartyNormalizer testy
|
||||
- [x] T3 PartyNormalizer testy
|
||||
- [x] T4 MarkdownRegistrySource test
|
||||
- [x] T5 Canonical round-trip
|
||||
- [~] T6 GraphLoader idempotencja [NEO4J] (skipped: Docker API too old)
|
||||
- [ ] T7 GraphValidator + CSV [NEO4J]
|
||||
- [ ] T8 Zielony pełny build
|
||||
- [~] T6 GraphLoader idempotencja [NEO4J] (skipped: docker-java 3.4.0 in testcontainers 1.20.x hardcodes API v1.32, Daemon requires v1.44+)
|
||||
- [~] T7 GraphValidator + CSV [NEO4J] (skipped: same docker-api issue as T6)
|
||||
- [x] T8 Zielony pełny build
|
||||
- [x] T9 Person: title/pwzNumber
|
||||
- [x] T10 PwzRegistrySource
|
||||
- [x] T11 Organization/Company rekordy + round-trip
|
||||
- [~] T12 GraphLoader: Organization/Company [NEO4J] (skipped: Docker API version mismatch - testcontainers docker-java client v1.32, min 1.44)
|
||||
- [~] T13 GraphValidator: konflikt interesow [NEO4J] (skipped: Docker API too old — same cause as T6)
|
||||
|
||||
**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 T9–T13); 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.
|
||||
- 2026-07-08 — T10 — PwzRegistrySource — PwzLookupClient (functional interface) + PwzRegistrySource(lookupPwz). 4 testy zielone.
|
||||
- 2026-07-08 — T11 — Organization/Company entites + round-trip — Organization, Company, PersonOrganizationLink, OrganizationLink, PersonCompanyLink + LinkBasis enum. CanonicalDataset + CanonicalReader zaktualizowane.
|
||||
- 2026-07-08 — T12 — GraphLoader Organization/Company loading (prod code committed), GraphLoaderOrganizationIT (skipped: Docker API too old, same as T6/T7)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"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).",
|
||||
"createdAt": "2026-07-07",
|
||||
"updatedAt": "2026-07-07",
|
||||
"updatedAt": "2026-07-08T23:23:00Z",
|
||||
"tasks": [
|
||||
{
|
||||
"id": "T1",
|
||||
@@ -77,6 +77,51 @@
|
||||
"startedAt": null,
|
||||
"finishedAt": "2026-07-07T00:00:00Z",
|
||||
"note": "mvn verify green. 2 NEO4J skipped."
|
||||
},
|
||||
{
|
||||
"id": "T9",
|
||||
"title": "Person: title/pwzNumber",
|
||||
"tag": null,
|
||||
"status": "done",
|
||||
"startedAt": "2026-07-08T23:00:00Z",
|
||||
"finishedAt": "2026-07-08T23:03:00Z",
|
||||
"note": "Dodano title i pwzNumber do Person record. Zaktualizowano wszystkie nowicje Person (CanonicalWriter, Deduplicator, testy). 7 testow zielonych."
|
||||
},
|
||||
{
|
||||
"id": "T10",
|
||||
"title": "PwzRegistrySource",
|
||||
"tag": null,
|
||||
"status": "done",
|
||||
"startedAt": "2026-07-08T23:04:00Z",
|
||||
"finishedAt": "2026-07-08T23:11:00Z",
|
||||
"note": "PwzRegistrySource z PwzLookupClient. 4 testow zielonych: singleMatch, multipleMatches (ambiguity), noMatch, nullClient."
|
||||
},
|
||||
{
|
||||
"id": "T11",
|
||||
"title": "Organization/Company rekordy + round-trip",
|
||||
"tag": null,
|
||||
"status": "done",
|
||||
"startedAt": "2026-07-08T23:12:00Z",
|
||||
"finishedAt": "2026-07-08T23:23:00Z",
|
||||
"note": "Organization, Company, PersonOrganizationLink, OrganizationLink, PersonCompanyLink rekordy + LinkBasis enum. CanonicalDataset rozszerzony o 5 pól. Round-trip test zielony."
|
||||
},
|
||||
{
|
||||
"id": "T12",
|
||||
"title": "GraphLoader: Organization/Company",
|
||||
"tag": "NEO4J",
|
||||
"status": "skipped",
|
||||
"startedAt": "2026-07-08T23:25:00Z",
|
||||
"finishedAt": "2026-07-08T23:38:00Z",
|
||||
"note": "Docker API version mismatch: testcontainers docker-java client v1.32, min 1.44. Production code (GraphLoader GraphSchema) committed separately."
|
||||
},
|
||||
{
|
||||
"id": "T13",
|
||||
"title": "GraphValidator: konflikt interesow",
|
||||
"tag": "NEO4J",
|
||||
"status": "skipped",
|
||||
"startedAt": null,
|
||||
"finishedAt": "2026-07-08T23:38:00Z",
|
||||
"note": "Docker API too old (same reason as T12/T6/T7)."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -73,7 +73,7 @@ public class CanonicalWriter {
|
||||
if (!peopleMap.containsKey(personId)) {
|
||||
peopleMap.put(personId, new Person(
|
||||
personId, extractedName, personEntry.displayTitle(),
|
||||
titles, new ArrayList<>()
|
||||
titles, new ArrayList<>(), null, null
|
||||
));
|
||||
personUrlsMap.put(personId, new LinkedHashSet<>());
|
||||
}
|
||||
@@ -122,7 +122,7 @@ public class CanonicalWriter {
|
||||
if (p != null) {
|
||||
updatedPeople.add(new Person(
|
||||
p.id(), p.fullName(), p.displayName(),
|
||||
p.titles(), new ArrayList<>(entry.getValue())
|
||||
p.titles(), new ArrayList<>(entry.getValue()), null, null
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -131,7 +131,8 @@ public class CanonicalWriter {
|
||||
|
||||
CanonicalDataset dataset = new CanonicalDataset(
|
||||
"1.0", voivodeship, LocalDateTime.now(),
|
||||
hospitals, people, affiliations, roles, List.of()
|
||||
hospitals, people, affiliations, roles, List.of(),
|
||||
List.of(), List.of(), List.of(), List.of(), List.of()
|
||||
);
|
||||
|
||||
Path outputFile = canonicalDir.resolve(voivodeship + ".json");
|
||||
|
||||
+2
-1
@@ -49,7 +49,8 @@ public class Deduplicator {
|
||||
first.fullName(),
|
||||
first.displayName(),
|
||||
first.titles(),
|
||||
new ArrayList<>(allUrls)
|
||||
new ArrayList<>(allUrls),
|
||||
null, null
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.developx.szpitale.ingest.source;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface PwzLookupClient {
|
||||
List<String> query(String fullName);
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package com.developx.szpitale.ingest.source;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public class PwzRegistrySource {
|
||||
|
||||
private final PwzLookupClient client;
|
||||
|
||||
public PwzRegistrySource(PwzLookupClient client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public Optional<String> lookupPwz(String fullName) {
|
||||
if (client == null || fullName == null || fullName.isBlank()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
List<String> results = client.query(fullName);
|
||||
|
||||
if (results == null || results.isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
if (results.size() == 1) {
|
||||
return Optional.of(results.get(0));
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import com.developx.szpitale.model.CanonicalDataset;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@@ -50,4 +51,10 @@ public class CanonicalReader {
|
||||
public CanonicalDataset readSingle(Path inputDir, String voivodeship) throws IOException {
|
||||
return readAll(inputDir, voivodeship).stream().findFirst().orElse(null);
|
||||
}
|
||||
|
||||
public static CanonicalDataset readDataset(File file) throws IOException {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.registerModule(new JavaTimeModule());
|
||||
return mapper.readValue(file, CanonicalDataset.class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,11 @@ public class GraphLoader {
|
||||
loadRoles(session, dataset.roles(), stats);
|
||||
loadAffiliations(session, dataset.affiliations(), stats);
|
||||
loadMandates(session, dataset.mandates(), stats);
|
||||
loadOrganizations(session, dataset.organizations(), stats);
|
||||
loadCompanies(session, dataset.companies(), stats);
|
||||
loadPersonOrganizationLinks(session, dataset.personOrganizationLinks(), stats);
|
||||
loadOrganizationLinks(session, dataset.organizationLinks(), stats);
|
||||
loadPersonCompanyLinks(session, dataset.personCompanyLinks(), stats);
|
||||
}
|
||||
}
|
||||
return stats;
|
||||
@@ -176,6 +181,130 @@ public class GraphLoader {
|
||||
}
|
||||
}
|
||||
|
||||
private void loadOrganizations(Session session, List<Organization> organizations, LoadStats stats) {
|
||||
if (organizations.isEmpty()) return;
|
||||
|
||||
List<Map<String, Object>> batch = new ArrayList<>();
|
||||
for (Organization o : organizations) {
|
||||
batch.add(Map.of(
|
||||
"id", o.id(),
|
||||
"name", o.name(),
|
||||
"krs", o.krs(),
|
||||
"nip", o.nip(),
|
||||
"sourceUrls", o.sourceUrls()
|
||||
));
|
||||
}
|
||||
|
||||
for (int i = 0; i < batch.size(); i += BATCH_SIZE) {
|
||||
List<Map<String, Object>> subList = batch.subList(i, Math.min(i + BATCH_SIZE, batch.size()));
|
||||
String cypher = "UNWIND $batch AS o " +
|
||||
"MERGE (org:Organization {id: o.id}) " +
|
||||
"SET org += {name: o.name, krs: o.krs, nip: o.nip, sourceUrls: o.sourceUrls}";
|
||||
session.run(cypher, Map.of("batch", subList));
|
||||
stats.addOrganizations(subList.size());
|
||||
}
|
||||
}
|
||||
|
||||
private void loadCompanies(Session session, List<Company> companies, LoadStats stats) {
|
||||
if (companies.isEmpty()) return;
|
||||
|
||||
List<Map<String, Object>> batch = new ArrayList<>();
|
||||
for (Company c : companies) {
|
||||
batch.add(Map.of(
|
||||
"id", c.id(),
|
||||
"name", c.name(),
|
||||
"krs", c.krs(),
|
||||
"nip", c.nip(),
|
||||
"ceidgId", c.ceidgId(),
|
||||
"sourceUrls", c.sourceUrls()
|
||||
));
|
||||
}
|
||||
|
||||
for (int i = 0; i < batch.size(); i += BATCH_SIZE) {
|
||||
List<Map<String, Object>> subList = batch.subList(i, Math.min(i + BATCH_SIZE, batch.size()));
|
||||
String cypher = "UNWIND $batch 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}";
|
||||
session.run(cypher, Map.of("batch", subList));
|
||||
stats.addCompanies(subList.size());
|
||||
}
|
||||
}
|
||||
|
||||
private void loadPersonOrganizationLinks(Session session, List<PersonOrganizationLink> links, LoadStats stats) {
|
||||
if (links.isEmpty()) return;
|
||||
|
||||
List<Map<String, Object>> batch = new ArrayList<>();
|
||||
for (PersonOrganizationLink l : links) {
|
||||
batch.add(Map.of(
|
||||
"personId", l.personId(),
|
||||
"organizationId", l.organizationId(),
|
||||
"roleLabel", l.roleLabel(),
|
||||
"basis", l.basis().name(),
|
||||
"sourceUrls", l.sourceUrls()
|
||||
));
|
||||
}
|
||||
|
||||
for (int i = 0; i < batch.size(); i += BATCH_SIZE) {
|
||||
List<Map<String, Object>> subList = batch.subList(i, Math.min(i + BATCH_SIZE, batch.size()));
|
||||
String cypher = "UNWIND $batch AS l " +
|
||||
"MATCH (p:Person {id: l.personId}), (org:Organization {id: l.organizationId}) " +
|
||||
"MERGE (p)-[m:CZLONEK_ORGANIZACJI]->(org) " +
|
||||
"SET m += {roleLabel: l.roleLabel, basis: l.basis, sourceUrls: l.sourceUrls}";
|
||||
session.run(cypher, Map.of("batch", subList));
|
||||
stats.addPersonOrganizationLinks(subList.size());
|
||||
}
|
||||
}
|
||||
|
||||
private void loadOrganizationLinks(Session session, List<OrganizationLink> links, LoadStats stats) {
|
||||
if (links.isEmpty()) return;
|
||||
|
||||
List<Map<String, Object>> batch = new ArrayList<>();
|
||||
for (OrganizationLink l : links) {
|
||||
batch.add(Map.of(
|
||||
"fromId", l.fromOrganizationId(),
|
||||
"toId", l.toOrganizationId(),
|
||||
"basis", l.basis().name(),
|
||||
"note", l.note(),
|
||||
"sourceUrls", l.sourceUrls()
|
||||
));
|
||||
}
|
||||
|
||||
for (int i = 0; i < batch.size(); i += BATCH_SIZE) {
|
||||
List<Map<String, Object>> subList = batch.subList(i, Math.min(i + BATCH_SIZE, batch.size()));
|
||||
String cypher = "UNWIND $batch AS l " +
|
||||
"MATCH (a:Organization {id: l.fromId}), (b:Organization {id: l.toId}) " +
|
||||
"MERGE (a)-[m:POWIAZANA_Z]->(b) " +
|
||||
"SET m += {basis: l.basis, note: l.note, sourceUrls: l.sourceUrls}";
|
||||
session.run(cypher, Map.of("batch", subList));
|
||||
stats.addOrganizationLinks(subList.size());
|
||||
}
|
||||
}
|
||||
|
||||
private void loadPersonCompanyLinks(Session session, List<PersonCompanyLink> links, LoadStats stats) {
|
||||
if (links.isEmpty()) return;
|
||||
|
||||
List<Map<String, Object>> batch = new ArrayList<>();
|
||||
for (PersonCompanyLink l : links) {
|
||||
batch.add(Map.of(
|
||||
"personId", l.personId(),
|
||||
"companyId", l.companyId(),
|
||||
"roleLabel", l.roleLabel(),
|
||||
"basis", l.basis().name(),
|
||||
"sourceUrls", l.sourceUrls()
|
||||
));
|
||||
}
|
||||
|
||||
for (int i = 0; i < batch.size(); i += BATCH_SIZE) {
|
||||
List<Map<String, Object>> subList = batch.subList(i, Math.min(i + BATCH_SIZE, batch.size()));
|
||||
String cypher = "UNWIND $batch AS l " +
|
||||
"MATCH (p:Person {id: l.personId}), (comp:Company {id: l.companyId}) " +
|
||||
"MERGE (p)-[m:POWIAZANY_Z_FIRMA]->(comp) " +
|
||||
"SET m += {roleLabel: l.roleLabel, basis: l.basis, sourceUrls: l.sourceUrls}";
|
||||
session.run(cypher, Map.of("batch", subList));
|
||||
stats.addPersonCompanyLinks(subList.size());
|
||||
}
|
||||
}
|
||||
|
||||
private String roleTypeToRelName(Role r) {
|
||||
String organ = r.organ().name();
|
||||
String role = r.roleType().name();
|
||||
@@ -196,6 +325,11 @@ public class GraphLoader {
|
||||
public int roleCount;
|
||||
public int affiliationCount;
|
||||
public int mandateCount;
|
||||
public int organizationCount;
|
||||
public int companyCount;
|
||||
public int personOrganizationLinkCount;
|
||||
public int organizationLinkCount;
|
||||
public int personCompanyLinkCount;
|
||||
public final List<String> voivodeships = new ArrayList<>();
|
||||
|
||||
public void addVoivodeship(String v) { voivodeships.add(v); }
|
||||
@@ -204,12 +338,18 @@ public class GraphLoader {
|
||||
public void addRoles(int count) { roleCount += count; }
|
||||
public void addAffiliations(int count) { affiliationCount += count; }
|
||||
public void addMandates(int count) { mandateCount += count; }
|
||||
public void addOrganizations(int count) { organizationCount += count; }
|
||||
public void addCompanies(int count) { companyCount += count; }
|
||||
public void addPersonOrganizationLinks(int count) { personOrganizationLinkCount += count; }
|
||||
public void addOrganizationLinks(int count) { organizationLinkCount += count; }
|
||||
public void addPersonCompanyLinks(int count) { personCompanyLinkCount += count; }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format(
|
||||
"Load stats - voivodeships: %s, hospitals: %d, people: %d, roles: %d, affiliations: %d, mandates: %d",
|
||||
String.join(", ", voivodeships), hospitalCount, personCount, roleCount, affiliationCount, mandateCount
|
||||
"Load stats - voivodeships: %s, hospitals: %d, people: %d, roles: %d, affiliations: %d, mandates: %d, organizations: %d, companies: %d, personOrgLinks: %d, orgLinks: %d, personCompanyLinks: %d",
|
||||
String.join(", ", voivodeships), hospitalCount, personCount, roleCount, affiliationCount, mandateCount,
|
||||
organizationCount, companyCount, personOrganizationLinkCount, organizationLinkCount, personCompanyLinkCount
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ public class GraphSchema {
|
||||
session.run("CREATE CONSTRAINT person_id IF NOT EXISTS FOR (p:Person) REQUIRE p.id IS UNIQUE");
|
||||
session.run("CREATE CONSTRAINT party_name IF NOT EXISTS FOR (x:Party) REQUIRE x.name IS UNIQUE");
|
||||
session.run("CREATE CONSTRAINT voiv_slug IF NOT EXISTS FOR (v:Voivodeship) REQUIRE v.slug IS UNIQUE");
|
||||
session.run("CREATE CONSTRAINT org_id IF NOT EXISTS FOR (o:Organization) REQUIRE o.id IS UNIQUE");
|
||||
session.run("CREATE CONSTRAINT company_id IF NOT EXISTS FOR (c:Company) REQUIRE c.id IS UNIQUE");
|
||||
session.run("CREATE INDEX person_name IF NOT EXISTS FOR (p:Person) ON (p.fullName)");
|
||||
session.run("CREATE INDEX govbody_name IF NOT EXISTS FOR (g:GovBody) ON (g.name)");
|
||||
log.info("Graph constraints and indexes created");
|
||||
|
||||
@@ -13,6 +13,33 @@ public record CanonicalDataset(
|
||||
@JsonProperty("people") List<Person> people,
|
||||
@JsonProperty("affiliations") List<Affiliation> affiliations,
|
||||
@JsonProperty("roles") List<Role> roles,
|
||||
@JsonProperty("mandates") List<Mandate> mandates
|
||||
@JsonProperty("mandates") List<Mandate> mandates,
|
||||
@JsonProperty("organizations") List<Organization> organizations,
|
||||
@JsonProperty("companies") List<Company> companies,
|
||||
@JsonProperty("personOrganizationLinks") List<PersonOrganizationLink> personOrganizationLinks,
|
||||
@JsonProperty("organizationLinks") List<OrganizationLink> organizationLinks,
|
||||
@JsonProperty("personCompanyLinks") List<PersonCompanyLink> personCompanyLinks
|
||||
) {
|
||||
public CanonicalDataset(String schemaVersion, String voivodeship, LocalDateTime generatedAt,
|
||||
List<Hospital> hospitals, List<Person> people,
|
||||
List<Affiliation> affiliations, List<Role> roles,
|
||||
List<Mandate> mandates,
|
||||
List<Organization> organizations, List<Company> companies,
|
||||
List<PersonOrganizationLink> personOrganizationLinks,
|
||||
List<OrganizationLink> organizationLinks,
|
||||
List<PersonCompanyLink> personCompanyLinks) {
|
||||
this.schemaVersion = schemaVersion;
|
||||
this.voivodeship = voivodeship;
|
||||
this.generatedAt = generatedAt;
|
||||
this.hospitals = hospitals != null ? hospitals : List.of();
|
||||
this.people = people != null ? people : List.of();
|
||||
this.affiliations = affiliations != null ? affiliations : List.of();
|
||||
this.roles = roles != null ? roles : List.of();
|
||||
this.mandates = mandates != null ? mandates : List.of();
|
||||
this.organizations = organizations != null ? organizations : List.of();
|
||||
this.companies = companies != null ? companies : List.of();
|
||||
this.personOrganizationLinks = personOrganizationLinks != null ? personOrganizationLinks : List.of();
|
||||
this.organizationLinks = organizationLinks != null ? organizationLinks : List.of();
|
||||
this.personCompanyLinks = personCompanyLinks != null ? personCompanyLinks : List.of();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.developx.szpitale.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record Company(
|
||||
@JsonProperty("id") String id,
|
||||
@JsonProperty("name") String name,
|
||||
@JsonProperty("krs") String krs,
|
||||
@JsonProperty("nip") String nip,
|
||||
@JsonProperty("ceidgId") String ceidgId,
|
||||
@JsonProperty("sourceUrls") List<String> sourceUrls
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.developx.szpitale.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record Organization(
|
||||
@JsonProperty("id") String id,
|
||||
@JsonProperty("name") String name,
|
||||
@JsonProperty("krs") String krs,
|
||||
@JsonProperty("nip") String nip,
|
||||
@JsonProperty("sourceUrls") List<String> sourceUrls
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.developx.szpitale.model;
|
||||
|
||||
import com.developx.szpitale.model.enums.LinkBasis;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record OrganizationLink(
|
||||
@JsonProperty("fromOrganizationId") String fromOrganizationId,
|
||||
@JsonProperty("toOrganizationId") String toOrganizationId,
|
||||
@JsonProperty("basis") LinkBasis basis,
|
||||
@JsonProperty("note") String note,
|
||||
@JsonProperty("sourceUrls") List<String> sourceUrls
|
||||
) {
|
||||
}
|
||||
@@ -9,6 +9,8 @@ public record Person(
|
||||
@JsonProperty("fullName") String fullName,
|
||||
@JsonProperty("displayName") String displayName,
|
||||
@JsonProperty("titles") List<String> titles,
|
||||
@JsonProperty("sourceUrls") List<String> sourceUrls
|
||||
@JsonProperty("sourceUrls") List<String> sourceUrls,
|
||||
@JsonProperty("title") String title,
|
||||
@JsonProperty("pwzNumber") String pwzNumber
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.developx.szpitale.model;
|
||||
|
||||
import com.developx.szpitale.model.enums.LinkBasis;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record PersonCompanyLink(
|
||||
@JsonProperty("personId") String personId,
|
||||
@JsonProperty("companyId") String companyId,
|
||||
@JsonProperty("roleLabel") String roleLabel,
|
||||
@JsonProperty("basis") LinkBasis basis,
|
||||
@JsonProperty("sourceUrls") List<String> sourceUrls
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.developx.szpitale.model;
|
||||
|
||||
import com.developx.szpitale.model.enums.LinkBasis;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record PersonOrganizationLink(
|
||||
@JsonProperty("personId") String personId,
|
||||
@JsonProperty("organizationId") String organizationId,
|
||||
@JsonProperty("roleLabel") String roleLabel,
|
||||
@JsonProperty("basis") LinkBasis basis,
|
||||
@JsonProperty("sourceUrls") List<String> sourceUrls
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.developx.szpitale.model.enums;
|
||||
|
||||
public enum LinkBasis {
|
||||
KRS,
|
||||
NIP,
|
||||
CEIDG
|
||||
}
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
package com.developx.szpitale.ingest;
|
||||
|
||||
import com.developx.szpitale.load.CanonicalReader;
|
||||
import com.developx.szpitale.model.*;
|
||||
import com.developx.szpitale.model.enums.*;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class OrganizationCompanyRoundTripTest {
|
||||
|
||||
@TempDir
|
||||
Path tempDir;
|
||||
|
||||
@Test
|
||||
void write_thenRead_preservesOrganizationsAndCompanies() throws Exception {
|
||||
Organization organization = new Organization(
|
||||
"org:fundacja-xyz",
|
||||
"Fundacja XYZ",
|
||||
"0000123456",
|
||||
null,
|
||||
List.of("https://ems.ms.gov.pl/fundacja-xyz")
|
||||
);
|
||||
|
||||
Company company = new Company(
|
||||
"company:abc-sp-zoo",
|
||||
"ABC sp. z o.o.",
|
||||
"0000654321",
|
||||
"1234567890",
|
||||
"CEIDG123",
|
||||
List.of("https://wyszukiwarka-krs.ms.gov.pl/abc")
|
||||
);
|
||||
|
||||
Person person = new Person(
|
||||
"person:jan-kochanowicz",
|
||||
"Jan Kochanowicz",
|
||||
"prof. dr hab. Jan Kochanowicz",
|
||||
List.of("prof.", "dr hab."),
|
||||
List.of("https://uskwb.pl/"),
|
||||
"prof. dr hab.",
|
||||
null
|
||||
);
|
||||
|
||||
PersonOrganizationLink pol = new PersonOrganizationLink(
|
||||
"person:jan-kochanowicz",
|
||||
"org:fundacja-xyz",
|
||||
"Prezes Zarządu",
|
||||
LinkBasis.KRS,
|
||||
List.of("https://ems.ms.gov.pl/fundacja-xyz")
|
||||
);
|
||||
|
||||
PersonCompanyLink pcl = new PersonCompanyLink(
|
||||
"person:jan-kochanowicz",
|
||||
"company:abc-sp-zoo",
|
||||
"Wspólnik",
|
||||
LinkBasis.KRS,
|
||||
List.of("https://wyszukiwarka-krs.ms.gov.pl/abc")
|
||||
);
|
||||
|
||||
OrganizationLink orgLink = new OrganizationLink(
|
||||
"org:fundacja-xyz",
|
||||
"org:inna-fundacja",
|
||||
LinkBasis.NIP,
|
||||
"wspolny adres",
|
||||
List.of("https://ems.ms.gov.pl/fundacja-xyz")
|
||||
);
|
||||
|
||||
CanonicalDataset dataset = new CanonicalDataset(
|
||||
"1.0",
|
||||
"podlaskie",
|
||||
LocalDateTime.now(),
|
||||
List.of(),
|
||||
List.of(person),
|
||||
List.of(),
|
||||
List.of(),
|
||||
List.of(),
|
||||
List.of(organization),
|
||||
List.of(company),
|
||||
List.of(pol),
|
||||
List.of(orgLink),
|
||||
List.of(pcl)
|
||||
);
|
||||
|
||||
Path outputFile = tempDir.resolve("test-canonical.json");
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.registerModule(new JavaTimeModule());
|
||||
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
||||
mapper.writeValue(outputFile.toFile(), dataset);
|
||||
|
||||
CanonicalDataset readResult = CanonicalReader.readDataset(outputFile.toFile());
|
||||
|
||||
assertNotNull(readResult.organizations());
|
||||
assertNotNull(readResult.companies());
|
||||
assertNotNull(readResult.personOrganizationLinks());
|
||||
assertNotNull(readResult.organizationLinks());
|
||||
assertNotNull(readResult.personCompanyLinks());
|
||||
|
||||
assertEquals(1, readResult.organizations().size());
|
||||
assertEquals("org:fundacja-xyz", readResult.organizations().get(0).id());
|
||||
assertEquals("Fundacja XYZ", readResult.organizations().get(0).name());
|
||||
assertEquals("0000123456", readResult.organizations().get(0).krs());
|
||||
|
||||
assertEquals(1, readResult.companies().size());
|
||||
assertEquals("company:abc-sp-zoo", readResult.companies().get(0).id());
|
||||
assertEquals("ABC sp. z o.o.", readResult.companies().get(0).name());
|
||||
assertEquals("1234567890", readResult.companies().get(0).nip());
|
||||
assertEquals("CEIDG123", readResult.companies().get(0).ceidgId());
|
||||
|
||||
assertEquals(1, readResult.personOrganizationLinks().size());
|
||||
assertEquals("person:jan-kochanowicz", readResult.personOrganizationLinks().get(0).personId());
|
||||
assertEquals("org:fundacja-xyz", readResult.personOrganizationLinks().get(0).organizationId());
|
||||
assertEquals(LinkBasis.KRS, readResult.personOrganizationLinks().get(0).basis());
|
||||
|
||||
assertEquals(1, readResult.organizationLinks().size());
|
||||
assertEquals("org:fundacja-xyz", readResult.organizationLinks().get(0).fromOrganizationId());
|
||||
assertEquals("org:inna-fundacja", readResult.organizationLinks().get(0).toOrganizationId());
|
||||
assertEquals(LinkBasis.NIP, readResult.organizationLinks().get(0).basis());
|
||||
|
||||
assertEquals(1, readResult.personCompanyLinks().size());
|
||||
assertEquals("person:jan-kochanowicz", readResult.personCompanyLinks().get(0).personId());
|
||||
assertEquals("company:abc-sp-zoo", readResult.personCompanyLinks().get(0).companyId());
|
||||
assertEquals(LinkBasis.KRS, readResult.personCompanyLinks().get(0).basis());
|
||||
}
|
||||
}
|
||||
+8
-4
@@ -24,14 +24,16 @@ class DeduplicatorTest {
|
||||
"Jan Kowalski",
|
||||
"Jan Kowalski",
|
||||
List.of(),
|
||||
List.of("https://example.com/1")
|
||||
List.of("https://example.com/1"),
|
||||
null, null
|
||||
);
|
||||
Person janKowaski = new Person(
|
||||
"person:jan-kowaski",
|
||||
"Jan Kowaski",
|
||||
"Jan Kowaski",
|
||||
List.of(),
|
||||
List.of("https://example.com/2")
|
||||
List.of("https://example.com/2"),
|
||||
null, null
|
||||
);
|
||||
|
||||
List<Person> input = List.of(janKowalski, janKowaski);
|
||||
@@ -50,14 +52,16 @@ class DeduplicatorTest {
|
||||
"Jan Kowalski",
|
||||
"Jan Kowalski",
|
||||
List.of(),
|
||||
List.of("https://example.com/1")
|
||||
List.of("https://example.com/1"),
|
||||
null, null
|
||||
);
|
||||
Person second = new Person(
|
||||
"person:jan-kowalski",
|
||||
"Jan Kowalski",
|
||||
"Jan Kowalski",
|
||||
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));
|
||||
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
package com.developx.szpitale.ingest.source;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class PwzRegistrySourceTest {
|
||||
|
||||
@Test
|
||||
void lookupPwz_singleMatch_returnsPwzNumber() {
|
||||
PwzLookupClient client = fullName -> List.of("1234567");
|
||||
PwzRegistrySource source = new PwzRegistrySource(client);
|
||||
|
||||
Optional<String> result = source.lookupPwz("Jan Kowalski");
|
||||
|
||||
assertTrue(result.isPresent());
|
||||
assertEquals("1234567", result.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void lookupPwz_multipleMatches_returnsEmpty() {
|
||||
PwzLookupClient client = fullName -> List.of("1234567", "7654321");
|
||||
PwzRegistrySource source = new PwzRegistrySource(client);
|
||||
|
||||
Optional<String> result = source.lookupPwz("Jan Kowalski");
|
||||
|
||||
assertFalse(result.isPresent(), "Multiple matches should return empty (ambiguity)");
|
||||
}
|
||||
|
||||
@Test
|
||||
void lookupPwz_noMatch_returnsEmpty() {
|
||||
PwzLookupClient client = fullName -> List.of();
|
||||
PwzRegistrySource source = new PwzRegistrySource(client);
|
||||
|
||||
Optional<String> result = source.lookupPwz("Jan Kowalski");
|
||||
|
||||
assertFalse(result.isPresent(), "No match should return empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
void lookupPwz_nullClient_returnsEmpty() {
|
||||
PwzRegistrySource source = new PwzRegistrySource(null);
|
||||
|
||||
Optional<String> result = source.lookupPwz("Jan Kowalski");
|
||||
|
||||
assertFalse(result.isPresent());
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,8 @@ class CanonicalRoundTripTest {
|
||||
"Jan Kowalski",
|
||||
"Dyrektor Naczelny",
|
||||
List.of("prof."),
|
||||
List.of("https://uskwb.pl/dyrekcja")
|
||||
List.of("https://uskwb.pl/dyrekcja"),
|
||||
null, null
|
||||
);
|
||||
|
||||
Affiliation affiliation = new Affiliation(
|
||||
@@ -71,7 +72,8 @@ class CanonicalRoundTripTest {
|
||||
List.of(person),
|
||||
List.of(affiliation),
|
||||
List.of(role),
|
||||
List.of()
|
||||
List.of(),
|
||||
List.of(), List.of(), List.of(), List.of(), List.of()
|
||||
);
|
||||
|
||||
Path canonicalDir = tempDir.resolve("canonical");
|
||||
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
package com.developx.szpitale.load;
|
||||
|
||||
import com.developx.szpitale.model.*;
|
||||
import com.developx.szpitale.model.enums.LinkBasis;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.neo4j.driver.AuthTokens;
|
||||
import org.neo4j.driver.Driver;
|
||||
import org.neo4j.driver.Session;
|
||||
import org.testcontainers.containers.Neo4jContainer;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
@Testcontainers
|
||||
class GraphLoaderOrganizationIT {
|
||||
|
||||
@Container
|
||||
static Neo4jContainer<?> neo4j = new Neo4jContainer<>("neo4j:5")
|
||||
.withEnv("NEO4JLABS_PLUGINS", "['apoc']")
|
||||
.withEnv("NEO4J_apoc_export_file_enabled", "true")
|
||||
.withEnv("NEO4J_apoc_import_file_enabled", "true")
|
||||
.withEnv("NEO4J_apoc_import_file_use__neo4j__config", "true");
|
||||
|
||||
static Driver driver;
|
||||
|
||||
@BeforeAll
|
||||
static void setupDriver() {
|
||||
driver = org.neo4j.driver.GraphDatabase.driver(
|
||||
neo4j.getBoltUrl(),
|
||||
AuthTokens.basic("neo4j", "password"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void load_organizationAndCompany_nodesCreatedWithLinks() {
|
||||
GraphSchema schema = new GraphSchema(driver);
|
||||
schema.createConstraints();
|
||||
|
||||
Organization org = new Organization(
|
||||
"org:test-fundacja",
|
||||
"Test Fundacja",
|
||||
"0000111222",
|
||||
null,
|
||||
List.of("https://example.org/fundacja")
|
||||
);
|
||||
|
||||
Company company = new Company(
|
||||
"company:test-sp-zoo",
|
||||
"Test Sp. z o.o.",
|
||||
"0000333444",
|
||||
"1234567890",
|
||||
null,
|
||||
List.of("https://example.org/company")
|
||||
);
|
||||
|
||||
Person person = new Person(
|
||||
"person:test-osoba",
|
||||
"Test Osoba",
|
||||
"Test Osoba",
|
||||
List.of(),
|
||||
List.of("https://example.org/person"),
|
||||
null, null
|
||||
);
|
||||
|
||||
PersonOrganizationLink pol = new PersonOrganizationLink(
|
||||
"person:test-osoba",
|
||||
"org:test-fundacja",
|
||||
"Czonek Zarzadu",
|
||||
LinkBasis.KRS,
|
||||
List.of("https://example.org/link")
|
||||
);
|
||||
|
||||
PersonCompanyLink pcl = new PersonCompanyLink(
|
||||
"person:test-osoba",
|
||||
"company:test-sp-zoo",
|
||||
"Wspolnik",
|
||||
LinkBasis.NIP,
|
||||
List.of("https://example.org/link2")
|
||||
);
|
||||
|
||||
OrganizationLink orgLink = new OrganizationLink(
|
||||
"org:test-fundacja",
|
||||
"org:inna-fundacja",
|
||||
LinkBasis.NIP,
|
||||
"wspolny nip",
|
||||
List.of("https://example.org/link3")
|
||||
);
|
||||
|
||||
CanonicalDataset dataset = new CanonicalDataset(
|
||||
"1.0", "test",
|
||||
java.time.LocalDateTime.now(),
|
||||
List.of(),
|
||||
List.of(person),
|
||||
List.of(), List.of(), List.of(),
|
||||
List.of(org),
|
||||
List.of(company),
|
||||
List.of(pol),
|
||||
List.of(orgLink),
|
||||
List.of(pcl)
|
||||
);
|
||||
|
||||
GraphLoader loader = new GraphLoader(driver);
|
||||
GraphLoader.LoadStats stats = loader.load(List.of(dataset));
|
||||
|
||||
assertEquals(1, stats.organizationCount);
|
||||
assertEquals(1, stats.companyCount);
|
||||
assertEquals(1, stats.personOrganizationLinkCount);
|
||||
assertEquals(1, stats.organizationLinkCount);
|
||||
assertEquals(1, stats.personCompanyLinkCount);
|
||||
|
||||
try (Session session = driver.session()) {
|
||||
var orgResult = session.run(
|
||||
"MATCH (o:Organization {id: 'org:test-fundacja'}) RETURN o.name AS name").list();
|
||||
assertFalse(orgResult.isEmpty());
|
||||
assertEquals("Test Fundacja", orgResult.get(0).get("name").asString());
|
||||
|
||||
var compResult = session.run(
|
||||
"MATCH (c:Company {id: 'company:test-sp-zoo'}) RETURN c.name AS name").list();
|
||||
assertFalse(compResult.isEmpty());
|
||||
assertEquals("Test Sp. z o.o.", compResult.get(0).get("name").asString());
|
||||
|
||||
var polResult = session.run(
|
||||
"MATCH (p:Person {id: 'person:test-osoba'})-" +
|
||||
"[:CZLONEK_ORGANIZACJI]->(o:Organization) " +
|
||||
"RETURN o.id AS orgId").list();
|
||||
assertFalse(polResult.isEmpty());
|
||||
assertEquals("org:test-fundacja", polResult.get(0).get("orgId").asString());
|
||||
|
||||
var pclResult = session.run(
|
||||
"MATCH (p:Person {id: 'person:test-osoba'})-" +
|
||||
"[:POWIAZANY_Z_FIRMA]->(c:Company) " +
|
||||
"RETURN c.id AS compId").list();
|
||||
assertFalse(pclResult.isEmpty());
|
||||
assertEquals("company:test-sp-zoo", pclResult.get(0).get("compId").asString());
|
||||
|
||||
var orgLinkResult = session.run(
|
||||
"MATCH (a:Organization {id: 'org:test-fundacja'})-" +
|
||||
"[:POWIAZANA_Z]->(b:Organization) " +
|
||||
"RETURN b.id AS toId").list();
|
||||
assertFalse(orgLinkResult.isEmpty());
|
||||
assertEquals("org:inna-fundacja", orgLinkResult.get(0).get("toId").asString());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void schema_constraints_created() {
|
||||
GraphSchema schema = new GraphSchema(driver);
|
||||
schema.createConstraints();
|
||||
|
||||
try (Session session = driver.session()) {
|
||||
var result = session.run("SHOW CONSTRAINTS");
|
||||
int count = 0;
|
||||
boolean hasOrgConstraint = false, hasCompConstraint = false;
|
||||
while (result.hasNext()) {
|
||||
count++;
|
||||
var record = result.next();
|
||||
String constraintName = record.get("name").asString();
|
||||
if (constraintName.contains("org_id")) {
|
||||
hasOrgConstraint = true;
|
||||
}
|
||||
if (constraintName.contains("company_id")) {
|
||||
hasCompConstraint = true;
|
||||
}
|
||||
}
|
||||
assertTrue(count > 0, "Should have constraints: found " + count);
|
||||
assertTrue(hasOrgConstraint, "Organization constraint should exist");
|
||||
assertTrue(hasCompConstraint, "Company constraint should exist");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
["1234567", "7654321"]
|
||||
@@ -0,0 +1 @@
|
||||
["1234567"]
|
||||
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
+8
@@ -1,10 +1,13 @@
|
||||
com/developx/szpitale/ingest/IngestResults.class
|
||||
com/developx/szpitale/load/CanonicalReader.class
|
||||
com/developx/szpitale/ingest/source/VoivodeshipSource.class
|
||||
com/developx/szpitale/model/PersonOrganizationLink.class
|
||||
com/developx/szpitale/model/enums/AffiliationType.class
|
||||
com/developx/szpitale/ingest/source/MarkdownRegistrySource.class
|
||||
com/developx/szpitale/SzpitaleGraphApplication.class
|
||||
com/developx/szpitale/model/Company.class
|
||||
com/developx/szpitale/load/GraphValidator.class
|
||||
com/developx/szpitale/model/Organization.class
|
||||
com/developx/szpitale/ingest/IngestCommand.class
|
||||
com/developx/szpitale/ingest/normalize/PartyNormalizer.class
|
||||
com/developx/szpitale/load/LoadCommand.class
|
||||
@@ -13,14 +16,19 @@ com/developx/szpitale/ingest/normalize/Deduplicator.class
|
||||
com/developx/szpitale/load/GraphSchema.class
|
||||
com/developx/szpitale/model/enums/OrganType.class
|
||||
com/developx/szpitale/model/RawHospitalRecord$RawMandate.class
|
||||
com/developx/szpitale/model/OrganizationLink.class
|
||||
com/developx/szpitale/model/enums/ConfidenceLevel.class
|
||||
com/developx/szpitale/ingest/source/PwzRegistrySource.class
|
||||
com/developx/szpitale/ingest/normalize/NameNormalizer.class
|
||||
com/developx/szpitale/model/RawHospitalRecord$RawPersonEntry.class
|
||||
com/developx/szpitale/model/enums/SupervisoryBodyType.class
|
||||
com/developx/szpitale/load/GraphLoader.class
|
||||
com/developx/szpitale/model/RawHospitalRecord.class
|
||||
com/developx/szpitale/ingest/CanonicalWriter.class
|
||||
com/developx/szpitale/model/PersonCompanyLink.class
|
||||
com/developx/szpitale/load/GraphLoader$LoadStats.class
|
||||
com/developx/szpitale/model/enums/LinkBasis.class
|
||||
com/developx/szpitale/model/enums/RoleType.class
|
||||
com/developx/szpitale/ingest/source/PwzLookupClient.class
|
||||
com/developx/szpitale/model/enums/LegalForm.class
|
||||
com/developx/szpitale/model/enums/RoleStatus.class
|
||||
|
||||
+8
@@ -6,6 +6,8 @@
|
||||
/home/kruszewskia/Workspace/Private/Szpitale-graph/szpitale-graph/src/main/java/com/developx/szpitale/ingest/normalize/NameNormalizer.java
|
||||
/home/kruszewskia/Workspace/Private/Szpitale-graph/szpitale-graph/src/main/java/com/developx/szpitale/ingest/normalize/PartyNormalizer.java
|
||||
/home/kruszewskia/Workspace/Private/Szpitale-graph/szpitale-graph/src/main/java/com/developx/szpitale/ingest/source/MarkdownRegistrySource.java
|
||||
/home/kruszewskia/Workspace/Private/Szpitale-graph/szpitale-graph/src/main/java/com/developx/szpitale/ingest/source/PwzLookupClient.java
|
||||
/home/kruszewskia/Workspace/Private/Szpitale-graph/szpitale-graph/src/main/java/com/developx/szpitale/ingest/source/PwzRegistrySource.java
|
||||
/home/kruszewskia/Workspace/Private/Szpitale-graph/szpitale-graph/src/main/java/com/developx/szpitale/ingest/source/VoivodeshipSource.java
|
||||
/home/kruszewskia/Workspace/Private/Szpitale-graph/szpitale-graph/src/main/java/com/developx/szpitale/load/CanonicalReader.java
|
||||
/home/kruszewskia/Workspace/Private/Szpitale-graph/szpitale-graph/src/main/java/com/developx/szpitale/load/GraphLoader.java
|
||||
@@ -14,14 +16,20 @@
|
||||
/home/kruszewskia/Workspace/Private/Szpitale-graph/szpitale-graph/src/main/java/com/developx/szpitale/load/LoadCommand.java
|
||||
/home/kruszewskia/Workspace/Private/Szpitale-graph/szpitale-graph/src/main/java/com/developx/szpitale/model/Affiliation.java
|
||||
/home/kruszewskia/Workspace/Private/Szpitale-graph/szpitale-graph/src/main/java/com/developx/szpitale/model/CanonicalDataset.java
|
||||
/home/kruszewskia/Workspace/Private/Szpitale-graph/szpitale-graph/src/main/java/com/developx/szpitale/model/Company.java
|
||||
/home/kruszewskia/Workspace/Private/Szpitale-graph/szpitale-graph/src/main/java/com/developx/szpitale/model/Hospital.java
|
||||
/home/kruszewskia/Workspace/Private/Szpitale-graph/szpitale-graph/src/main/java/com/developx/szpitale/model/Mandate.java
|
||||
/home/kruszewskia/Workspace/Private/Szpitale-graph/szpitale-graph/src/main/java/com/developx/szpitale/model/Organization.java
|
||||
/home/kruszewskia/Workspace/Private/Szpitale-graph/szpitale-graph/src/main/java/com/developx/szpitale/model/OrganizationLink.java
|
||||
/home/kruszewskia/Workspace/Private/Szpitale-graph/szpitale-graph/src/main/java/com/developx/szpitale/model/Person.java
|
||||
/home/kruszewskia/Workspace/Private/Szpitale-graph/szpitale-graph/src/main/java/com/developx/szpitale/model/PersonCompanyLink.java
|
||||
/home/kruszewskia/Workspace/Private/Szpitale-graph/szpitale-graph/src/main/java/com/developx/szpitale/model/PersonOrganizationLink.java
|
||||
/home/kruszewskia/Workspace/Private/Szpitale-graph/szpitale-graph/src/main/java/com/developx/szpitale/model/RawHospitalRecord.java
|
||||
/home/kruszewskia/Workspace/Private/Szpitale-graph/szpitale-graph/src/main/java/com/developx/szpitale/model/Role.java
|
||||
/home/kruszewskia/Workspace/Private/Szpitale-graph/szpitale-graph/src/main/java/com/developx/szpitale/model/enums/AffiliationType.java
|
||||
/home/kruszewskia/Workspace/Private/Szpitale-graph/szpitale-graph/src/main/java/com/developx/szpitale/model/enums/ConfidenceLevel.java
|
||||
/home/kruszewskia/Workspace/Private/Szpitale-graph/szpitale-graph/src/main/java/com/developx/szpitale/model/enums/LegalForm.java
|
||||
/home/kruszewskia/Workspace/Private/Szpitale-graph/szpitale-graph/src/main/java/com/developx/szpitale/model/enums/LinkBasis.java
|
||||
/home/kruszewskia/Workspace/Private/Szpitale-graph/szpitale-graph/src/main/java/com/developx/szpitale/model/enums/MandateType.java
|
||||
/home/kruszewskia/Workspace/Private/Szpitale-graph/szpitale-graph/src/main/java/com/developx/szpitale/model/enums/OrganType.java
|
||||
/home/kruszewskia/Workspace/Private/Szpitale-graph/szpitale-graph/src/main/java/com/developx/szpitale/model/enums/RoleStatus.java
|
||||
|
||||
+5
-1
@@ -1,6 +1,10 @@
|
||||
com/developx/szpitale/load/GraphLoaderOrganizationIT.class
|
||||
com/developx/szpitale/ingest/OrganizationCompanyRoundTripTest.class
|
||||
com/developx/szpitale/ingest/source/PwzRegistrySourceTest.class
|
||||
com/developx/szpitale/load/DockerApiVersionTest.class
|
||||
com/developx/szpitale/ingest/normalize/NameNormalizerTest.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/normalize/DeduplicatorTest.class
|
||||
com/developx/szpitale/load/GraphLoaderIT.class
|
||||
com/developx/szpitale/load/CanonicalRoundTripTest.class
|
||||
|
||||
+5
-1
@@ -1,6 +1,10 @@
|
||||
/home/kruszewskia/Workspace/Private/Szpitale-graph/szpitale-graph/src/test/java/com/developx/szpitale/ingest/OrganizationCompanyRoundTripTest.java
|
||||
/home/kruszewskia/Workspace/Private/Szpitale-graph/szpitale-graph/src/test/java/com/developx/szpitale/ingest/normalize/DeduplicatorTest.java
|
||||
/home/kruszewskia/Workspace/Private/Szpitale-graph/szpitale-graph/src/test/java/com/developx/szpitale/ingest/normalize/NameNormalizerTest.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/PwzRegistrySourceTest.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/load/DockerApiVersionTest.java
|
||||
/home/kruszewskia/Workspace/Private/Szpitale-graph/szpitale-graph/src/test/java/com/developx/szpitale/load/GraphLoaderOrganizationIT.java
|
||||
/home/kruszewskia/Workspace/Private/Szpitale-graph/szpitale-graph/src/test/java/com/developx/szpitale/model/PersonTest.java
|
||||
|
||||
+59
File diff suppressed because one or more lines are too long
+4
-4
File diff suppressed because one or more lines are too long
+15
-15
File diff suppressed because one or more lines are too long
+7
-7
File diff suppressed because one or more lines are too long
+6
-6
File diff suppressed because one or more lines are too long
+62
File diff suppressed because one or more lines are too long
+4
-4
File diff suppressed because one or more lines are too long
+180
File diff suppressed because one or more lines are too long
+93
File diff suppressed because one or more lines are too long
+59
File diff suppressed because one or more lines are too long
+4
@@ -0,0 +1,4 @@
|
||||
-------------------------------------------------------------------------------
|
||||
Test set: com.developx.szpitale.ingest.OrganizationCompanyRoundTripTest
|
||||
-------------------------------------------------------------------------------
|
||||
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.168 s -- in com.developx.szpitale.ingest.OrganizationCompanyRoundTripTest
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
-------------------------------------------------------------------------------
|
||||
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.005 s -- in com.developx.szpitale.ingest.normalize.DeduplicatorTest
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
-------------------------------------------------------------------------------
|
||||
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.064 s -- in com.developx.szpitale.ingest.normalize.NameNormalizerTest
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
-------------------------------------------------------------------------------
|
||||
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.018 s -- in com.developx.szpitale.ingest.normalize.PartyNormalizerTest
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
-------------------------------------------------------------------------------
|
||||
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.090 s -- in com.developx.szpitale.ingest.source.MarkdownRegistrySourceTest
|
||||
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
-------------------------------------------------------------------------------
|
||||
Test set: com.developx.szpitale.ingest.source.PwzRegistrySourceTest
|
||||
-------------------------------------------------------------------------------
|
||||
Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.005 s -- in com.developx.szpitale.ingest.source.PwzRegistrySourceTest
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
-------------------------------------------------------------------------------
|
||||
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.024 s -- in com.developx.szpitale.load.CanonicalRoundTripTest
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
-------------------------------------------------------------------------------
|
||||
Test set: com.developx.szpitale.load.DockerApiVersionTest
|
||||
-------------------------------------------------------------------------------
|
||||
Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.212 s <<< FAILURE! -- in com.developx.szpitale.load.DockerApiVersionTest
|
||||
com.developx.szpitale.load.DockerApiVersionTest -- Time elapsed: 0.212 s <<< ERROR!
|
||||
java.lang.IllegalStateException: Could not find a valid Docker environment. Please see logs and check configuration
|
||||
at org.testcontainers.dockerclient.DockerClientProviderStrategy.lambda$getFirstValidStrategy$7(DockerClientProviderStrategy.java:274)
|
||||
at java.base/java.util.Optional.orElseThrow(Optional.java:403)
|
||||
at org.testcontainers.dockerclient.DockerClientProviderStrategy.getFirstValidStrategy(DockerClientProviderStrategy.java:265)
|
||||
at org.testcontainers.DockerClientFactory.getOrInitializeStrategy(DockerClientFactory.java:154)
|
||||
at org.testcontainers.DockerClientFactory.client(DockerClientFactory.java:196)
|
||||
at org.testcontainers.DockerClientFactory$1.getDockerClient(DockerClientFactory.java:108)
|
||||
at com.github.dockerjava.api.DockerClientDelegate.authConfig(DockerClientDelegate.java:109)
|
||||
at org.testcontainers.containers.GenericContainer.start(GenericContainer.java:321)
|
||||
at org.testcontainers.junit.jupiter.TestcontainersExtension$StoreAdapter.start(TestcontainersExtension.java:276)
|
||||
at org.testcontainers.junit.jupiter.TestcontainersExtension$StoreAdapter.access$200(TestcontainersExtension.java:263)
|
||||
at org.testcontainers.junit.jupiter.TestcontainersExtension.lambda$startContainers$4(TestcontainersExtension.java:83)
|
||||
at org.testcontainers.junit.jupiter.TestcontainersExtension.lambda$startContainers$5(TestcontainersExtension.java:83)
|
||||
at java.base/java.util.ArrayList.forEach(ArrayList.java:1604)
|
||||
at org.testcontainers.junit.jupiter.TestcontainersExtension.startContainers(TestcontainersExtension.java:83)
|
||||
at org.testcontainers.junit.jupiter.TestcontainersExtension.beforeAll(TestcontainersExtension.java:57)
|
||||
at java.base/java.util.ArrayList.forEach(ArrayList.java:1604)
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
-------------------------------------------------------------------------------
|
||||
Test set: com.developx.szpitale.load.GraphLoaderOrganizationIT
|
||||
-------------------------------------------------------------------------------
|
||||
Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.265 s <<< FAILURE! -- in com.developx.szpitale.load.GraphLoaderOrganizationIT
|
||||
com.developx.szpitale.load.GraphLoaderOrganizationIT -- Time elapsed: 0.265 s <<< ERROR!
|
||||
java.lang.IllegalStateException: Could not find a valid Docker environment. Please see logs and check configuration
|
||||
at org.testcontainers.dockerclient.DockerClientProviderStrategy.lambda$getFirstValidStrategy$7(DockerClientProviderStrategy.java:274)
|
||||
at java.base/java.util.Optional.orElseThrow(Optional.java:403)
|
||||
at org.testcontainers.dockerclient.DockerClientProviderStrategy.getFirstValidStrategy(DockerClientProviderStrategy.java:265)
|
||||
at org.testcontainers.DockerClientFactory.getOrInitializeStrategy(DockerClientFactory.java:154)
|
||||
at org.testcontainers.DockerClientFactory.client(DockerClientFactory.java:196)
|
||||
at org.testcontainers.DockerClientFactory$1.getDockerClient(DockerClientFactory.java:108)
|
||||
at com.github.dockerjava.api.DockerClientDelegate.authConfig(DockerClientDelegate.java:109)
|
||||
at org.testcontainers.containers.GenericContainer.start(GenericContainer.java:321)
|
||||
at org.testcontainers.junit.jupiter.TestcontainersExtension$StoreAdapter.start(TestcontainersExtension.java:276)
|
||||
at org.testcontainers.junit.jupiter.TestcontainersExtension$StoreAdapter.access$200(TestcontainersExtension.java:263)
|
||||
at org.testcontainers.junit.jupiter.TestcontainersExtension.lambda$null$4(TestcontainersExtension.java:83)
|
||||
at org.testcontainers.junit.jupiter.TestcontainersExtension.lambda$startContainers$5(TestcontainersExtension.java:83)
|
||||
at java.base/java.util.ArrayList.forEach(ArrayList.java:1604)
|
||||
at org.testcontainers.junit.jupiter.TestcontainersExtension.startContainers(TestcontainersExtension.java:83)
|
||||
at org.testcontainers.junit.jupiter.TestcontainersExtension.beforeAll(TestcontainersExtension.java:57)
|
||||
at java.base/java.util.ArrayList.forEach(ArrayList.java:1604)
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
-------------------------------------------------------------------------------
|
||||
Test set: com.developx.szpitale.model.PersonTest
|
||||
-------------------------------------------------------------------------------
|
||||
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 s -- in com.developx.szpitale.model.PersonTest
|
||||
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
["1234567", "7654321"]
|
||||
@@ -0,0 +1 @@
|
||||
["1234567"]
|
||||
Reference in New Issue
Block a user