opensanctions / opensanctions/opensanctions
Adopt the new FtM Control schema (edges, analyzer, crawlers)
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 810
- Forks
- 187
- Avg merge
- 1d 7h
- Merged PRs (30d)
- 133
Description
followthemoney is gaining a Control schema (opensanctions/followthemoney#362): a sibling of Ownership under Interest with a directed controller → controlled edge, both ranged LegalEntity, plus controlType (voting, appointment, management, contractual, trust, nominee, state, indirect) and legalBasis. It is for one entity controlling another without necessarily owning it, and it removes the need to cast controlled Organizations as Company just to satisfy Ownership.asset's Asset range.
Once a followthemoney release with Control is pinned here, the following needs doing.
1. Infrastructure that hardcodes Ownership
-
zavod/zavod/integration/edges.py:59—PROTECTED_PROPShas per-schema entries forOwnership,Directorship,UnknownLink, … Add"Control": ["controlType", "role"]so control edges with conflicting descriptors are not merged on endpoints+dates alone. -
datasets/_analysis/ann_graph_topics/analyzer.py:SANCTION_ADJACENCY_EDGES(line 93) — add"Control".rule_sanction_control_descent(line 274) — currentlyadjacent.schema.name != "Ownership" or prop.reverse.name != "owner"; it should also descendControledges viacontroller. The topic it emits is already calledsanction.control.rule_export_control_descent(line 312) —is_a("Ownership")check; decide whether control edges should propagateexport.control.linkedtoo (the docstring warns against addingDirectorship;Controlis a different, deliberate assertion of control by the source).- Extend
test_ann_graph_topics.pyaccordingly.
2. Crawlers that currently force control into other schemata
Ordered roughly by how clearly the source distinguishes control from ownership:
-
zavod/zavod/shed/bods.py:110— BODSownershipOrControlStatementinterests are flattened intoOwnershipwithinterests[].typeinrole. BODS interest typesvotingRights,appointmentOfBoard,influenceOrControl,seniorManagingOfficialshould becomeControl(withcontrolTypemapped), whileshareholdingstaysOwnership. Affectsdatasets/dk/cvr. -
datasets/gb/coh/psc_parse.py— every PSC nature of control becomes a singleOwnership; voting-rights ranges are parsed intopercentageindistinguishably from share percentages. Split:ownership-of-shares-*→Ownership;voting-rights-*,right-to-appoint-and-remove-*,significant-influence-or-control*→Control. Voting-rights percentages will need a property onControl— raise that on followthemoney when this migration starts. -
datasets/us/ofac/us_ofac_sdn.ymllookups.relations— "Owned or Controlled By" (15003) and "Owns, controls, or operates" (92019) map toOwnership. ConsiderControlfor these (OFAC's own wording is disjunctive), keeping "Property in the interest of" (92122) asOwnership. Consumed bydatasets/us/ofac/ofac_advanced.py. -
datasets/lv/fiu_sanctions/crawler.py:160-181— a list of "entities owned or controlled by" sanctioned persons, modelled asUnknownLinkwith the freeze type inrole. -
datasets/lt/fiu_freezes/crawler.py:74-77— "property legally owned or controlled by a sanctioned entity", role-lessUnknownLink. -
datasets/_global/thesentry_atlas/crawler.py:20-41— a literalCONTROLSrelation is mapped toDirectorship. -
datasets/sg/mas_investor_alert/crawler.py:40-52,105-123— "controlled by", "operated by", "managed by", "run by" all go throughemit_ownership()into a role-lessOwnership. -
datasets/ee/ariregister/crawler.pyparse_bfo()— beneficial owners withkontrolli_teostamise_viis_tekstina("manner of exercising control") inOwnership.role; the control-based manners should becomeControl. -
datasets/ua/war_sanctions/crawler_zyte.py— "Commercial ship manager" / "Ship Safety Management Manager" areUnknownLink; these are operational control of the owning entity's vessel and may fitControlonly if the controlled party is aLegalEntity— check before migrating.
3. Remove the Company cast workaround
Where crawlers upgrade an Organization to Company solely so it can be an Ownership.asset, switch the relationship to Control where the source is asserting control, and drop the cast:
-
datasets/gb/fcdo_sanctions/crawler.py:352-356(comment: "Ownership:asset must reference an Asset, which Organization isn't") -
datasets/_global/gem_energy_ownership/crawler.py:95-98 - Others found with the same pattern:
us/hhs_exclusions,_global/gleif,sa/pcct_terrorism_list,br/*— audit each; some are genuine ownership of a company and should stay as they are.
Not affected: nomenklatura, zavod/exporters/senzing.py, consolidate.py, runner/util.py — all use schema.edge generically and pick up Control for free.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Pin a followthemoney release containing Control, then inspect zavod/zavod/integration/edges.py, datasets/_analysis/ann_graph_topics/analyzer.py, and the named crawler entry points. Start with test_ann_graph_topics.py and the crawler tests to establish current Ownership behavior. Done means Control is handled in infrastructure and applicable crawlers, with ambiguous mappings resolved and regression tests updated.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- backend, data-engineering, testing
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100