History of estonia_company_ownership
The changes to the source code of this entry in the repository, newest first, each with the differences it made to the entry's files. They are also listed on GitLab.
[entries] Add the date when each entry was added, and remove icon_hash beff7c9
The dates are those of the commits that first added the entries' files (2020-08-11 for the entries of the initial commit); renamed entries keep the date of their first name. Changed: none
diff --git a/entries/estonia_company_ownership/__init__.py b/entries/estonia_company_ownership/__init__.py
index afc1ba1..f9f9240 100644
--- a/entries/estonia_company_ownership/__init__.py
+++ b/entries/estonia_company_ownership/__init__.py
@@ -62,7 +62,7 @@ tags = ['Economic', 'Commerce', 'Social', 'Affiliation', 'Multigraph', 'Weighted
url = ['https://avaandmed.ariregister.rik.ee/en/downloading-open-data']
citation = [('Centre of Registers and Information Systems (RIK), "e-Business Register open data." (2026)',
'https://avaandmed.ariregister.rik.ee/en/downloading-open-data')]
-icon_hash = None
+date_added = '2026-10-31'
upstream_license = ("CC BY 4.0", "https://creativecommons.org/licenses/by/4.0/legalcode")
# The files are replaced daily by the current state of the register (no
[entries] Prefix the properties in the descriptions with (Node), (Edge) or (Graph) bc3dbbd
The lists of properties at the end of the descriptions marked edge properties with "Edge" but left node properties unmarked, or grouped them in "Nodes: `a`, ...; `b`, ..." items, so the kind of a property was often unclear. Every item now starts with (Node), (Edge) or (Graph), followed by the property names, a colon and the explanation, one item per property or group of properties; qualifications (kinds of nodes, networks) follow the names in parentheses. Properties that were only named got a short explanation, and a few items that named the wrong kind of property were corrected.
diff --git a/entries/estonia_company_ownership/__init__.py b/entries/estonia_company_ownership/__init__.py
index f7416c6..afc1ba1 100644
--- a/entries/estonia_company_ownership/__init__.py
+++ b/entries/estonia_company_ownership/__init__.py
@@ -34,24 +34,30 @@ The legal entities are identified by their registry code, and described by the g
The networks have the following properties:
-- `kind`: `legal_entity` (an Estonian legal entity, in the register), `foreign_entity`, `natural_person`.
-- `name`: the name of the legal entity, or of the person (first name and surname).
-- `regcode` (Estonian legal entities): the registry code (empty for the others).
-- `person_hash` (natural persons): the pseudonymous hash of the Estonian personal identification code (empty if none).
-- `birth_date` (natural persons without Estonian code): the date of birth (`YYYY-MM-DD`, empty if not given).
-- `foreign_code`, `foreign_code_country` (foreign legal entities): the foreign registry code and its country.
-- `country`: the country of the address of the person (as first found; empty if not given).
-- `legal_form`, `legal_form_subtype` (Estonian legal entities): the legal form (e.g. `Osaühing`, private limited company; `Korteriühistu`, apartment association) and its subtype.
-- `status` (Estonian legal entities): the status (`Registrisse kantud`, entered in the register; `Likvideerimisel`, in liquidation; `Pankrotis`, in bankruptcy).
-- `first_entry_date` (Estonian legal entities): the date of the first entry in the register (`YYYY-MM-DD`).
-- `vat_number`, `address`, `postal_code`, `ehak_code` (Estonian legal entities): the VAT number, the normalized address, its postal code and the code of its administrative unit (EHAK).
-- Edge `record_id` (all networks): the id of the entry in the register.
-- Edge `start_date` (all networks): the date since which the relation holds (`YYYY-MM-DD`).
-- Edges of `shareholders`: `share_percent` (the percentage of the share capital, NaN if not given), `share_amount` and `share_currency` (the nominal value of the share), `ownership` (`Ainuomand` sole ownership, `Ühisomand` joint ownership, `Kaasomand` co-ownership), `ownership_group` (the id of the group of the joint owners of a share, -1 if none).
-- Edges of `officers` and `other_roles`: `role` and `role_code` (e.g. `Juhatuse liige`/`JUHL`, member of the management board; `Asutaja`/`A`, founder; `Nõukogu liige`, member of the supervisory board), `mandate_end` (the date of expiry of the mandate, if given).
-- Edges of `officers`: `card_type` (the kind of registry card: `R` commercial register, `MTÜ` non-profit associations, `SA` foundations, `KÜ` apartment associations, `G` state and municipal agencies, ...), `contribution` and `contribution_currency` (the contribution of general partners, NaN if not given).
-- Edges of `other_roles`: `share_amount`, `share_currency` and `ownership`, as for `shareholders` (for founders and shareholders; NaN or empty otherwise).
-- Edges of `beneficial_owners`: `control` and `control_code` (the way the control is exercised, e.g. `otsene osalus`/`O` direct shareholding, `kaudne osalus` indirect shareholding, membership of the management board); `discrepancy` (whether a notice of discrepancy about the data was filed)."""
+- (Node) `kind`: `legal_entity` (an Estonian legal entity, in the register), `foreign_entity`, `natural_person`.
+- (Node) `name`: the name of the legal entity, or of the person (first name and surname).
+- (Node) `regcode` (Estonian legal entities): the registry code (empty for the others).
+- (Node) `person_hash` (natural persons): the pseudonymous hash of the Estonian personal identification code (empty if none).
+- (Node) `birth_date` (natural persons without Estonian code): the date of birth (`YYYY-MM-DD`, empty if not given).
+- (Node) `foreign_code`, `foreign_code_country` (foreign legal entities): the foreign registry code and its country.
+- (Node) `country`: the country of the address of the person (as first found; empty if not given).
+- (Node) `legal_form`, `legal_form_subtype` (Estonian legal entities): the legal form (e.g. `Osaühing`, private limited company; `Korteriühistu`, apartment association) and its subtype.
+- (Node) `status` (Estonian legal entities): the status (`Registrisse kantud`, entered in the register; `Likvideerimisel`, in liquidation; `Pankrotis`, in bankruptcy).
+- (Node) `first_entry_date` (Estonian legal entities): the date of the first entry in the register (`YYYY-MM-DD`).
+- (Node) `vat_number`, `address`, `postal_code`, `ehak_code` (Estonian legal entities): the VAT number, the normalized address, its postal code and the code of its administrative unit (EHAK).
+- (Edge) `record_id` (all networks): the id of the entry in the register.
+- (Edge) `start_date` (all networks): the date since which the relation holds (`YYYY-MM-DD`).
+- (Edge) `share_percent` (`shareholders`): the percentage of the share capital, NaN if not given.
+- (Edge) `share_amount` and `share_currency` (`shareholders`): the nominal value of the share.
+- (Edge) `ownership` (`shareholders`): `Ainuomand` sole ownership, `Ühisomand` joint ownership, `Kaasomand` co-ownership.
+- (Edge) `ownership_group` (`shareholders`): the id of the group of the joint owners of a share, -1 if none.
+- (Edge) `role` and `role_code` (`officers` and `other_roles`): the role (e.g. `Juhatuse liige`/`JUHL`, member of the management board; `Asutaja`/`A`, founder; `Nõukogu liige`, member of the supervisory board).
+- (Edge) `mandate_end` (`officers` and `other_roles`): the date of expiry of the mandate, if given.
+- (Edge) `card_type` (`officers`): the kind of registry card: `R` commercial register, `MTÜ` non-profit associations, `SA` foundations, `KÜ` apartment associations, `G` state and municipal agencies, ...
+- (Edge) `contribution` and `contribution_currency` (`officers`): the contribution of general partners, NaN if not given.
+- (Edge) `share_amount`, `share_currency` and `ownership` (`other_roles`): as for `shareholders` (for founders and shareholders; NaN or empty otherwise).
+- (Edge) `control` and `control_code` (`beneficial_owners`): the way the control is exercised, e.g. `otsene osalus`/`O` direct shareholding, `kaudne osalus` indirect shareholding, membership of the management board.
+- (Edge) `discrepancy` (`beneficial_owners`): whether a notice of discrepancy about the data was filed."""
tags = ['Economic', 'Commerce', 'Social', 'Affiliation', 'Multigraph', 'Weighted', 'Metadata']
url = ['https://avaandmed.ariregister.rik.ee/en/downloading-open-data']
citation = [('Centre of Registers and Information Systems (RIK), "e-Business Register open data." (2026)',
[entries] New entry: estonia_company_ownership 9d87b15
diff --git a/entries/estonia_company_ownership/__init__.py b/entries/estonia_company_ownership/__init__.py
new file mode 100644
index 0000000..f7416c6
--- /dev/null
+++ b/entries/estonia_company_ownership/__init__.py
@@ -0,0 +1,245 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2026 Tiago de Paula Peixoto <tiago@skewed.de>
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Affero General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option) any
+# later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Affero General Public License along
+# with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+from .. import *
+import json
+
+title = "Estonian company shareholders, officers and beneficial owners (2026)"
+description = r"""Networks of the ownership and management of the legal entities of Estonia (companies, sole proprietors, non-profit associations, foundations, apartment associations, state and municipal agencies, ...), from the open data of the Estonian e-Business Register (e-äriregister), kept by the Centre of Registers and Information Systems (RIK). The data are those in force on the download date (31 October 2026): the register publishes only the current state, and its files are replaced daily (ended relations are not included).
+
+There are four networks, in which a directed edge goes from a person (natural or legal) to the legal entity:
+
+- `shareholders`: the shareholders of the private limited companies (osaühing), with their shares (`osanikud`).
+- `officers`: the persons entered on the registry card of the legal entity (`kaardile kantud isikud`): the members of the management board, sole proprietors, general and limited partners, liquidators, procurators, contact persons, keepers of the share register, etc.
+- `other_roles`: the persons related to the legal entity who are not entered on its registry card (`kandevälised isikud`): founders, members of the supervisory board, auditors, shareholders of public limited companies, members of bankruptcy committees, etc.
+- `beneficial_owners`: the beneficial owners (natural persons who ultimately own or control the legal entity), with the way in which they exercise control (`kasusaajad`).
+
+The legal entities are identified by their registry code, and described by the general data of the register (`lihtandmed`: legal form, status, address, VAT number). The persons that are Estonian legal entities are the same nodes as the legal entities themselves, so that the networks include chains of ownership and control between companies. The natural persons with an Estonian personal identification code are identified by a pseudonymous hash of it given by the register (the same in all files), so that they are identified across companies and networks. The natural persons without an Estonian code (foreigners) are only given by name and (mostly) date of birth: they are merged by name and date of birth together. The foreign legal entities are identified by their name, their foreign registry code and its country. Only the legal entities that take part in some edge are included. The memberships of building associations (given on the registry cards, without members) and the texts of the rules of representation are not included.
+
+The networks have the following properties:
+
+- `kind`: `legal_entity` (an Estonian legal entity, in the register), `foreign_entity`, `natural_person`.
+- `name`: the name of the legal entity, or of the person (first name and surname).
+- `regcode` (Estonian legal entities): the registry code (empty for the others).
+- `person_hash` (natural persons): the pseudonymous hash of the Estonian personal identification code (empty if none).
+- `birth_date` (natural persons without Estonian code): the date of birth (`YYYY-MM-DD`, empty if not given).
+- `foreign_code`, `foreign_code_country` (foreign legal entities): the foreign registry code and its country.
+- `country`: the country of the address of the person (as first found; empty if not given).
+- `legal_form`, `legal_form_subtype` (Estonian legal entities): the legal form (e.g. `Osaühing`, private limited company; `Korteriühistu`, apartment association) and its subtype.
+- `status` (Estonian legal entities): the status (`Registrisse kantud`, entered in the register; `Likvideerimisel`, in liquidation; `Pankrotis`, in bankruptcy).
+- `first_entry_date` (Estonian legal entities): the date of the first entry in the register (`YYYY-MM-DD`).
+- `vat_number`, `address`, `postal_code`, `ehak_code` (Estonian legal entities): the VAT number, the normalized address, its postal code and the code of its administrative unit (EHAK).
+- Edge `record_id` (all networks): the id of the entry in the register.
+- Edge `start_date` (all networks): the date since which the relation holds (`YYYY-MM-DD`).
+- Edges of `shareholders`: `share_percent` (the percentage of the share capital, NaN if not given), `share_amount` and `share_currency` (the nominal value of the share), `ownership` (`Ainuomand` sole ownership, `Ühisomand` joint ownership, `Kaasomand` co-ownership), `ownership_group` (the id of the group of the joint owners of a share, -1 if none).
+- Edges of `officers` and `other_roles`: `role` and `role_code` (e.g. `Juhatuse liige`/`JUHL`, member of the management board; `Asutaja`/`A`, founder; `Nõukogu liige`, member of the supervisory board), `mandate_end` (the date of expiry of the mandate, if given).
+- Edges of `officers`: `card_type` (the kind of registry card: `R` commercial register, `MTÜ` non-profit associations, `SA` foundations, `KÜ` apartment associations, `G` state and municipal agencies, ...), `contribution` and `contribution_currency` (the contribution of general partners, NaN if not given).
+- Edges of `other_roles`: `share_amount`, `share_currency` and `ownership`, as for `shareholders` (for founders and shareholders; NaN or empty otherwise).
+- Edges of `beneficial_owners`: `control` and `control_code` (the way the control is exercised, e.g. `otsene osalus`/`O` direct shareholding, `kaudne osalus` indirect shareholding, membership of the management board); `discrepancy` (whether a notice of discrepancy about the data was filed)."""
+tags = ['Economic', 'Commerce', 'Social', 'Affiliation', 'Multigraph', 'Weighted', 'Metadata']
+url = ['https://avaandmed.ariregister.rik.ee/en/downloading-open-data']
+citation = [('Centre of Registers and Information Systems (RIK), "e-Business Register open data." (2026)',
+ 'https://avaandmed.ariregister.rik.ee/en/downloading-open-data')]
+icon_hash = None
+upstream_license = ("CC BY 4.0", "https://creativecommons.org/licenses/by/4.0/legalcode")
+
+# The files are replaced daily by the current state of the register (no
+# dated snapshots are kept): the cached copies are of 2026-10-31
+upstream_prefix = 'https://avaandmed.ariregister.rik.ee/sites/default/files/avaandmed'
+_simple = "ettevotja_rekvisiidid__lihtandmed.csv.zip:ettevotja_rekvisiidid__lihtandmed.csv"
+_kinds = {"shareholders": ("osanikud", "osanikud"),
+ "officers": ("kaardile_kantud_isikud", "kaardile_kantud_isikud"),
+ "other_roles": ("kandevalised_isikud", "kaardivalised_isikud"),
+ "beneficial_owners": ("kasusaajad", "kasusaajad")}
+files = [((_simple, f"ettevotja_rekvisiidid__{f}.json.zip:ettevotja_rekvisiidid__{f}.json"),
+ alt, None) for alt, (f, key) in _kinds.items()]
+
+def fetch_upstream(force=False):
+ return fetch_upstream_files(__name__.split(".")[-1], upstream_prefix, files,
+ force)
+
+def iter_json_array(f, chunk=1 << 22):
+ "Iterate over the elements of a JSON array in the text file `f`, streamed."
+ dec = json.JSONDecoder()
+ buf = f.read(chunk).lstrip()
+ if not buf.startswith("["):
+ raise ValueError("not a JSON array")
+ pos = 1
+ eof = False
+ ws = " \t\r\n,"
+ while True:
+ while pos < len(buf) and buf[pos] in ws:
+ pos += 1
+ if pos < len(buf) and buf[pos] == "]":
+ return
+ try:
+ if pos >= len(buf):
+ raise json.JSONDecodeError("end of buffer", buf, pos)
+ obj, end = dec.raw_decode(buf, pos)
+ except json.JSONDecodeError:
+ if eof:
+ raise
+ more = f.read(chunk)
+ eof = not more
+ buf = buf[pos:] + more
+ pos = 0
+ continue
+ yield obj
+ pos = end
+
+def _date(x):
+ "dd.mm.yyyy as yyyy-mm-dd"
+ if not x:
+ return ""
+ d = x.split(".")
+ if len(d) != 3:
+ return x
+ return f"{d[2]}-{d[1]}-{d[0]}"
+
+def _num(x):
+ if x is None or x == "":
+ return numpy.nan
+ return float(x)
+
+def _str(x):
+ return "" if x is None else " ".join(str(x).split())
+
+_vprops = ["kind", "name", "regcode", "person_hash", "birth_date",
+ "foreign_code", "foreign_code_country", "country", "legal_form",
+ "legal_form_subtype", "status", "first_entry_date", "vat_number",
+ "address", "postal_code", "ehak_code"]
+_etypes = {"record_id": "int64_t", "start_date": "string",
+ "share_percent": "double", "share_amount": "double",
+ "share_currency": "string", "ownership": "string",
+ "ownership_group": "int64_t", "role": "string",
+ "role_code": "string", "mandate_end": "string",
+ "card_type": "string", "contribution": "double",
+ "contribution_currency": "string", "control": "string",
+ "control_code": "string", "discrepancy": "bool"}
+
+# For checks only: if set, only this many rows are read of each file
+_check_rows = None
+
+@cache_network()
+@coerce_props()
+@annotate()
+def parse(alts=None):
+ name = __name__.split(".")[-1]
+ for fnames, alt, fmt in files:
+ if alts is not None and alt not in alts:
+ continue
+ companies = {}
+ with open_upstream_file(name, fnames[0], "r", encoding="utf-8-sig") as f:
+ for r in csv.DictReader(f, delimiter=";"):
+ companies[r["ariregistri_kood"].strip()] = \
+ dict(kind="legal_entity", name=_str(r["nimi"]),
+ regcode=r["ariregistri_kood"].strip(),
+ legal_form=_str(r["ettevotja_oiguslik_vorm"]),
+ legal_form_subtype=_str(r["ettevotja_oigusliku_vormi_alaliik"]),
+ status=_str(r["ettevotja_staatus_tekstina"]),
+ first_entry_date=_date(r["ettevotja_esmakande_kpv"].strip()),
+ vat_number=_str(r["kmkr_nr"]),
+ address=_str(r["ads_normaliseeritud_taisaadress"]),
+ postal_code=_str(r["indeks_ettevotja_aadressis"]),
+ ehak_code=_str(r["asukoha_ehak_kood"]))
+ idx = {}
+ attrs = []
+
+ def node(key, a):
+ v = idx.get(key)
+ if v is None:
+ v = idx[key] = len(attrs)
+ attrs.append(a)
+ return v
+
+ def company(code, cname):
+ code = str(code)
+ return node(("E", code),
+ companies.get(code, dict(kind="legal_entity",
+ name=_str(cname),
+ regcode=code)))
+
+ def person(p):
+ first = _str(p.get("eesnimi"))
+ last = _str(p.get("nimi_arinimi", p.get("nimi")))
+ pname = " ".join(x for x in [first, last] if x)
+ country = _str(p.get("aadress_riik"))
+ if p.get("isiku_tyyp", "F") == "J":
+ if p.get("isikukood_registrikood"):
+ return company(p["isikukood_registrikood"], pname)
+ fc = _str(p.get("valis_kood"))
+ fcc = _str(p.get("valis_kood_riik"))
+ return node(("J", pname, fc, fcc),
+ dict(kind="foreign_entity", name=pname,
+ foreign_code=fc, foreign_code_country=fcc,
+ country=country))
+ h = p.get("isikukood_hash")
+ if h:
+ return node(("F", h), dict(kind="natural_person", name=pname,
+ person_hash=h, country=country))
+ bd = _date(p.get("synniaeg"))
+ return node(("F", pname, bd), dict(kind="natural_person",
+ name=pname, birth_date=bd,
+ country=country))
+
+ edges = []
+ ep = defaultdict(list)
+ key = _kinds[alt][1]
+ with open_upstream_file(name, fnames[1], "r") as f:
+ for n, o in enumerate(iter_json_array(f)):
+ if _check_rows is not None and n >= _check_rows:
+ break
+ persons = o[key]
+ if not persons:
+ continue
+ t = company(o["ariregistri_kood"], o["nimi"])
+ for p in persons:
+ edges.append((person(p), t))
+ ep["record_id"].append(p["kirje_id"])
+ ep["start_date"].append(_date(p.get("algus_kpv")))
+ if alt in ("shareholders", "other_roles"):
+ if alt == "shareholders":
+ ep["share_percent"].append(_num(p.get("osaluse_protsent")))
+ g_ = p.get("grupp")
+ ep["ownership_group"].append(-1 if g_ is None else g_)
+ ep["share_amount"].append(_num(p.get("osaluse_suurus")))
+ ep["share_currency"].append(_str(p.get("osaluse_valuuta")))
+ ep["ownership"].append(_str(p.get("osaluse_omandiliik_tekstina")))
+ if alt in ("officers", "other_roles"):
+ ep["role"].append(_str(p.get("isiku_roll_tekstina")))
+ ep["role_code"].append(_str(p.get("isiku_roll")))
+ ep["mandate_end"].append(_date(p.get("volituste_loppemise_kpv")))
+ if alt == "officers":
+ ep["card_type"].append(_str(p.get("kaardi_tyyp")))
+ ep["contribution"].append(_num(p.get("osamaks")))
+ ep["contribution_currency"].append(_str(p.get("osamaksu_valuuta")))
+ if alt == "beneficial_owners":
+ ep["control"].append(_str(p.get("kontrolli_teostamise_viis_tekstina")))
+ ep["control_code"].append(_str(p.get("kontrolli_teostamise_viis")))
+ ep["discrepancy"].append(bool(p.get("lahknevusteade_esitatud")))
+ del companies, idx
+ g = Graph(directed=True)
+ g.add_vertex(len(attrs))
+ g.add_edge_list(edges)
+ del edges
+ for k in _vprops:
+ g.vp[k] = g.new_vp("string", vals=[a.get(k, "") for a in attrs])
+ for k, vals in ep.items():
+ g.ep[k] = g.new_ep(_etypes[k], vals=vals)
+ yield alt, g