History of latvia_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/latvia_company_ownership/__init__.py b/entries/latvia_company_ownership/__init__.py
index da85bc0..e58a3da 100644
--- a/entries/latvia_company_ownership/__init__.py
+++ b/entries/latvia_company_ownership/__init__.py
@@ -64,7 +64,7 @@ url = ['https://data.gov.lv/dati/eng/dataset/members',
'https://data.gov.lv/dati/eng/dataset/uz']
citation = [('Uzņēmumu reģistrs (Enterprise Register of the Republic of Latvia), "Open data of the Enterprise Register." Latvian open data portal (2026)',
'https://data.gov.lv/dati/eng/dataset/uz')]
-icon_hash = None
+date_added = '2026-10-31'
upstream_license = ("CC0 1.0", "https://creativecommons.org/publicdomain/zero/1.0/")
# 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/latvia_company_ownership/__init__.py b/entries/latvia_company_ownership/__init__.py
index 1e14927..da85bc0 100644
--- a/entries/latvia_company_ownership/__init__.py
+++ b/entries/latvia_company_ownership/__init__.py
@@ -33,19 +33,29 @@ The legal entities are identified by their registration number in the register,
The networks have the following properties:
-- `kind`: the kind of node: `legal_entity` (a Latvian legal entity in the register), `foreign_entity`, `natural_person`.
-- `name`: the name of the legal entity (e.g. `SIA "Kronos"`), or of the person (surname and forename).
-- `regcode`: the registration number of the Latvian legal entity, or the registration number of the foreign entity as given (empty for persons).
-- `identity_code_masked` (persons): the masked personal identity code (empty if none).
-- `birth_date` (persons): the date of birth given for persons without a Latvian personal code (empty if not given).
-- `register`, `entity_type` (Latvian legal entities): the register in which the entity is recorded (e.g. `Komercreģistrs`, the commercial register) and its legal form (e.g. `Sabiedrība ar ierobežotu atbildību`, limited liability company).
-- `registered`, `terminated` (Latvian legal entities): the dates of registration and termination (`YYYY-MM-DD`, empty if not terminated).
-- `status` (Latvian legal entities): `active`, `liquidated` or `reorganized`.
-- `address`, `postal_code` (Latvian legal entities): the address of the legal entity.
-- `nationality`, `residence` (`beneficial_owners`, persons): two-letter country codes.
-- Edge `registered_on` (all networks): the date on which the relation was registered.
-- Edges of `shareholders`: `company_kind` (`SIA` for the members of limited liability companies, `AS` for the stockholders of joint-stock companies); `shares` (the number of shares); `share_nominal_value` (the nominal value of one share); `share_currency` (`EUR`, or `LVL` for the Latvian lats of shares registered before 2014); `votes` (`AS`: the number of votes, -1 for `SIA`); `stock_type` (`AS`: `REGISTERED_STOCKS`, `DEMATERIALISED_STOCKS`, or empty); `date_from` (the date since which the shares are held); `joint` (whether the shares are held jointly with other owners).
-- Edges of `officers`: `position` (e.g. `BOARD_MEMBER`, `CHAIR_OF_BOARD`, `OWNER`, `LIQUIDATOR`; empty if not given), `governing_body` (e.g. `EXECUTIVE_BOARD`, `SUPERVISORY_BOARD`), `representation` (the rights of representation, e.g. `INDIVIDUALLY`, `WITH_AT_LEAST`), and `representation_with_at_least` (the number of other officers needed to represent the entity jointly)."""
+- (Node) `kind`: the kind of node: `legal_entity` (a Latvian legal entity in the register), `foreign_entity`, `natural_person`.
+- (Node) `name`: the name of the legal entity (e.g. `SIA "Kronos"`), or of the person (surname and forename).
+- (Node) `regcode`: the registration number of the Latvian legal entity, or the registration number of the foreign entity as given (empty for persons).
+- (Node) `identity_code_masked` (persons): the masked personal identity code (empty if none).
+- (Node) `birth_date` (persons): the date of birth given for persons without a Latvian personal code (empty if not given).
+- (Node) `register`, `entity_type` (Latvian legal entities): the register in which the entity is recorded (e.g. `Komercreģistrs`, the commercial register) and its legal form (e.g. `Sabiedrība ar ierobežotu atbildību`, limited liability company).
+- (Node) `registered`, `terminated` (Latvian legal entities): the dates of registration and termination (`YYYY-MM-DD`, empty if not terminated).
+- (Node) `status` (Latvian legal entities): `active`, `liquidated` or `reorganized`.
+- (Node) `address`, `postal_code` (Latvian legal entities): the address of the legal entity.
+- (Node) `nationality`, `residence` (`beneficial_owners`, persons): two-letter country codes.
+- (Edge) `registered_on` (all networks): the date on which the relation was registered.
+- (Edge) `company_kind` (`shareholders`): `SIA` for the members of limited liability companies, `AS` for the stockholders of joint-stock companies.
+- (Edge) `shares` (`shareholders`): the number of shares.
+- (Edge) `share_nominal_value` (`shareholders`): the nominal value of one share.
+- (Edge) `share_currency` (`shareholders`): `EUR`, or `LVL` for the Latvian lats of shares registered before 2014.
+- (Edge) `votes` (`shareholders`): for `AS`, the number of votes (-1 for `SIA`).
+- (Edge) `stock_type` (`shareholders`): for `AS`, `REGISTERED_STOCKS`, `DEMATERIALISED_STOCKS`, or empty.
+- (Edge) `date_from` (`shareholders`): the date since which the shares are held.
+- (Edge) `joint` (`shareholders`): whether the shares are held jointly with other owners.
+- (Edge) `position` (`officers`): e.g. `BOARD_MEMBER`, `CHAIR_OF_BOARD`, `OWNER`, `LIQUIDATOR`; empty if not given.
+- (Edge) `governing_body` (`officers`): e.g. `EXECUTIVE_BOARD`, `SUPERVISORY_BOARD`.
+- (Edge) `representation` (`officers`): the rights of representation, e.g. `INDIVIDUALLY`, `WITH_AT_LEAST`.
+- (Edge) `representation_with_at_least` (`officers`): the number of other officers needed to represent the entity jointly."""
tags = ['Economic', 'Commerce', 'Social', 'Affiliation', 'Multigraph', 'Weighted', 'Metadata']
url = ['https://data.gov.lv/dati/eng/dataset/members',
'https://data.gov.lv/dati/eng/dataset/stockholders',
[entries] New entry: latvia_company_ownership f7bb1f2
diff --git a/entries/latvia_company_ownership/__init__.py b/entries/latvia_company_ownership/__init__.py
new file mode 100644
index 0000000..1e14927
--- /dev/null
+++ b/entries/latvia_company_ownership/__init__.py
@@ -0,0 +1,219 @@
+#! /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 pandas
+
+title = "Latvian company shareholders, officers and beneficial owners (2026)"
+description = r"""Networks of the ownership and management of the legal entities of Latvia (companies, associations, foundations, etc.), from the open data of the Latvian Enterprise Register (Uzņēmumu reģistrs), published daily on the Latvian open data portal. The data are those in force on the download date (30 October 2026): the register publishes only the current state, and the files are replaced daily.
+
+There are three networks, in which a directed edge goes from an owner, officer or beneficial owner to the legal entity:
+
+- `shareholders`: the members (shareholders) of the limited liability companies (SIA), and the stockholders of the joint-stock companies (AS) that are recorded in the register (the shares of most listed joint-stock companies are held through the central securities depository, which then appears as their stockholder), with the number and nominal value of the shares (and the votes, for stockholders).
+- `officers`: the officers of the legal entities: the members of their executive boards, the chairs, the owners of sole proprietorships, liquidators, etc.
+- `beneficial_owners`: the beneficial owners (natural persons who ultimately own or control the legal entity), as declared to the register.
+
+The legal entities are identified by their registration number in the register, and are described by their records there (name, type, address, dates of registration and termination). The owners and officers that are Latvian legal entities are the same nodes as the legal entities themselves, so that the networks include chains of ownership between companies. The foreign legal entities are identified by their name and foreign registration number. The register gives no identifier of the natural persons, only their name and their masked personal identity code (e.g. `140368-*****`, whose visible part is the date of birth for the codes issued before 2017, and a random number starting with 32 for the later ones) or, for foreigners, their date of birth: the natural persons are merged by their name, masked code and date of birth together, so that two different persons with the same name and date of birth are taken as one, and the same person written differently is split (in `beneficial_owners`, the name is written as surname and forename, as in the other files). Shares held jointly by several persons are represented by one edge from each joint owner, marked as joint. Only the legal entities that take part in some edge are included.
+
+The networks have the following properties:
+
+- `kind`: the kind of node: `legal_entity` (a Latvian legal entity in the register), `foreign_entity`, `natural_person`.
+- `name`: the name of the legal entity (e.g. `SIA "Kronos"`), or of the person (surname and forename).
+- `regcode`: the registration number of the Latvian legal entity, or the registration number of the foreign entity as given (empty for persons).
+- `identity_code_masked` (persons): the masked personal identity code (empty if none).
+- `birth_date` (persons): the date of birth given for persons without a Latvian personal code (empty if not given).
+- `register`, `entity_type` (Latvian legal entities): the register in which the entity is recorded (e.g. `Komercreģistrs`, the commercial register) and its legal form (e.g. `Sabiedrība ar ierobežotu atbildību`, limited liability company).
+- `registered`, `terminated` (Latvian legal entities): the dates of registration and termination (`YYYY-MM-DD`, empty if not terminated).
+- `status` (Latvian legal entities): `active`, `liquidated` or `reorganized`.
+- `address`, `postal_code` (Latvian legal entities): the address of the legal entity.
+- `nationality`, `residence` (`beneficial_owners`, persons): two-letter country codes.
+- Edge `registered_on` (all networks): the date on which the relation was registered.
+- Edges of `shareholders`: `company_kind` (`SIA` for the members of limited liability companies, `AS` for the stockholders of joint-stock companies); `shares` (the number of shares); `share_nominal_value` (the nominal value of one share); `share_currency` (`EUR`, or `LVL` for the Latvian lats of shares registered before 2014); `votes` (`AS`: the number of votes, -1 for `SIA`); `stock_type` (`AS`: `REGISTERED_STOCKS`, `DEMATERIALISED_STOCKS`, or empty); `date_from` (the date since which the shares are held); `joint` (whether the shares are held jointly with other owners).
+- Edges of `officers`: `position` (e.g. `BOARD_MEMBER`, `CHAIR_OF_BOARD`, `OWNER`, `LIQUIDATOR`; empty if not given), `governing_body` (e.g. `EXECUTIVE_BOARD`, `SUPERVISORY_BOARD`), `representation` (the rights of representation, e.g. `INDIVIDUALLY`, `WITH_AT_LEAST`), and `representation_with_at_least` (the number of other officers needed to represent the entity jointly)."""
+tags = ['Economic', 'Commerce', 'Social', 'Affiliation', 'Multigraph', 'Weighted', 'Metadata']
+url = ['https://data.gov.lv/dati/eng/dataset/members',
+ 'https://data.gov.lv/dati/eng/dataset/stockholders',
+ 'https://data.gov.lv/dati/eng/dataset/officers',
+ 'https://data.gov.lv/dati/eng/dataset/patiesie-labuma-guveji',
+ 'https://data.gov.lv/dati/eng/dataset/uz']
+citation = [('Uzņēmumu reģistrs (Enterprise Register of the Republic of Latvia), "Open data of the Enterprise Register." Latvian open data portal (2026)',
+ 'https://data.gov.lv/dati/eng/dataset/uz')]
+icon_hash = None
+upstream_license = ("CC0 1.0", "https://creativecommons.org/publicdomain/zero/1.0/")
+
+# The files are replaced daily by the current state of the register (no
+# dated snapshots are kept): the cached copies are of 2026-10-30
+upstream_prefix = 'https://data.gov.lv/dati/dataset'
+_f = {"register": "4de9697f-850b-45ec-8bba-61fa09ce932f/resource/25e80bf3-f107-4ab4-89ef-251b5b9374e9/download/register.csv",
+ "members": "e1162626-e02a-4545-9236-37553609a988/resource/837b451a-4833-4fd1-bfdd-b45b35a994fd/download/members.csv",
+ "members_joint": "e1162626-e02a-4545-9236-37553609a988/resource/466e5b9b-b31b-4e68-aaee-ae34254b902b/download/members_joint_owners.csv",
+ "stockholders": "8a66567b-3583-4aa0-841b-eb13f025cd78/resource/6adabd83-93f9-4d7f-bebd-fa109bbf794a/download/stockholders.csv",
+ "stockholders_joint": "8a66567b-3583-4aa0-841b-eb13f025cd78/resource/55370dca-6765-4566-b2cc-42537782f750/download/stockholders_joint_owners.csv",
+ "officers": "096c7a47-33cd-4dc9-a876-2c86e86230fd/resource/e665114a-73c2-4375-9470-55874b4cfa6b/download/officers.csv",
+ "beneficial_owners": "b7848ab9-7886-4df0-8bc6-70052a8d9e1a/resource/20a9b26d-d056-4dbb-ae18-9ff23c87bdee/download/beneficial_owners.csv"}
+files = [((_f["register"], _f["members"], _f["members_joint"],
+ _f["stockholders"], _f["stockholders_joint"]), "shareholders", None),
+ ((_f["register"], _f["officers"]), "officers", None),
+ ((_f["register"], _f["beneficial_owners"]), "beneficial_owners", None)]
+
+def fetch_upstream(force=False):
+ return fetch_upstream_files(__name__.split(".")[-1], upstream_prefix, files,
+ force)
+
+# For checks only: if set, only this many rows are read of each file
+_check_rows = None
+
+def _read(name, fname):
+ with open_upstream_file(name, fname, "rb") as f:
+ df = pandas.read_csv(f, sep=";", dtype=str, keep_default_na=False,
+ nrows=_check_rows)
+ return df.apply(lambda c: c.str.strip())
+
+_status = {"L": "liquidated", "R": "reorganized", "": "active"}
+
+class _Nodes:
+ "The nodes, by key, with their attributes."
+ def __init__(self, register):
+ self.reg = register.set_index("regcode")
+ self.idx = {}
+ self.attrs = []
+
+ def get(self, kind, name, regcode="", code="", birth="", extra=None):
+ if kind == "legal_entity":
+ key = ("legal_entity", regcode)
+ elif kind == "foreign_entity":
+ key = (kind, " ".join(name.split()), regcode)
+ else:
+ key = (kind, " ".join(name.split()), code, birth)
+ v = self.idx.get(key)
+ if v is None:
+ v = self.idx[key] = len(self.idx)
+ a = dict(kind=kind, name=" ".join(name.split()), regcode=regcode,
+ identity_code_masked=code, birth_date=birth)
+ if kind == "legal_entity" and regcode in self.reg.index:
+ r = self.reg.loc[regcode]
+ a.update(name=r["name"], register=r["regtype_text"],
+ entity_type=r["type_text"],
+ registered=r["registered"],
+ terminated=r["terminated"],
+ status=_status.get(r["closed"], r["closed"]),
+ address=r["address"], postal_code=r["index"])
+ if extra:
+ a.update(extra)
+ self.attrs.append(a)
+ return v
+
+ def owner(self, row):
+ t = row["entity_type"]
+ if t in ("LEGAL_ENTITY", "DEPOSITORY"):
+ rc = row["legal_entity_registration_number"] or \
+ row.get("depository_registration_number", "")
+ return self.get("legal_entity", row["name"] or row.get("depository_name", ""),
+ regcode=rc)
+ if t == "FOREIGN_ENTITY":
+ return self.get("foreign_entity", row["name"],
+ regcode=row["legal_entity_registration_number"])
+ return self.get("natural_person", row["name"],
+ code=row["latvian_identity_number_masked"],
+ birth=row["birth_date"])
+
+def _num(x, default=numpy.nan):
+ try:
+ return float(x)
+ except ValueError:
+ return default
+
+_etypes = {"shares": "double", "share_nominal_value": "double",
+ "votes": "int64_t", "joint": "bool",
+ "representation_with_at_least": "int16_t"}
+_etypes |= {k: "string" for k in
+ ["company_kind", "share_currency", "stock_type", "date_from",
+ "registered_on", "position", "governing_body", "representation"]}
+
+@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
+ register = _read(name, fnames[0])
+ nodes = _Nodes(register)
+ del register
+ edges = []
+ ep = defaultdict(list)
+ if alt == "shareholders":
+ for k, kind in [(1, "SIA"), (3, "AS")]:
+ tab = _read(name, fnames[k])
+ joint = defaultdict(list)
+ for r in _read(name, fnames[k + 1]).to_dict("records"):
+ joint[r["member_id"]].append(r)
+ for r in tab.to_dict("records"):
+ t = nodes.get("legal_entity", "",
+ regcode=r["at_legal_entity_registration_number"])
+ if r["entity_type"] == "JOINT_OWNERS":
+ owners = [(nodes.owner(j), True) for j in joint[r["id"]]]
+ else:
+ owners = [(nodes.owner(r), False)]
+ for s, is_joint in owners:
+ edges.append((s, t))
+ ep["company_kind"].append(kind)
+ ep["shares"].append(_num(r["number_of_shares"]))
+ ep["share_nominal_value"].append(_num(r["share_nominal_value"]))
+ ep["share_currency"].append(r["share_currency"])
+ ep["votes"].append(int(_num(r.get("votes", -1), -1)))
+ ep["stock_type"].append(r.get("stock_type", ""))
+ ep["date_from"].append(r["date_from"])
+ ep["registered_on"].append(r["registered_on"])
+ ep["joint"].append(is_joint)
+ elif alt == "officers":
+ for r in _read(name, fnames[1]).to_dict("records"):
+ t = nodes.get("legal_entity", "",
+ regcode=r["at_legal_entity_registration_number"])
+ edges.append((nodes.owner(r), t))
+ ep["position"].append(r["position"])
+ ep["governing_body"].append(r["governing_body"])
+ ep["representation"].append(r["rights_of_representation_type"])
+ ep["representation_with_at_least"].append(int(_num(r["representation_with_at_least"], -1)))
+ ep["registered_on"].append(r["registered_on"])
+ else:
+ for r in _read(name, fnames[1]).to_dict("records"):
+ t = nodes.get("legal_entity", "",
+ regcode=r["legal_entity_registration_number"])
+ s = nodes.get("natural_person", r["surname"] + " " + r["forename"],
+ code=r["latvian_identity_number_masked"],
+ birth=r["birth_date"],
+ extra=dict(nationality=r["nationality"],
+ residence=r["residence"]))
+ edges.append((s, t))
+ ep["registered_on"].append(r["registered_on"])
+ g = Graph(directed=True)
+ g.add_vertex(len(nodes.attrs))
+ g.add_edge_list(edges)
+ keys = ["kind", "name", "regcode", "identity_code_masked", "birth_date",
+ "register", "entity_type", "registered", "terminated", "status",
+ "address", "postal_code"]
+ if alt == "beneficial_owners":
+ keys += ["nationality", "residence"]
+ for k in keys:
+ g.vp[k] = g.new_vp("string", vals=[a.get(k, "") for a in nodes.attrs])
+ for k, vals in ep.items():
+ g.ep[k] = g.new_ep(_etypes[k], vals=vals)
+ yield alt, g