Files
Szpitale-graph/old/build_hospital_graph.py

1176 lines
75 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
build_hospital_graph.py
Builds a Neo4j graph linking hospitals in Podlaskie voivodeship,
their directors, supervisory-board members, political parties,
and regional councilors.
Usage:
python3 build_hospital_graph.py
Neo4j connection (env vars):
NEO4J_URI (default: bolt://localhost:7687)
NEO4J_USER (default: neo4j)
NEO4J_PASS (default: graphdb123)
"""
import json
import os
import re
import textwrap
import unicodedata
import uuid
from dataclasses import dataclass, field, asdict
from datetime import datetime
from typing import Optional
from neo4j import GraphDatabase
# ---------------------------------------------------------------------------
# Data model
# ---------------------------------------------------------------------------
@dataclass
class Party:
slug: str # normalized key, e.g. "pis", "koalicja-obywatelska"
name: str # display name, e.g. "Prawo i Sprawiedliwość"
@dataclass
class Person:
uid: str
name: str
party_note: str = "" # context string (e.g. "brak/niepotwierdzona", or compound)
party_slugs: list = field(default_factory=list) # list of Party slugs
source_urls: list = field(default_factory=list)
note: str = ""
@property
def norm_name(self):
return norm_name(self.name)
@dataclass
class Hospital:
id: str
name: str
city: str
legal_form: str = ""
website: str = ""
notes: str = ""
@dataclass
class Role:
hospital_id: str
person: Person
role: str # "DYREKTOR", "Z_CA", "RADA_SPOLECZNA", "RADNIA", "PREZES_ZARZADU", "RADA_NADZORCZA"
sub_role: str = "" # e.g. "Przewodniczacy", "Z-ca ds. Lecznictwa"
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def norm_name(s: str) -> str:
"""Normalise a person name for deduplication."""
s = unicodedata.normalize("NFKD", s)
s = re.sub(r"[^\w\s\-\.]+", "", s, flags=re.UNICODE)
s = re.sub(r"\s+", " ", s).strip().lower()
# Remove academic titles
s = re.sub(
r"\b(prof\.\s*dr\s*hab\s*dr\s*inż?\s*inż\.\s*mgr\s*dr\s*inż?\s*)"
r"|(\binż?\.\s*dr\s*hab\s*hab\.\s*dr\s*hab)\b",
"", s, flags=re.IGNORECASE
)
return s
# ---------------------------------------------------------------------------
# Party normalisation
# ---------------------------------------------------------------------------
_PARTY_CANONICAL = {
# Major parties
"pis": ("Prawo i Sprawiedliwość", "pis"),
"pi-sprawiedliwosc": ("Prawo i Sprawiedliwość", "pis"),
"prawo-i-sprawiedliwosc": ("Prawo i Sprawiedliwość", "pis"),
"koalicja-obywatelska": ("Koalicja Obywatelska", "ko"),
"ko": ("Koalicja Obywatelska", "ko"),
"po": ("Platforma Obywatelska", "ko"),
"ko-po": ("Koalicja Obywatelska", "ko"),
"po-ko": ("Koalicja Obywatelska", "ko"),
"psl": ("Polskie Stronnictwo Ludowe", "psl"),
"polskie-ustronnictwo-ludowe": ("Polskie Stronnictwo Ludowe", "psl"),
"polska-2050": ("Polska 2050", "polska-2050"),
"trzecia-droga": ("Trzecia Droga", "trzecia-droga"),
"slp": ("SLP", "slp"),
"sld": ("Sojusz Lewicy Demokratycznej", "sld"),
"lewica": ("Lewica", "lewica"),
"prawica-rzeczypospolitej": ("Prawica Rzeczypospolitej", "prawica-rzeczpospolita"),
"prawa-rzeczypospolitej": ("Prawica Rzeczypospolitej", "prawica-rzeczpospolita"),
# KWW / local committees - keep them as-is but normalized
"kww-ziemia-hajnowska": ("KWW Ziemia Hajnowska", "kww-ziemia-hajnowska"),
"kww-razem-dla-sokolki": ("KWW Razem dla Sokółki", "kww-razem-dla-sokolki"),
"kww-nasze-podlasie": ("KWW Nasze Podlasie", "kww-nasze-podlasie"),
"kww-przyjazna-lomza": ("KWW Przyjazna Łomża", "kww-przyjazna-lomza"),
"kww-samorzad-powiatu-augustowskiego": ("KWW Samorząd Powiatu Augustowskiego", "kww-spa"),
"kww-spa": ("KWW Samorząd Powiatu Augustowskiego", "kww-spa"),
"kww-bau-2024": ("KWW BAU 2024", "kww-bau-2024"),
"kww-suwalskie-porozumienie-prawicy": ("KWW Suwalskie Porozumienie Prawicy", "kww-sup"),
"kww-hajnowa-razem": ("KWW Hajnówka Razem", "kww-hajnowa-razem"),
"kww-nasza-przyszlosc": ("KWW Nasza Przyszłość", "kww-nasza-przyszlosc"),
"kww-roztrotna-troska-o-dobro-gminy": ("KWW Roztrotna Troska o Dobro Gminy", "kww-rt"),
"kww-narew-odnowa": ("KWW Narew Odnowa", "kww-narew-odnowa"),
"kww-wspolna-narewka": ("KWW Wspólna Narewka", "kww-wspolna-narewka"),
"kww-spw": ("KWW SPW", "kww-spw"),
"kww-wlasny": ("KWW Własny", "kww-wlasny"),
"kww-bmn-aktywny-samorzad": ("KWW BMN Aktywny Samorząd", "kww-bmn"),
"kww-dla-siemiatycz-razem": ("KWW Dla Siemiatycz Razem", "kww-dla-siemiatycz-razem"),
"kww-macieja-pawla-bednarko": ("KWW Macieja Pawła Bednarko", "kww-mpb"),
"kww-dobry-powiat": ("KWW Dobry Powiat", "kww-dobry-powiat"),
"kww-skuteczny-i-przyjazny-samorzad": ("KWW Skuteczny i Przyjazny Samorząd", "kww-sps"),
"kww-ziemia-sejneńska": ("KWW Ziemia Sejneńska", "kww-sejny"),
"kww-razem-dla-suwalk": ("KWW Razem dla Suwałk", "kww-razem-dla-suwalk"),
"kww-porozumienie-samorzadowe-powiatu-kolno": ("KWW Porozumienie Samorządowe Powiatu Kolno", "kww-pspk"),
"kww-pspk": ("KWW Porozumienie Samorządowe Powiatu Kolno", "kww-pspk"),
"kw-koalicja-bielska": ("KW Koalicja Bielska", "kw-koalicja-bielska"),
}
# Second-level mapping for names that didn't match the first pass
_PARTY_SECONDARY = {
"historycznie sld/lewica": ("historycznie SLD/Lewica", "sld"),
"możliwie trzecia droga": ("Trzecia Droga", "trzecia-droga"),
"wicemarszałek woj.; koalicja obywatelska; niezrzeszony": ("Koalicja Obywatelska", "ko"),
}
def normalise_party(raw: str) -> tuple[str, str]:
"""
Given a raw party string, return (canonical_name, slug).
If no match, normalise the input itself.
"""
key = re.sub(r"[^\w\s\-\/\.]+", "", raw).strip().lower()
# Replace Polish characters
key = unicodedata.normalize("NFD", key)
key = re.sub(r"[^\w\s\-]+", "", key)
# Check if it's a KWW/KW local committee
if "kww" in key or "kw " in key:
display = re.sub(r"\s+", " ", raw.strip().title())
slug = re.sub(r"[^\w\-]+", "-", display.lower())
slug = re.sub(r"-+", "-", slug).strip("-")
return display, slug
if key in _PARTY_CANONICAL:
return _PARTY_CANONICAL[key]
# Check secondary (full-string) matches
if key in _PARTY_SECONDARY:
return _PARTY_SECONDARY[key]
# Check for compound strings like "Trzecia Droga (PSL / Polska 2050)" - extract main parties
# Split on " / " or " - " or "(" and process each
parts = re.split(r"[\(\)\/\-\,\;]+", raw)
parts = [p.strip() for p in parts if p.strip()]
slugs_found = []
names_found = []
for part in parts:
part_key = unicodedata.normalize("NFD", re.sub(r"[^\w\s\-]+", "", part).strip().lower())
# Check primary canonical matching
if part_key in _PARTY_CANONICAL:
name, slug = _PARTY_CANONICAL[part_key]
if slug not in slugs_found:
slugs_found.append(slug)
names_found.append(name)
continue
# Check secondary canonical matching
if part_key in _PARTY_SECONDARY:
name, slug = _PARTY_SECONDARY[part_key]
if slug not in slugs_found:
slugs_found.append(slug)
names_found.append(name)
continue
if slugs_found:
return " + ".join(names_found), "|".join(slugs_found)
# No match - use the raw string as-is (normalised)
display = re.sub(r"\s+", " ", raw.strip().title())
slug = re.sub(r"[^\w\-]+", "-", display.lower())
slug = re.sub(r"-+", "-", slug).strip("-")
return display, slug
# ---------------------------------------------------------------------------
# Neo4j helpers
# ---------------------------------------------------------------------------
BATCH_SIZE = 500
def _connect():
uri = os.getenv("NEO4J_URI", "bolt://localhost:7687")
user = os.getenv("NEO4J_USER", "neo4j")
pwd = os.getenv("NEO4J_PASS", "graphdb123")
driver = GraphDatabase.driver(uri, auth=(user, pwd))
# Verify connectivity
driver.verify_connectivity()
return driver
def load_hospitals(driver, hospitals: list[Hospital]):
"""Bulk merge Hospital nodes."""
with driver.session() as session:
batches = [hospitals[i:i + BATCH_SIZE] for i in range(0, len(hospitals), BATCH_SIZE)]
for batch in batches:
records = [
{
"id": h.id,
"name": h.name,
"city": h.city,
"legal_form": h.legal_form,
"website": h.website,
"notes": h.notes,
}
for h in batch
]
session.execute_write(_merge_hospitals, records)
def _merge_hospitals(tx, records):
cypher = """
UNWIND $records AS r
MERGE (h:Hospital {uid: r.id})
ON CREATE SET h.name = r.name, h.city = r.city,
h.legalForm = r.legal_form, h.website = r.website, h.notes = r.notes
ON MATCH SET h.name = r.name, h.city = r.city,
h.legalForm = r.legal_form, h.website = r.website, h.notes = r.notes
"""
tx.run(cypher, records=records)
def load_persons(driver, persons: list[Person]):
"""Bulk merge Person nodes."""
with driver.session() as session:
batches = [persons[i:i + BATCH_SIZE] for i in range(0, len(persons), BATCH_SIZE)]
for batch in batches:
records = [
{
"uid": p.uid,
"name": p.name,
"partyNote": p.party_note,
"partySlugs": p.party_slugs,
"sourceUrls": p.source_urls,
"note": p.note,
"normName": p.norm_name,
}
for p in batch
]
session.execute_write(_merge_persons, records)
def _merge_persons(tx, records):
cypher = """
UNWIND $records AS r
MERGE (p:Person {uid: r.uid})
ON CREATE SET p.name = r.name, p.partyNote = r.partyNote,
p.sourceUrls = r.sourceUrls, p.note = r.note, p.normName = r.normName
ON MATCH SET p.name = r.name, p.partyNote = r.partyNote,
p.note = r.note, p.normName = r.normName,
p.sourceUrls = p.sourceUrls + r.sourceUrls
WITH p, r
// Link to Party nodes via slug
UNWIND r.partySlugs AS slug
MATCH (pt:Party {slug: slug})
MERGE (p)-[rel:AFILIACJA_PARTYJNA]->(pt)
ON CREATE SET rel.createdAt = timestamp()
"""
tx.run(cypher, records=records)
def load_parties(driver, parties: list[Party]):
"""Bulk merge Party nodes."""
with driver.session() as session:
records = [
{
"slug": p.slug,
"name": p.name,
}
for p in parties
]
session.execute_write(_merge_parties, records)
def _merge_parties(tx, records):
cypher = """
UNWIND $records AS r
MERGE (pt:Party {slug: r.slug})
ON CREATE SET pt.name = r.name
ON MATCH SET pt.name = r.name
"""
tx.run(cypher, records=records)
def load_roles(driver, roles: list[Role], persons_by_uid: dict):
"""Create relationships between hospitals and persons."""
with driver.session() as session:
batches = [roles[i:i + BATCH_SIZE] for i in range(0, len(roles), BATCH_SIZE)]
for batch in batches:
records = [
{
"hospitalId": r.hospital_id,
"personUid": r.person.uid,
"relationship": r.role,
"subRole": r.sub_role,
}
for r in batch
]
session.execute_write(_merge_roles, records)
def _merge_roles(tx, records):
type_to_cypher = {
"DYREKTOR": "MERGE (h)-[rel:DYREKTOR]->(p)",
"Z_CA": "MERGE (h)-[rel:Z_CA]->(p)",
"RADA_SPOLECZNA": "MERGE (h)-[rel:RADA_SPOLECZNA]->(p)",
"RADNIA": "MERGE (h)-[rel:RADNIA]->(p)",
"PREZES_ZARZADU": "MERGE (h)-[rel:PREZES_ZARZADU]->(p)",
"RADA_NADZORCZA": "MERGE (h)-[rel:RADA_NADZORCZA]->(p)",
}
for rel_type, pattern in type_to_cypher.items():
rel_records = [r for r in records if r["relationship"] == rel_type]
if rel_records:
tx.run(
f"""
UNWIND $records AS r
MATCH (h:Hospital {{uid: r.hospitalId}})
MATCH (p:Person {{uid: r.personUid}})
{pattern}
ON CREATE SET rel.createdAt = timestamp()
""",
records=rel_records
)
def validate(driver):
"""Run validation queries and print results."""
with driver.session() as session:
queries = {
"Total hospitals": "MATCH (h:Hospital) RETURN count(h) AS total",
"Total persons": "MATCH (p:Person) RETURN count(p) AS total",
"Total parties": "MATCH (pt:Party) RETURN count(pt) AS total",
"Persons linked to parties": (
"MATCH (p:Person)-[r:AFILIACJA_PARTYJNA]->(:Party) RETURN count(p) AS total"
),
"Person->Party relationships": "MATCH ()-[r:AFILIACJA_PARTYJNA]->() RETURN count(r) AS total",
"Director relationships": "MATCH ()-[rel:DYREKTOR]->() RETURN count(rel) AS total",
"Deputy director relationships": "MATCH ()-[rel:Z_CA]->() RETURN count(rel) AS total",
"Social board relationships": "MATCH ()-[rel:RADA_SPOLECZNA]->() RETURN count(rel) AS total",
"Supervisory board relationships": "MATCH ()-[rel:RADA_NADZORCZA]->() RETURN count(rel) AS total",
"Board member relationships": "MATCH ()-[rel:RADNIA]->() RETURN count(rel) AS total",
}
print("\n=== VALIDATION ===")
for label, query in queries.items():
result = session.run(query)
record = result.single()
val = record.get('total', record.get('overlappingPersons', 0))
print(f" {label}: {val}")
print("==================\n")
def export_overlap_csv(driver, filename="overlap_report.csv"):
"""Export persons who hold multiple roles to CSV."""
cypher = """
MATCH (p:Person)
OPTIONAL MATCH (p)-[r:AFILIACJA_PARTYJNA]->(pt:Party)
RETURN p.name AS name, pt.name AS party, p.sourceUrls AS sourceUrls, p.note AS note
ORDER BY p.name
"""
with driver.session() as session:
result = session.run(cypher)
rows = [{"name": r["name"], "party": r["party"] or "", "sourceUrls": r["sourceUrls"], "note": r["note"]} for r in result]
with open(filename, "w", encoding="utf-8") as f:
f.write("name,party,sourceUrls,note\n")
for row in rows:
urls = "|".join(row["sourceUrls"]) if row["sourceUrls"] else ""
note = row["note"].replace('"', '""') if row["note"] else ""
f.write(f'"{row["name"]}","{row["party"]}","{urls}","{note}"\n')
print(f"Exported {len(rows)} persons to {filename}")
return rows
# ---------------------------------------------------------------------------
# Build the graph from embedded research data
# ---------------------------------------------------------------------------
HOSPITALS = [] # (id, name, city, legalForm, website, notes)
# === CZESC I — Szpitale wojewodzkie, uniwersyteckie i resortowe ===
HOSPITALS.append((
"uskwb",
"Uniwersytecki Szpital Kliniczny w Białymstoku (USK)",
"Białystok",
"SPZOZ",
"https://uskwb.pl/",
"Organ nadzorczy: Rada Społeczna"
))
HOSPITALS.append((
"udsk",
"Uniwersytecki Dziecięcy Szpital Kliniczny im. L. Zamenhofa (UDSK)",
"Białystok",
"SPZOZ",
"https://szpitaldzieciecybialystok.sisco.info/",
"Organ nadzorczy: Rada Społeczna"
))
HOSPITALS.append((
"sniadeckiego",
"SP ZOZ Wojewódzki Szpital Zespolony im. J. Śniadeckiego",
"Białystok",
"SPZOZ",
"https://podlaskie.eu/zdrowie/bogdan-kalicki-oficjalnie-objal-stanowisko-dyrektora-sniadecji.html",
"Organ tworzący: Województwo Podlaskie. Organ nadzorczy: Rada Społeczna"
))
HOSPITALS.append((
"bco",
"Białostockie Centrum Onkologii im. M. Skłodowskiej-Curie",
"Białystok",
"SPZOZ",
"https://www.onkologia.bialystok.pl/",
"Organ tworzący: Województwo Podlaskie. Rada Społeczna powołana Uchwałą Nr 72/1442/2025"
))
HOSPITALS.append((
"mswia",
"SP ZOZ MSWiA w Białymstoku im. M. Zyndrama-Kościałkowskiego",
"Białystok",
"SPZOZ",
"https://bip.zozmswia.bialystok.pl/",
"Organ tworzący: Minister Spraw Wewnętrznych i Administracji"
))
HOSPITALS.append((
"choroszcz",
"SP Psychiatryczny ZOZ im. dr. St. Deresza",
"Choroszcz",
"SP Psychiatryczny ZOZ",
"https://bip-sppzozc.podlaskie.eu/",
"Organ tworzący: Województwo Podlaskie"
))
HOSPITALS.append((
"lomza",
"Szpital Wojewódzki im. Kardynała Stefana Wyszyńskiego",
"Łomża",
"SPZOZ",
"https://www.szpital-lomza.pl/",
"Organ tworzący: Samorząd Województwa Podlaskiego"
))
HOSPITALS.append((
"suwalki_rundyger",
"Szpital Wojewódzki im. dr. Ludwika Rydygiera",
"Suwałki",
"SPZOZ",
"https://bip-spsws.podlaskie.eu/",
"Organ tworzący: Województwo Podlaskie"
))
HOSPITALS.append((
"suwalki_psych",
"Specjalistyczny Psychiatryczny SP ZOZ",
"Suwałki",
"SPZOZ",
"https://bip-spspzozs.podlaskie.eu/",
"Organ tworzący: Województwo Podlaskie"
))
# === CZESC II — Szpitale powiatowe ===
HOSPITALS.append((
"augustow",
"SP ZOZ",
"Augustów",
"SPZOZ",
"https://bip-spzoz-staugustow.podlaskie.eu/",
"Organ tworzący: Powiat Augustowski"
))
HOSPITALS.append((
"bielsk",
"SP ZOZ",
"Bielsk Podlaski",
"SPZOZ",
"https://www.spzoz-bielsk.pl/",
"Organ tworzący: Powiat Bielski"
))
HOSPITALS.append((
"hajnowka",
"SP ZOZ",
"Hajnówka",
"SPZOZ",
"https://spzozhajnowka.pl/",
"Organ tworzący: Powiat Hajnowski"
))
HOSPITALS.append((
"siemiatycze",
"SP ZOZ (Szpital Powiatowy)",
"Siemiatycze",
"SPZOZ",
"https://spzozsiemiatycze.pl/",
"Organ tworzący: Powiat Siemiatycki"
))
HOSPITALS.append((
"sokolka",
"SP ZOZ",
"Sokółka",
"SPZOZ",
"https://szpitalsokolka.pl/",
"Organ tworzący: Powiat Sokólski"
))
HOSPITALS.append((
"monki",
"SP ZOZ",
"Mońki",
"SPZOZ",
"https://spzoz.szpital-monki.h2.pl/",
"Organ tworzący: Powiat Moniecki"
))
HOSPITALS.append((
"grajewo",
"Szpital Ogólny im. dr. Witolda Ginela",
"Grajewo",
"SPZOZ",
"https://www.szpital-grajewo.pl/",
"Organ tworzący: Powiat Grajewski"
))
HOSPITALS.append((
"kolno",
"Szpital Ogólny w Kolnie",
"Kolno",
"SPZOZ",
"https://www.szpitalkolno.pl/",
"Organ tworzący: Powiat Kolneński"
))
HOSPITALS.append((
"wysokie",
"Szpital Ogólny w Wysokiem Mazowieckiem",
"Wysokie Mazowieckie",
"SPZOZ",
"https://szpitalwysmaz.pl/",
"Organ tworzący: Powiat Wysokomazowiecki"
))
HOSPITALS.append((
"sejny",
"SP ZOZ (Szpital Powiatowy)",
"Sejny",
"SPZOZ",
"https://szpital.sejny.pl/",
"Organ tworzący: Powiat Sejneński"
))
HOSPITALS.append((
"zambrow",
"Szpital Powiatowy w Zambrowie sp. z o.o.",
"Zambrow",
"sp. z o.o.",
"",
"Jedyny szpital jako spółka handlowa. Organ: Rada Nadzorcza"
))
def person_key(hospital_id: str, name: str, role: str) -> str:
"""Deterministic key to detect duplicates within hospital+role."""
raw = f"{hospital_id}:{name}:{role}"
return hash(raw)
def build_data():
"""
Returns (hospitals, persons_list, roles_list) from the embedded data.
Deduplicates persons by normalised name across all roles/hospitals.
"""
persons_map = {} # norm_name -> person_uid -> Person
roles = []
hospitals = []
# Part-of-speech helper (for KWW/local committees and "brak danych" entries)
def is_kww_local(raw: str) -> bool:
return raw.startswith("KWW") or raw.startswith("KW") or "KWW" in raw or "KW " in raw or "komitet" in raw.lower()
def is_uncertain(raw: str) -> bool:
return raw in ("brak/niepotwierdzona", "brak danych", "NIEZWERYFIKOWANE", "brak/niepotwierdzona", "niepotwierdzona", "brak potwierdzonej afiliacji", "komitet niepotwierdzony", "no confirmed affiliation")
# ---- Register a person and optionally link a party ----
def add_person(party_raw: str) -> list[Person]:
"""Normalize the party string, look up Person, link Party. Returns list of matched persons."""
party_display, party_slug = normalise_party(party_raw)
persons = [p for p in persons_by_name.values()]
return persons
# ---- Helper to register person ----
def get_person(name: str, party: str = "", source_url: str = "", note: str = "") -> Person:
n = norm_name(name)
if n not in persons_map:
persons_map[n] = {}
# Check exact name match first
for uid, p in persons_map[n].items():
if p.name == name:
if source_url and source_url not in p.source_urls:
p.source_urls.append(source_url)
# If party is uncertain, keep it as note; if it's a KWW or real party, add slug
if party and not is_uncertain(party) and not party.startswith("histor"):
_, slug = normalise_party(party)
if slug and slug not in p.party_slugs:
p.party_slugs.append(slug)
else:
if party and not is_uncertain(party) and not party.startswith("histor"):
p.party_note = party
elif is_uncertain(party) and (not p.party_note or p.party_note == "brak/niepotwierdzona"):
p.party_note = party
return p
uid = str(uuid.uuid4())
p = Person(uid=uid, name=name, party_note=party, source_urls=[] if not source_url else [source_url], note=note)
if party and not is_uncertain(party) and not party.startswith("histor"):
_, slug = normalise_party(party)
if slug:
p.party_slugs.append(slug)
persons_map[n][uid] = p
return p
# =========================================================================
# 1. Uniwersytecki Szpital Kliniczny w Białymstoku (USK)
# =========================================================================
src_dyr = "https://uskwb.pl/dyrekcja-szpitala/"
src_rada = "https://uskbialystok.sisco.info/wladze,36_8"
hospitals.append(Hospital(id="uskwb", name="Uniwersytecki Szpital Kliniczny w Białymstoku (USK)",
city="Białystok", legal_form="SPZOZ", website=src_dyr))
roles.append(Role("uskwb", get_person("prof. dr hab. n. med. Jan Kochanowicz", party="brak/niepotwierdzona", source_url=src_dyr), "DYREKTOR", "Dyrektor Naczelny"))
roles.append(Role("uskwb", get_person("dr hab. n. med. Marzena Wojewódzka-Żelezniakowicz", party="brak/niepotwierdzona", source_url=src_dyr), "Z_CA", "Z-ca ds. Lecznictwa"))
roles.append(Role("uskwb", get_person("mgr Beata Kropiewnicka", party="brak/niepotwierdzona", source_url=src_dyr), "Z_CA", "Z-ca ds. Finansowych / Gł. Księgowy"))
roles.append(Role("uskwb", get_person("mgr Piotr Kołos", party="brak/niepotwierdzona", source_url=src_dyr), "Z_CA", "Z-ca ds. Techniczno-Inwestycyjnych"))
roles.append(Role("uskwb", get_person("prof. dr hab. Dariusz Lebensztejn", party="brak/niepotwierdzona", source_url=src_rada), "RADA_SPOLECZNA", "Przewodniczący"))
roles.append(Role("uskwb", get_person("prof. dr hab. Wojciech Miltyk", party="brak/niepotwierdzona", source_url=src_rada), "RADA_SPOLECZNA", "Z-ca Przewodniczącego"))
roles.append(Role("uskwb", get_person("dr hab. Joanna Reszeć-Giełażyn", party="brak/niepotwierdzona", source_url=src_rada), "RADA_SPOLECZNA", "Sekretarz"))
roles.append(Role("uskwb", get_person("Bartosz Niebrzydowski", party="brak/niepotwierdzona", source_url=src_rada), "RADA_SPOLECZNA", "członek"))
roles.append(Role("uskwb", get_person("Marcin Borsuk", party="brak/niepotwierdzona", source_url=src_rada), "RADA_SPOLECZNA", "członek"))
roles.append(Role("uskwb", get_person("Marek Malinowski", party="Wicemarszałek woj.; koalicja Obywatelska; niezrzeszony", source_url=src_rada, note="do potwierdzenia"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("uskwb", get_person("Henryk Grzesiak", party="brak/niepotwierdzona", source_url=src_rada), "RADA_SPOLECZNA", "członek"))
roles.append(Role("uskwb", get_person("Elżbieta Sienkiewicz", party="brak/niepotwierdzona", source_url=src_rada), "RADA_SPOLECZNA", "członek"))
roles.append(Role("uskwb", get_person("prof. dr hab. Sławomir Terlikowski", party="brak/niepotwierdzona", source_url=src_rada), "RADA_SPOLECZNA", "członek"))
roles.append(Role("uskwb", get_person("prof. dr hab. Jerzy Pałka", party="brak/niepotwierdzona", source_url=src_rada), "RADA_SPOLECZNA", "członek"))
# =========================================================================
# 2. UDSK
# =========================================================================
src_udsk = "https://szpitaldzieciecybialystok.sisco.info/?id=316"
src_udsk_rada = "https://szpitaldzieciecybialystok.sisco.info/?id=416"
hospitals.append(Hospital(id="udsk", name="Uniwersytecki Dziecięcy Szpital Kliniczny im. L. Zamenhofa",
city="Białystok", legal_form="SPZOZ", website=src_udsk))
roles.append(Role("udsk", get_person("prof. dr hab. n. med. Anna Wasilewska", party="brak/niepotwierdzona", source_url=src_udsk), "DYREKTOR", "Dyrektor"))
roles.append(Role("udsk", get_person("dr n. o zdr. Cezary Rzemek", party="brak/niepotwierdzona", source_url=src_udsk), "Z_CA", "Z-ca ds. Ekonomicznych"))
roles.append(Role("udsk", get_person("mgr inż. Wojciech Roszkowski", party="brak/niepotwierdzona", source_url=src_udsk), "Z_CA", "Z-ca ds. Administr.-Techn."))
roles.append(Role("udsk", get_person("Elżbieta Sienkiewicz", party="brak/niepotwierdzona", source_url=src_udsk), "Z_CA", "Z-ca ds. Pielęgniarstwa"))
roles.append(Role("udsk", get_person("prof. dr hab. n. med. Beata Naumnik", party="brak/niepotwierdzona", source_url=src_udsk_rada), "RADA_SPOLECZNA", "Przewodnicząca"))
roles.append(Role("udsk", get_person("mgr Sylwia Ignaczak-Piotrowska", party="brak/niepotwierdzona", source_url=src_udsk_rada), "RADA_SPOLECZNA", "członek"))
roles.append(Role("udsk", get_person("mgr Katarzyna Bardłowska", party="brak/niepotwierdzona", source_url=src_udsk_rada), "RADA_SPOLECZNA", "członek"))
roles.append(Role("udsk", get_person("mgr Monika Anna Borkowska", party="brak/niepotwierdzona", source_url=src_udsk_rada), "RADA_SPOLECZNA", "członek"))
roles.append(Role("udsk", get_person("dr n. med. Ryta Filipkowska", party="brak/niepotwierdzona", source_url=src_udsk_rada), "RADA_SPOLECZNA", "członek"))
roles.append(Role("udsk", get_person("mgr Bożena Kostro", party="brak/niepotwierdzona", source_url=src_udsk_rada), "RADA_SPOLECZNA", "członek"))
roles.append(Role("udsk", get_person("prof. dr hab. n. med. Anna Niemcunowicz-Janica", party="brak/niepotwierdzona", source_url=src_udsk_rada), "RADA_SPOLECZNA", "członek"))
roles.append(Role("udsk", get_person("prof. dr hab. n. med. Elżbieta Tryniszewska", party="brak/niepotwierdzona", source_url=src_udsk_rada), "RADA_SPOLECZNA", "członek"))
roles.append(Role("udsk", get_person("prof. dr hab. n. med. Halina Car", party="brak/niepotwierdzona", source_url=src_udsk_rada), "RADA_SPOLECZNA", "członek"))
roles.append(Role("udsk", get_person("prof. dr hab. n. med. Jarosław Daniluk", party="brak/niepotwierdzona", source_url=src_udsk_rada), "RADA_SPOLECZNA", "członek"))
# =========================================================================
# 3. Śniadeckiego
# =========================================================================
src_sn = "https://podlaskie.eu/zdrowie/bogdan-kalicki-oficjalnie-objal-stanowisko-dyrektora-sniadecji.html"
src_sn_rada = "https://bip-spzozwszjs.podlaskie.eu/rada.html"
hospitals.append(Hospital(id="sniaskiego", name="SP ZOZ Wojewódzki Szpital Zespolony im. J. Śniadeckiego",
city="Białystok", legal_form="SPZOZ", website=src_sn))
roles.append(Role("sniaskiego", get_person("Bogdan Kalicki", party="brak/niepotwierdzona", source_url=src_sn), "DYREKTOR", "Dyrektor (p.o.)"))
roles.append(Role("sniaskiego", get_person("Karol Pilecki", party="Koalicja Obywatelska", source_url=src_sn_rada, note="radny Sejmiku; historycznie PSL"), "RADA_SPOLECZNA", "Przewodniczący"))
roles.append(Role("sniaskiego", get_person("Joanna Sosnowska", party="brak danych", source_url=src_sn_rada), "RADA_SPOLECZNA", "członek"))
roles.append(Role("sniaskiego", get_person("Bartosz Czaczkowski", party="brak danych", source_url=src_sn_rada), "RADA_SPOLECZNA", "członek"))
roles.append(Role("sniaskiego", get_person("Bogusław Dębski", party="Prawica Rzeczypospolitej", source_url=src_sn_rada, note="radny Sejmiku; historycznie PSL"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("sniaskiego", get_person("Jarosław Zygmunt Dworzański", party="Koalicja Obywatelska", source_url=src_sn_rada, note="radny Sejmiku; b. marszałek woj."), "RADA_SPOLECZNA", "członek"))
roles.append(Role("sniaskiego", get_person("Henryk Gryko", party="brak danych", source_url=src_sn_rada), "RADA_SPOLECZNA", "członek"))
roles.append(Role("sniaskiego", get_person("Paweł Jamróz", party="brak danych", source_url=src_sn_rada), "RADA_SPOLECZNA", "członek"))
roles.append(Role("sniaskiego", get_person("Jan Maksymilian Janiszewski", party="brak danych", source_url=src_sn_rada), "RADA_SPOLECZNA", "członek"))
roles.append(Role("sniaskiego", get_person("Katarzyna Kluczyk-Ceckowska", party="brak danych", source_url=src_sn_rada), "RADA_SPOLECZNA", "członek"))
roles.append(Role("sniaskiego", get_person("Rafał Kołakowski", party="brak danych", source_url=src_sn_rada), "RADA_SPOLECZNA", "członek"))
roles.append(Role("sniaskiego", get_person("Wojciech Łuczaj", party="brak danych", source_url=src_sn_rada, note="możliwie Łuczaj-Pogorzelski, PSL — NIEZWERYFIKOWANE"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("sniaskiego", get_person("Tomasz Stefan Madras", party="Prawo i Sprawiedliwość", source_url=src_sn_rada, note="radny Sejmiku; b. wicewojewoda"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("sniaskiego", get_person("Jarosław Matwiejuk", party="historycznie SLD/Lewica", source_url=src_sn_rada, note="b. poseł SLD — NIEZWERYFIKOWANE"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("sniaskiego", get_person("Andrzej Parafiniuk", party="brak danych", source_url=src_sn_rada), "RADA_SPOLECZNA", "członek"))
roles.append(Role("sniaskiego", get_person("Lech Rutkowski", party="brak danych", source_url=src_sn_rada), "RADA_SPOLECZNA", "członek"))
roles.append(Role("sniaskiego", get_person("Wojciech Strzałkowski", party="brak danych", source_url=src_sn_rada, note="NIEZWERYFIKOWANE"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("sniaskiego", get_person("Irina Kowalska", party="brak/niepotwierdzona", source_url=src_sn_rada), "RADA_SPOLECZNA", "członek"))
# =========================================================================
# 4. Białostockie Centrum Onkologii
# =========================================================================
src_bco = "https://bip-bco.podlaskie.eu/wadze_i_struktura_organizacyjna/kierownictwo-szpitala.html"
src_bco_rada = "https://www.onkologia.bialystok.pl/pages/rada-spoleczna"
hospitals.append(Hospital(id="bco", name="Białostockie Centrum Onkologii im. M. Skłodowskiej-Curie",
city="Białystok", legal_form="SPZOZ", website=src_bco))
roles.append(Role("bco", get_person("dr n. o zdr. Magdalena Joanna Borkowska", party="brak/niepotwierdzona", source_url=src_bco), "DYREKTOR", "Dyrektor"))
roles.append(Role("bco", get_person("dr n. med. Dorota Elżbieta Kazberuk", party="brak/niepotwierdzona", source_url=src_bco), "Z_CA", "Z-ca ds. Lecznictwa"))
roles.append(Role("bco", get_person("dr n. o zdr. Elżbieta Potentas", party="brak/niepotwierdzona", source_url=src_bco), "Z_CA", "Z-ca ds. Koordynacji Onkologicznej"))
roles.append(Role("bco", get_person("Anna Augustyn", party="Koalicja Obywatelska", source_url=src_bco_rada, note="radna Sejmiku"), "RADA_SPOLECZNA", "Przewodnicząca"))
roles.append(Role("bco", get_person("Cezary Cieślukowski", party="PSL", source_url=src_bco_rada, note="Przewodniczący Sejmiku VII kad."), "RADA_SPOLECZNA", "członek"))
roles.append(Role("bco", get_person("Jarosław Zygmunt Dworzański", party="Koalicja Obywatelska", source_url=src_bco_rada, note="radny Sejmiku"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("bco", get_person("Bogdan Dyjuk", party="PSL", source_url=src_bco_rada, note="radny Sejmiku; czł. Zarządu Woj."), "RADA_SPOLECZNA", "członek"))
roles.append(Role("bco", get_person("Janusz Dzięcioł", party="NIEZWERYFIKOWANE", source_url=src_bco_rada, note="inna osoba niż zmarły poseł PO; afiliacji nie potwierdzono"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("bco", get_person("Ewa Feszler", party="brak danych", source_url=src_bco_rada), "RADA_SPOLECZNA", "członek"))
roles.append(Role("bco", get_person("Robert Jabłoński", party="Prawo i Sprawiedliwość", source_url=src_bco_rada, note="prawdopodobnie PiS; NIEZWERYFIKOWANE"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("bco", get_person("Leszek Maciej Lulewicz", party="brak danych", source_url=src_bco_rada), "RADA_SPOLECZNA", "członek"))
roles.append(Role("bco", get_person("Jacek Piorunek", party="Koalicja Obywatelska", source_url=src_bco_rada, note="radny Sejmiku"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("bco", get_person("Agnieszka Zaręba", party="brak danych", source_url=src_bco_rada), "RADA_SPOLECZNA", "członek"))
roles.append(Role("bco", get_person("Agnieszka Zawistowska", party="brak danych", source_url=src_bco_rada), "RADA_SPOLECZNA", "członek"))
roles.append(Role("bco", get_person("Maciej Żywno", party="Polska 2050", source_url="https://www.senat.gov.pl/sklad/senatorowie/senator,1072,11,maciej-zywno.html", note="senator, wicemarszałek Senatu"), "RADA_SPOLECZNA", "członek"))
# =========================================================================
# 5. MSWiA
# =========================================================================
src_mswia = "https://bip.zozmswia.bialystok.pl/wladze-i-struktura-organizacyjna"
src_mswia_rada = "https://bip.zozmswia.bialystok.pl/wladze-i-struktura-organizacyjna/rada-spoleczna/"
hospitals.append(Hospital(id="mswia", name="SP ZOZ MSWiA w Białymstoku im. M. Zyndrama-Kościałkowskiego",
city="Białystok", legal_form="SPZOZ", website=src_mswia))
roles.append(Role("mswia", get_person("Marek Stanisław Karp", party="brak/niepotwierdzona", source_url=src_mswia, note="p.o. od 20.02.2024"), "DYREKTOR", "Dyrektor"))
roles.append(Role("mswia", get_person("Daniel Szutko", party="brak/niepotwierdzona", source_url=src_mswia_rada), "RADA_SPOLECZNA", "Przewodniczący"))
roles.append(Role("mswia", get_person("mgr inż. Sławomir Wilczewski", party="brak/niepotwierdzona", source_url=src_mswia_rada), "RADA_SPOLECZNA", "Z-ca Przewodniczącego (Policja)"))
roles.append(Role("mswia", get_person("st. bryg. Marcin Wierel", party="brak/niepotwierdzona", source_url=src_mswia_rada), "RADA_SPOLECZNA", "członek (PSP)"))
roles.append(Role("mswia", get_person("płk SG Marek Sochański", party="brak/niepotwierdzona", source_url=src_mswia_rada), "RADA_SPOLECZNA", "członek (Straż Graniczna)"))
roles.append(Role("mswia", get_person("ppłk Małgorzata Błażewicz", party="brak/niepotwierdzona", source_url=src_mswia_rada), "RADA_SPOLECZNA", "członek (Służba Więzienna)"))
roles.append(Role("mswia", get_person("dr n. med. Anatol Aksiucik", party="brak/niepotwierdzona", source_url=src_mswia_rada), "RADA_SPOLECZNA", "członek (Naczelna Rada Lekarska)"))
roles.append(Role("mswia", get_person("dr n. med. Beata Olejnik", party="brak/niepotwierdzona", source_url=src_mswia_rada), "RADA_SPOLECZNA", "członek (NRPiP)"))
roles.append(Role("mswia", get_person("prof. dr hab. n. med. Andrzej Dąbrowski", party="brak/niepotwierdzona", source_url=src_mswia_rada), "RADA_SPOLECZNA", "członek (UMB)"))
roles.append(Role("mswia", get_person("Krzysztof Turowski", party="brak/niepotwierdzona", source_url=src_mswia_rada), "RADA_SPOLECZNA", "członek (MSWiA)"))
# =========================================================================
# 6. Choroszcz
# =========================================================================
src_ch = "https://bip-sppzozc.podlaskie.eu/"
hospitals.append(Hospital(id="choroszcz", name="SP Psychiatryczny ZOZ im. dr. St. Deresza",
city="Choroszcz", legal_form="SP Psychiatryczny ZOZ", website=src_ch))
roles.append(Role("choroszcz", get_person("Alicja Skindzielewska", party="brak/niepotwierdzona", source_url=src_ch, note="p.o. Dyrektora"), "DYREKTOR", "p.o. Dyrektora"))
roles.append(Role("choroszcz", get_person("Andrzej Szewczuk", party="brak/niepotwierdzona", source_url=src_ch, note="dyrektor-elekt od VIII.2026"), "DYREKTOR", "Dyrektor-elekt (od VIII.2026)"))
roles.append(Role("choroszcz", get_person("lek. Justyna Anna Piłasiewicz", party="brak/niepotwierdzona", source_url=src_ch), "Z_CA", "Z-ca ds. Lecznictwa"))
roles.append(Role("choroszcz", get_person("Ewelina Cecotka-Białokozowicz", party="brak/niepotwierdzona", source_url=src_ch), "Z_CA", "Gł. Księgowa"))
roles.append(Role("choroszcz", get_person("dr n. med. Nadzieja Sołowiej", party="brak/niepotwierdzona", source_url=src_ch), "Z_CA", "Naczelna Pielęgniarka"))
roles.append(Role("choroszcz", get_person("Monika Miklaszewicz", party="brak danych", source_url=src_ch), "RADA_SPOLECZNA", "Przewodnicząca"))
# =========================================================================
# 7. Łomża
# =========================================================================
src_lomza = "https://www.szpital-lomza.pl/"
src_lomza_rada = "https://www.szpital-lomza.pl/index.php/wiadomosc/rada-spoleczna"
src_lomza_dyr = "https://www.rynekzdrowia.pl/.../Nowy-dyrektor-w-Szpitalu-Wojewodzkim-w-Lomzy,269250,1.html"
hospitals.append(Hospital(id="lomza", name="Szpital Wojewódzki im. Kardynała Stefana Wyszyńskiego",
city="Łomża", legal_form="SPZOZ", website=src_lomza))
roles.append(Role("lomza", get_person("Dariusz Domasiewicz", party="KWW Przyjazna Łomża", source_url=src_lomza_dyr, note="Radny Rady Miejskiej Łomży"), "DYREKTOR", "Dyrektor"))
roles.append(Role("lomza", get_person("Maria Konopka", party="Koalicja Obywatelska", source_url=src_lomza_rada, note="kandydatka PO do sejmiku 2024"), "RADA_SPOLECZNA", "Przewodnicząca"))
roles.append(Role("lomza", get_person("Marek Olbryś", party="PiS", source_url="https://www.portalsamorzadowy.pl/osoba/marek-olbrys,876.html", note="wicemarszałek woj. / radny Sejmiku"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("lomza", get_person("Jacek Piorunek", party="Koalicja Obywatelska", source_url="https://www.portalsamorzadowy.pl/osoba/jacek-piorunek,6388.html", note="radny Sejmiku"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("lomza", get_person("Bernadeta Krynicka", party="PiS", source_url="https://pl.wikipedia.org/wiki/Bernadeta_Krynicka", note="radna Sejmiku; b. posłanka PiS"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("lomza", get_person("Lech Marek Szabłowski", party="PiS", source_url="https://www.portalsamorzadowy.pl/osoba/lech-szablowski,55269.html", note="starosta łomżyński / radny powiatu"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("lomza", get_person("Krzysztof Ryszard Kozicki", party="PiS", source_url="https://samorzad2024.pkw.gov.pl/samorzad2024/pl/wbp/kandydat/3469307", note="wójt Piątnicy"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("lomza", get_person("Andrzej Borusiewicz", party="brak potwierdzonej afiliacji", source_url="https://pl.wikipedia.org/wiki/Andrzej_Borusiewicz", note="prorektor MANS w Łomży, wiceprezes ARiMR"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("lomza", get_person("Karol Kossakowski", party="niepotwierdzona", source_url=src_lomza_rada, note="kandydat do sejmiku; rej. Zambrów"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("lomza", get_person("Stanisław Kossakowski", party="brak danych", source_url=src_lomza_rada), "RADA_SPOLECZNA", "członek"))
roles.append(Role("lomza", get_person("Andrzej Szymański", party="brak danych", source_url=src_lomza_rada), "RADA_SPOLECZNA", "członek"))
roles.append(Role("lomza", get_person("Anna Malwina Szynkiewicz", party="brak danych", source_url=src_lomza_rada), "RADA_SPOLECZNA", "członek"))
roles.append(Role("lomza", get_person("Dorota Waszkiewicz", party="brak danych", source_url=src_lomza_rada), "RADA_SPOLECZNA", "członek"))
roles.append(Role("lomza", get_person("Renata Szydłowska", party="brak danych", source_url=src_lomza_rada), "RADA_SPOLECZNA", "członek"))
roles.append(Role("lomza", get_person("Milena Siok", party="brak danych", source_url=src_lomza_rada, note="NIEZWERYFIKOWANE"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("lomza", get_person("Marcin Biedrzycki", party="brak danych", source_url=src_lomza_rada), "RADA_SPOLECZNA", "członek"))
roles.append(Role("lomza", get_person("Janusz Józef Cieciórski", party="brak danych", source_url=src_lomza_rada), "RADA_SPOLECZNA", "członek"))
# =========================================================================
# 8. Suwałki — Rydygiera
# =========================================================================
src_sw = "https://bip-spsws.podlaskie.eu/6b54de5acb7.html"
src_sw_rada1 = "https://samorzad.podlaskie.eu/radni/naszkiewicz-anna.html"
src_sw_rada2 = "https://www.portalsamorzadowy.pl/osoba/bogdan-dyjuk,2177.html"
hospitals.append(Hospital(id="suwalki_rundyger", name="Szpital Wojewódzki im. dr. Ludwika Rydygiera",
city="Suwałki", legal_form="SPZOZ", website=src_sw))
roles.append(Role("suwalki_rundyger", get_person("Adam Szałanda", party="brak/niepotwierdzona", source_url=src_sw), "DYREKTOR", "Dyrektor"))
roles.append(Role("suwalki_rundyger", get_person("lek. Włodzimierz Żaworonok", party="brak danych", source_url=src_sw), "Z_CA", "Z-ca ds. Lecznictwa"))
roles.append(Role("suwalki_rundyger", get_person("Agnieszka Kapczyńska", party="brak danych", source_url=src_sw), "Z_CA", "Gł. Księgowy"))
roles.append(Role("suwalki_rundyger", get_person("Anna Naszkiewicz", party="Koalicja Obywatelska", source_url=src_sw_rada1, note="radna Sejmiku; b. wicemarszałek"), "RADA_SPOLECZNA", "Przewodnicząca"))
roles.append(Role("suwalki_rundyger", get_person("Bogdan Dyjuk", party="Trzecia Droga (PSLPolska 2050)", source_url=src_sw_rada2, note="radny Sejmiku, czł. Zarządu Woj."), "RADA_SPOLECZNA", "członek"))
roles.append(Role("suwalki_rundyger", get_person("Tadeusz Buchowiecki", party="brak/niepotwierdzona", source_url=src_sw, note="NIEZWERYFIKOWANE"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("suwalki_rundyger", get_person("Wojciech Wasilewski", party="brak/niepotwierdzona", source_url=src_sw, note="NIEZWERYFIKOWANE"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("suwalki_rundyger", get_person("Marta Żybura", party="Koalicja Obywatelska", source_url=src_sw, note="kandydatka KO do Rady Miejskiej 2024; NIEZWERYFIKOWANE"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("suwalki_rundyger", get_person("Dariusz Bogdan", party="brak/niepotwierdzona", source_url="https://pl.wikipedia.org/wiki/Dariusz_Bogdan", note="b. wiceminister gospodarki; przynależność partyjna niepotwierdzona"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("suwalki_rundyger", get_person("Joanna Jędzul", party="KWW Razem dla Suwałk", source_url=src_sw), "RADA_SPOLECZNA", "członek"))
roles.append(Role("suwalki_rundyger", get_person("Dorota Iwanowicz", party="Trzecia Droga", source_url="https://www.farmer.pl/wybory/osoba/dorota-iwanowicz,221972.html", note="radna gm. Suwałki; NIEZWERYFIKOWANE"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("suwalki_rundyger", get_person("Daniel Leszek Makarewicz", party="KWW Suwalskie Porozumienie Prawicy", source_url=src_sw, note="NIEZWERYFIKOWANE"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("suwalki_rundyger", get_person("Elżbieta Luto", party="brak/niepotwierdzona", source_url=src_sw), "RADA_SPOLECZNA", "członek"))
roles.append(Role("suwalki_rundyger", get_person("Justyna Drażba", party="brak/niepotwierdzona", source_url=src_sw), "RADA_SPOLECZNA", "członek"))
roles.append(Role("suwalki_rundyger", get_person("Grzegorz Paweł Matys", party="brak/niepotwierdzona", source_url=src_sw), "RADA_SPOLECZNA", "członek"))
roles.append(Role("suwalki_rundyger", get_person("Krystian Małyszko", party="brak/niepotwierdzona", source_url=src_sw), "RADA_SPOLECZNA", "członek"))
# =========================================================================
# 9. Suwałki — Psychiatryczny
# =========================================================================
src_spp_s = "https://bip-spspzozs.podlaskie.eu/"
src_spp_s_rada = "https://bip-spspzozs.podlaskie.eu/rada_spoleczna.html"
hospitals.append(Hospital(id="suwalki_psych", name="Specjalistyczny Psychiatryczny SP ZOZ",
city="Suwałki", legal_form="SPZOZ", website=src_spp_s))
roles.append(Role("suwalki_psych", get_person("Bożena Łapińska", party="brak/niepotwierdzona", source_url=src_spp_s), "DYREKTOR", "Dyrektor"))
roles.append(Role("suwalki_psych", get_person("lek. Michał Naczas", party="brak danych", source_url=src_spp_s), "Z_CA", "Z-ca ds. Lecznictwa"))
roles.append(Role("suwalki_psych", get_person("Marek Wasilewski", party="brak danych", source_url=src_spp_s), "Z_CA", "Gł. Księgowy"))
roles.append(Role("suwalki_psych", get_person("Władysław Renowicki", party="brak/niepotwierdzona", source_url=src_spp_s_rada), "RADA_SPOLECZNA", "Przewodniczący"))
roles.append(Role("suwalki_psych", get_person("Eliza Kupich", party="brak danych", source_url=src_spp_s_rada), "RADA_SPOLECZNA", "członek"))
roles.append(Role("suwalki_psych", get_person("Waldemar Kwaterski", party="Koalicja Obywatelska", source_url=src_spp_s_rada, note="radny Sejmiku; dyr. SP ZOZ Sejny; NIEZWERYFIKOWANE"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("suwalki_psych", get_person("Jacek Niedźwiedzki", party="KO/PO", source_url=src_spp_s_rada, note="poseł z Suwałk; NIEZWERYFIKOWANE"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("suwalki_psych", get_person("Elżbieta Baranowska", party="brak danych", source_url=src_spp_s_rada), "RADA_SPOLECZNA", "członek"))
roles.append(Role("suwalki_psych", get_person("Natalia Bogdan-Czarnowska", party="brak danych", source_url=src_spp_s_rada), "RADA_SPOLECZNA", "członek"))
roles.append(Role("suwalki_psych", get_person("Anna Maria Gawlińska", party="brak danych", source_url=src_spp_s_rada), "RADA_SPOLECZNA", "członek"))
roles.append(Role("suwalki_psych", get_person("Anna Kumiszcza", party="brak danych", source_url=src_spp_s_rada), "RADA_SPOLECZNA", "członek"))
roles.append(Role("suwalki_psych", get_person("Tomasz Romualdowski", party="brak danych", source_url=src_spp_s_rada), "RADA_SPOLECZNA", "członek"))
roles.append(Role("suwalki_psych", get_person("Bożena Jadwiga Chmielewska", party="brak danych", source_url=src_spp_s_rada), "RADA_SPOLECZNA", "członek"))
roles.append(Role("suwalki_psych", get_person("Dorota Iwanowicz", party="brak danych", source_url=src_spp_s_rada), "RADA_SPOLECZNA", "członek"))
roles.append(Role("suwalki_psych", get_person("Sławomir Jan Szeszko", party="brak danych", source_url=src_spp_s_rada), "RADA_SPOLECZNA", "członek"))
roles.append(Role("suwalki_psych", get_person("Aneta Jolanta Szmidt", party="brak danych", source_url=src_spp_s_rada), "RADA_SPOLECZNA", "członek"))
# =========================================================================
# 10. Augustów
# =========================================================================
src_a = "https://bip-spzoz-staugustow.podlaskie.eu/organy_wadze/kierownictwo_szpitala/"
src_a_rada = "https://bip-staugustow.podlaskie.eu/resource/116181/14.pdf/attachment.1"
src_a_dziennik = "https://www.dziennikpowiatowy.pl/artykul/13664"
hospitals.append(Hospital(id="augustow", name="SP ZOZ", city="Augustów",
legal_form="SPZOZ", website=src_a))
roles.append(Role("augustow", get_person("Adam Dębski", party="brak/niepotwierdzona", source_url=src_a, note="dyrektor od 1.09.2024"), "DYREKTOR", "Dyrektor"))
roles.append(Role("augustow", get_person("Wioletta Tomaszycka-Bednarczyk", party="brak danych", source_url=src_a), "Z_CA", "Z-ca ds. Lecznictwa"))
roles.append(Role("augustow", get_person("Piotr Rusiecki", party="PiS", source_url=src_a_rada, note="starosta augustowski, radny powiatu"), "RADA_SPOLECZNA", "Przewodniczący"))
roles.append(Role("augustow", get_person("Marek Dobkowski", party="KWW Samorząd Powiatu Augustowskiego", source_url=src_a_dziennik), "RADA_SPOLECZNA", "członek"))
roles.append(Role("augustow", get_person("Waldemar Jedliński", party="PiS", source_url=src_a_dziennik, note="członek Zarządu Powiatu"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("augustow", get_person("Elżbieta Puczyłowska", party="PiS", source_url=src_a_dziennik, note="wiczeprzewodnicząca Rady Powiatu"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("augustow", get_person("Alicja Dobrowolska", party="PiS", source_url=src_a_rada, note="członek Zarządu Powiatu"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("augustow", get_person("Dariusz Jan Szkiłądź", party="KWW BAU 2024", source_url=src_a_dziennik), "RADA_SPOLECZNA", "członek"))
roles.append(Role("augustow", get_person("Jadwiga Józefa Milewska", party="brak danych", source_url=src_a_rada, note="NIEZWERYFIKOWANE"), "RADA_SPOLECZNA", "członek"))
# =========================================================================
# 11. Bielsk Podlaski
# =========================================================================
src_b = "https://www.spzoz-bielsk.pl/o-nas/dyrekcja/"
src_b_portal = "https://www.portalsamorzadowy.pl/powiat/bielski,295.html"
src_b_rada = "https://samorzad.gov.pl/web/powiat-bielski/ii-sesja-rady-powiatu-w-bielsku-podlaskim"
hospitals.append(Hospital(id="bielsk", name="SP ZOZ", city="Bielsk Podlaski",
legal_form="SPZOZ", website=src_b))
roles.append(Role("bielsk", get_person("Piotr Selwesiuk", party="KW Koalicja Bielska", source_url=src_b, note="wypowiedzenie z 29.06.2026"), "DYREKTOR", "Dyrektor"))
roles.append(Role("bielsk", get_person("dr n. med. Arsalan Azzaddin", party="brak/niepotwierdzona", source_url=src_b), "Z_CA", "Z-ca ds. Lecznictwa"))
roles.append(Role("bielsk", get_person("Piotr Bożko", party="KW Koalicja Bielska", source_url=src_b_rada, note="wicestarosta, radny powiatu"), "RADA_SPOLECZNA", "Przewodniczący"))
roles.append(Role("bielsk", get_person("Elżbieta Fionik", party="KW Koalicja Bielska", source_url=src_b_portal, note="radna powiatu"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("bielsk", get_person("Maria Ryżyk", party="KW Koalicja Bielska", source_url=src_b_portal, note="radna powiatu"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("bielsk", get_person("Andrzej Leszczyński", party="KWW Nasze Podlasie", source_url=src_b_portal, note="Przew. Rady Powiatu"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("bielsk", get_person("Adam Łęczycki", party="KWW Nasze Podlasie", source_url=src_b_portal, note="radny powiatu"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("bielsk", get_person("Zbigniew Jan Szpakowski", party="KWW Nasze Podlasie", source_url=src_b_portal, note="radny powiatu, czł. Zarządu"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("bielsk", get_person("Jan Niewiński", party="PiS", source_url="https://www.portalsamorzadowy.pl/osoba/jan-niewinski,48951.html", note="radny powiatu"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("bielsk", get_person("Walentyna Tyszko-Dubow", party="NIEZWERYFIKOWANE", source_url=src_b_rada, note="PO/KO z Hajnówki"), "RADA_SPOLECZNA", "członek"))
# =========================================================================
# 12. Hajnówka
# =========================================================================
src_h = "https://spzozhajnowka.pl/strona-3343-dyrekcja.html"
src_h_rada = "Uchwała III/13/2024 Rady Powiatu Hajnowskiego"
src_h_portal = "https://www.portalsamorzadowy.pl/"
hospitals.append(Hospital(id="hajnowka", name="SP ZOZ", city="Hajnówka",
legal_form="SPZOZ", website=src_h))
roles.append(Role("hajnowka", get_person("Grzegorz Tomaszuk", party="brak/niepotwierdzona", source_url=src_h), "DYREKTOR", "Dyrektor"))
roles.append(Role("hajnowka", get_person("Tomasz Musiuk", party="brak/niepotwierdzona", source_url=src_h), "Z_CA", "Z-ca ds. Lecznictwa"))
roles.append(Role("hajnowka", get_person("Andrzej Skiepko", party="KWW Ziemia Hajnowska", source_url=src_h_portal + "osoba/andrzej-skiepko,17764.html", note="Źródła rozbieżne: KWW Ziemia Hajnowska lub KWW Porozumienie Samorządowe"), "RADA_SPOLECZNA", "Przewodniczący"))
roles.append(Role("hajnowka", get_person("Jerzy Sirak", party="NIEZWERYFIKOWANE", source_url=src_h_portal + "osoba/jerzy-sirak,1302.html", note="rozbieżne: KO vs historycznie SLD; b. burmistrz"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("hajnowka", get_person("Justyna Jarmocik", party="Trzecia Droga (PSL / Polska 2050)", source_url=src_h_portal + "powiat/hajnowski,350.html", note="Wicestarosta"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("hajnowka", get_person("Albert Waldemar Litwinowicz", party="komitet niepotwierdzony", source_url=src_h_rada, note="wójt Białowieży"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("hajnowka", get_person("Jerzy Wasiluk", party="możliwie Trzecia Droga", source_url=src_h_rada, note="wójt Czyż; do potwierdzenia"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("hajnowka", get_person("Leon Małaszewski", party="KWW Ziemia Hajnowska", source_url=src_h_rada, note="wójt; niepotwierdzone partyjnie"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("hajnowka", get_person("Lucyna Smoktunowicz", party="KWW Nasza Przyszłość; bezpartyjna", source_url="https://samorzad2024.pkw.gov.pl/samorzad2024/pl/wbp/kandydat/3412858", note="wójt"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("hajnowka", get_person("Ireneusz Roman Kiendyś", party="KWW Hajnówka Razem; bezpartyjny", source_url="https://samorzad2024.pkw.gov.pl/samorzad2024/pl/wbp/kandydat/3459046", note="burmistrz"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("hajnowka", get_person("Mirosław Markiewicz", party="KWW Roztropna Troska o Dobro Gminy", source_url="https://w.radio.bialystok.pl/wiadomosci/index/id/236847", note="burmistrz Kleszczele"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("hajnowka", get_person("Aneta Leonowicz", party="KWW Narew Odnowa", source_url="https://samorzad2024.pkw.gov.pl/samorzad2024/pl/wbp/kandydat/3588266", note="wójt"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("hajnowka", get_person("Jarosław Gołubowski", party="KWW Wspólna Narewka", source_url="https://samorzad2024.pkw.gov.pl/samorzad2024/pl/wbp/kandydat/3421407", note="wójt"), "RADA_SPOLECZNA", "członek"))
# =========================================================================
# 13. Siemiatycze
# =========================================================================
src_sm = "https://spzozsiemiatycze.pl/?page_id=395"
src_sm_radio = "https://www.radio.bialystok.pl/wiadomosci/index/id/238459"
src_sm_siem = "https://siemiatycze.podlasie24.pl/region/zarzad-powiatu-siemiatyckiego-z-absolutorium-za-rok-2023-20240615102143"
src_sm_portal = "https://www.portalsamorzadowy.pl/"
hospitals.append(Hospital(id="siemiatycze", name="SP ZOZ (Szpital Powiatowy)", city="Siemiatycze",
legal_form="SPZOZ", website=src_sm))
roles.append(Role("siemiatycze", get_person("Andrzej Szewczuk", party="brak/niepotwierdzona", source_url=src_sm, note="przejście do Choroszczy od VIII.2026"), "DYREKTOR", "Dyrektor"))
roles.append(Role("siemiatycze", get_person("Lech Zenon Hackiewicz", party="brak/niepotwierdzona", source_url=src_sm), "Z_CA", "Z-ca ds. Lecznictwa"))
roles.append(Role("siemiatycze", get_person("Mariusz Cieślik", party="PSL", source_url=src_sm_radio, note="wybrany z KWW Ruch Ludowy"), "RADA_SPOLECZNA", "Przewodniczący"))
roles.append(Role("siemiatycze", get_person("Sylwester Zgierun", party="PSL", source_url=src_sm_siem, note="KWW Ruch Ludowy"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("siemiatycze", get_person("Mariusz Pyzowski", party="Koalicja Obywatelska", source_url=src_sm_portal + "osoba/mariusz-pyzowski,34169.html", note="Przew. Rady Powiatu"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("siemiatycze", get_person("Jan Żeruń", party="KWW SPW", source_url=src_sm_siem.replace("2023-20240615102143", "")), "RADA_SPOLECZNA", "członek"))
roles.append(Role("siemiatycze", get_person("Edward Krasowski", party="KWW własny", source_url="https://samorzad2024.pkw.gov.pl/samorzad2024/pl/wbp/kandydat/3739772", note="wójt; afiliacja niepotwierdzona"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("siemiatycze", get_person("Piotr Siniakowicz", party="PO/KO", source_url="https://pl.wikipedia.org/wiki/Piotr_Siniakowicz", note="KWW Dla Siemiatycz Razem"), "RADA_SPOLECZNA", "członek"))
# =========================================================================
# 14. Sokółka
# =========================================================================
src_sk = "https://szpitalsokolka.pl/dyrekcja/"
src_sk_radio = "https://www.radio.bialystok.pl/wiadomosci/index/id/228040"
src_sk_pkw = "https://samorzad2024.pkw.gov.pl/samorzad2024/pl/rada_powiatu/kandydat/3296821"
src_sk_radio2 = "https://www.radio.bialystok.pl/wiadomosci/index/id/237569"
hospitals.append(Hospital(id="sokolka", name="SP ZOZ", city="Sokółka",
legal_form="SPZOZ", website=src_sk))
roles.append(Role("sokolka", get_person("Anna Marta Aniśkiewicz", party="niepotwierdzona", source_url=src_sk_radio, note="KWW Razem dla Sokółki"), "DYREKTOR", "Dyrektor"))
roles.append(Role("sokolka", get_person("Adam Pietruczuk", party="brak/niepotwierdzona", source_url=src_sk), "Z_CA", "Z-ca ds. Lecznictwa"))
roles.append(Role("sokolka", get_person("Maria Julita Budrowska", party="brak/niepotwierdzona", source_url=src_sk), "Z_CA", "Z-ca ds. Ekonomicznych"))
roles.append(Role("sokolka", get_person("Piotr Rećko", party="PiS", source_url=src_sk_pkw, note="starosta sokólski, radny powiatu"), "RADA_SPOLECZNA", "Przewodniczący"))
roles.append(Role("sokolka", get_person("Cezary Możejko", party="NIEZWERYFIKOWANE", source_url=src_sk, note="możliwie wójt gm. Sidra; zbieżność nazwisk"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("sokolka", get_person("Krzysztof Pawłowski", party="PiS", source_url=src_sk_radio2, note="radny powiatu, wiceprzew. Rady Powiatu"), "RADA_SPOLECZNA", "członek"))
# =========================================================================
# 15. Mońki
# =========================================================================
src_m = "https://spzoz.szpital-monki.h2.pl/kadra-zarzadzajaca/"
src_m_portal = "https://www.portalsamorzadowy.pl/powiat/moniecki,362.html"
hospitals.append(Hospital(id="monki", name="SP ZOZ", city="Mońki",
legal_form="SPZOZ", website=src_m))
roles.append(Role("monki", get_person("lek. med. Sebastian Wysocki", party="brak/niepotwierdzona", source_url=src_m), "DYREKTOR", "Dyrektor"))
roles.append(Role("monki", get_person("lek. med. Łucja Chojnowska", party="brak/niepotwierdzona", source_url=src_m, note="p.o. Z-ca ds. Lecznictwa"), "Z_CA", "Z-ca ds. Lecznictwa (p.o.)"))
roles.append(Role("monki", get_person("mgr Halina Skibicka", party="brak/niepotwierdzona", source_url=src_m), "Z_CA", "Z-ca ds. Pielęgniarstwa"))
roles.append(Role("monki", get_person("Barbara Lubecka", party="brak/niepotwierdzona", source_url=src_m, note="p.o. Gł. Księgowa"), "Z_CA", "Gł. Księgowa (p.o.)"))
roles.append(Role("monki", get_person("Joanna Kitlas", party="KWW BMN Aktywny Samorząd", source_url=src_m_portal, note="NIEZWERYFIKOWANE"), "RADA_SPOLECZNA", "Przewodnicząca"))
# =========================================================================
# 16. Grajewo
# =========================================================================
src_gr = "https://www.szpital-grajewo.pl/dyrekcja"
src_gr_portal = "https://www.portalsamorzadowy.pl/powiat/grajewski,329.html"
src_gr_biels = "https://bip.starostwograjewo.pl/612-kadencja-vii-2024-2029.html"
src_gr_e = "https://e-grajewo.pl/wiadomosc,barbara-ciszewska--kandydatka-na-burmistrza-grajewa,54754.html"
src_gr_grajewo = "https://www.grajewo.pl/pl/aktualnosci/maciej-pawel-bednarko-zaprzysiezony..."
hospitals.append(Hospital(id="grajewo", name="Szpital Ogólny im. dr. Witolda Ginela",
city="Grajewo", legal_form="SPZOZ", website=src_gr))
roles.append(Role("grajewo", get_person("mgr Marta Romanowska", party="brak/niepotwierdzona", source_url=src_gr), "DYREKTOR", "Dyrektor"))
roles.append(Role("grajewo", get_person("lek. med. Adam Leszczyński", party="brak/niepotwierdzona", source_url=src_gr, note="p.o. Z-ca ds. Lecznictwa"), "Z_CA", "Z-ca ds. Lecznictwa (p.o.)"))
roles.append(Role("grajewo", get_person("mgr Małgorzata Zdanewicz", party="brak/niepotwierdzona", source_url=src_gr), "Z_CA", "Gł. Księgowa"))
roles.append(Role("grajewo", get_person("mgr Beata Szyszkowska", party="brak/niepotwierdzona", source_url=src_gr), "Z_CA", "Naczelna Pielęgniarka"))
roles.append(Role("grajewo", get_person("Waldemar Remfeld", party="PiS", source_url=src_gr_portal, note="starosta grajewski, radny powiatu"), "RADA_SPOLECZNA", "Przewodniczący"))
roles.append(Role("grajewo", get_person("Milena Formejster", party="brak danych", source_url=src_gr), "RADA_SPOLECZNA", "członek"))
roles.append(Role("grajewo", get_person("Barbara Ciszewska", party="komitet 2024 niepotwierdzony", source_url=src_gr_e, note="b. radna powiatu; Stow. Nasza Przyszłość"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("grajewo", get_person("Maciej Paweł Bednarko", party="KWW Macieja Pawła Bednarko", source_url=src_gr_grajewo, note="burmistrz Grajewa"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("grajewo", get_person("Marek Kostrzewski", party="KWW Dobry Powiat", source_url=src_gr_biels, note="radny powiatu"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("grajewo", get_person("Jerzy Jan Nikliński", party="brak danych", source_url=src_gr, note="NIEZWERYFIKOWANE"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("grajewo", get_person("Bolesław Ruszczyk", party="PiS", source_url=src_gr_biels, note="radny powiatu"), "RADA_SPOLECZNA", "członek"))
# =========================================================================
# 17. Kolno
# =========================================================================
src_kol = "https://www.szpitalkolno.pl/struktura/dyrekcja/"
src_kol_portal = "https://www.portalsamorzadowy.pl/"
hospitals.append(Hospital(id="kolno", name="Szpital Ogólny w Kolnie",
city="Kolno", legal_form="SPZOZ", website=src_kol))
roles.append(Role("kolno", get_person("mgr Aneta Długozima", party="brak/niepotwierdzona", source_url=src_kol), "DYREKTOR", "Dyrektor"))
roles.append(Role("kolno", get_person("lek. Oksana Dowlaszewicz", party="brak/niepotwierdzona", source_url=src_kol, note="p.o. Z-ca ds. Lecznictwa"), "Z_CA", "p.o. Z-ca ds. Lecznictwa"))
roles.append(Role("kolno", get_person("mgr Izabela Górska", party="brak/niepotwierdzona", source_url=src_kol, note="p.o. Z-ca ds. Ekon.-Fin. / Gł. Księgowy"), "Z_CA", "p.o. Z-ca ds. Ekon.-Fin. / Gł. Księgowy"))
roles.append(Role("kolno", get_person("mgr Alicja Urbańska", party="brak/niepotwierdzona", source_url=src_kol), "Z_CA", "Przełożona Pielęgniarek"))
roles.append(Role("kolno", get_person("Tadeusz Klama", party="KWW Porozumienie Samorządowe Powiatu Kolno", source_url=src_kol_portal + "osoba/tadeusz-klama,58658.html", note="radny powiatu"), "RADA_SPOLECZNA", "Przewodniczący"))
roles.append(Role("kolno", get_person("Michał Kulczewski", party="KWW Porozumienie Samorządowe Powiatu Kolno", source_url=src_kol_portal + "osoba/michal-kulczewski,57069.html", note="radny powiatu"), "RADA_SPOLECZNA", "Wiceprzewodniczący"))
roles.append(Role("kolno", get_person("Marek Samul", party="PO", source_url=src_kol, note="b. starosta kolneński; NIEZWERYFIKOWANE"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("kolno", get_person("Anita Krasińska", party="brak/niepotwierdzona", source_url=src_kol, note="kier. OPS Grabowo"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("kolno", get_person("Beata Helena Muniak", party="brak/niepotwierdzona", source_url=src_kol, note="Sekretarz Gminy"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("kolno", get_person("Robert Wacław Nadara", party="KWW Skuteczny i Przyjazny Samorząd", source_url=src_sk_radio2, note="radny powiatu"), "RADA_SPOLECZNA", "członek"))
roles.append(Role("kolno", get_person("Mariusz Rakowski", party="PiS", source_url=src_kol_portal + "osoba/mariusz-rakowski,106463.html", note="radny Rady Miasta Kolno"), "RADA_SPOLECZNA", "członek"))
# =========================================================================
# 18. Wysokie Mazowieckie
# =========================================================================
src_wm = "https://szpitalwysmaz.pl/pl/onas"
src_wm_portal = "https://www.portalsamorzadowy.pl/osoba/bogdan-zielinski,4307.html"
src_wm_pkw = "https://samorzad2024.pkw.gov.pl/samorzad2024/pl/wbp/kandydat/3739309"
hospitals.append(Hospital(id="wysokie", name="Szpital Ogólny w Wysokiem Mazowieckiem",
city="Wysokie Mazowieckie", legal_form="SPZOZ", website=src_wm))
roles.append(Role("wysokie", get_person("mgr Wojciech Łuczaj", party="PiS", source_url=src_wm_pkw, note="kandydat na burmistrza Czyżewa 2024 z KW PiS"), "DYREKTOR", "Dyrektor"))
roles.append(Role("wysokie", get_person("lek. Julita Nikołajuk-Stasiuk", party="brak/niepotwierdzona", source_url=src_wm), "Z_CA", "Z-ca ds. Lecznictwa"))
roles.append(Role("wysokie", get_person("mgr Aleksandra Łapiak", party="brak/niepotwierdzona", source_url=src_wm), "Z_CA", "Z-ca ds. Ekonomicznych"))
roles.append(Role("wysokie", get_person("mgr Anna Wądołkowska", party="brak/niepotwierdzona", source_url=src_wm), "Z_CA", "Przełożona Pielęgniarek"))
roles.append(Role("wysokie", get_person("Bogdan Zieliński", party="PiS", source_url=src_wm_portal, note="starosta, radny powiatu — NIEZWERYFIKOWANE czy przewodniczy"), "RADA_SPOLECZNA", "Przewodniczący"))
# =========================================================================
# 19. Sejny
# =========================================================================
src_se = "https://szpital.sejny.pl/kadra"
src_se_radio = "https://www.radio.bialystok.pl/gosc/suwalki/id/202941"
src_se_suwalki = "https://www.suwalki24.pl/article/63,powiat-sejnenski-maciej-plesiewicz-nadal-starosta..."
hospitals.append(Hospital(id="sejny", name="SP ZOZ (Szpital Powiatowy)",
city="Sejny", legal_form="SPZOZ", website=src_se))
roles.append(Role("sejny", get_person("Waldemar Kwaterski", party="Koalicja Obywatelska", source_url=src_se_radio, note="radny Sejmiku od 2010, przew. Komisji Zdrowia"), "DYREKTOR", "Dyrektor"))
roles.append(Role("sejny", get_person("Urszula Ordowska", party="brak/niepotwierdzona", source_url=src_se), "Z_CA", "Z-ca ds. Lecznictwa"))
roles.append(Role("sejny", get_person("Maciej Plesiewicz", party="KW Ziemia Sejneńska", source_url=src_se_suwalki, note="starosta; NIEZWERYFIKOWANE czy przewodniczy"), "RADA_SPOLECZNA", "Przewodniczący"))
# =========================================================================
# 20. Zambrow — SPÓŁKA
# =========================================================================
src_zb = "https://aleo.com/int/company/szpital-powiatowy-w-zambrowie"
src_zb_rada = "https://zambrowiacy.pl/info/bartosz-ziemianowicz/"
hospitals.append(Hospital(id="zambrow", name="Szpital Powiatowy w Zambrowie sp. z o.o.",
city="Zambrow", legal_form="sp. z o.o.", website=src_zb,
notes="Jedyny szpital jako spółka handlowa. KRS 0000130730"))
roles.append(Role("zambrow", get_person("Kamil Kowaleczko", party="brak/niepotwierdzona", source_url=src_zb, note="Prezes Zarządu od 16.07.2025"), "PREZES_ZARZADU", "Prezes Zarządu"))
roles.append(Role("zambrow", get_person("Bartosz Rafał Ziemianowicz", party="NIEZWERYFIKOWANE", source_url=src_zb_rada, note="b. radny miasta; historycznie PO; PO vs PiS"), "RADA_NADZORCZA", "członek"))
roles.append(Role("zambrow", get_person("Krzysztof Rodzik", party="brak danych", source_url=src_zb), "RADA_NADZORCZA", "członek"))
roles.append(Role("zambrow", get_person("Michał Kulesza", party="brak danych", source_url=src_zb), "RADA_NADZORCZA", "członek"))
roles.append(Role("zambrow", get_person("Patrycja Magdalena Bugaj", party="brak danych", source_url=src_zb), "RADA_NADZORCZA", "członek"))
# =========================================================================
# Deduplicate persons across all hospitals
# =========================================================================
persons = []
party_set = {} # canonical_name -> Party
person_party_map = {} # person name -> list of (display_name, slug)
for uid_map in persons_map.values():
for p in uid_map.values():
persons.append(p)
# Collect party slugs
for slug in p.party_slugs:
if slug not in party_set:
party_name, _ = normalise_party(slug)
party_set[slug] = Party(slug=slug, name=party_name)
# Parse party_note for additional parties
if p.party_note and p.party_note not in ("brak/niepotwierdzona", "brak danych", "NIEZWERYFIKOWANE",
"niepotwierdzona", "brak potwierdzonej afiliacji",
"komitet niepotwierdzony", "histor"):
_, slug = normalise_party(p.party_note)
if slug not in party_set and not any(s.isdigit() for s in slug):
party_name, _ = normalise_party(p.party_note)
party_set[slug] = Party(slug=slug, name=party_name)
person_party_map.setdefault(p.name, []).append((p.party_note, slug))
# Deduplicate party nodes: merge entries with same canonical name but different slugs
# Keep the "best" slug (shortest and most canonical) for each canonical name
best_slug_for_name = {}
for slug, party in party_set.items():
canonical = party.name.lower()
if canonical not in best_slug_for_name or len(slug) < len(best_slug_for_name[canonical]):
best_slug_for_name[canonical] = slug
# Build a mapping: old_slug -> best_slug
slug_mapping = {}
for old_slug, party in party_set.items():
canonical = party.name.lower()
if canonical in best_slug_for_name:
slug_mapping[old_slug] = best_slug_for_name[canonical]
# Replace slugs in persons
for uid_map in persons_map.values():
for p in uid_map.values():
new_slugs = []
for slug in p.party_slugs:
new_slug = slug_mapping.get(slug, slug)
if new_slug not in new_slugs:
new_slugs.append(new_slug)
# Also add slugs from party_note
if p.party_note and p.party_note not in ("brak/niepotwierdzona", "brak danych", "NIEZWERYFIKOWANE",
"niepotwierdzona", "brak potwierdzonej afiliacji",
"komitet niepotwierdzony", "histor"):
_, slug = normalise_party(p.party_note)
new_slug = slug_mapping.get(slug, slug)
if new_slug and new_slug not in new_slugs:
new_slugs.append(new_slug)
p.party_slugs = new_slugs
# Build final party list using only the "best" slugs
final_parties = {}
for slug, party in party_set.items():
canonical = party.name.lower()
if canonical in best_slug_for_name and best_slug_for_name[canonical] == slug:
final_parties[slug] = party
parties = list(final_parties.values())
print(f"Collected {len(hospitals)} hospitals, {len(persons)} unique persons, {len(roles)} roles, {len(parties)} deduplicated parties")
return hospitals, persons, roles, parties
# =========================================================================
# Main
# =========================================================================
def main():
print("=" * 60)
print("Hospital Graph Builder — Podlaskie Voivodeship")
print("=" * 60)
# Step 1: Build data
print("\n[1/5] Building graph data from research...")
hospitals, persons, roles, parties = build_data()
# Step 2: Connect to Neo4j
print("[2/5] Connecting to Neo4j...")
driver = _connect()
print(f" ✓ Connected to {os.getenv('NEO4J_URI', 'bolt://localhost:7687')}")
# Step 3: Load data
print("[3/5] Loading data into Neo4j...")
# Clear existing data
with driver.session() as session:
session.execute_write(_clear_database)
load_hospitals(driver, hospitals)
load_parties(driver, parties)
load_persons(driver, persons)
load_roles(driver, roles, {p.uid: p for p in persons})
print(" ✓ Data loaded successfully")
# Step 4: Validate
print("[4/5] Validating graph...")
validate(driver)
# Export
print("[5/5] Exporting reports...")
export_overlap_csv(driver)
print(" ↓ overlap_report.csv")
# Create indexes (for performance)
with driver.session() as session:
session.execute_write(_create_indexes)
driver.close()
print("\nDone! Graph has been built in Neo4j.")
print("Access Neo4j Browser at: http://localhost:7474")
print(" Username: neo4j")
print(" Password: graphdb123")
def _clear_database(tx):
tx.run("MATCH (n) DETACH DELETE n")
def _create_indexes(tx):
tx.run("CREATE INDEX hospital_uid IF NOT EXISTS FOR (h:Hospital) ON (h.uid)")
tx.run("CREATE INDEX person_uid IF NOT EXISTS FOR (p:Person) ON (p.uid)")
tx.run("CREATE INDEX person_normname IF NOT EXISTS FOR (p:Person) ON (p.normName)")
if __name__ == "__main__":
main()