nordabiz/krs_api_service.py
2026-01-01 14:01:49 +01:00

381 lines
12 KiB
Python

#!/usr/bin/env python3
"""
KRS Open API Integration Service for NordaBiznes.
Fetches official company data from Krajowy Rejestr Sądowy (Ministry of Justice).
API Documentation: https://prs.ms.gov.pl/krs/openApi
"""
import requests
from datetime import datetime
from typing import Optional, Dict, Any, List
from dataclasses import dataclass
# API Configuration
KRS_API_BASE_URL = "https://api-krs.ms.gov.pl/api/krs"
KRS_API_TIMEOUT = 15 # seconds
@dataclass
class KRSCompanyData:
"""Parsed company data from KRS API."""
krs: str
nazwa: str
nazwa_skrocona: Optional[str]
nip: Optional[str]
regon: Optional[str]
forma_prawna: str
# Address
ulica: Optional[str]
nr_domu: Optional[str]
nr_lokalu: Optional[str]
kod_pocztowy: Optional[str]
miejscowosc: Optional[str]
wojewodztwo: Optional[str]
powiat: Optional[str]
gmina: Optional[str]
kraj: str
# Capital
kapital_zakladowy: Optional[float]
kapital_waluta: str
# Dates
data_rejestracji: Optional[str]
data_ostatniego_wpisu: Optional[str]
numer_ostatniego_wpisu: Optional[int]
# Management (anonymized in Open API)
zarzad: List[Dict[str, str]]
sposob_reprezentacji: Optional[str]
# Shareholders (anonymized in Open API)
wspolnicy: List[Dict[str, Any]]
# Other
przedmiot_dzialalnosci: List[str]
czy_opp: bool
# Metadata
data_odpisu: str
stan_z_dnia: str
def to_dict(self) -> Dict[str, Any]:
"""Convert to dictionary for JSON serialization."""
return {
'krs': self.krs,
'nazwa': self.nazwa,
'nazwa_skrocona': self.nazwa_skrocona,
'nip': self.nip,
'regon': self.regon,
'forma_prawna': self.forma_prawna,
'adres': {
'ulica': self.ulica,
'nr_domu': self.nr_domu,
'nr_lokalu': self.nr_lokalu,
'kod_pocztowy': self.kod_pocztowy,
'miejscowosc': self.miejscowosc,
'wojewodztwo': self.wojewodztwo,
'powiat': self.powiat,
'gmina': self.gmina,
'kraj': self.kraj,
},
'kapital': {
'zakladowy': self.kapital_zakladowy,
'waluta': self.kapital_waluta,
},
'daty': {
'rejestracji': self.data_rejestracji,
'ostatniego_wpisu': self.data_ostatniego_wpisu,
'numer_ostatniego_wpisu': self.numer_ostatniego_wpisu,
},
'zarzad': self.zarzad,
'sposob_reprezentacji': self.sposob_reprezentacji,
'wspolnicy': self.wspolnicy,
'przedmiot_dzialalnosci': self.przedmiot_dzialalnosci,
'czy_opp': self.czy_opp,
'metadata': {
'data_odpisu': self.data_odpisu,
'stan_z_dnia': self.stan_z_dnia,
'zrodlo': 'KRS Open API (prs.ms.gov.pl)',
}
}
def fetch_krs_data(krs_number: str, rejestr: str = 'P') -> Optional[Dict[str, Any]]:
"""
Fetch raw data from KRS Open API.
Args:
krs_number: KRS number (with or without leading zeros)
rejestr: 'P' for przedsiębiorców (companies), 'S' for stowarzyszeń (associations)
Returns:
Raw JSON response or None if not found/error
"""
# Normalize KRS number to 10 digits with leading zeros
krs_normalized = krs_number.zfill(10)
url = f"{KRS_API_BASE_URL}/OdpisAktualny/{krs_normalized}"
params = {
'rejestr': rejestr,
'format': 'json'
}
try:
response = requests.get(url, params=params, timeout=KRS_API_TIMEOUT)
if response.status_code == 200:
return response.json()
elif response.status_code == 404:
return None # Company not found
else:
print(f"KRS API error: {response.status_code}")
return None
except requests.RequestException as e:
print(f"KRS API request failed: {e}")
return None
def parse_krs_response(data: Dict[str, Any]) -> Optional[KRSCompanyData]:
"""
Parse raw KRS API response into structured KRSCompanyData.
Args:
data: Raw JSON response from KRS API
Returns:
Parsed KRSCompanyData or None if parsing fails
"""
try:
odpis = data.get('odpis', {})
naglowek = odpis.get('naglowekA', {})
dane = odpis.get('dane', {})
dzial1 = dane.get('dzial1', {})
dzial2 = dane.get('dzial2', {})
dzial3 = dane.get('dzial3', {})
# Basic data
dane_podmiotu = dzial1.get('danePodmiotu', {})
identyfikatory = dane_podmiotu.get('identyfikatory', {})
# Address
siedziba_adres = dzial1.get('siedzibaIAdres', {})
siedziba = siedziba_adres.get('siedziba', {})
adres = siedziba_adres.get('adres', {})
# Capital
kapital = dzial1.get('kapital', {})
kapital_zakladowy = kapital.get('wysokoscKapitaluZakladowego', {})
# Management
reprezentacja = dzial2.get('reprezentacja', {})
sklad_zarzadu = reprezentacja.get('sklad', [])
zarzad = []
for osoba in sklad_zarzadu:
zarzad.append({
'imie': osoba.get('imiona', {}).get('imie', ''),
'imie_drugie': osoba.get('imiona', {}).get('imieDrugie', ''),
'nazwisko': osoba.get('nazwisko', {}).get('nazwiskoICzlon', ''),
'funkcja': osoba.get('funkcjaWOrganie', ''),
# Note: Data is anonymized in Open API
})
# Shareholders
wspolnicy_raw = dzial1.get('wspolnicySpzoo', [])
wspolnicy = []
for wspolnik in wspolnicy_raw:
wspolnicy.append({
'imie': wspolnik.get('imiona', {}).get('imie', ''),
'nazwisko': wspolnik.get('nazwisko', {}).get('nazwiskoICzlon', ''),
'udzialy': wspolnik.get('posiadaneUdzialy', ''),
'calosc_udzialow': wspolnik.get('czyPosiadaCaloscUdzialow', False),
})
# Business activities (PKD)
przedmiot = []
przedmiot_dzial = dzial3.get('przedmiotDzialalnosci', {})
for pkd in przedmiot_dzial.get('przedmiotPrzewazajacejDzialalnosci', []):
przedmiot.append(f"{pkd.get('kodDzial', '')} - {pkd.get('opis', '')} (główna)")
for pkd in przedmiot_dzial.get('przedmiotPozostalejDzialalnosci', []):
przedmiot.append(f"{pkd.get('kodDzial', '')} - {pkd.get('opis', '')}")
# Parse capital value
kapital_value = None
if kapital_zakladowy.get('wartosc'):
try:
kapital_value = float(kapital_zakladowy['wartosc'].replace(',', '.').replace(' ', ''))
except ValueError:
pass
return KRSCompanyData(
krs=naglowek.get('numerKRS', ''),
nazwa=dane_podmiotu.get('nazwa', ''),
nazwa_skrocona=dane_podmiotu.get('nazwaSkrocona'),
nip=identyfikatory.get('nip'),
regon=identyfikatory.get('regon'),
forma_prawna=dane_podmiotu.get('formaPrawna', ''),
ulica=adres.get('ulica'),
nr_domu=adres.get('nrDomu'),
nr_lokalu=adres.get('nrLokalu'),
kod_pocztowy=adres.get('kodPocztowy'),
miejscowosc=adres.get('miejscowosc'),
wojewodztwo=siedziba.get('wojewodztwo'),
powiat=siedziba.get('powiat'),
gmina=siedziba.get('gmina'),
kraj=adres.get('kraj', 'POLSKA'),
kapital_zakladowy=kapital_value,
kapital_waluta=kapital_zakladowy.get('waluta', 'PLN'),
data_rejestracji=naglowek.get('dataRejestracjiWKRS'),
data_ostatniego_wpisu=naglowek.get('dataOstatniegoWpisu'),
numer_ostatniego_wpisu=naglowek.get('numerOstatniegoWpisu'),
zarzad=zarzad,
sposob_reprezentacji=reprezentacja.get('sposobReprezentacji'),
wspolnicy=wspolnicy,
przedmiot_dzialalnosci=przedmiot,
czy_opp=dane_podmiotu.get('czyPosiadaStatusOPP', False),
data_odpisu=naglowek.get('dataCzasOdpisu', ''),
stan_z_dnia=naglowek.get('stanZDnia', ''),
)
except Exception as e:
print(f"Error parsing KRS response: {e}")
return None
def get_company_from_krs(krs_number: str) -> Optional[KRSCompanyData]:
"""
Fetch and parse company data from KRS Open API.
Args:
krs_number: KRS number
Returns:
Parsed KRSCompanyData or None if not found/error
"""
raw_data = fetch_krs_data(krs_number)
if raw_data:
return parse_krs_response(raw_data)
return None
def verify_company_data(company_krs: str, company_nip: str = None, company_regon: str = None) -> Dict[str, Any]:
"""
Verify company data against KRS Open API.
Args:
company_krs: KRS number to verify
company_nip: Expected NIP (optional)
company_regon: Expected REGON (optional)
Returns:
Dictionary with verification results
"""
result = {
'verified': False,
'krs_found': False,
'nip_match': None,
'regon_match': None,
'krs_data': None,
'errors': [],
'timestamp': datetime.now().isoformat(),
}
krs_data = get_company_from_krs(company_krs)
if krs_data is None:
result['errors'].append(f"Nie znaleziono podmiotu o KRS {company_krs}")
return result
result['krs_found'] = True
result['krs_data'] = krs_data.to_dict()
# Verify NIP if provided
if company_nip:
krs_nip = krs_data.nip.replace('-', '').replace(' ', '') if krs_data.nip else ''
expected_nip = company_nip.replace('-', '').replace(' ', '')
result['nip_match'] = krs_nip == expected_nip
if not result['nip_match']:
result['errors'].append(f"NIP niezgodny: oczekiwano {expected_nip}, w KRS: {krs_nip}")
# Verify REGON if provided
if company_regon:
krs_regon = krs_data.regon[:9] if krs_data.regon else '' # Use first 9 digits
expected_regon = company_regon[:9].replace('-', '').replace(' ', '')
result['regon_match'] = krs_regon == expected_regon
if not result['regon_match']:
result['errors'].append(f"REGON niezgodny: oczekiwano {expected_regon}, w KRS: {krs_regon}")
# Overall verification
result['verified'] = result['krs_found'] and len(result['errors']) == 0
return result
def format_address(krs_data: KRSCompanyData) -> str:
"""Format address from KRS data."""
parts = []
if krs_data.ulica:
addr = krs_data.ulica
if krs_data.nr_domu:
addr += f" {krs_data.nr_domu}"
if krs_data.nr_lokalu:
addr += f"/{krs_data.nr_lokalu}"
parts.append(addr)
if krs_data.kod_pocztowy and krs_data.miejscowosc:
parts.append(f"{krs_data.kod_pocztowy} {krs_data.miejscowosc}")
elif krs_data.miejscowosc:
parts.append(krs_data.miejscowosc)
return ', '.join(parts)
# CLI for testing
if __name__ == '__main__':
import sys
import json
if len(sys.argv) < 2:
print("Usage: python krs_api_service.py <KRS_NUMBER>")
print("Example: python krs_api_service.py 0000817317")
sys.exit(1)
krs = sys.argv[1]
print(f"Pobieranie danych z KRS Open API dla: {krs}")
print("=" * 60)
data = get_company_from_krs(krs)
if data:
print(f"Nazwa: {data.nazwa}")
print(f"NIP: {data.nip}")
print(f"REGON: {data.regon}")
print(f"Forma prawna: {data.forma_prawna}")
print(f"Adres: {format_address(data)}")
print(f"Kapitał zakładowy: {data.kapital_zakladowy} {data.kapital_waluta}")
print(f"Data rejestracji: {data.data_rejestracji}")
print(f"Ostatni wpis: {data.data_ostatniego_wpisu} (nr {data.numer_ostatniego_wpisu})")
print()
print("Zarząd (dane zanonimizowane w Open API):")
for osoba in data.zarzad:
print(f" - {osoba['imie']} {osoba['nazwisko']} - {osoba['funkcja']}")
print()
print("Wspólnicy (dane zanonimizowane w Open API):")
for w in data.wspolnicy:
print(f" - {w['imie']} {w['nazwisko']}: {w['udzialy']}")
print()
print(f"Stan z dnia: {data.stan_z_dnia}")
print(f"Data odpisu: {data.data_odpisu}")
else:
print(f"Nie znaleziono podmiotu o KRS {krs}")