#!/usr/bin/env python3 """Pool builder for BENCHMARK_COUNTERPARTY_V1 (goal 2026-09-30 ChatGPT Space). Real Dutch companies from the official register; read-only calls. Zoeken is free; each basisprofiel costs 0.02 EUR on AstraNL's existing KVK subscription and is capped here at MAX_PAID calls. VIES is free. Output pool.json with the observed source state and the exact spend.""" import json, random, sys, time, urllib.parse, urllib.request sys.path.insert(0, '/opt/astranl/core'); sys.path.insert(0, '/opt/astranl/organism') import kvk_lookup import counterparty_verification as cv MAX_PAID = 30 R = random.Random(20260930) WORDS = ['installatie', 'transport', 'techniek', 'logistiek', 'bouw', 'schilders', 'metaal', 'elektro', 'loodgieter', 'dakdekker', 'machines', 'verhuur', 'groothandel', 'onderhoud', 'staalbouw', 'koeltechniek', 'grondwerk', 'isolatie'] KEY = kvk_lookup._key() def zoeken(params): q = urllib.parse.urlencode(dict(params, resultatenPerPagina=100)) req = urllib.request.Request('https://api.kvk.nl/api/v2/zoeken?' + q, headers={'apikey': KEY, 'Accept': 'application/json'}) try: return json.loads(urllib.request.urlopen(req, timeout=20).read()).get('resultaten') or [] except Exception as e: return [] if getattr(e, 'code', 0) == 404 else None def all_rows(kvk): return zoeken({'kvkNummer': kvk, 'inclusiefInactieveRegistraties': 'true'}) def main(): paid = 0 active, inactive = {}, {} for w in WORDS: rows = zoeken({'naam': w, 'type': 'rechtspersoon', 'inclusiefInactieveRegistraties': 'true'}) or [] for x in rows: k, n = x.get('kvkNummer'), x.get('naam') or '' if not k or 'b.v' not in n.lower(): continue (inactive if str(x.get('actief', 'Ja')).lower() == 'nee' else active)[k] = n time.sleep(0.3) act = sorted(active.items()); R.shuffle(act) ina = sorted(inactive.items()); R.shuffle(ina) companies = [] for k, n in act: if paid >= MAX_PAID or len(companies) >= 24: break rows = all_rows(k) if not rows or any(str(r.get('actief', 'Ja')).lower() == 'nee' for r in rows if r.get('type') == 'hoofdvestiging'): continue bp = kvk_lookup.lookup(k); paid += 1 rsin = bp.get('rsin') if bp.get('ok') else None if not rsin: continue vat = 'NL%09dB01' % int(rsin) v = cv.check_vat(vat) if v.get('status') != 'ok' or not v.get('valid'): continue companies.append({'kvk': k, 'register_names': sorted({r.get('naam') for r in rows if r.get('naam')}), 'name': n, 'vat': vat, 'vies_name': v.get('name'), 'observed_at': v.get('checked_at')}) time.sleep(0.5) dead = [] for k, n in ina: if len(dead) >= 10: break rows = all_rows(k) if rows and all(str(r.get('actief', 'Ja')).lower() == 'nee' for r in rows): dead.append({'kvk': k, 'name': n, 'register_names': sorted({r.get('naam') for r in rows if r.get('naam')})}) time.sleep(0.3) missing = [] while len(missing) < 10: k = '%08d' % R.randint(10000000, 99999999) rows = all_rows(k) if rows == []: missing.append(k) time.sleep(0.3) out = {'built_at': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()), 'paid_basisprofiel_calls': paid, 'spend_eur': round(paid * 0.02, 2), 'companies_active_with_valid_vat': companies, 'companies_all_inactive': dead, 'kvk_numbers_not_in_register': missing} json.dump(out, open('/opt/astranl/var/benchmark_counterparty_v1/pool.json', 'w'), ensure_ascii=False, indent=1) print(json.dumps({'active_valid_vat': len(companies), 'inactive': len(dead), 'missing': len(missing), 'paid': paid})) if __name__ == '__main__': main()