History of opensanctions
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/opensanctions/__init__.py b/entries/opensanctions/__init__.py
index 55b1b86..6d146a2 100644
--- a/entries/opensanctions/__init__.py
+++ b/entries/opensanctions/__init__.py
@@ -49,7 +49,7 @@ tags = ['Social', 'Economic', 'Political', 'Affiliation', 'Multigraph', 'Unweigh
url = ['https://www.opensanctions.org/datasets/default/']
citation = [('OpenSanctions, "OpenSanctions Default collection." Version 20261031125407-kkl (2026)',
'https://www.opensanctions.org/datasets/default/')]
-icon_hash = None
+date_added = '2026-10-31'
upstream_license = ("CC BY-NC 4.0 (commercial use requires a license from OpenSanctions)",
"https://www.opensanctions.org/licensing/")
[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/opensanctions/__init__.py b/entries/opensanctions/__init__.py
index 2819f0c..55b1b86 100644
--- a/entries/opensanctions/__init__.py
+++ b/entries/opensanctions/__init__.py
@@ -29,22 +29,22 @@ The entities are compiled from many sources of different precision: the same per
The network has the following properties:
-- `entity_id`: the id of the entity in OpenSanctions (its page is `https://www.opensanctions.org/entities/<entity_id>/`).
-- `schema`: its FtM schema (e.g. `Person`, `Company`, `Position`, `Vessel`).
-- `name`: its caption (display name).
-- `topics`: its topics (e.g. `sanction`, `role.pep`, `role.rca` for relatives and close associates, `crime`, `debarment`, `poi` for persons of interest).
-- `countries`: its countries (two-letter codes, from the `country`, `jurisdiction`, `nationality` and `citizenship` properties).
-- `birth_date` (persons): the first date of birth given (`YYYY`, `YYYY-MM` or `YYYY-MM-DD`; empty if none).
-- `datasets`: the source datasets of OpenSanctions in which it appears.
-- `is_target`: whether it is a "target" of screening (sanctioned, PEP, wanted, etc.), as opposed to a related entity.
-- `first_seen`, `last_seen`, `last_change`: the times of its first and last appearance in the sources and of its last change (ISO 8601).
-- `properties`: all its FtM properties, as a JSON object of lists of values.
-- Edge `schema`: the FtM schema of the relationship (e.g. `Ownership`, `Family`), or the name of the referencing property (e.g. `addressEntity`).
-- Edge `relation_id`: the id of the relationship entity (empty for references).
-- Edge `role`: the role or kind of the relationship (`role` or `relationship` property, e.g. `father`, `subsidiary of`; empty if none).
-- Edge `start_date`, `end_date`: the first start and end dates given (empty if none).
-- Edge `datasets`: the source datasets of the relationship.
-- Edge `properties`: all the FtM properties of the relationship entity, as a JSON object (empty for references)."""
+- (Node) `entity_id`: the id of the entity in OpenSanctions (its page is `https://www.opensanctions.org/entities/<entity_id>/`).
+- (Node) `schema`: its FtM schema (e.g. `Person`, `Company`, `Position`, `Vessel`).
+- (Node) `name`: its caption (display name).
+- (Node) `topics`: its topics (e.g. `sanction`, `role.pep`, `role.rca` for relatives and close associates, `crime`, `debarment`, `poi` for persons of interest).
+- (Node) `countries`: its countries (two-letter codes, from the `country`, `jurisdiction`, `nationality` and `citizenship` properties).
+- (Node) `birth_date` (persons): the first date of birth given (`YYYY`, `YYYY-MM` or `YYYY-MM-DD`; empty if none).
+- (Node) `datasets`: the source datasets of OpenSanctions in which it appears.
+- (Node) `is_target`: whether it is a "target" of screening (sanctioned, PEP, wanted, etc.), as opposed to a related entity.
+- (Node) `first_seen`, `last_seen`, `last_change`: the times of its first and last appearance in the sources and of its last change (ISO 8601).
+- (Node) `properties`: all its FtM properties, as a JSON object of lists of values.
+- (Edge) `schema`: the FtM schema of the relationship (e.g. `Ownership`, `Family`), or the name of the referencing property (e.g. `addressEntity`).
+- (Edge) `relation_id`: the id of the relationship entity (empty for references).
+- (Edge) `role`: the role or kind of the relationship (`role` or `relationship` property, e.g. `father`, `subsidiary of`; empty if none).
+- (Edge) `start_date`, `end_date`: the first start and end dates given (empty if none).
+- (Edge) `datasets`: the source datasets of the relationship.
+- (Edge) `properties`: all the FtM properties of the relationship entity, as a JSON object (empty for references)."""
tags = ['Social', 'Economic', 'Political', 'Affiliation', 'Multigraph', 'Unweighted', 'Metadata']
url = ['https://www.opensanctions.org/datasets/default/']
citation = [('OpenSanctions, "OpenSanctions Default collection." Version 20261031125407-kkl (2026)',
[entries] New entry: opensanctions 475617d
diff --git a/entries/opensanctions/__init__.py b/entries/opensanctions/__init__.py
new file mode 100644
index 0000000..2819f0c
--- /dev/null
+++ b/entries/opensanctions/__init__.py
@@ -0,0 +1,180 @@
+#! /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 = "OpenSanctions persons and companies of interest (2026)"
+description = r"""The network of the persons, companies, organizations and other entities of interest for sanctions screening and investigations of OpenSanctions, an open database that consolidates the sanctions lists of governments and international organizations, the lists of politically exposed persons (PEPs, politicians and their family members and close associates), lists of wanted criminals, debarred companies, and related corporate registry and ownership data, from more than 300 sources, with de-duplicated entities. The network is built from the `default` collection, the broadest one ("any entity that should receive enhanced public scrutiny"), in the version of 31 October 2026, in the FollowTheMoney (FtM) data model.
+
+Each node is an FtM "thing": a person, a company, an organization, a legal entity of unknown kind, a public body, a political or public position, an address, a security, a vessel, an airplane, a crypto wallet, an asset, or an article, all of them included. Each directed edge is a relationship between two of them: either an FtM relationship entity, from its source to its target (`Occupancy`: holder → position; `Ownership`: owner → asset; `Directorship`: director → organization; `Employment`: employee → employer; `Membership`: member → organization; `Family`: person → relative; `Associate`: person → associate; `Representation`: agent → client; `Succession`: predecessor → successor; `UnknownLink`: subject → object; `Documentation`: entity → document; and the other relationship schemata, if any), or a reference from one thing to another (`addressEntity`: thing → its address; `holder`: holder → crypto wallet; `issuer`: issuer → security; `organization`: organization → position; `parent`: parent → organization; `operator` and `owner` of assets). The network is a multigraph. The relationships whose ends are not both in the collection (e.g. relatives that are not themselves of interest) are not included, and neither are the sanction records (`Sanction`, which link an entity to a sanctioning authority given as text; whether an entity is sanctioned is given by its topics), nor the identification documents, passports and other records that do not join two things.
+
+The entities are compiled from many sources of different precision: the same person or company can still appear as several nodes when the sources could not be matched, and many PEP records lack exact dates of birth or full names. The data change daily; the version used is pinned (`20261031125407-kkl`).
+
+The network has the following properties:
+
+- `entity_id`: the id of the entity in OpenSanctions (its page is `https://www.opensanctions.org/entities/<entity_id>/`).
+- `schema`: its FtM schema (e.g. `Person`, `Company`, `Position`, `Vessel`).
+- `name`: its caption (display name).
+- `topics`: its topics (e.g. `sanction`, `role.pep`, `role.rca` for relatives and close associates, `crime`, `debarment`, `poi` for persons of interest).
+- `countries`: its countries (two-letter codes, from the `country`, `jurisdiction`, `nationality` and `citizenship` properties).
+- `birth_date` (persons): the first date of birth given (`YYYY`, `YYYY-MM` or `YYYY-MM-DD`; empty if none).
+- `datasets`: the source datasets of OpenSanctions in which it appears.
+- `is_target`: whether it is a "target" of screening (sanctioned, PEP, wanted, etc.), as opposed to a related entity.
+- `first_seen`, `last_seen`, `last_change`: the times of its first and last appearance in the sources and of its last change (ISO 8601).
+- `properties`: all its FtM properties, as a JSON object of lists of values.
+- Edge `schema`: the FtM schema of the relationship (e.g. `Ownership`, `Family`), or the name of the referencing property (e.g. `addressEntity`).
+- Edge `relation_id`: the id of the relationship entity (empty for references).
+- Edge `role`: the role or kind of the relationship (`role` or `relationship` property, e.g. `father`, `subsidiary of`; empty if none).
+- Edge `start_date`, `end_date`: the first start and end dates given (empty if none).
+- Edge `datasets`: the source datasets of the relationship.
+- Edge `properties`: all the FtM properties of the relationship entity, as a JSON object (empty for references)."""
+tags = ['Social', 'Economic', 'Political', 'Affiliation', 'Multigraph', 'Unweighted', 'Metadata']
+url = ['https://www.opensanctions.org/datasets/default/']
+citation = [('OpenSanctions, "OpenSanctions Default collection." Version 20261031125407-kkl (2026)',
+ 'https://www.opensanctions.org/datasets/default/')]
+icon_hash = None
+upstream_license = ("CC BY-NC 4.0 (commercial use requires a license from OpenSanctions)",
+ "https://www.opensanctions.org/licensing/")
+
+# A versioned export of the collection (the "latest" path redirects to the
+# current one); OpenSanctions keeps the exports only for a few weeks
+upstream_prefix = 'https://data.opensanctions.org/artifacts/default/20261031125407-kkl'
+files = [("entities.ftm.json", None, None)]
+
+def fetch_upstream(force=False):
+ return fetch_upstream_files(__name__.split(".")[-1], upstream_prefix, files,
+ force)
+
+
+# The source and target properties of the FtM relationship schemata
+_edge_schemata = {"Occupancy": ("holder", "post"),
+ "Ownership": ("owner", "asset"),
+ "Directorship": ("director", "organization"),
+ "Employment": ("employee", "employer"),
+ "Membership": ("member", "organization"),
+ "Family": ("person", "relative"),
+ "Associate": ("person", "associate"),
+ "Representation": ("agent", "client"),
+ "Succession": ("predecessor", "successor"),
+ "UnknownLink": ("subject", "object"),
+ "Documentation": ("entity", "document"),
+ "Similar": ("candidate", "match"),
+ "Debt": ("debtor", "creditor"),
+ "Payment": ("payer", "beneficiary"),
+ "ContractAward": ("supplier", "contract"),
+ "ProjectParticipant": ("participant", "project"),
+ "EconomicActivity": ("declarant", "receiver")}
+# The non-thing schemata that do not join two things
+_other_schemata = {"Sanction", "Identification", "Passport", "Interval",
+ "Mention", "TaxRoll", "Event", "Note", "Trip"}
+# References between things: property -> whether the edge goes from the
+# referenced entity to the referencing one (e.g. the holder of a wallet)
+_references = {"addressEntity": False, "holder": True, "issuer": True,
+ "organization": True, "parent": True, "operator": True,
+ "owner": True}
+
+# For checks only: if set, only this many lines are read
+_check_rows = None
+
+def _first(p, *keys):
+ for k in keys:
+ v = p.get(k)
+ if v:
+ return v[0]
+ return ""
+
+def _json(x):
+ return json.dumps(x, separators=(",", ":"), ensure_ascii=False, sort_keys=True)
+
+@cache_network()
+@coerce_props()
+@annotate()
+def parse(alts=None):
+ name = __name__.split(".")[-1]
+ for fname, alt, fmt in files:
+ if alts is not None and alt not in alts:
+ continue
+ idx = {}
+ vp = defaultdict(list)
+ rels = [] # (source id, target id, schema, relation id, entity)
+ with open_upstream_file(name, fname, "r") as f:
+ for n, line in enumerate(f):
+ if _check_rows is not None and n >= _check_rows:
+ break
+ o = json.loads(line)
+ s = o["schema"]
+ p = o["properties"]
+ if s in _edge_schemata:
+ src, tgt = _edge_schemata[s]
+ for a in p.get(src, []):
+ for b in p.get(tgt, []):
+ rels.append((a, b, s, o["id"], o))
+ continue
+ if s in _other_schemata:
+ continue
+ idx[o["id"]] = len(idx)
+ vp["entity_id"].append(o["id"])
+ vp["schema"].append(s)
+ vp["name"].append(o.get("caption") or "")
+ vp["topics"].append(p.get("topics", []))
+ cs = []
+ for k in ["country", "jurisdiction", "nationality", "citizenship"]:
+ cs.extend(c for c in p.get(k, []) if c not in cs)
+ vp["countries"].append(cs)
+ vp["birth_date"].append(_first(p, "birthDate"))
+ vp["datasets"].append(o.get("datasets", []))
+ vp["is_target"].append(bool(o.get("target")))
+ for k in ["first_seen", "last_seen", "last_change"]:
+ vp[k].append(o.get(k) or "")
+ vp["properties"].append(_json(p))
+ for k, rev in _references.items():
+ for b in p.get(k, []):
+ if rev:
+ rels.append((b, o["id"], k, "", None))
+ else:
+ rels.append((o["id"], b, k, "", None))
+ edges = []
+ ep = defaultdict(list)
+ for a, b, s, rid, o in rels:
+ if a not in idx or b not in idx:
+ continue
+ edges.append((idx[a], idx[b]))
+ ep["schema"].append(s)
+ ep["relation_id"].append(rid)
+ p = o["properties"] if o is not None else {}
+ ep["role"].append(_first(p, "role", "relationship"))
+ ep["start_date"].append(_first(p, "startDate", "date"))
+ ep["end_date"].append(_first(p, "endDate"))
+ ep["datasets"].append(o.get("datasets", []) if o is not None else [])
+ ep["properties"].append(_json(p) if o is not None else "")
+ del rels, idx
+ g = Graph(directed=True)
+ g.add_vertex(len(vp["entity_id"]))
+ g.add_edge_list(edges)
+ del edges
+ for k, vals in vp.items():
+ t = {"topics": "vector<string>", "countries": "vector<string>",
+ "datasets": "vector<string>", "is_target": "bool"}.get(k, "string")
+ g.vp[k] = g.new_vp(t, vals=vals)
+ del vp
+ for k, vals in ep.items():
+ t = "vector<string>" if k == "datasets" else "string"
+ g.ep[k] = g.new_ep(t, vals=vals)
+ yield alt, g