followthemoney 4.1.1__py3-none-any.whl → 4.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of followthemoney might be problematic. Click here for more details.
- followthemoney/__init__.py +1 -1
- followthemoney/entity.py +15 -0
- followthemoney/helpers.py +12 -14
- followthemoney/schema/Risk.yaml +43 -0
- followthemoney/schema/Vessel.yaml +3 -0
- followthemoney/statement/entity.py +5 -0
- followthemoney/types/country.py +2 -8
- followthemoney/types/topic.py +32 -0
- {followthemoney-4.1.1.dist-info → followthemoney-4.2.0.dist-info}/METADATA +2 -3
- {followthemoney-4.1.1.dist-info → followthemoney-4.2.0.dist-info}/RECORD +13 -12
- {followthemoney-4.1.1.dist-info → followthemoney-4.2.0.dist-info}/WHEEL +0 -0
- {followthemoney-4.1.1.dist-info → followthemoney-4.2.0.dist-info}/entry_points.txt +0 -0
- {followthemoney-4.1.1.dist-info → followthemoney-4.2.0.dist-info}/licenses/LICENSE +0 -0
followthemoney/__init__.py
CHANGED
|
@@ -9,7 +9,7 @@ from followthemoney.statement import Statement, StatementEntity, SE
|
|
|
9
9
|
from followthemoney.dataset import Dataset, DefaultDataset, DS
|
|
10
10
|
from followthemoney.util import set_model_locale
|
|
11
11
|
|
|
12
|
-
__version__ = "4.
|
|
12
|
+
__version__ = "4.2.0"
|
|
13
13
|
|
|
14
14
|
# Data model singleton
|
|
15
15
|
model = Model.instance()
|
followthemoney/entity.py
CHANGED
|
@@ -20,6 +20,21 @@ class ValueEntity(EntityProxy):
|
|
|
20
20
|
applications should use this entity class as the base class.
|
|
21
21
|
"""
|
|
22
22
|
|
|
23
|
+
__slots__ = [
|
|
24
|
+
"schema",
|
|
25
|
+
"id",
|
|
26
|
+
"key_prefix",
|
|
27
|
+
"context",
|
|
28
|
+
"_properties",
|
|
29
|
+
"_size",
|
|
30
|
+
"_caption",
|
|
31
|
+
"datasets",
|
|
32
|
+
"referents",
|
|
33
|
+
"first_seen",
|
|
34
|
+
"last_seen",
|
|
35
|
+
"last_change",
|
|
36
|
+
]
|
|
37
|
+
|
|
23
38
|
def __init__(
|
|
24
39
|
self,
|
|
25
40
|
schema: Schema,
|
followthemoney/helpers.py
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
# probably be the first place to break.
|
|
8
8
|
from os.path import splitext
|
|
9
9
|
from typing import Iterable, List, Optional, Set
|
|
10
|
-
from normality import safe_filename
|
|
10
|
+
from normality import safe_filename, squash_spaces
|
|
11
11
|
from mimetypes import guess_extension
|
|
12
12
|
from itertools import product
|
|
13
13
|
from datetime import datetime, timedelta
|
|
@@ -148,23 +148,21 @@ def inline_names(entity: E, related: E) -> None:
|
|
|
148
148
|
|
|
149
149
|
|
|
150
150
|
def combine_names(entity: E) -> E:
|
|
151
|
-
"""
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
(e.g. matching) processes."""
|
|
151
|
+
"""Build a full names from name parts of a Person entity and add them as aliases.
|
|
152
|
+
|
|
153
|
+
This is of course impossible to do culturally correctly for the whole planet at
|
|
154
|
+
once, so it should be mostly used for internal-facing (e.g. matching) processes."""
|
|
155
155
|
if entity.schema.is_a("Person"):
|
|
156
156
|
first_names = entity.get("firstName")
|
|
157
|
-
second_names = entity.get("secondName")
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
father_names = entity.get("fatherName")
|
|
162
|
-
father_names.append("")
|
|
157
|
+
second_names = entity.get("secondName") + [""]
|
|
158
|
+
middle_names = entity.get("middleName") + [""]
|
|
159
|
+
father_names = entity.get("fatherName") + [""]
|
|
160
|
+
mother_names = entity.get("motherName") + [""]
|
|
163
161
|
last_names = entity.get("lastName")
|
|
164
|
-
for (first, second, middle, father, last) in product(
|
|
165
|
-
first_names, second_names, middle_names, father_names, last_names
|
|
162
|
+
for (first, second, middle, father, mother, last) in product(
|
|
163
|
+
first_names, second_names, middle_names, father_names, mother_names, last_names
|
|
166
164
|
):
|
|
167
|
-
name =
|
|
165
|
+
name = squash_spaces(" ".join([first, second, middle, father, mother, last]))
|
|
168
166
|
if name is not None:
|
|
169
167
|
entity.add("alias", name)
|
|
170
168
|
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
Risk:
|
|
2
|
+
label: Risk
|
|
3
|
+
plural: Risks
|
|
4
|
+
description: "A risk associated with an entity"
|
|
5
|
+
extends:
|
|
6
|
+
- Interval
|
|
7
|
+
matchable: false
|
|
8
|
+
featured:
|
|
9
|
+
- entity
|
|
10
|
+
- country
|
|
11
|
+
- reason
|
|
12
|
+
- status
|
|
13
|
+
- startDate
|
|
14
|
+
required:
|
|
15
|
+
- entity
|
|
16
|
+
caption:
|
|
17
|
+
- reason
|
|
18
|
+
- status
|
|
19
|
+
properties:
|
|
20
|
+
entity:
|
|
21
|
+
label: "Entity"
|
|
22
|
+
reverse:
|
|
23
|
+
name: risks
|
|
24
|
+
label: "Risks"
|
|
25
|
+
type: entity
|
|
26
|
+
range: Thing
|
|
27
|
+
topics:
|
|
28
|
+
label: Topics
|
|
29
|
+
type: topic
|
|
30
|
+
status:
|
|
31
|
+
label: "Status"
|
|
32
|
+
duration:
|
|
33
|
+
label: "Duration"
|
|
34
|
+
type: number
|
|
35
|
+
reason:
|
|
36
|
+
label: "Reason"
|
|
37
|
+
type: text
|
|
38
|
+
country:
|
|
39
|
+
label: "Country"
|
|
40
|
+
type: country
|
|
41
|
+
listingDate:
|
|
42
|
+
label: "Listing date"
|
|
43
|
+
type: date
|
|
@@ -372,6 +372,7 @@ class StatementEntity(EntityProxy):
|
|
|
372
372
|
}
|
|
373
373
|
referents: Set[Optional[str]] = set(self.extra_referents)
|
|
374
374
|
datasets = set(self.datasets)
|
|
375
|
+
origins: Set[str] = set()
|
|
375
376
|
first_seen = None
|
|
376
377
|
last_seen = None
|
|
377
378
|
for stmts in self._statements.values():
|
|
@@ -385,9 +386,13 @@ class StatementEntity(EntityProxy):
|
|
|
385
386
|
if stmt.entity_id is not None and stmt.entity_id != self.id:
|
|
386
387
|
referents.add(stmt.entity_id)
|
|
387
388
|
datasets.add(stmt.dataset)
|
|
389
|
+
if stmt.origin is not None:
|
|
390
|
+
origins.add(stmt.origin)
|
|
388
391
|
|
|
389
392
|
data["referents"] = list(referents)
|
|
390
393
|
data["datasets"] = list(datasets)
|
|
394
|
+
if origins:
|
|
395
|
+
data["origin"] = list(origins)
|
|
391
396
|
|
|
392
397
|
if first_seen is not None:
|
|
393
398
|
data["first_seen"] = first_seen
|
followthemoney/types/country.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import countrynames
|
|
2
1
|
from typing import Optional, TYPE_CHECKING
|
|
3
2
|
from babel.core import Locale
|
|
4
|
-
from rigour.territories import
|
|
3
|
+
from rigour.territories import get_ftm_countries, lookup_territory
|
|
5
4
|
|
|
6
5
|
from followthemoney.types.common import EnumType, EnumValues
|
|
7
6
|
from followthemoney.util import const, defer as _
|
|
@@ -37,16 +36,11 @@ class CountryType(EnumType):
|
|
|
37
36
|
|
|
38
37
|
The input may be a country code, a country name, etc.
|
|
39
38
|
"""
|
|
40
|
-
territory =
|
|
39
|
+
territory = lookup_territory(text, fuzzy=fuzzy)
|
|
41
40
|
if territory is not None:
|
|
42
41
|
ftm_country = territory.ftm_country
|
|
43
42
|
if ftm_country is not None:
|
|
44
43
|
return ftm_country
|
|
45
|
-
code = countrynames.to_code(text, fuzzy=fuzzy)
|
|
46
|
-
if code is not None:
|
|
47
|
-
territory = get_territory(code)
|
|
48
|
-
if territory is not None:
|
|
49
|
-
return territory.ftm_country
|
|
50
44
|
return None
|
|
51
45
|
|
|
52
46
|
def country_hint(self, value: str) -> str:
|
followthemoney/types/topic.py
CHANGED
|
@@ -55,10 +55,14 @@ class TopicType(EnumType):
|
|
|
55
55
|
"gov.judicial": _("Judicial branch of government"),
|
|
56
56
|
"gov.security": _("Security services"),
|
|
57
57
|
"gov.financial": _("Central banking and financial integrity"),
|
|
58
|
+
"gov.religion": _("Religious leadership"),
|
|
58
59
|
"fin": _("Financial services"),
|
|
59
60
|
"fin.bank": _("Bank"),
|
|
60
61
|
"fin.fund": _("Fund"),
|
|
61
62
|
"fin.adivsor": _("Financial advisor"),
|
|
63
|
+
"mare.detained": _("Maritime detention"),
|
|
64
|
+
"mare.shadow": _("Shadow fleet"),
|
|
65
|
+
"mare.sts": _("Ship-to-ship transfer"),
|
|
62
66
|
"reg.action": _("Regulator action"),
|
|
63
67
|
"reg.warn": _("Regulator warning"),
|
|
64
68
|
"role.pep": _("Politician"),
|
|
@@ -87,5 +91,33 @@ class TopicType(EnumType):
|
|
|
87
91
|
"poi": _("Person of interest"),
|
|
88
92
|
}
|
|
89
93
|
|
|
94
|
+
RISKS = {
|
|
95
|
+
"corp.disqual",
|
|
96
|
+
"crime.boss",
|
|
97
|
+
"crime.fin",
|
|
98
|
+
"crime.fraud",
|
|
99
|
+
"crime.terror",
|
|
100
|
+
"crime.theft",
|
|
101
|
+
"crime.traffick",
|
|
102
|
+
"crime.war",
|
|
103
|
+
"crime",
|
|
104
|
+
"debarment",
|
|
105
|
+
"export.control",
|
|
106
|
+
"export.risk",
|
|
107
|
+
"poi",
|
|
108
|
+
"mare.detained",
|
|
109
|
+
"mare.shadow",
|
|
110
|
+
"reg.action",
|
|
111
|
+
"reg.warn",
|
|
112
|
+
"role.oligarch",
|
|
113
|
+
"role.pep",
|
|
114
|
+
"role.rca",
|
|
115
|
+
"sanction.counter",
|
|
116
|
+
"sanction.linked",
|
|
117
|
+
"sanction",
|
|
118
|
+
"wanted",
|
|
119
|
+
}
|
|
120
|
+
"""A set of topics that imply a counterparty risk in business dealings."""
|
|
121
|
+
|
|
90
122
|
def _locale_names(self, locale: Locale) -> EnumValues:
|
|
91
123
|
return {k: gettext(v) for (k, v) in self._TOPICS.items()}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: followthemoney
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.2.0
|
|
4
4
|
Summary: A data model for anti corruption data modeling and analysis.
|
|
5
5
|
Project-URL: Documentation, https://followthemoney.tech/
|
|
6
6
|
Project-URL: Repository, https://github.com/opensanctions/followthemoney.git
|
|
@@ -39,7 +39,6 @@ Requires-Python: >=3.10
|
|
|
39
39
|
Requires-Dist: babel<3.0.0,>=2.14.0
|
|
40
40
|
Requires-Dist: banal<1.1.0,>=1.0.6
|
|
41
41
|
Requires-Dist: click<9.0.0,>=8.0
|
|
42
|
-
Requires-Dist: countrynames<2.0.0,>=1.13.0
|
|
43
42
|
Requires-Dist: networkx<3.5,>=2.5
|
|
44
43
|
Requires-Dist: normality<4.0.0,>=3.0.1
|
|
45
44
|
Requires-Dist: openpyxl<4.0.0,>=3.0.5
|
|
@@ -51,7 +50,7 @@ Requires-Dist: pytz>=2021.1
|
|
|
51
50
|
Requires-Dist: pyyaml<7.0.0,>=5.0.0
|
|
52
51
|
Requires-Dist: rdflib<7.2.0,>=6.2.0
|
|
53
52
|
Requires-Dist: requests<3.0.0,>=2.21.0
|
|
54
|
-
Requires-Dist: rigour<2.0.0,>=1.
|
|
53
|
+
Requires-Dist: rigour<2.0.0,>=1.2.0
|
|
55
54
|
Requires-Dist: sqlalchemy[mypy]<3.0.0,>=2.0.0
|
|
56
55
|
Provides-Extra: dev
|
|
57
56
|
Requires-Dist: build; extra == 'dev'
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
followthemoney/__init__.py,sha256=
|
|
1
|
+
followthemoney/__init__.py,sha256=NqtJnba71wycGIhiZrBIfOYbafL_Tm_Qd0ouNm2EYPA,856
|
|
2
2
|
followthemoney/compare.py,sha256=bZlnj2VMoe67q4Lyq_VwS1a-EJnEK1kC8prbs8jyL9E,5774
|
|
3
|
-
followthemoney/entity.py,sha256=
|
|
3
|
+
followthemoney/entity.py,sha256=bBiX7hNquXemS3vYCUHKtWI_IqX43Z6i8RQDbZ7gXsg,3449
|
|
4
4
|
followthemoney/exc.py,sha256=GyMgwY4QVm87hLevDfV7gM1MJsDqfNCi_UQw7F_A8X8,858
|
|
5
5
|
followthemoney/graph.py,sha256=7X1CGHGvmktS2LSZqld2iXWzG7B831eCNYyBqamqEJ8,10921
|
|
6
|
-
followthemoney/helpers.py,sha256=
|
|
6
|
+
followthemoney/helpers.py,sha256=EsneNJ5DZXHTPUYfXLxGESphsPwCtGiWv-71vvVBWus,7982
|
|
7
7
|
followthemoney/messages.py,sha256=zUEa9CFecU8nRafIzhN6TKCh1kEihiIyIS1qr8PxY4g,806
|
|
8
8
|
followthemoney/model.py,sha256=bWFVNa-DhYzc8BdSXBZdG2ev6Nh9uHx6i4tin8DvEEU,7374
|
|
9
9
|
followthemoney/names.py,sha256=LODQqExKEHdH4z6Mmbhlm0KeKRzGcptaSWzYXZ7lONI,1120
|
|
@@ -97,6 +97,7 @@ followthemoney/schema/ProjectParticipant.yaml,sha256=xNehEu90uqUfboNouezhZQ8ZQLx
|
|
|
97
97
|
followthemoney/schema/PublicBody.yaml,sha256=BNfLBqH1OapoEninAjWmqZx_n-G5QUnzzydW7300TiY,301
|
|
98
98
|
followthemoney/schema/RealEstate.yaml,sha256=NWFHXqEHskYQN-kvQESZpu74nztShqoYSZEjZAr-DHM,1363
|
|
99
99
|
followthemoney/schema/Representation.yaml,sha256=sCvFnUDQaElq2cqSB0rILcMYb2gaMZqlzxlHxyX9IGg,792
|
|
100
|
+
followthemoney/schema/Risk.yaml,sha256=2BRVBqb6wiLHxb_V50P-YMAOhjC64UVHDyh5PASpCIA,728
|
|
100
101
|
followthemoney/schema/Sanction.yaml,sha256=-3_NrTp1KPSsSMkttFzwo81zFKplx15w-9EmwPnE8-o,1278
|
|
101
102
|
followthemoney/schema/Security.yaml,sha256=w8Och0cslWjHPAs60HZ6JarEXdIbqGlIbN1NlvgN_7Y,1212
|
|
102
103
|
followthemoney/schema/Similar.yaml,sha256=gD8rZEaPQWzU-rEfsKdn62uEucF3KxYBcPMoSdnxvME,817
|
|
@@ -109,11 +110,11 @@ followthemoney/schema/UnknownLink.yaml,sha256=lneS_HZNgeLyJxwzWnLx0ZoyY3MXt99I_K
|
|
|
109
110
|
followthemoney/schema/UserAccount.yaml,sha256=2bbPKNtt1R3zWSSkaq_SVzRPfFzX74kAxwtIxTymHA8,840
|
|
110
111
|
followthemoney/schema/Value.yaml,sha256=cNHTCtakMTXDW0Qpb6ArZodi9rMJ-Ebvp1WsOIRRzw4,310
|
|
111
112
|
followthemoney/schema/Vehicle.yaml,sha256=Ypl4A5HJFOZfZh3DK0ewN-hyJuCMcovR0mPNddIZrOA,1051
|
|
112
|
-
followthemoney/schema/Vessel.yaml,sha256=
|
|
113
|
+
followthemoney/schema/Vessel.yaml,sha256=zWHUfSK8g6Pz58ZyCaK0AFJ4u_UHjEIUGC4c_7oS11Y,1170
|
|
113
114
|
followthemoney/schema/Video.yaml,sha256=LY3DYMWTHXiAhL0hxBCNCz50cp2sPbUlEhhig5Fbjos,327
|
|
114
115
|
followthemoney/schema/Workbook.yaml,sha256=iikWPElz4klA7SkWH7eae6xqhbkMCIP_3zdeXzFEMU0,354
|
|
115
116
|
followthemoney/statement/__init__.py,sha256=7m2VUCAuqNZXIY0WFJRFkw5UG14QuxATL4f_xbqKwhw,633
|
|
116
|
-
followthemoney/statement/entity.py,sha256=
|
|
117
|
+
followthemoney/statement/entity.py,sha256=MKHGmFeDwcW2lTbAeKSdU53YwDuoLm5iKy4roeb8_lo,16172
|
|
117
118
|
followthemoney/statement/serialize.py,sha256=9eXzQ1biR2mSxWRID5C7xDdku4b4ZImHeRJ53yLZ0yo,7225
|
|
118
119
|
followthemoney/statement/statement.py,sha256=Ae-EYuzS8S12BkaRqrvMuI1C7YwlRKa5C_pTBELyNMM,8029
|
|
119
120
|
followthemoney/statement/util.py,sha256=B-ozuRc1TWvpop52873Pqt5OPj8H6uk4KyRJLfAhr10,780
|
|
@@ -144,7 +145,7 @@ followthemoney/types/__init__.py,sha256=rWwQeiuMh2BNIuvhpMfJ4bPADDvt9Axu1eedvNFi
|
|
|
144
145
|
followthemoney/types/address.py,sha256=nMFCj5QJyqA1ddpUmDLpRTum0nGXE-J70_WGnaLXnYo,2130
|
|
145
146
|
followthemoney/types/checksum.py,sha256=zZrU8WX4CY3Vta_vOyfgDNzIwbmtje7AaDv3O1fBMnk,823
|
|
146
147
|
followthemoney/types/common.py,sha256=4ks7zPT8rknrGSd4JFc1zRkS-TL4SX-25_ZbjcVDos0,10081
|
|
147
|
-
followthemoney/types/country.py,sha256=
|
|
148
|
+
followthemoney/types/country.py,sha256=n8vihijDVud_3Ra-as4Ize0jf_HbcdKVR5YX3TlKZy0,1533
|
|
148
149
|
followthemoney/types/date.py,sha256=PjcaEyW6CBzf0-gHWKUsKjWIaD3AVBEl0zLSRQOVXxc,3105
|
|
149
150
|
followthemoney/types/email.py,sha256=L3RTYrMABlNQF7hCynXGfzoj6YNEHW5JAY_BwuhoZdA,3375
|
|
150
151
|
followthemoney/types/entity.py,sha256=oDxVEhuxyU1ScpOpebPpUm3o0I9j_p7Qrq-t5yNpluQ,2338
|
|
@@ -158,10 +159,10 @@ followthemoney/types/name.py,sha256=ZWGDebv01qByh_yBYOVoS3Edlm3_JVPShQMklKc6ZOA,
|
|
|
158
159
|
followthemoney/types/number.py,sha256=OdVuHDd4IYIIHhx_317JKeMjBAGtsJ2TAcxoZKZ4MkY,3948
|
|
159
160
|
followthemoney/types/phone.py,sha256=r8uRqWinS0CYnYBTs405k5gO4jeatUDgjdzzijoMKJE,3811
|
|
160
161
|
followthemoney/types/string.py,sha256=fqyTauAm4mNnNaoH-yH087RBbNh-G5ZZUO3awTGQUUg,1230
|
|
161
|
-
followthemoney/types/topic.py,sha256=
|
|
162
|
+
followthemoney/types/topic.py,sha256=Mi0Gx0m3bDeTmyuvM6jdRMqv81O03U4eI99R13KGu2Y,4503
|
|
162
163
|
followthemoney/types/url.py,sha256=QFpS_JIV8unFHuh_uGv22SWUUkocBoOpzLsAJWom_gI,1455
|
|
163
|
-
followthemoney-4.
|
|
164
|
-
followthemoney-4.
|
|
165
|
-
followthemoney-4.
|
|
166
|
-
followthemoney-4.
|
|
167
|
-
followthemoney-4.
|
|
164
|
+
followthemoney-4.2.0.dist-info/METADATA,sha256=I1JXOtpniNIMi2jPoM1Vpx_IaYezqCP8l9e3qhldGtc,6748
|
|
165
|
+
followthemoney-4.2.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
166
|
+
followthemoney-4.2.0.dist-info/entry_points.txt,sha256=caoFTlf213jhg5sz3TNSofutjUTzaKtWATuSIdd9Cps,653
|
|
167
|
+
followthemoney-4.2.0.dist-info/licenses/LICENSE,sha256=H6_EVXisnJC0-18CjXIaqrBSFq_VH3OnS7u3dccOv6g,1148
|
|
168
|
+
followthemoney-4.2.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|