kvk-connect 0.1.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kvk_connect/__init__.py +11 -0
- kvk_connect/api/__init__.py +4 -0
- kvk_connect/api/client.py +183 -0
- kvk_connect/api/endpoints.py +24 -0
- kvk_connect/api/session.py +34 -0
- kvk_connect/cli/main.py +26 -0
- kvk_connect/db/__init__.py +0 -0
- kvk_connect/db/basisprofiel_reader.py +67 -0
- kvk_connect/db/basisprofiel_writer.py +73 -0
- kvk_connect/db/init.py +25 -0
- kvk_connect/db/kvkvestigingen_reader.py +41 -0
- kvk_connect/db/kvkvestigingen_writer.py +73 -0
- kvk_connect/db/signaal_reader.py +23 -0
- kvk_connect/db/signaal_writer.py +73 -0
- kvk_connect/db/vestigingenprofiel_reader.py +66 -0
- kvk_connect/db/vestigingsprofiel_writer.py +92 -0
- kvk_connect/logging_config.py +27 -0
- kvk_connect/mappers/__init__.py +1 -0
- kvk_connect/mappers/kvk_record_mapper.py +100 -0
- kvk_connect/mappers/map_mutatie_abonnement_api_to_mutatieabonnement.py +11 -0
- kvk_connect/mappers/map_vestigingen_api_to_vestigingsnummers.py +14 -0
- kvk_connect/mappers/map_vestigingsprofiel_api_to_vestigingsprofiel_domain.py +41 -0
- kvk_connect/models/__init__.py +0 -0
- kvk_connect/models/api/__init__.py +0 -0
- kvk_connect/models/api/abonnementen_api.py +42 -0
- kvk_connect/models/api/basisprofiel_api.py +233 -0
- kvk_connect/models/api/mutatie_abonnementen_api.py +40 -0
- kvk_connect/models/api/mutatiesignalen_api.py +44 -0
- kvk_connect/models/api/vestigingen_api.py +73 -0
- kvk_connect/models/api/vestigingsprofiel_api.py +71 -0
- kvk_connect/models/domain/__init__.py +6 -0
- kvk_connect/models/domain/basisprofiel.py +65 -0
- kvk_connect/models/domain/kvkvestigingsnummersdomain.py +28 -0
- kvk_connect/models/domain/mutatie_abonnement.py +20 -0
- kvk_connect/models/domain/vestigingsadresdomain.py +62 -0
- kvk_connect/models/domain/vestigingsadressendomain.py +48 -0
- kvk_connect/models/domain/vestigingsprofiel_domain.py +58 -0
- kvk_connect/models/orm/base.py +5 -0
- kvk_connect/models/orm/basisprofiel_orm.py +52 -0
- kvk_connect/models/orm/kvkvestigingen_orm.py +53 -0
- kvk_connect/models/orm/signaal_orm.py +40 -0
- kvk_connect/models/orm/vestigingsprofiel_orm.py +58 -0
- kvk_connect/services/__init__.py +4 -0
- kvk_connect/services/record_service.py +66 -0
- kvk_connect/utils/__init__.py +5 -0
- kvk_connect/utils/env.py +16 -0
- kvk_connect/utils/formatting.py +11 -0
- kvk_connect/utils/rate_limit.py +21 -0
- kvk_connect/utils/tools.py +131 -0
- kvk_connect-0.1.6.dist-info/METADATA +352 -0
- kvk_connect-0.1.6.dist-info/RECORD +52 -0
- kvk_connect-0.1.6.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from sqlalchemy import select
|
|
2
|
+
from sqlalchemy.orm import Session
|
|
3
|
+
|
|
4
|
+
from kvk_connect.models.orm.kvkvestigingen_orm import VestigingenORM
|
|
5
|
+
from kvk_connect.models.orm.signaal_orm import SignaalORM
|
|
6
|
+
from kvk_connect.models.orm.vestigingsprofiel_orm import VestigingsProfielORM
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class VestigingsProfielReader:
|
|
10
|
+
def __init__(self, engine):
|
|
11
|
+
self.engine = engine
|
|
12
|
+
|
|
13
|
+
def get_vestigingen_zonder_vestigingsprofielen(self) -> list[str]:
|
|
14
|
+
"""Haalt alle vestigingsnummers op die wel in kvk_vestigingen staan maar nog niet in vestigingsprofielen.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
list[str]: Lijst met vestigingsnummers zonder vestigingsprofiel
|
|
18
|
+
"""
|
|
19
|
+
with Session(self.engine) as session:
|
|
20
|
+
stmt = (
|
|
21
|
+
select(VestigingenORM.vestigingsnummer)
|
|
22
|
+
.outerjoin(
|
|
23
|
+
VestigingsProfielORM, VestigingenORM.vestigingsnummer == VestigingsProfielORM.vestigingsnummer
|
|
24
|
+
)
|
|
25
|
+
.where(VestigingsProfielORM.vestigingsnummer.is_(None))
|
|
26
|
+
.where(VestigingenORM.vestigingsnummer != VestigingenORM.SENTINEL_VESTIGINGSNUMMER)
|
|
27
|
+
.distinct()
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
result = session.execute(stmt)
|
|
31
|
+
return [row[0] for row in result.fetchall()]
|
|
32
|
+
|
|
33
|
+
def get_outdated_vestigingen(self) -> list[str]:
|
|
34
|
+
"""Return lijst van vestigingsnummers met vestiging nieuwer dan de lastupdated van het vestigingenprofiel."""
|
|
35
|
+
|
|
36
|
+
with Session(self.engine) as session:
|
|
37
|
+
stmt = (
|
|
38
|
+
select(VestigingenORM.vestigingsnummer)
|
|
39
|
+
.join(VestigingsProfielORM, VestigingenORM.vestigingsnummer == VestigingsProfielORM.vestigingsnummer)
|
|
40
|
+
.where(VestigingenORM.last_updated > VestigingsProfielORM.last_updated)
|
|
41
|
+
.where(VestigingenORM.vestigingsnummer != VestigingenORM.SENTINEL_VESTIGINGSNUMMER)
|
|
42
|
+
.distinct()
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
result = session.execute(stmt).scalars().all()
|
|
46
|
+
return list(result)
|
|
47
|
+
|
|
48
|
+
def get_outdated_vestigingen_signaal(self) -> list[str]:
|
|
49
|
+
"""Return lijst van vestigingsnummers met signaal nieuwer is dan de last_updated van het vestigingenprofiel.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
list[str]: Lijst met vestigingsnummers die een update nodig hebben
|
|
53
|
+
"""
|
|
54
|
+
with Session(self.engine) as session:
|
|
55
|
+
stmt = (
|
|
56
|
+
select(SignaalORM.vestigingsnummer)
|
|
57
|
+
.join(VestigingsProfielORM, SignaalORM.vestigingsnummer == VestigingsProfielORM.vestigingsnummer)
|
|
58
|
+
.where(
|
|
59
|
+
SignaalORM.timestamp > VestigingsProfielORM.last_updated,
|
|
60
|
+
SignaalORM.vestigingsnummer.is_not(None), # Alleen vestigingsprofielen, geen basisprofielen
|
|
61
|
+
)
|
|
62
|
+
.distinct()
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
result = session.execute(stmt).scalars().all()
|
|
66
|
+
return [v for v in result if v is not None] # filter out possible None values
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# ruff: noqa: D102
|
|
2
|
+
import logging
|
|
3
|
+
from datetime import UTC, datetime
|
|
4
|
+
|
|
5
|
+
from sqlalchemy.orm import Session, sessionmaker
|
|
6
|
+
|
|
7
|
+
from kvk_connect.models.domain.vestigingsprofiel_domain import VestigingsProfielDomain
|
|
8
|
+
from kvk_connect.models.orm.vestigingsprofiel_orm import VestigingsProfielORM
|
|
9
|
+
from kvk_connect.utils.tools import parse_kvk_datum
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class VestigingsProfielWriter:
|
|
15
|
+
def __init__(self, engine, batch_size: int = 1):
|
|
16
|
+
logger.info("Initializing BasisProfielWriter, met batch size: %d", batch_size)
|
|
17
|
+
self.Session = sessionmaker(bind=engine)
|
|
18
|
+
self.batch_size = batch_size
|
|
19
|
+
self._session: Session | None = None
|
|
20
|
+
self._count = 0
|
|
21
|
+
|
|
22
|
+
def __enter__(self):
|
|
23
|
+
"""Start een nieuwe database sessie."""
|
|
24
|
+
self._session = self.Session()
|
|
25
|
+
return self
|
|
26
|
+
|
|
27
|
+
def __exit__(self, exc_type, exc, tb):
|
|
28
|
+
"""Commit changes on successful exit, rollback on exception."""
|
|
29
|
+
try:
|
|
30
|
+
if exc is None:
|
|
31
|
+
self.flush()
|
|
32
|
+
else:
|
|
33
|
+
if self._session:
|
|
34
|
+
self._session.rollback()
|
|
35
|
+
finally:
|
|
36
|
+
if self._session:
|
|
37
|
+
self._session.close()
|
|
38
|
+
self._session = None
|
|
39
|
+
|
|
40
|
+
def flush(self) -> None:
|
|
41
|
+
if self._session:
|
|
42
|
+
self._session.commit()
|
|
43
|
+
|
|
44
|
+
def add(self, domain_vestigingsprofiel: VestigingsProfielDomain) -> None:
|
|
45
|
+
if not self._session:
|
|
46
|
+
raise RuntimeError("Session not initialized. Use context manager.")
|
|
47
|
+
|
|
48
|
+
orm_obj = self._to_orm(domain_vestigingsprofiel)
|
|
49
|
+
orm_obj.last_updated = datetime.now(UTC)
|
|
50
|
+
|
|
51
|
+
self._session.merge(orm_obj)
|
|
52
|
+
self._count += 1
|
|
53
|
+
|
|
54
|
+
if self._count % self.batch_size == 0:
|
|
55
|
+
self._session.commit()
|
|
56
|
+
|
|
57
|
+
@staticmethod
|
|
58
|
+
def _to_orm(domein_obj: VestigingsProfielDomain) -> VestigingsProfielORM:
|
|
59
|
+
# Convert GPS coordinates from string to float (handle comma decimal separator)
|
|
60
|
+
gps_lat = None
|
|
61
|
+
gps_lon = None
|
|
62
|
+
|
|
63
|
+
if domein_obj.bzk_adres_gps_latitude:
|
|
64
|
+
try:
|
|
65
|
+
gps_lat = float(domein_obj.bzk_adres_gps_latitude.replace(",", "."))
|
|
66
|
+
except (ValueError, AttributeError):
|
|
67
|
+
logger.warning("Invalid latitude value: %s", domein_obj.bzk_adres_gps_latitude)
|
|
68
|
+
|
|
69
|
+
if domein_obj.bzk_adres_gps_longitude:
|
|
70
|
+
try:
|
|
71
|
+
gps_lon = float(domein_obj.bzk_adres_gps_longitude.replace(",", "."))
|
|
72
|
+
except (ValueError, AttributeError):
|
|
73
|
+
logger.warning("Invalid longitude value: %s", domein_obj.bzk_adres_gps_longitude)
|
|
74
|
+
|
|
75
|
+
return VestigingsProfielORM(
|
|
76
|
+
vestigingsnummer=domein_obj.vestigingsnummer,
|
|
77
|
+
cor_adres_volledig=domein_obj.cor_adres_volledig,
|
|
78
|
+
cor_adres_postcode=domein_obj.cor_adres_postcode,
|
|
79
|
+
cor_adres_postbusnummer=domein_obj.cor_adres_postbusnummer,
|
|
80
|
+
cor_adres_plaats=domein_obj.cor_adres_plaats,
|
|
81
|
+
cor_adres_land=domein_obj.cor_adres_land,
|
|
82
|
+
bzk_adres_volledig=domein_obj.bzk_adres_volledig,
|
|
83
|
+
bzk_adres_straatnaam=domein_obj.bzk_adres_straatnaam,
|
|
84
|
+
bzk_adres_huisnummer=domein_obj.bzk_adres_huisnummer,
|
|
85
|
+
bzk_adres_postcode=domein_obj.bzk_adres_postcode,
|
|
86
|
+
bzk_adres_plaats=domein_obj.bzk_adres_plaats,
|
|
87
|
+
bzk_adres_land=domein_obj.bzk_adres_land,
|
|
88
|
+
bzk_adres_gps_latitude=gps_lat,
|
|
89
|
+
bzk_adres_gps_longitude=gps_lon,
|
|
90
|
+
registratie_datum_aanvang_vestiging=parse_kvk_datum(domein_obj.registratie_datum_aanvang_vestiging),
|
|
91
|
+
registratie_datum_einde_vestiging=parse_kvk_datum(domein_obj.registratie_datum_einde_vestiging),
|
|
92
|
+
)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from datetime import UTC, datetime
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class LocalTimezoneFormatter(logging.Formatter):
|
|
6
|
+
"""Formatter that converts UTC timestamps to local system timezone."""
|
|
7
|
+
|
|
8
|
+
def formatTime(self, record, datefmt=None): # noqa: N802, overrides base method
|
|
9
|
+
"""Convert UTC timestamp to timezone-aware datetime, then to local time."""
|
|
10
|
+
dt_utc = datetime.fromtimestamp(record.created, tz=UTC)
|
|
11
|
+
dt_local = dt_utc.astimezone()
|
|
12
|
+
|
|
13
|
+
if datefmt:
|
|
14
|
+
return dt_local.strftime(datefmt)
|
|
15
|
+
else:
|
|
16
|
+
return dt_local.strftime("%Y-%m-%d %H:%M:%S")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def configure(level: int = logging.INFO):
|
|
20
|
+
"""Configure logging with local timezone formatter."""
|
|
21
|
+
handler = logging.StreamHandler()
|
|
22
|
+
formatter = LocalTimezoneFormatter(
|
|
23
|
+
fmt="%(asctime)s [%(levelname)s] %(name)s: %(message)s", datefmt="%Y-%m-%d %H:%M:%S"
|
|
24
|
+
)
|
|
25
|
+
handler.setFormatter(formatter)
|
|
26
|
+
|
|
27
|
+
logging.basicConfig(level=level, handlers=[handler], force=True)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# mappers package initialization
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from kvk_connect.models.api.basisprofiel_api import Adres, BasisProfielAPI, Hoofdvestiging, SBIActiviteit
|
|
4
|
+
from kvk_connect.models.domain import BasisProfielDomain
|
|
5
|
+
from kvk_connect.utils.tools import clean_and_pad, formatteer_datum, truncate_float
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _select_adres(adressen: list[Adres] | None) -> Adres | None:
|
|
9
|
+
if not adressen:
|
|
10
|
+
return None
|
|
11
|
+
# Prefer "bezoekadres", else fall back to "correspondentieadres", else first
|
|
12
|
+
bezoek = next((a for a in adressen if a and a.type == "bezoekadres"), None)
|
|
13
|
+
if bezoek:
|
|
14
|
+
return bezoek
|
|
15
|
+
corr = next((a for a in adressen if a and a.type == "correspondentieadres"), None)
|
|
16
|
+
return corr or adressen[0]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _format_straatnaam(adres: Adres) -> str:
|
|
20
|
+
if not adres or not adres.straatnaam:
|
|
21
|
+
return ""
|
|
22
|
+
parts = [adres.straatnaam]
|
|
23
|
+
if adres.huisnummer is not None:
|
|
24
|
+
parts.append(str(adres.huisnummer))
|
|
25
|
+
# The parser used 'toevoegingAdres'; our model has 'huisletter'. Prefer huisletter if present.
|
|
26
|
+
if adres.huisletter:
|
|
27
|
+
parts[-1] = f"{parts[-1]}{adres.huisletter}" if parts else adres.huisletter
|
|
28
|
+
return " ".join(parts)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _map_sbi(api: BasisProfielAPI) -> tuple[str, str, str]:
|
|
32
|
+
hoofd_code = ""
|
|
33
|
+
hoofd_oms = ""
|
|
34
|
+
overige_codes: list[str] = []
|
|
35
|
+
|
|
36
|
+
activiteiten: list[SBIActiviteit] = api.sbi_activiteiten or []
|
|
37
|
+
for act in activiteiten:
|
|
38
|
+
if (act.ind_hoofdactiviteit or "").lower() == "ja":
|
|
39
|
+
hoofd_code = clean_and_pad(act.sbi_code, 5) if act.sbi_code else ""
|
|
40
|
+
hoofd_oms = act.sbi_omschrijving or ""
|
|
41
|
+
else:
|
|
42
|
+
if act.sbi_code:
|
|
43
|
+
overige_codes.append(clean_and_pad(act.sbi_code, 5))
|
|
44
|
+
|
|
45
|
+
return hoofd_code, hoofd_oms, ", ".join(overige_codes)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _map_hoofdvestiging(hv: Hoofdvestiging | None, out: BasisProfielDomain) -> None:
|
|
49
|
+
if not hv:
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
out.eerste_handelsnaam = hv.eerste_handelsnaam or ""
|
|
53
|
+
out.vestigingsnummer = hv.vestigingsnummer or ""
|
|
54
|
+
out.totaal_werkzame_personen = hv.totaal_werkzame_personen if hv.totaal_werkzame_personen is not None else None
|
|
55
|
+
out.websites = ", ".join(hv.websites or [])
|
|
56
|
+
|
|
57
|
+
# Adres
|
|
58
|
+
adres = _select_adres(hv.adressen or [])
|
|
59
|
+
if adres:
|
|
60
|
+
out.adres_type = adres.type or ""
|
|
61
|
+
# The Adres model has no 'postbusnummer' field; keep empty to match parser behavior when absent.
|
|
62
|
+
out.postbusnummer = ""
|
|
63
|
+
out.adres_straatnaam = _format_straatnaam(adres)
|
|
64
|
+
out.adres_toevoeging = "" # parser used 'toevoegingAdres', not present in model; keep empty
|
|
65
|
+
out.adres_postcode = adres.postcode or ""
|
|
66
|
+
out.adres_plaats = adres.plaats or ""
|
|
67
|
+
if adres.geo_data:
|
|
68
|
+
out.gps_latitude = truncate_float(adres.geo_data.gps_latitude)
|
|
69
|
+
out.gps_longitude = truncate_float(adres.geo_data.gps_longitude)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def map_kvkbasisprofiel_api_to_kvkrecord(api: BasisProfielAPI) -> BasisProfielDomain:
|
|
73
|
+
"""Map a KVKRecordAPI model (Basisprofiel) to a KVKRecord (BasisprofielOutput).
|
|
74
|
+
|
|
75
|
+
This function is mirroring the logic from parsers.kvkparser.parse_basisprofiel.
|
|
76
|
+
"""
|
|
77
|
+
out = BasisProfielDomain()
|
|
78
|
+
|
|
79
|
+
# Top-level
|
|
80
|
+
out.kvk_nummer = api.kvk_nummer or ""
|
|
81
|
+
out.naam = api.naam or ""
|
|
82
|
+
|
|
83
|
+
# SBI activiteiten
|
|
84
|
+
out.hoofdactiviteit, out.hoofdactiviteit_omschrijving, out.activiteit_overig = _map_sbi(api)
|
|
85
|
+
|
|
86
|
+
# Eigenaar
|
|
87
|
+
if api.embedded and api.embedded.eigenaar:
|
|
88
|
+
out.rechtsvorm = api.embedded.eigenaar.rechtsvorm or ""
|
|
89
|
+
out.rechtsvorm_uitgebreid = api.embedded.eigenaar.uitgebreide_rechtsvorm or ""
|
|
90
|
+
|
|
91
|
+
# Registratie
|
|
92
|
+
if api.materiele_registratie:
|
|
93
|
+
out.registratie_datum_aanvang = formatteer_datum(str(api.materiele_registratie.datum_aanvang or ""))
|
|
94
|
+
out.registratie_datum_einde = formatteer_datum(str(api.materiele_registratie.datum_einde or ""))
|
|
95
|
+
|
|
96
|
+
# Hoofdvestiging
|
|
97
|
+
hv = api.embedded.hoofdvestiging if api.embedded else None
|
|
98
|
+
_map_hoofdvestiging(hv, out)
|
|
99
|
+
|
|
100
|
+
return out
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from kvk_connect.models.api.mutatie_abonnementen_api import MutatieAbonnementenAPI
|
|
2
|
+
from kvk_connect.models.domain.mutatie_abonnement import MutatieAbonnementDomain
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def map_mutatie_abonnement_api_to_mutatieabonnement(api_model: MutatieAbonnementenAPI) -> MutatieAbonnementDomain:
|
|
6
|
+
"""Maps MutatieAbonnementenAPI to MutatieAbonnement.
|
|
7
|
+
|
|
8
|
+
Extracts abonnement IDs from the API model.
|
|
9
|
+
"""
|
|
10
|
+
abonnement_ids = [abonnement.id for abonnement in api_model.abonnementen]
|
|
11
|
+
return MutatieAbonnementDomain(abonnement_ids=abonnement_ids)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from ..models.api.vestigingen_api import VestigingenAPI
|
|
4
|
+
from ..models.domain import KvKVestigingsNummersDomain
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def map_vestigingen_api_to_vestigingsnummers(api_model: VestigingenAPI) -> KvKVestigingsNummersDomain:
|
|
8
|
+
"""Zet een VestigingenAPI (API-model) om naar ons domeinmodel KvKVestigingsnummers.
|
|
9
|
+
|
|
10
|
+
Haalt de lijst van 'vestigingsnummer' waarden op in de originele volgorde.
|
|
11
|
+
"""
|
|
12
|
+
nummers: list[str] = [v.vestigingsnummer for v in (api_model.vestigingen or []) if v and v.vestigingsnummer]
|
|
13
|
+
|
|
14
|
+
return KvKVestigingsNummersDomain(kvk_nummer=api_model.kvk_nummer or "", vestigingsnummers=nummers)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from kvk_connect.models.api.vestigingsprofiel_api import VestigingsProfielAPI
|
|
4
|
+
from kvk_connect.models.domain.vestigingsprofiel_domain import VestigingsProfielDomain
|
|
5
|
+
from kvk_connect.utils.tools import formatteer_datum, truncate_float
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def map_vestigingsprofiel_api_to_vestigingsprofiel_domain(api_model: VestigingsProfielAPI) -> VestigingsProfielDomain:
|
|
9
|
+
"""Zet een VestigingsProfielAPI (API-model) om naar een VestigingsProfielDomain.
|
|
10
|
+
|
|
11
|
+
Extracteert correspondentieadres en bezoekadres uit de adressen lijst.
|
|
12
|
+
"""
|
|
13
|
+
cor_adres = next((a for a in (api_model.adressen or []) if a.type == "correspondentieadres"), None)
|
|
14
|
+
bzk_adres = next((a for a in (api_model.adressen or []) if a.type == "bezoekadres"), None)
|
|
15
|
+
|
|
16
|
+
return VestigingsProfielDomain(
|
|
17
|
+
vestigingsnummer=api_model.vestigingsnummer if api_model.vestigingsnummer else None,
|
|
18
|
+
cor_adres_volledig=cor_adres.volledig_adres if cor_adres and cor_adres.volledig_adres else None,
|
|
19
|
+
cor_adres_postcode=cor_adres.postcode if cor_adres and cor_adres.postcode else None,
|
|
20
|
+
cor_adres_postbusnummer=getattr(cor_adres, "postbusnummer", None) if cor_adres else None,
|
|
21
|
+
cor_adres_plaats=cor_adres.plaats if cor_adres and cor_adres.plaats else None,
|
|
22
|
+
cor_adres_land=cor_adres.land if cor_adres and cor_adres.land else None,
|
|
23
|
+
bzk_adres_volledig=bzk_adres.volledig_adres if bzk_adres and bzk_adres.volledig_adres else None,
|
|
24
|
+
bzk_adres_straatnaam=bzk_adres.straatnaam if bzk_adres else None,
|
|
25
|
+
bzk_adres_huisnummer=getattr(bzk_adres, "huisnummer", None) if bzk_adres else None,
|
|
26
|
+
bzk_adres_postcode=bzk_adres.postcode if bzk_adres and bzk_adres.postcode else None,
|
|
27
|
+
bzk_adres_plaats=bzk_adres.plaats if bzk_adres and bzk_adres.plaats else None,
|
|
28
|
+
bzk_adres_land=bzk_adres.land if bzk_adres and bzk_adres.land else None,
|
|
29
|
+
bzk_adres_gps_latitude=truncate_float(bzk_adres.geo_data.gps_latitude)
|
|
30
|
+
if bzk_adres and bzk_adres.geo_data and bzk_adres.geo_data.gps_latitude
|
|
31
|
+
else None,
|
|
32
|
+
bzk_adres_gps_longitude=truncate_float(bzk_adres.geo_data.gps_longitude)
|
|
33
|
+
if bzk_adres and bzk_adres.geo_data and bzk_adres.geo_data.gps_longitude
|
|
34
|
+
else None,
|
|
35
|
+
registratie_datum_aanvang_vestiging=formatteer_datum(str(api_model.materiele_registratie.datum_aanvang or ""))
|
|
36
|
+
if api_model.materiele_registratie
|
|
37
|
+
else None,
|
|
38
|
+
registratie_datum_einde_vestiging=formatteer_datum(str(api_model.materiele_registratie.datum_einde or ""))
|
|
39
|
+
if api_model.materiele_registratie
|
|
40
|
+
else None,
|
|
41
|
+
)
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# ruff: noqa: D102
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class Contract:
|
|
8
|
+
id: str
|
|
9
|
+
|
|
10
|
+
@classmethod
|
|
11
|
+
def from_dict(cls, data: dict[str, Any]) -> "Contract":
|
|
12
|
+
return cls(id=data.get("id", ""))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class Abonnement:
|
|
17
|
+
id: str
|
|
18
|
+
contract: Contract
|
|
19
|
+
start_datum: str
|
|
20
|
+
actief: bool
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def from_dict(cls, data: dict[str, Any]) -> "Abonnement":
|
|
24
|
+
return cls(
|
|
25
|
+
id=data.get("id", ""),
|
|
26
|
+
contract=Contract.from_dict(data.get("contract", {})),
|
|
27
|
+
start_datum=data.get("startDatum", ""),
|
|
28
|
+
actief=data.get("actief", False),
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class AbonnementenAPI:
|
|
34
|
+
klant_id: str
|
|
35
|
+
abonnementen: list[Abonnement] = field(default_factory=list)
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def from_dict(cls, data: dict[str, Any]) -> "AbonnementenAPI":
|
|
39
|
+
return cls(
|
|
40
|
+
klant_id=data.get("klantId", ""),
|
|
41
|
+
abonnementen=[Abonnement.from_dict(a) for a in data.get("abonnementen", [])],
|
|
42
|
+
)
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
# ruff: noqa: D102
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
from dataclasses import asdict, dataclass, field
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
"""
|
|
9
|
+
Naming conventions voor API models is appenden van *API
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class Link:
|
|
15
|
+
rel: str = ""
|
|
16
|
+
href: str = ""
|
|
17
|
+
|
|
18
|
+
@staticmethod
|
|
19
|
+
def from_dict(d: dict[str, Any] | None) -> Link | None:
|
|
20
|
+
if not d:
|
|
21
|
+
return None
|
|
22
|
+
return Link(rel=d.get("rel", "") or "", href=d.get("href", "") or "")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class GeoData:
|
|
27
|
+
addresseerbaar_object_id: str | None = None
|
|
28
|
+
nummer_aanduiding_id: str | None = None
|
|
29
|
+
gps_latitude: float | None = None
|
|
30
|
+
gps_longitude: float | None = None
|
|
31
|
+
rijksdriehoek_x: float | None = None
|
|
32
|
+
rijksdriehoek_y: float | None = None
|
|
33
|
+
rijksdriehoek_z: float | None = None
|
|
34
|
+
|
|
35
|
+
@staticmethod
|
|
36
|
+
def from_dict(d: dict[str, Any] | None) -> GeoData | None:
|
|
37
|
+
if not d:
|
|
38
|
+
return None
|
|
39
|
+
return GeoData(
|
|
40
|
+
addresseerbaar_object_id=d.get("addresseerbaarObjectId"),
|
|
41
|
+
nummer_aanduiding_id=d.get("nummerAanduidingId"),
|
|
42
|
+
gps_latitude=d.get("gpsLatitude"),
|
|
43
|
+
gps_longitude=d.get("gpsLongitude"),
|
|
44
|
+
rijksdriehoek_x=d.get("rijksdriehoekX"),
|
|
45
|
+
rijksdriehoek_y=d.get("rijksdriehoekY"),
|
|
46
|
+
rijksdriehoek_z=d.get("rijksdriehoekZ"),
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass
|
|
51
|
+
class Adres:
|
|
52
|
+
type: str = ""
|
|
53
|
+
ind_afgeschermd: str = ""
|
|
54
|
+
volledig_adres: str = ""
|
|
55
|
+
straatnaam: str = ""
|
|
56
|
+
huisnummer: int | None = None
|
|
57
|
+
huisletter: str | None = None
|
|
58
|
+
postbusnummer: int | None = None
|
|
59
|
+
postcode: str = ""
|
|
60
|
+
plaats: str = ""
|
|
61
|
+
land: str = ""
|
|
62
|
+
geo_data: GeoData | None = None
|
|
63
|
+
|
|
64
|
+
@staticmethod
|
|
65
|
+
def from_dict(d: dict[str, Any] | None) -> Adres | None:
|
|
66
|
+
if not d:
|
|
67
|
+
return None
|
|
68
|
+
return Adres(
|
|
69
|
+
type=d.get("type", "") or "",
|
|
70
|
+
ind_afgeschermd=d.get("indAfgeschermd", "") or "",
|
|
71
|
+
volledig_adres=d.get("volledigAdres", "") or "",
|
|
72
|
+
straatnaam=d.get("straatnaam", "") or "",
|
|
73
|
+
huisnummer=d.get("huisnummer"),
|
|
74
|
+
huisletter=d.get("huisletter"),
|
|
75
|
+
postbusnummer=d.get("postbusnummer"),
|
|
76
|
+
postcode=d.get("postcode", "") or "",
|
|
77
|
+
plaats=d.get("plaats", "") or "",
|
|
78
|
+
land=d.get("land", "") or "",
|
|
79
|
+
geo_data=GeoData.from_dict(d.get("geoData")),
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@dataclass
|
|
84
|
+
class MaterieleRegistratie:
|
|
85
|
+
datum_aanvang: str | None = None
|
|
86
|
+
datum_einde: str | None = None
|
|
87
|
+
|
|
88
|
+
@staticmethod
|
|
89
|
+
def from_dict(d: dict[str, Any] | None) -> MaterieleRegistratie | None:
|
|
90
|
+
if not d:
|
|
91
|
+
return None
|
|
92
|
+
return MaterieleRegistratie(datum_aanvang=d.get("datumAanvang"), datum_einde=d.get("datumEinde"))
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@dataclass
|
|
96
|
+
class HandelNaam:
|
|
97
|
+
naam: str = ""
|
|
98
|
+
volgorde: int | None = None
|
|
99
|
+
|
|
100
|
+
@staticmethod
|
|
101
|
+
def from_dict(d: dict[str, Any] | None) -> HandelNaam | None:
|
|
102
|
+
if not d:
|
|
103
|
+
return None
|
|
104
|
+
return HandelNaam(naam=d.get("naam", "") or "", volgorde=d.get("volgorde"))
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@dataclass
|
|
108
|
+
class SBIActiviteit:
|
|
109
|
+
sbi_code: str = ""
|
|
110
|
+
sbi_omschrijving: str = ""
|
|
111
|
+
ind_hoofdactiviteit: str = ""
|
|
112
|
+
|
|
113
|
+
@staticmethod
|
|
114
|
+
def from_dict(d: dict[str, Any] | None) -> SBIActiviteit | None:
|
|
115
|
+
if not d:
|
|
116
|
+
return None
|
|
117
|
+
return SBIActiviteit(
|
|
118
|
+
sbi_code=d.get("sbiCode", "") or "",
|
|
119
|
+
sbi_omschrijving=d.get("sbiOmschrijving", "") or "",
|
|
120
|
+
ind_hoofdactiviteit=d.get("indHoofdactiviteit", "") or "",
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@dataclass
|
|
125
|
+
class Hoofdvestiging:
|
|
126
|
+
vestigingsnummer: str = ""
|
|
127
|
+
kvk_nummer: str = ""
|
|
128
|
+
formele_registratiedatum: str = ""
|
|
129
|
+
materiele_registratie: MaterieleRegistratie | None = None
|
|
130
|
+
eerste_handelsnaam: str = ""
|
|
131
|
+
ind_hoofdvestiging: str = ""
|
|
132
|
+
ind_commerciele_vestiging: str = ""
|
|
133
|
+
totaal_werkzame_personen: int | None = None
|
|
134
|
+
adressen: list[Adres] = field(default_factory=list)
|
|
135
|
+
websites: list[str] = field(default_factory=list)
|
|
136
|
+
links: list[Link] = field(default_factory=list)
|
|
137
|
+
|
|
138
|
+
@staticmethod
|
|
139
|
+
def from_dict(d: dict[str, Any] | None) -> Hoofdvestiging | None: # noqa: D102
|
|
140
|
+
if not d:
|
|
141
|
+
return None
|
|
142
|
+
return Hoofdvestiging(
|
|
143
|
+
vestigingsnummer=d.get("vestigingsnummer", "") or "",
|
|
144
|
+
kvk_nummer=d.get("kvkNummer", "") or "",
|
|
145
|
+
formele_registratiedatum=d.get("formeleRegistratiedatum", "") or "",
|
|
146
|
+
materiele_registratie=MaterieleRegistratie.from_dict(d.get("materieleRegistratie")),
|
|
147
|
+
eerste_handelsnaam=d.get("eersteHandelsnaam", "") or "",
|
|
148
|
+
ind_hoofdvestiging=d.get("indHoofdvestiging", "") or "",
|
|
149
|
+
ind_commerciele_vestiging=d.get("indCommercieleVestiging", "") or "",
|
|
150
|
+
totaal_werkzame_personen=d.get("totaalWerkzamePersonen"),
|
|
151
|
+
adressen=[a for a in (Adres.from_dict(x) for x in d.get("adressen", [])) if a],
|
|
152
|
+
websites=list(d.get("websites", []) or []),
|
|
153
|
+
links=[link for link in (Link.from_dict(x) for x in d.get("links", [])) if link],
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@dataclass
|
|
158
|
+
class Eigenaar:
|
|
159
|
+
rechtsvorm: str = ""
|
|
160
|
+
uitgebreide_rechtsvorm: str = ""
|
|
161
|
+
links: list[Link] = field(default_factory=list)
|
|
162
|
+
|
|
163
|
+
@staticmethod
|
|
164
|
+
def from_dict(d: dict[str, Any] | None) -> Eigenaar | None: # noqa: D102
|
|
165
|
+
if not d:
|
|
166
|
+
return None
|
|
167
|
+
return Eigenaar(
|
|
168
|
+
rechtsvorm=d.get("rechtsvorm", "") or "",
|
|
169
|
+
uitgebreide_rechtsvorm=d.get("uitgebreideRechtsvorm", "") or "",
|
|
170
|
+
links=[link for link in (Link.from_dict(x) for x in d.get("links", [])) if link],
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@dataclass
|
|
175
|
+
class Embedded:
|
|
176
|
+
hoofdvestiging: Hoofdvestiging | None = None
|
|
177
|
+
eigenaar: Eigenaar | None = None
|
|
178
|
+
|
|
179
|
+
@staticmethod
|
|
180
|
+
def from_dict(d: dict[str, Any] | None) -> Embedded | None: # noqa: D102
|
|
181
|
+
if not d:
|
|
182
|
+
return None
|
|
183
|
+
return Embedded(
|
|
184
|
+
hoofdvestiging=Hoofdvestiging.from_dict(d.get("hoofdvestiging")),
|
|
185
|
+
eigenaar=Eigenaar.from_dict(d.get("eigenaar")),
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@dataclass
|
|
190
|
+
class BasisProfielAPI:
|
|
191
|
+
kvk_nummer: str = ""
|
|
192
|
+
ind_non_mailing: str = ""
|
|
193
|
+
naam: str = ""
|
|
194
|
+
formele_registratiedatum: str = ""
|
|
195
|
+
materiele_registratie: MaterieleRegistratie | None = None
|
|
196
|
+
totaal_werkzame_personen: int | None = None
|
|
197
|
+
handelsnamen: list[HandelNaam] = field(default_factory=list)
|
|
198
|
+
sbi_activiteiten: list[SBIActiviteit] = field(default_factory=list)
|
|
199
|
+
links: list[Link] = field(default_factory=list)
|
|
200
|
+
embedded: Embedded | None = None
|
|
201
|
+
|
|
202
|
+
@staticmethod
|
|
203
|
+
def from_dict(d: dict[str, Any]) -> BasisProfielAPI: # noqa: D102
|
|
204
|
+
return BasisProfielAPI(
|
|
205
|
+
kvk_nummer=d.get("kvkNummer", "") or "",
|
|
206
|
+
ind_non_mailing=d.get("indNonMailing", "") or "",
|
|
207
|
+
naam=d.get("naam", "") or "",
|
|
208
|
+
formele_registratiedatum=d.get("formeleRegistratiedatum", "") or "",
|
|
209
|
+
materiele_registratie=MaterieleRegistratie.from_dict(d.get("materieleRegistratie")),
|
|
210
|
+
totaal_werkzame_personen=d.get("totaalWerkzamePersonen"),
|
|
211
|
+
handelsnamen=[h for h in (HandelNaam.from_dict(x) for x in d.get("handelsnamen", [])) if h],
|
|
212
|
+
sbi_activiteiten=[a for a in (SBIActiviteit.from_dict(x) for x in d.get("sbiActiviteiten", [])) if a],
|
|
213
|
+
links=[link for link in (Link.from_dict(x) for x in d.get("links", [])) if link],
|
|
214
|
+
embedded=Embedded.from_dict(d.get("_embedded")),
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
@staticmethod
|
|
218
|
+
def load_from_file(path: str, encoding: str = "utf-8") -> BasisProfielAPI: # noqa: D102
|
|
219
|
+
with open(path, encoding=encoding) as f:
|
|
220
|
+
data = json.load(f)
|
|
221
|
+
return BasisProfielAPI.from_dict(data)
|
|
222
|
+
|
|
223
|
+
@staticmethod
|
|
224
|
+
def load_from_json(json_str: str) -> BasisProfielAPI: # noqa: D102
|
|
225
|
+
data = json.loads(json_str)
|
|
226
|
+
return BasisProfielAPI.from_dict(data)
|
|
227
|
+
|
|
228
|
+
@staticmethod
|
|
229
|
+
def load_from_dict(data: dict[str, Any]) -> BasisProfielAPI: # noqa: D102
|
|
230
|
+
return BasisProfielAPI.from_dict(data)
|
|
231
|
+
|
|
232
|
+
def to_dict(self) -> dict[str, Any]: # noqa: D102
|
|
233
|
+
return asdict(self)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# ruff: noqa: D102
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@dataclass
|
|
6
|
+
class Contract:
|
|
7
|
+
id: str
|
|
8
|
+
|
|
9
|
+
@staticmethod
|
|
10
|
+
def from_dict(data: dict) -> "Contract": # noqa: D102
|
|
11
|
+
return Contract(id=data["id"])
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class Abonnement:
|
|
16
|
+
id: str
|
|
17
|
+
contract: Contract
|
|
18
|
+
start_datum: str
|
|
19
|
+
actief: bool
|
|
20
|
+
|
|
21
|
+
@staticmethod
|
|
22
|
+
def from_dict(data: dict) -> "Abonnement": # noqa: D102
|
|
23
|
+
return Abonnement(
|
|
24
|
+
id=data["id"],
|
|
25
|
+
contract=Contract.from_dict(data["contract"]),
|
|
26
|
+
start_datum=data["startDatum"],
|
|
27
|
+
actief=data["actief"],
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class MutatieAbonnementenAPI:
|
|
33
|
+
klant_id: str
|
|
34
|
+
abonnementen: list[Abonnement]
|
|
35
|
+
|
|
36
|
+
@staticmethod
|
|
37
|
+
def from_dict(data: dict) -> "MutatieAbonnementenAPI": # noqa: D102
|
|
38
|
+
return MutatieAbonnementenAPI(
|
|
39
|
+
klant_id=data["klantId"], abonnementen=[Abonnement.from_dict(a) for a in data["abonnementen"]]
|
|
40
|
+
)
|