T11: Organization, Company, link entities + round-trip [checkpoint]

This commit is contained in:
Artur Kruszewski
2026-07-08 23:23:56 +02:00
parent a8bdaf4cae
commit 6e87fd1b8d
44 changed files with 385 additions and 57 deletions
+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.",
"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-08T23:11:00Z",
"updatedAt": "2026-07-08T23:23:00Z",
"tasks": [
{
"id": "T1",
@@ -100,10 +100,10 @@
"id": "T11",
"title": "Organization/Company rekordy + round-trip",
"tag": null,
"status": "todo",
"startedAt": null,
"finishedAt": null,
"note": "Nowe encje kanoniczne: Organization, Company, PersonOrganizationLink, OrganizationLink, PersonCompanyLink (PLAN.md 4.8, T11)."
"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",
@@ -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");
@@ -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);
}
}
@@ -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
) {
}
@@ -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
}
@@ -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());
}
}
@@ -72,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");
@@ -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,6 +16,7 @@ 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
@@ -21,7 +25,9 @@ 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
@@ -16,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
@@ -1,7 +1,8 @@
com/developx/szpitale/ingest/OrganizationCompanyRoundTripTest.class
com/developx/szpitale/ingest/source/PwzRegistrySourceTest.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/PwzRegistrySourceTest.class
com/developx/szpitale/ingest/source/MarkdownRegistrySourceTest.class
com/developx/szpitale/ingest/normalize/DeduplicatorTest.class
com/developx/szpitale/load/CanonicalRoundTripTest.class
@@ -1,3 +1,4 @@
/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
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
File diff suppressed because one or more lines are too long
@@ -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,4 +1,4 @@
-------------------------------------------------------------------------------
Test set: com.developx.szpitale.ingest.normalize.NameNormalizerTest
-------------------------------------------------------------------------------
Tests run: 25, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.068 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,4 +1,4 @@
-------------------------------------------------------------------------------
Test set: com.developx.szpitale.ingest.normalize.PartyNormalizerTest
-------------------------------------------------------------------------------
Tests run: 15, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.021 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,4 +1,4 @@
-------------------------------------------------------------------------------
Test set: com.developx.szpitale.ingest.source.MarkdownRegistrySourceTest
-------------------------------------------------------------------------------
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.097 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
@@ -1,4 +1,4 @@
-------------------------------------------------------------------------------
Test set: com.developx.szpitale.ingest.source.PwzRegistrySourceTest
-------------------------------------------------------------------------------
Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.007 s -- in 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,4 +1,4 @@
-------------------------------------------------------------------------------
Test set: com.developx.szpitale.load.CanonicalRoundTripTest
-------------------------------------------------------------------------------
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.193 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