followthemoney 4.2.1__py3-none-any.whl → 4.3.4__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.
- followthemoney/__init__.py +1 -1
- followthemoney/compare.py +19 -0
- followthemoney/entity.py +15 -15
- followthemoney/helpers.py +13 -14
- followthemoney/model.py +2 -0
- followthemoney/property.py +23 -4
- followthemoney/proxy.py +8 -9
- followthemoney/schema/Company.yaml +9 -4
- followthemoney/schema/LegalEntity.yaml +18 -4
- followthemoney/schema/Organization.yaml +4 -0
- followthemoney/schema/Person.yaml +12 -0
- followthemoney/schema/PublicBody.yaml +3 -0
- followthemoney/schema/Thing.yaml +3 -2
- followthemoney/schema.py +16 -2
- followthemoney/statement/entity.py +39 -12
- followthemoney/statement/util.py +2 -2
- followthemoney/types/address.py +6 -4
- followthemoney/types/checksum.py +3 -3
- followthemoney/types/country.py +3 -3
- followthemoney/types/date.py +3 -3
- followthemoney/types/entity.py +3 -3
- followthemoney/types/gender.py +6 -6
- followthemoney/types/identifier.py +8 -8
- followthemoney/types/ip.py +3 -3
- followthemoney/types/json.py +2 -2
- followthemoney/types/language.py +3 -3
- followthemoney/types/mimetype.py +3 -3
- followthemoney/types/name.py +3 -3
- followthemoney/types/number.py +2 -2
- followthemoney/types/phone.py +3 -3
- followthemoney/types/string.py +2 -2
- followthemoney/types/topic.py +6 -3
- followthemoney/types/url.py +3 -3
- {followthemoney-4.2.1.dist-info → followthemoney-4.3.4.dist-info}/METADATA +3 -3
- {followthemoney-4.2.1.dist-info → followthemoney-4.3.4.dist-info}/RECORD +38 -38
- {followthemoney-4.2.1.dist-info → followthemoney-4.3.4.dist-info}/WHEEL +1 -1
- {followthemoney-4.2.1.dist-info → followthemoney-4.3.4.dist-info}/entry_points.txt +0 -0
- {followthemoney-4.2.1.dist-info → followthemoney-4.3.4.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.3.4"
|
|
13
13
|
|
|
14
14
|
# Data model singleton
|
|
15
15
|
model = Model.instance()
|
followthemoney/compare.py
CHANGED
|
@@ -71,12 +71,31 @@ def _compare(scores: Scores, weights: Weights, n_std: int = 1) -> float:
|
|
|
71
71
|
return 1.0 / (1.0 + math.exp(-prob))
|
|
72
72
|
|
|
73
73
|
|
|
74
|
+
def entity_is_same(left: EntityProxy, right: EntityProxy) -> bool:
|
|
75
|
+
"""Check if two entities are the same apart from their ID."""
|
|
76
|
+
if left.schema != right.schema:
|
|
77
|
+
return False
|
|
78
|
+
|
|
79
|
+
props = set(left.properties.keys()).union(right.properties.keys())
|
|
80
|
+
if 0 == len(props):
|
|
81
|
+
return False
|
|
82
|
+
|
|
83
|
+
for prop in props:
|
|
84
|
+
left_vals = sorted(left.get(prop))
|
|
85
|
+
right_vals = sorted(right.get(prop))
|
|
86
|
+
if left_vals != right_vals:
|
|
87
|
+
return False
|
|
88
|
+
return True
|
|
89
|
+
|
|
90
|
+
|
|
74
91
|
def compare(
|
|
75
92
|
left: EntityProxy,
|
|
76
93
|
right: EntityProxy,
|
|
77
94
|
weights: Weights = COMPARE_WEIGHTS,
|
|
78
95
|
) -> float:
|
|
79
96
|
"""Compare two entities and return a match score."""
|
|
97
|
+
if entity_is_same(left, right):
|
|
98
|
+
return 1.0
|
|
80
99
|
scores = compare_scores(left, right)
|
|
81
100
|
return _compare(scores, weights)
|
|
82
101
|
|
followthemoney/entity.py
CHANGED
|
@@ -42,25 +42,28 @@ class ValueEntity(EntityProxy):
|
|
|
42
42
|
key_prefix: Optional[str] = None,
|
|
43
43
|
cleaned: bool = True,
|
|
44
44
|
):
|
|
45
|
+
self._caption: Optional[str] = data.pop("caption", None)
|
|
46
|
+
self.datasets: Set[str] = set(data.pop("datasets", []))
|
|
47
|
+
self.referents: Set[str] = set(data.pop("referents", []))
|
|
48
|
+
self.first_seen: Optional[str] = data.pop("first_seen", None)
|
|
49
|
+
self.last_seen: Optional[str] = data.pop("last_seen", None)
|
|
50
|
+
self.last_change: Optional[str] = data.pop("last_change", None)
|
|
45
51
|
super().__init__(schema, data, key_prefix=key_prefix, cleaned=cleaned)
|
|
46
|
-
self._caption: Optional[str] = data.get("caption")
|
|
47
|
-
self.datasets: Set[str] = set(data.get("datasets", []))
|
|
48
|
-
self.referents: Set[str] = set(data.get("referents", []))
|
|
49
|
-
self.first_seen: Optional[str] = data.get("first_seen")
|
|
50
|
-
self.last_seen: Optional[str] = data.get("last_seen")
|
|
51
|
-
self.last_change: Optional[str] = data.get("last_change")
|
|
52
52
|
|
|
53
53
|
# add data from statement dict if present.
|
|
54
54
|
# this updates the dataset and referents set
|
|
55
55
|
for stmt_data in data.pop("statements", []):
|
|
56
56
|
stmt = Statement.from_dict(stmt_data)
|
|
57
|
+
prop = schema.get(stmt.prop)
|
|
58
|
+
if prop is None:
|
|
59
|
+
continue
|
|
57
60
|
self.datasets.add(stmt.dataset)
|
|
58
61
|
if stmt.schema != self.schema.name:
|
|
59
62
|
self.schema = schema.model.common_schema(self.schema, stmt.schema)
|
|
60
63
|
if stmt.entity_id != self.id:
|
|
61
64
|
self.referents.add(stmt.entity_id)
|
|
62
65
|
if stmt.prop != BASE_ID:
|
|
63
|
-
self.
|
|
66
|
+
self.unsafe_add(prop, stmt.value, cleaned=cleaned)
|
|
64
67
|
|
|
65
68
|
def merge(self: VE, other: EntityProxy) -> VE:
|
|
66
69
|
merged = super().merge(other)
|
|
@@ -79,14 +82,11 @@ class ValueEntity(EntityProxy):
|
|
|
79
82
|
return merged
|
|
80
83
|
|
|
81
84
|
def to_dict(self) -> Dict[str, Any]:
|
|
82
|
-
data
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
"
|
|
87
|
-
"referents": list(self.referents),
|
|
88
|
-
"datasets": list(self.datasets),
|
|
89
|
-
}
|
|
85
|
+
data = super().to_dict()
|
|
86
|
+
data["referents"] = list(self.referents)
|
|
87
|
+
data["datasets"] = list(self.datasets)
|
|
88
|
+
if self._caption is not None:
|
|
89
|
+
data["caption"] = self._caption
|
|
90
90
|
if self.first_seen is not None:
|
|
91
91
|
data["first_seen"] = self.first_seen
|
|
92
92
|
if self.last_seen is not None:
|
followthemoney/helpers.py
CHANGED
|
@@ -13,8 +13,7 @@ from itertools import product
|
|
|
13
13
|
from datetime import datetime, timedelta
|
|
14
14
|
|
|
15
15
|
from followthemoney.types import registry
|
|
16
|
-
from followthemoney.proxy import E
|
|
17
|
-
from followthemoney.util import join_text
|
|
16
|
+
from followthemoney.proxy import E, EntityProxy
|
|
18
17
|
|
|
19
18
|
PROV_MIN_DATES = ("createdAt", "authoredAt", "publishedAt")
|
|
20
19
|
PROV_MAX_DATES = ("modifiedAt", "retrievedAt")
|
|
@@ -47,7 +46,7 @@ def simplify_provenance(proxy: E) -> E:
|
|
|
47
46
|
|
|
48
47
|
|
|
49
48
|
def entity_filename(
|
|
50
|
-
proxy:
|
|
49
|
+
proxy: EntityProxy, base_name: Optional[str] = None, extension: Optional[str] = None
|
|
51
50
|
) -> Optional[str]:
|
|
52
51
|
"""Derive a safe filename for the given entity."""
|
|
53
52
|
if proxy.schema.is_a("Document"):
|
|
@@ -85,7 +84,7 @@ def name_entity(entity: E) -> E:
|
|
|
85
84
|
|
|
86
85
|
|
|
87
86
|
def check_person_cutoff(
|
|
88
|
-
entity:
|
|
87
|
+
entity: EntityProxy,
|
|
89
88
|
death_cutoff: datetime = datetime(2000, 1, 1),
|
|
90
89
|
birth_cutoff: Optional[datetime] = None,
|
|
91
90
|
) -> bool:
|
|
@@ -153,17 +152,17 @@ def combine_names(entity: E) -> E:
|
|
|
153
152
|
This is of course impossible to do culturally correctly for the whole planet at
|
|
154
153
|
once, so it should be mostly used for internal-facing (e.g. matching) processes."""
|
|
155
154
|
if entity.schema.is_a("Person"):
|
|
156
|
-
first_names = entity.get("firstName")
|
|
157
|
-
second_names = entity.get("secondName") + [""]
|
|
158
|
-
middle_names = entity.get("middleName") + [""]
|
|
159
|
-
father_names = entity.get("fatherName") + [""]
|
|
160
|
-
mother_names = entity.get("motherName") + [""]
|
|
161
155
|
last_names = entity.get("lastName")
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
)
|
|
165
|
-
|
|
166
|
-
|
|
156
|
+
names_seq = [entity.get("firstName")]
|
|
157
|
+
names_seq.append(entity.get("secondName"))
|
|
158
|
+
names_seq.append(entity.get("middleName"))
|
|
159
|
+
names_seq.append(entity.get("fatherName"))
|
|
160
|
+
names_seq.append(entity.get("motherName"))
|
|
161
|
+
names_seq.append(last_names)
|
|
162
|
+
names_seq = [n for n in names_seq if len(n)]
|
|
163
|
+
for pairing in product(*names_seq):
|
|
164
|
+
name = squash_spaces(" ".join(pairing))
|
|
165
|
+
if len(name):
|
|
167
166
|
entity.add("alias", name)
|
|
168
167
|
|
|
169
168
|
# If no first name is given, at least add the last name:
|
followthemoney/model.py
CHANGED
|
@@ -9,6 +9,7 @@ from followthemoney.types.common import PropertyType, PropertyTypeToDict
|
|
|
9
9
|
from followthemoney.schema import Schema, SchemaToDict
|
|
10
10
|
from followthemoney.property import Property
|
|
11
11
|
from followthemoney.exc import InvalidModel, InvalidData
|
|
12
|
+
from followthemoney.util import const
|
|
12
13
|
|
|
13
14
|
if TYPE_CHECKING:
|
|
14
15
|
from followthemoney.proxy import EntityProxy
|
|
@@ -72,6 +73,7 @@ class Model(object):
|
|
|
72
73
|
if not isinstance(data, dict):
|
|
73
74
|
raise InvalidModel("Model file is not a mapping: %s" % filepath)
|
|
74
75
|
for name, config in data.items():
|
|
76
|
+
name = const(name)
|
|
75
77
|
self.schemata[name] = Schema(self, name, config)
|
|
76
78
|
|
|
77
79
|
def get(self, name: Union[str, Schema]) -> Optional[Schema]:
|
followthemoney/property.py
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import re
|
|
2
2
|
from banal import is_mapping, as_bool
|
|
3
|
+
from rigour.ids import get_identifier_format
|
|
3
4
|
from typing import TYPE_CHECKING, Any, List, Optional, TypedDict
|
|
4
5
|
|
|
5
|
-
from followthemoney.exc import InvalidModel
|
|
6
|
+
from followthemoney.exc import InvalidData, InvalidModel
|
|
6
7
|
from followthemoney.types import registry
|
|
7
8
|
from followthemoney.util import gettext, get_entity_id, const
|
|
8
9
|
|
|
@@ -86,17 +87,16 @@ class Property:
|
|
|
86
87
|
self.schema = schema
|
|
87
88
|
|
|
88
89
|
#: Machine-readable name for this property.
|
|
89
|
-
self.name =
|
|
90
|
+
self.name = name
|
|
90
91
|
if not check_property_name(self.name):
|
|
91
92
|
raise InvalidModel("Invalid name: %s" % self.name)
|
|
92
93
|
|
|
93
94
|
#: Qualified property name, which also includes the schema name.
|
|
94
95
|
self.qname = const("%s:%s" % (schema.name, self.name))
|
|
95
96
|
|
|
96
|
-
self._hash = hash("<Property(%r)>" % self.qname)
|
|
97
|
-
|
|
98
97
|
self._label = data.get("label", name)
|
|
99
98
|
self._description = data.get("description")
|
|
99
|
+
self._hash = hash("<Property(%r)>" % self.qname)
|
|
100
100
|
|
|
101
101
|
#: This property is deprecated and should not be used.
|
|
102
102
|
self.deprecated = as_bool(data.get("deprecated", False))
|
|
@@ -157,6 +157,13 @@ class Property:
|
|
|
157
157
|
raise InvalidModel("Invalid reverse: %s" % self)
|
|
158
158
|
self.reverse = self.range._add_reverse(model, self._reverse, self)
|
|
159
159
|
|
|
160
|
+
if self.type == registry.identifier and self.format is not None:
|
|
161
|
+
format_ = get_identifier_format(self.format)
|
|
162
|
+
if format_ is None or format_.NAME != self.format:
|
|
163
|
+
raise InvalidModel("Invalid identifier format: %s" % self.format)
|
|
164
|
+
# Internalize the string:
|
|
165
|
+
self.format = format_.NAME
|
|
166
|
+
|
|
160
167
|
@property
|
|
161
168
|
def label(self) -> str:
|
|
162
169
|
"""User-facing title for this property."""
|
|
@@ -229,6 +236,18 @@ class Property:
|
|
|
229
236
|
data["format"] = self.format
|
|
230
237
|
return data
|
|
231
238
|
|
|
239
|
+
def __reduce__(self) -> Any:
|
|
240
|
+
return (self._reconstruct, (self.qname,))
|
|
241
|
+
|
|
242
|
+
@classmethod
|
|
243
|
+
def _reconstruct(cls, qname: str) -> "Property":
|
|
244
|
+
from followthemoney.model import Model
|
|
245
|
+
|
|
246
|
+
prop = Model.instance().get_qname(qname)
|
|
247
|
+
if prop is None:
|
|
248
|
+
raise InvalidData("Unknown property: %r" % qname)
|
|
249
|
+
return prop
|
|
250
|
+
|
|
232
251
|
def __repr__(self) -> str:
|
|
233
252
|
return "<Property(%r)>" % self.qname
|
|
234
253
|
|
followthemoney/proxy.py
CHANGED
|
@@ -323,7 +323,7 @@ class EntityProxy(object):
|
|
|
323
323
|
@property
|
|
324
324
|
def countries(self) -> List[str]:
|
|
325
325
|
"""Get the set of all country-type values set of the entity."""
|
|
326
|
-
return self.get_type_values(registry.country)
|
|
326
|
+
return self.get_type_values(registry.country, matchable=True)
|
|
327
327
|
|
|
328
328
|
@property
|
|
329
329
|
def temporal_start(self) -> Optional[Tuple[Property, str]]:
|
|
@@ -386,6 +386,8 @@ class EntityProxy(object):
|
|
|
386
386
|
countries = set(self.countries)
|
|
387
387
|
if not len(countries):
|
|
388
388
|
for prop, value in self.itervalues():
|
|
389
|
+
if not prop.matchable:
|
|
390
|
+
continue
|
|
389
391
|
hint = prop.type.country_hint(value)
|
|
390
392
|
if hint is not None:
|
|
391
393
|
countries.add(hint)
|
|
@@ -401,13 +403,10 @@ class EntityProxy(object):
|
|
|
401
403
|
schema and any contextual values that were handed in initially. The resulting
|
|
402
404
|
dictionary can be used to make a new proxy, and it is commonly written to disk
|
|
403
405
|
or a database."""
|
|
404
|
-
data = dict(self.context)
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
"properties": self.properties,
|
|
409
|
-
}
|
|
410
|
-
data.update(extra)
|
|
406
|
+
data: Dict[str, Any] = dict(self.context)
|
|
407
|
+
data["id"] = self.id
|
|
408
|
+
data["schema"] = self.schema.name
|
|
409
|
+
data["properties"] = self.properties
|
|
411
410
|
return data
|
|
412
411
|
|
|
413
412
|
def to_full_dict(self, matchable: bool = False) -> Dict[str, Any]:
|
|
@@ -460,7 +459,7 @@ class EntityProxy(object):
|
|
|
460
459
|
return self._size
|
|
461
460
|
|
|
462
461
|
def __hash__(self) -> int:
|
|
463
|
-
if
|
|
462
|
+
if self.id is None:
|
|
464
463
|
raise RuntimeError("Cannot hash entity without an ID")
|
|
465
464
|
return hash(self.id)
|
|
466
465
|
|
|
@@ -18,6 +18,10 @@ Company:
|
|
|
18
18
|
- name
|
|
19
19
|
caption:
|
|
20
20
|
- name
|
|
21
|
+
- alias
|
|
22
|
+
- weakAlias
|
|
23
|
+
- previousName
|
|
24
|
+
- registrationNumber
|
|
21
25
|
properties:
|
|
22
26
|
jurisdiction:
|
|
23
27
|
label: Jurisdiction
|
|
@@ -58,20 +62,20 @@ Company:
|
|
|
58
62
|
type: identifier
|
|
59
63
|
caemCode:
|
|
60
64
|
label: "COD CAEM"
|
|
61
|
-
description: "
|
|
65
|
+
description: "Romanian classifier used to identify the types of economic activities that a business can provide in Romania"
|
|
62
66
|
matchable: false
|
|
63
67
|
kppCode:
|
|
64
68
|
label: "KPP"
|
|
65
|
-
description: "
|
|
69
|
+
description: "Russian code issued by the tax authority, identifies the reason for registration at the Federal Tax Service (Russian: КПП). A company may have multiple KPP codes (e.g. for different branches), and the codes are not unique across companies."
|
|
66
70
|
type: identifier
|
|
67
71
|
matchable: false
|
|
68
72
|
okvedCode:
|
|
69
73
|
label: "OKVED(2) Classifier"
|
|
70
|
-
description: "
|
|
74
|
+
description: "Russian classifier that that categorizes businesses by their (primary and secondary) economic activities (Russian: ОКВЭД)"
|
|
71
75
|
matchable: false
|
|
72
76
|
okopfCode:
|
|
73
77
|
label: "OKOPF"
|
|
74
|
-
description: "
|
|
78
|
+
description: "Russian classifier that that categorizes different types of legal entities in Russia based on their organizational and legal structure (Russian: ОКОПФ)"
|
|
75
79
|
matchable: false
|
|
76
80
|
fnsCode:
|
|
77
81
|
label: "Federal tax service code"
|
|
@@ -84,6 +88,7 @@ Company:
|
|
|
84
88
|
bikCode:
|
|
85
89
|
label: "BIK"
|
|
86
90
|
description: "Russian bank account code"
|
|
91
|
+
type: identifier
|
|
87
92
|
pfrNumber:
|
|
88
93
|
label: "PFR Number"
|
|
89
94
|
description: "(RU, ПФР) Pension Fund Registration number. AAA-BBB-CCCCCC, where AAA is organisation region, BBB is district, CCCCCC number at a specific branch"
|
|
@@ -17,6 +17,9 @@ LegalEntity:
|
|
|
17
17
|
- name
|
|
18
18
|
caption:
|
|
19
19
|
- name
|
|
20
|
+
- alias
|
|
21
|
+
- weakAlias
|
|
22
|
+
- previousName
|
|
20
23
|
- email
|
|
21
24
|
- phone
|
|
22
25
|
- registrationNumber
|
|
@@ -74,7 +77,7 @@ LegalEntity:
|
|
|
74
77
|
idNumber:
|
|
75
78
|
label: ID Number
|
|
76
79
|
type: identifier
|
|
77
|
-
description: "ID
|
|
80
|
+
description: "ID of any applicable personal identification document. Used mainly for people and their national ID cards."
|
|
78
81
|
taxNumber:
|
|
79
82
|
label: Tax Number
|
|
80
83
|
type: identifier
|
|
@@ -102,6 +105,17 @@ LegalEntity:
|
|
|
102
105
|
bvdId:
|
|
103
106
|
label: Bureau van Dijk ID
|
|
104
107
|
type: identifier
|
|
108
|
+
sayariId:
|
|
109
|
+
label: Sayari Entity ID
|
|
110
|
+
type: identifier
|
|
111
|
+
brightQueryId:
|
|
112
|
+
label: BrightQuery ID
|
|
113
|
+
type: identifier
|
|
114
|
+
brightQueryOrgId:
|
|
115
|
+
label: BrightQuery Organization ID
|
|
116
|
+
type: identifier
|
|
117
|
+
hidden: true
|
|
118
|
+
matchable: false
|
|
105
119
|
uscCode:
|
|
106
120
|
# cf. https://en.wikipedia.org/wiki/Unified_Social_Credit_Identifier
|
|
107
121
|
label: "USCC"
|
|
@@ -118,13 +132,13 @@ LegalEntity:
|
|
|
118
132
|
matchable: false
|
|
119
133
|
innCode:
|
|
120
134
|
label: "INN"
|
|
121
|
-
description: "Russian
|
|
135
|
+
description: "Russian tax identification number (Russian: ИНН). Issued to businesses and individuals in Russia"
|
|
122
136
|
type: identifier
|
|
123
137
|
format: inn
|
|
124
138
|
maxLength: 32
|
|
125
139
|
ogrnCode:
|
|
126
140
|
label: "OGRN"
|
|
127
|
-
description: "
|
|
141
|
+
description: "Identification number used in Russia's Unified State Register of Legal Entities (EGRUL) (Russian: ОГРН)"
|
|
128
142
|
type: identifier
|
|
129
143
|
format: ogrn
|
|
130
144
|
maxLength: 32
|
|
@@ -148,7 +162,7 @@ LegalEntity:
|
|
|
148
162
|
maxLength: 32
|
|
149
163
|
npiCode:
|
|
150
164
|
label: "NPI"
|
|
151
|
-
description: "National Provider Identifier"
|
|
165
|
+
description: "National Provider Identifier, issued to health care providers in the United States"
|
|
152
166
|
type: identifier
|
|
153
167
|
format: npi
|
|
154
168
|
maxLength: 16
|
|
@@ -14,6 +14,9 @@ Person:
|
|
|
14
14
|
- name
|
|
15
15
|
caption:
|
|
16
16
|
- name
|
|
17
|
+
- alias
|
|
18
|
+
- weakAlias
|
|
19
|
+
- previousName
|
|
17
20
|
- lastName
|
|
18
21
|
- email
|
|
19
22
|
- phone
|
|
@@ -31,16 +34,23 @@ Person:
|
|
|
31
34
|
# too many false positives.
|
|
32
35
|
firstName:
|
|
33
36
|
label: First name
|
|
37
|
+
description: "The part of a name that indicates the person, also often called given name or forename"
|
|
34
38
|
secondName:
|
|
35
39
|
label: Second name
|
|
40
|
+
description: "Deprecated, use one of the other more specific name properties instead."
|
|
41
|
+
deprecated: true
|
|
36
42
|
middleName:
|
|
37
43
|
label: Middle name
|
|
44
|
+
description: "The part of name written between a person's given name and family name. Often abbreviated as a middle initial."
|
|
38
45
|
fatherName:
|
|
39
46
|
label: Patronymic
|
|
47
|
+
description: "The part of a name based on the given name of one's father"
|
|
40
48
|
motherName:
|
|
41
49
|
label: Matronymic
|
|
50
|
+
description: "The part of a name based on the given name of one's mother"
|
|
42
51
|
lastName:
|
|
43
52
|
label: Last name
|
|
53
|
+
description: "The part of a name that indicates one's family, also often called surname or family name"
|
|
44
54
|
nameSuffix:
|
|
45
55
|
label: Name suffix
|
|
46
56
|
birthDate:
|
|
@@ -92,6 +102,8 @@ Person:
|
|
|
92
102
|
label: Religion
|
|
93
103
|
political:
|
|
94
104
|
label: Political association
|
|
105
|
+
profession:
|
|
106
|
+
label: Profession
|
|
95
107
|
education:
|
|
96
108
|
label: Education
|
|
97
109
|
spokenLanguage:
|
followthemoney/schema/Thing.yaml
CHANGED
|
@@ -24,7 +24,7 @@ Thing:
|
|
|
24
24
|
label: Country
|
|
25
25
|
type: country
|
|
26
26
|
alias:
|
|
27
|
-
label:
|
|
27
|
+
label: Alias
|
|
28
28
|
type: name
|
|
29
29
|
previousName:
|
|
30
30
|
label: Previous name
|
|
@@ -32,6 +32,7 @@ Thing:
|
|
|
32
32
|
weakAlias:
|
|
33
33
|
label: Weak alias
|
|
34
34
|
type: name
|
|
35
|
+
description: "A relatively broad or generic alias that should not be used for matching in screening systems. It may still may be useful for identification purposes, particularly in confirming a possible match triggered by other identifier information."
|
|
35
36
|
matchable: false
|
|
36
37
|
sourceUrl:
|
|
37
38
|
label: Source link
|
|
@@ -55,7 +56,7 @@ Thing:
|
|
|
55
56
|
wikidataId:
|
|
56
57
|
label: Wikidata ID
|
|
57
58
|
type: identifier
|
|
58
|
-
format:
|
|
59
|
+
format: wikidata
|
|
59
60
|
maxLength: 32
|
|
60
61
|
keywords:
|
|
61
62
|
label: Keywords
|
followthemoney/schema.py
CHANGED
|
@@ -106,7 +106,7 @@ class Schema:
|
|
|
106
106
|
|
|
107
107
|
def __init__(self, model: "Model", name: str, data: SchemaSpec) -> None:
|
|
108
108
|
#: Machine-readable name of the schema, used for identification.
|
|
109
|
-
self.name =
|
|
109
|
+
self.name = name
|
|
110
110
|
self.model = model
|
|
111
111
|
self._label = data.get("label", name)
|
|
112
112
|
self._plural = data.get("plural", self.label)
|
|
@@ -191,6 +191,7 @@ class Schema:
|
|
|
191
191
|
#: inherited from parent schemata.
|
|
192
192
|
self.properties: Dict[str, Property] = {}
|
|
193
193
|
for pname, prop in data.get("properties", {}).items():
|
|
194
|
+
pname = const(pname)
|
|
194
195
|
self.properties[pname] = Property(self, pname, prop)
|
|
195
196
|
|
|
196
197
|
def generate(self, model: "Model") -> None:
|
|
@@ -264,6 +265,7 @@ class Schema:
|
|
|
264
265
|
name = data.get("name")
|
|
265
266
|
if name is None:
|
|
266
267
|
raise InvalidModel("Unnamed reverse: %s" % other)
|
|
268
|
+
name = const(name)
|
|
267
269
|
|
|
268
270
|
prop = self.get(name)
|
|
269
271
|
if prop is None:
|
|
@@ -272,7 +274,7 @@ class Schema:
|
|
|
272
274
|
"type": registry.entity.name,
|
|
273
275
|
"reverse": {"name": other.name},
|
|
274
276
|
"range": other.schema.name,
|
|
275
|
-
"hidden": data.get("hidden", other.hidden),
|
|
277
|
+
"hidden": as_bool(data.get("hidden", other.hidden)),
|
|
276
278
|
}
|
|
277
279
|
prop = Property(self, name, spec)
|
|
278
280
|
prop.stub = True
|
|
@@ -466,6 +468,18 @@ class Schema:
|
|
|
466
468
|
data["properties"] = properties
|
|
467
469
|
return data
|
|
468
470
|
|
|
471
|
+
def __reduce__(self) -> Any:
|
|
472
|
+
return (self._reconstruct, (self.name,))
|
|
473
|
+
|
|
474
|
+
@classmethod
|
|
475
|
+
def _reconstruct(cls, name: str) -> "Schema":
|
|
476
|
+
from followthemoney.model import Model
|
|
477
|
+
|
|
478
|
+
schema = Model.instance().get(name)
|
|
479
|
+
if schema is None:
|
|
480
|
+
raise InvalidData("Unknown schema: %r" % name)
|
|
481
|
+
return schema
|
|
482
|
+
|
|
469
483
|
def __eq__(self, other: Any) -> bool:
|
|
470
484
|
"""Compare two schemata (via hash)."""
|
|
471
485
|
try:
|
|
@@ -7,6 +7,7 @@ from rigour.names.pick import pick_lang_name
|
|
|
7
7
|
|
|
8
8
|
from followthemoney.model import Model
|
|
9
9
|
from followthemoney.exc import InvalidData
|
|
10
|
+
from followthemoney.schema import Schema
|
|
10
11
|
from followthemoney.types.common import PropertyType
|
|
11
12
|
from followthemoney.property import Property
|
|
12
13
|
from followthemoney.util import gettext
|
|
@@ -361,11 +362,11 @@ class StatementEntity(EntityProxy):
|
|
|
361
362
|
name = pick_lang_name(values)
|
|
362
363
|
if name is not None:
|
|
363
364
|
self._caption = name
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
365
|
+
return self._caption
|
|
366
|
+
|
|
367
|
+
for stmt in sorted(stmts):
|
|
368
|
+
self._caption = stmt.value
|
|
369
|
+
return self._caption
|
|
369
370
|
if self._caption is None:
|
|
370
371
|
self._caption = self.schema.label
|
|
371
372
|
return self._caption
|
|
@@ -477,12 +478,38 @@ class StatementEntity(EntityProxy):
|
|
|
477
478
|
dataset: Dataset,
|
|
478
479
|
statements: Iterable[Statement],
|
|
479
480
|
) -> SE:
|
|
480
|
-
|
|
481
|
+
model = Model.instance()
|
|
482
|
+
canonical_id: Optional[str] = None
|
|
483
|
+
schemata: Set[str] = set()
|
|
484
|
+
first_seens: Set[str] = set()
|
|
485
|
+
props: Dict[str, Set[Statement]] = {}
|
|
481
486
|
for stmt in statements:
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
487
|
+
schemata.add(stmt.schema)
|
|
488
|
+
canonical_id = stmt.canonical_id or canonical_id or stmt.entity_id
|
|
489
|
+
if stmt.prop == BASE_ID:
|
|
490
|
+
if stmt.first_seen is not None:
|
|
491
|
+
first_seens.add(stmt.first_seen)
|
|
492
|
+
else:
|
|
493
|
+
if stmt.prop not in props:
|
|
494
|
+
props[stmt.prop] = set()
|
|
495
|
+
props[stmt.prop].add(stmt)
|
|
496
|
+
|
|
497
|
+
schema: Optional[Schema] = None
|
|
498
|
+
for name in schemata:
|
|
499
|
+
if schema is None:
|
|
500
|
+
schema = model.get(name)
|
|
501
|
+
elif schema.name != name:
|
|
502
|
+
try:
|
|
503
|
+
schema = model.common_schema(schema, name)
|
|
504
|
+
except InvalidData as exc:
|
|
505
|
+
raise InvalidData(f"{canonical_id}: {exc}") from exc
|
|
506
|
+
|
|
507
|
+
if schema is None:
|
|
508
|
+
err = "No valid schema for entity: %s %r" % (canonical_id, schemata)
|
|
509
|
+
raise InvalidData(err)
|
|
510
|
+
|
|
511
|
+
data = {"schema": schema, "id": canonical_id}
|
|
512
|
+
obj = cls(dataset, data)
|
|
513
|
+
obj.last_change = max(first_seens, default=None)
|
|
514
|
+
obj._statements = {p: s for p, s in props.items()}
|
|
488
515
|
return obj
|
followthemoney/statement/util.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import sys
|
|
2
1
|
from functools import cache
|
|
3
2
|
from typing import Tuple
|
|
4
3
|
|
|
5
4
|
from followthemoney.model import Model
|
|
5
|
+
from followthemoney.util import const
|
|
6
6
|
|
|
7
7
|
BASE_ID = "id"
|
|
8
8
|
|
|
@@ -28,4 +28,4 @@ def get_prop_type(schema: str, prop: str) -> str:
|
|
|
28
28
|
def unpack_prop(id: str) -> Tuple[str, str, str]:
|
|
29
29
|
schema, prop = id.split(":", 1)
|
|
30
30
|
prop_type = get_prop_type(schema, prop)
|
|
31
|
-
return
|
|
31
|
+
return const(schema), prop_type, const(prop)
|
followthemoney/types/address.py
CHANGED
|
@@ -6,7 +6,7 @@ from rigour.text.distance import levenshtein_similarity
|
|
|
6
6
|
|
|
7
7
|
from followthemoney.types.common import PropertyType
|
|
8
8
|
from followthemoney.util import defer as _
|
|
9
|
-
from followthemoney.util import dampen
|
|
9
|
+
from followthemoney.util import dampen
|
|
10
10
|
|
|
11
11
|
if TYPE_CHECKING:
|
|
12
12
|
from followthemoney.proxy import EntityProxy
|
|
@@ -20,8 +20,8 @@ class AddressType(PropertyType):
|
|
|
20
20
|
|
|
21
21
|
LINE_BREAKS = re.compile(r"(\r\n|\n|<BR/>|<BR>|\t|ESQ\.,|ESQ,|;)")
|
|
22
22
|
COMMATA = re.compile(r"(,\s?[,\.])")
|
|
23
|
-
name =
|
|
24
|
-
group =
|
|
23
|
+
name = "address"
|
|
24
|
+
group = "addresses"
|
|
25
25
|
label = _("Address")
|
|
26
26
|
plural = _("Addresses")
|
|
27
27
|
matchable = True
|
|
@@ -47,7 +47,9 @@ class AddressType(PropertyType):
|
|
|
47
47
|
right_norm = normalize_address(right)
|
|
48
48
|
if left_norm is None or right_norm is None:
|
|
49
49
|
return 0.0
|
|
50
|
-
|
|
50
|
+
base_len = min(len(left_norm), len(right_norm))
|
|
51
|
+
max_edits = int(base_len * 0.33)
|
|
52
|
+
return levenshtein_similarity(left_norm, right_norm, max_edits=max_edits)
|
|
51
53
|
|
|
52
54
|
def _specificity(self, value: str) -> float:
|
|
53
55
|
return dampen(10, 60, value)
|
followthemoney/types/checksum.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from followthemoney.types.common import PropertyType
|
|
2
|
-
from followthemoney.util import
|
|
2
|
+
from followthemoney.util import defer as _
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
class ChecksumType(PropertyType):
|
|
@@ -12,8 +12,8 @@ class ChecksumType(PropertyType):
|
|
|
12
12
|
of this type are scrubbed when submitted via the normal API. Checksums can only
|
|
13
13
|
be defined by uploading a document to be ingested."""
|
|
14
14
|
|
|
15
|
-
name =
|
|
16
|
-
group =
|
|
15
|
+
name = "checksum"
|
|
16
|
+
group = "checksums"
|
|
17
17
|
label = _("Checksum")
|
|
18
18
|
plural = _("Checksums")
|
|
19
19
|
matchable = True
|
followthemoney/types/country.py
CHANGED
|
@@ -3,7 +3,7 @@ from babel.core import Locale
|
|
|
3
3
|
from rigour.territories import get_ftm_countries, lookup_territory
|
|
4
4
|
|
|
5
5
|
from followthemoney.types.common import EnumType, EnumValues
|
|
6
|
-
from followthemoney.util import
|
|
6
|
+
from followthemoney.util import defer as _
|
|
7
7
|
|
|
8
8
|
if TYPE_CHECKING:
|
|
9
9
|
from followthemoney.proxy import EntityProxy
|
|
@@ -15,8 +15,8 @@ class CountryType(EnumType):
|
|
|
15
15
|
a number of unusual and controversial designations (e.g. the Soviet Union,
|
|
16
16
|
Transnistria, Somaliland, Kosovo)."""
|
|
17
17
|
|
|
18
|
-
name =
|
|
19
|
-
group =
|
|
18
|
+
name = "country"
|
|
19
|
+
group = "countries"
|
|
20
20
|
label = _("Country")
|
|
21
21
|
plural = _("Countries")
|
|
22
22
|
matchable = True
|
followthemoney/types/date.py
CHANGED
|
@@ -5,7 +5,7 @@ from prefixdate import parse, parse_format, Precision
|
|
|
5
5
|
|
|
6
6
|
from followthemoney.types.common import PropertyType
|
|
7
7
|
from followthemoney.util import defer as _
|
|
8
|
-
from followthemoney.util import dampen
|
|
8
|
+
from followthemoney.util import dampen
|
|
9
9
|
|
|
10
10
|
if TYPE_CHECKING:
|
|
11
11
|
from followthemoney.proxy import EntityProxy
|
|
@@ -20,8 +20,8 @@ class DateType(PropertyType):
|
|
|
20
20
|
The timezone is always expected to be UTC and cannot be specified otherwise. There is
|
|
21
21
|
no support for calendar weeks (`2021-W7`) and date ranges (`2021-2024`)."""
|
|
22
22
|
|
|
23
|
-
name =
|
|
24
|
-
group =
|
|
23
|
+
name = "date"
|
|
24
|
+
group = "dates"
|
|
25
25
|
label = _("Date")
|
|
26
26
|
plural = _("Dates")
|
|
27
27
|
matchable = True
|
followthemoney/types/entity.py
CHANGED
|
@@ -4,7 +4,7 @@ from typing import Any, Optional, TYPE_CHECKING
|
|
|
4
4
|
from followthemoney.types.common import PropertyType
|
|
5
5
|
from followthemoney.value import Value
|
|
6
6
|
from followthemoney.util import ENTITY_ID_LEN, get_entity_id, sanitize_text
|
|
7
|
-
from followthemoney.util import
|
|
7
|
+
from followthemoney.util import gettext, defer as _
|
|
8
8
|
from followthemoney.exc import InvalidData
|
|
9
9
|
|
|
10
10
|
if TYPE_CHECKING:
|
|
@@ -22,8 +22,8 @@ class EntityType(PropertyType):
|
|
|
22
22
|
|
|
23
23
|
REGEX_RAW = r"^[0-9a-zA-Z]([0-9a-zA-Z\.\-]*[0-9a-zA-Z])?$"
|
|
24
24
|
REGEX = re.compile(REGEX_RAW)
|
|
25
|
-
name =
|
|
26
|
-
group =
|
|
25
|
+
name = "entity"
|
|
26
|
+
group = "entities"
|
|
27
27
|
label = _("Entity")
|
|
28
28
|
plural = _("Entities")
|
|
29
29
|
matchable = True
|
followthemoney/types/gender.py
CHANGED
|
@@ -2,7 +2,7 @@ from typing import Optional, TYPE_CHECKING
|
|
|
2
2
|
from babel.core import Locale
|
|
3
3
|
|
|
4
4
|
from followthemoney.types.common import EnumType, EnumValues
|
|
5
|
-
from followthemoney.util import
|
|
5
|
+
from followthemoney.util import gettext, defer as _
|
|
6
6
|
|
|
7
7
|
if TYPE_CHECKING:
|
|
8
8
|
from followthemoney.proxy import EntityProxy
|
|
@@ -14,9 +14,9 @@ class GenderType(EnumType):
|
|
|
14
14
|
government databases and represent it in a way that can be used by
|
|
15
15
|
structured tools. I'm not sure this justifies the simplification."""
|
|
16
16
|
|
|
17
|
-
MALE =
|
|
18
|
-
FEMALE =
|
|
19
|
-
OTHER =
|
|
17
|
+
MALE = "male"
|
|
18
|
+
FEMALE = "female"
|
|
19
|
+
OTHER = "other"
|
|
20
20
|
|
|
21
21
|
LOOKUP = {
|
|
22
22
|
"m": MALE,
|
|
@@ -34,8 +34,8 @@ class GenderType(EnumType):
|
|
|
34
34
|
"divers": OTHER,
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
name =
|
|
38
|
-
group =
|
|
37
|
+
name = "gender"
|
|
38
|
+
group = "genders"
|
|
39
39
|
label = _("Gender")
|
|
40
40
|
plural = _("Genders")
|
|
41
41
|
matchable = False
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import re
|
|
2
2
|
from typing import Optional, TYPE_CHECKING
|
|
3
|
-
from rigour.ids import
|
|
3
|
+
from rigour.ids import get_identifier_format
|
|
4
4
|
|
|
5
5
|
from followthemoney.types.common import PropertyType
|
|
6
6
|
from followthemoney.util import dampen, shortest, longest
|
|
7
|
-
from followthemoney.util import
|
|
7
|
+
from followthemoney.util import defer as _
|
|
8
8
|
|
|
9
9
|
if TYPE_CHECKING:
|
|
10
10
|
from followthemoney.proxy import EntityProxy
|
|
@@ -20,8 +20,8 @@ class IdentifierType(PropertyType):
|
|
|
20
20
|
Four- or five-digit industry classifiers create more noise than value."""
|
|
21
21
|
|
|
22
22
|
COMPARE_CLEAN = re.compile(r"[\W_]+")
|
|
23
|
-
name =
|
|
24
|
-
group =
|
|
23
|
+
name = "identifier"
|
|
24
|
+
group = "identifiers"
|
|
25
25
|
label = _("Identifier")
|
|
26
26
|
plural = _("Identifiers")
|
|
27
27
|
matchable = True
|
|
@@ -35,8 +35,8 @@ class IdentifierType(PropertyType):
|
|
|
35
35
|
format: Optional[str] = None,
|
|
36
36
|
proxy: Optional["EntityProxy"] = None,
|
|
37
37
|
) -> Optional[str]:
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
format_ = get_identifier_format(format)
|
|
39
|
+
if format_ is not None:
|
|
40
40
|
return format_.normalize(text)
|
|
41
41
|
return text
|
|
42
42
|
|
|
@@ -61,7 +61,7 @@ class IdentifierType(PropertyType):
|
|
|
61
61
|
return f"id:{value}"
|
|
62
62
|
|
|
63
63
|
def caption(self, value: str, format: Optional[str] = None) -> str:
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
format_ = get_identifier_format(format)
|
|
65
|
+
if format_ is not None:
|
|
66
66
|
return format_.format(value)
|
|
67
67
|
return value
|
followthemoney/types/ip.py
CHANGED
|
@@ -2,7 +2,7 @@ from typing import Optional, TYPE_CHECKING
|
|
|
2
2
|
from ipaddress import ip_address
|
|
3
3
|
|
|
4
4
|
from followthemoney.types.common import PropertyType
|
|
5
|
-
from followthemoney.util import
|
|
5
|
+
from followthemoney.util import defer as _
|
|
6
6
|
|
|
7
7
|
if TYPE_CHECKING:
|
|
8
8
|
from followthemoney.proxy import EntityProxy
|
|
@@ -13,8 +13,8 @@ class IpType(PropertyType):
|
|
|
13
13
|
by the protocol versions 4 (e.g. `192.168.1.143`) and 6
|
|
14
14
|
(e.g. `0:0:0:0:0:ffff:c0a8:18f`)."""
|
|
15
15
|
|
|
16
|
-
name =
|
|
17
|
-
group =
|
|
16
|
+
name = "ip"
|
|
17
|
+
group = "ips"
|
|
18
18
|
label = _("IP Address")
|
|
19
19
|
plural = _("IP Addresses")
|
|
20
20
|
matchable = True
|
followthemoney/types/json.py
CHANGED
|
@@ -3,7 +3,7 @@ from typing import Any, Optional, Sequence, TYPE_CHECKING
|
|
|
3
3
|
from banal import ensure_list
|
|
4
4
|
|
|
5
5
|
from followthemoney.types.common import PropertyType
|
|
6
|
-
from followthemoney.util import
|
|
6
|
+
from followthemoney.util import sanitize_text, defer as _
|
|
7
7
|
|
|
8
8
|
if TYPE_CHECKING:
|
|
9
9
|
from followthemoney.proxy import EntityProxy
|
|
@@ -14,7 +14,7 @@ class JsonType(PropertyType):
|
|
|
14
14
|
and some other edge cases. It's a really bad idea and we should try to get rid
|
|
15
15
|
of JSON properties."""
|
|
16
16
|
|
|
17
|
-
name =
|
|
17
|
+
name = "json"
|
|
18
18
|
group = None
|
|
19
19
|
label = _("Nested data")
|
|
20
20
|
plural = _("Nested data")
|
followthemoney/types/language.py
CHANGED
|
@@ -4,7 +4,7 @@ from rigour.langs import iso_639_alpha3
|
|
|
4
4
|
|
|
5
5
|
from followthemoney.types.common import EnumType, EnumValues
|
|
6
6
|
from followthemoney.util import defer as _, gettext
|
|
7
|
-
from followthemoney.util import
|
|
7
|
+
from followthemoney.util import get_env_list
|
|
8
8
|
|
|
9
9
|
if TYPE_CHECKING:
|
|
10
10
|
from followthemoney.proxy import EntityProxy
|
|
@@ -16,8 +16,8 @@ class LanguageType(EnumType):
|
|
|
16
16
|
for additional languages once there is a specific need for them to be
|
|
17
17
|
supported."""
|
|
18
18
|
|
|
19
|
-
name =
|
|
20
|
-
group =
|
|
19
|
+
name = "language"
|
|
20
|
+
group = "languages"
|
|
21
21
|
label = _("Language")
|
|
22
22
|
plural = _("Languages")
|
|
23
23
|
matchable = False
|
followthemoney/types/mimetype.py
CHANGED
|
@@ -3,7 +3,7 @@ from rigour.mime import normalize_mimetype, parse_mimetype
|
|
|
3
3
|
from rigour.mime import DEFAULT
|
|
4
4
|
|
|
5
5
|
from followthemoney.types.common import PropertyType
|
|
6
|
-
from followthemoney.util import
|
|
6
|
+
from followthemoney.util import defer as _
|
|
7
7
|
|
|
8
8
|
if TYPE_CHECKING:
|
|
9
9
|
from followthemoney.proxy import EntityProxy
|
|
@@ -18,8 +18,8 @@ class MimeType(PropertyType):
|
|
|
18
18
|
MIME type properties do not contain parameters as used in HTTP headers,
|
|
19
19
|
like `charset=UTF-8`."""
|
|
20
20
|
|
|
21
|
-
name =
|
|
22
|
-
group =
|
|
21
|
+
name = "mimetype"
|
|
22
|
+
group = "mimetypes"
|
|
23
23
|
label = _("MIME-Type")
|
|
24
24
|
plural = _("MIME-Types")
|
|
25
25
|
matchable = False
|
followthemoney/types/name.py
CHANGED
|
@@ -7,7 +7,7 @@ from rigour.text.distance import levenshtein_similarity
|
|
|
7
7
|
|
|
8
8
|
from followthemoney.types.common import PropertyType
|
|
9
9
|
from followthemoney.util import dampen
|
|
10
|
-
from followthemoney.util import
|
|
10
|
+
from followthemoney.util import defer as _
|
|
11
11
|
|
|
12
12
|
if TYPE_CHECKING:
|
|
13
13
|
from followthemoney.proxy import EntityProxy
|
|
@@ -21,8 +21,8 @@ class NameType(PropertyType):
|
|
|
21
21
|
No validation rules apply, and things having multiple names must be considered
|
|
22
22
|
a perfectly ordinary case."""
|
|
23
23
|
|
|
24
|
-
name =
|
|
25
|
-
group =
|
|
24
|
+
name = "name"
|
|
25
|
+
group = "names"
|
|
26
26
|
label = _("Name")
|
|
27
27
|
plural = _("Names")
|
|
28
28
|
matchable = True
|
followthemoney/types/number.py
CHANGED
|
@@ -2,7 +2,7 @@ import re
|
|
|
2
2
|
from typing import Optional, Tuple
|
|
3
3
|
|
|
4
4
|
from followthemoney.types.common import PropertyType
|
|
5
|
-
from followthemoney.util import
|
|
5
|
+
from followthemoney.util import defer as _
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class NumberType(PropertyType):
|
|
@@ -24,7 +24,7 @@ class NumberType(PropertyType):
|
|
|
24
24
|
_FLOAT_FMT = "{:" + SEPARATOR + "." + str(PRECISION) + "f}"
|
|
25
25
|
_INT_FMT = "{:" + SEPARATOR + "d}"
|
|
26
26
|
|
|
27
|
-
name =
|
|
27
|
+
name = "number"
|
|
28
28
|
label = _("Number")
|
|
29
29
|
plural = _("Numbers")
|
|
30
30
|
matchable = False
|
followthemoney/types/phone.py
CHANGED
|
@@ -6,7 +6,7 @@ from phonenumbers.phonenumberutil import region_code_for_number, NumberParseExce
|
|
|
6
6
|
|
|
7
7
|
from followthemoney.types.common import PropertyType
|
|
8
8
|
from followthemoney.util import defer as _
|
|
9
|
-
from followthemoney.util import
|
|
9
|
+
from followthemoney.util import dampen
|
|
10
10
|
|
|
11
11
|
if TYPE_CHECKING:
|
|
12
12
|
from followthemoney.proxy import EntityProxy
|
|
@@ -29,8 +29,8 @@ class PhoneType(PropertyType):
|
|
|
29
29
|
validation outcome from doing the two operations the other way around. Always
|
|
30
30
|
define the country first."""
|
|
31
31
|
|
|
32
|
-
name =
|
|
33
|
-
group =
|
|
32
|
+
name = "phone"
|
|
33
|
+
group = "phones"
|
|
34
34
|
label = _("Phone number")
|
|
35
35
|
plural = _("Phone numbers")
|
|
36
36
|
matchable = True
|
followthemoney/types/string.py
CHANGED
|
@@ -6,7 +6,7 @@ from followthemoney.util import MEGABYTE
|
|
|
6
6
|
class StringType(PropertyType):
|
|
7
7
|
"""A simple string property with no additional semantics."""
|
|
8
8
|
|
|
9
|
-
name =
|
|
9
|
+
name = "string"
|
|
10
10
|
label = _("Label")
|
|
11
11
|
plural = _("Labels")
|
|
12
12
|
matchable = False
|
|
@@ -21,7 +21,7 @@ class TextType(StringType):
|
|
|
21
21
|
string properties, it might make sense to treat properties of this type as
|
|
22
22
|
full-text search material."""
|
|
23
23
|
|
|
24
|
-
name =
|
|
24
|
+
name = "text"
|
|
25
25
|
label = _("Text")
|
|
26
26
|
plural = _("Texts")
|
|
27
27
|
total_size = 30 * MEGABYTE
|
followthemoney/types/topic.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from babel.core import Locale
|
|
2
2
|
|
|
3
3
|
from followthemoney.types.common import EnumType, EnumValues
|
|
4
|
-
from followthemoney.util import
|
|
4
|
+
from followthemoney.util import gettext, defer as _
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class TopicType(EnumType):
|
|
@@ -15,8 +15,8 @@ class TopicType(EnumType):
|
|
|
15
15
|
enable queries such as _find all paths between a government procurement
|
|
16
16
|
award and a politician_."""
|
|
17
17
|
|
|
18
|
-
name =
|
|
19
|
-
group =
|
|
18
|
+
name = "topic"
|
|
19
|
+
group = "topics"
|
|
20
20
|
label = _("Topic")
|
|
21
21
|
plural = _("Topics")
|
|
22
22
|
matchable = False
|
|
@@ -86,6 +86,8 @@ class TopicType(EnumType):
|
|
|
86
86
|
"sanction.linked": _("Sanction-linked entity"),
|
|
87
87
|
"sanction.counter": _("Counter-sanctioned entity"),
|
|
88
88
|
"export.control": _("Export controlled"),
|
|
89
|
+
# For BIS 50% rule:
|
|
90
|
+
"export.control.linked": _("Export control-linked"),
|
|
89
91
|
"export.risk": _("Trade risk"),
|
|
90
92
|
"debarment": _("Debarred entity"),
|
|
91
93
|
"poi": _("Person of interest"),
|
|
@@ -103,6 +105,7 @@ class TopicType(EnumType):
|
|
|
103
105
|
"crime",
|
|
104
106
|
"debarment",
|
|
105
107
|
"export.control",
|
|
108
|
+
"export.control.linked",
|
|
106
109
|
"export.risk",
|
|
107
110
|
"poi",
|
|
108
111
|
"mare.detained",
|
followthemoney/types/url.py
CHANGED
|
@@ -2,7 +2,7 @@ from typing import Optional, TYPE_CHECKING
|
|
|
2
2
|
from rigour.urls import clean_url, compare_urls
|
|
3
3
|
|
|
4
4
|
from followthemoney.types.common import PropertyType
|
|
5
|
-
from followthemoney.util import
|
|
5
|
+
from followthemoney.util import dampen, defer as _
|
|
6
6
|
|
|
7
7
|
if TYPE_CHECKING:
|
|
8
8
|
from followthemoney.proxy import EntityProxy
|
|
@@ -16,8 +16,8 @@ class UrlType(PropertyType):
|
|
|
16
16
|
SCHEMES = ("http", "https", "ftp", "mailto")
|
|
17
17
|
DEFAULT_SCHEME = "http"
|
|
18
18
|
|
|
19
|
-
name =
|
|
20
|
-
group =
|
|
19
|
+
name = "url"
|
|
20
|
+
group = "urls"
|
|
21
21
|
label = _("URL")
|
|
22
22
|
plural = _("URLs")
|
|
23
23
|
matchable = True
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: followthemoney
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.3.4
|
|
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
|
|
@@ -48,9 +48,9 @@ Requires-Dist: prefixdate<1.0.0,>=0.5.0
|
|
|
48
48
|
Requires-Dist: pydantic<3.0.0,>=2.11.0
|
|
49
49
|
Requires-Dist: pytz>=2021.1
|
|
50
50
|
Requires-Dist: pyyaml<7.0.0,>=5.0.0
|
|
51
|
-
Requires-Dist: rdflib<7.
|
|
51
|
+
Requires-Dist: rdflib<7.5.0,>=6.2.0
|
|
52
52
|
Requires-Dist: requests<3.0.0,>=2.21.0
|
|
53
|
-
Requires-Dist: rigour<2.0.0,>=1.
|
|
53
|
+
Requires-Dist: rigour<2.0.0,>=1.4.0
|
|
54
54
|
Requires-Dist: sqlalchemy[mypy]<3.0.0,>=2.0.0
|
|
55
55
|
Provides-Extra: dev
|
|
56
56
|
Requires-Dist: build; extra == 'dev'
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
followthemoney/__init__.py,sha256=
|
|
2
|
-
followthemoney/compare.py,sha256=
|
|
3
|
-
followthemoney/entity.py,sha256=
|
|
1
|
+
followthemoney/__init__.py,sha256=UHPYwFuppho0TsOPG7vZw6KSsIDHX_Ar7gO1Vw7dLig,856
|
|
2
|
+
followthemoney/compare.py,sha256=frgumsDv4Ru9UkNof62jDjKCxxpCgV1Rusfu8s20uGA,6327
|
|
3
|
+
followthemoney/entity.py,sha256=YB6u7BMeQX5toAe7DndZBiPtzy0BQ5CKp3ix6kHxk3Y,3499
|
|
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=KCdv1XAE7KQEXBiXp52Kvuck7wMaeNVBM3uaFemcvb4,7873
|
|
7
7
|
followthemoney/messages.py,sha256=zUEa9CFecU8nRafIzhN6TKCh1kEihiIyIS1qr8PxY4g,806
|
|
8
|
-
followthemoney/model.py,sha256=
|
|
8
|
+
followthemoney/model.py,sha256=chAUGob5tXWS0o8f0X6mSFCCnI2HoHE5pXU9O5ukrpc,7447
|
|
9
9
|
followthemoney/names.py,sha256=LODQqExKEHdH4z6Mmbhlm0KeKRzGcptaSWzYXZ7lONI,1120
|
|
10
10
|
followthemoney/namespace.py,sha256=utggu9IGA8bhgEYom3OUB1KxkAJR_TrMNbY5MUF_db8,4536
|
|
11
11
|
followthemoney/ontology.py,sha256=WWY_PYQGl5Ket4zZBuZglzQxD2Bh9UqHok6GJNNX7GA,3001
|
|
12
|
-
followthemoney/property.py,sha256=
|
|
13
|
-
followthemoney/proxy.py,sha256=
|
|
12
|
+
followthemoney/property.py,sha256=1w7p9aKLxRqFRnl3PlssqmvulSErl_0D5T2SefT3UFU,8675
|
|
13
|
+
followthemoney/proxy.py,sha256=xZUsT4W9sLojaSD8j6P2JQyQuOQKBWDVovm5epxvtI0,19674
|
|
14
14
|
followthemoney/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
-
followthemoney/schema.py,sha256=
|
|
15
|
+
followthemoney/schema.py,sha256=dwZg0eZF7yaxP9fJ5NQUKWadWZYTo9U-sVzzXZn_6do,18500
|
|
16
16
|
followthemoney/util.py,sha256=LoCSp1iE6VwXjotCkBXFRppeQs55726GzOuNIu3CvRE,4409
|
|
17
17
|
followthemoney/value.py,sha256=BJ4Sj5Tg2kMrslR6FjQUr96d8Kt75U7ny9NgzVGT0ZE,2335
|
|
18
18
|
followthemoney/cli/__init__.py,sha256=0mmz84uhXRp2qUn3syKnDXofU3MMAAe291s7htqX0Bg,187
|
|
@@ -54,7 +54,7 @@ followthemoney/schema/Audio.yaml,sha256=Eb1rZGUEOX7XDAj_1YIN28NCBzMvkopQBNwgHt_k
|
|
|
54
54
|
followthemoney/schema/BankAccount.yaml,sha256=60v-VD296lW1Qq7fx--CzxfPNwfCcyMV6xIl8OrSy5g,1431
|
|
55
55
|
followthemoney/schema/Call.yaml,sha256=kbVCnVxucBrEplxehXHThLSJAJjy_GhWan-IeZZjr0M,980
|
|
56
56
|
followthemoney/schema/CallForTenders.yaml,sha256=2IWonTzfSbrkynMoEWqv5fekUeFM_xDKpKIbRe1XDbo,3227
|
|
57
|
-
followthemoney/schema/Company.yaml,sha256=
|
|
57
|
+
followthemoney/schema/Company.yaml,sha256=1P_JA2LIIQtzP_Y8FYM-a47VNSWneX8QjdmGD1DFpWQ,3527
|
|
58
58
|
followthemoney/schema/Contract.yaml,sha256=aSPB64T1h-0nuLDv6krasUvvoPZgo6sWUbv60c3vmzI,1541
|
|
59
59
|
followthemoney/schema/ContractAward.yaml,sha256=b2spaZHYCaP1yR1RCsrI7mUjk-fAF7BUE3dc8Vl3cUQ,1689
|
|
60
60
|
followthemoney/schema/CourtCase.yaml,sha256=lcovnY0Ne_xcggvkqfCW_RHvsRKo8kFTCPCyovAXRtI,599
|
|
@@ -75,26 +75,26 @@ followthemoney/schema/Identification.yaml,sha256=6txjZs6-3Kn94c3G4tDeDt9Jb4FW55-
|
|
|
75
75
|
followthemoney/schema/Image.yaml,sha256=wuznboWECGiV96_GQiXq1-oKNoxO8zKisR4xyusnEn8,394
|
|
76
76
|
followthemoney/schema/Interest.yaml,sha256=VUrehmsN1WgtS1oAa5jn_JGtSkZGGYLGNahp-R5JhOQ,282
|
|
77
77
|
followthemoney/schema/Interval.yaml,sha256=8YJQ51GI-GxvbjYs3uC593kQtCepWW_7ZiNnlbPm2aM,2084
|
|
78
|
-
followthemoney/schema/LegalEntity.yaml,sha256=
|
|
78
|
+
followthemoney/schema/LegalEntity.yaml,sha256=Yy28a9NYl6mgnNvNvApO2MAnfkkaZRfzl-GtRn56CFI,5101
|
|
79
79
|
followthemoney/schema/License.yaml,sha256=bXESXY-JpSmc5sthZe4sssXhx50UoLPAMED9FvEUyRU,534
|
|
80
80
|
followthemoney/schema/Membership.yaml,sha256=IPmaOX4Ai2r4sGcA5ig2WmLvWHb38akdxp4smEdDWOE,710
|
|
81
81
|
followthemoney/schema/Mention.yaml,sha256=nBeulR_Jm4x75aJ7yNF0TAVhHJqXQaEzOutLIn_YU-4,1086
|
|
82
82
|
followthemoney/schema/Message.yaml,sha256=PAxZ2NRFVvnOlp9Ohh5fJDEThjJ0jm3M2YCbJ9KtMuE,1565
|
|
83
83
|
followthemoney/schema/Note.yaml,sha256=NohwtFupxIssZuEgQowiQWqKit4uQ-OatAu3yp9eJj4,411
|
|
84
84
|
followthemoney/schema/Occupancy.yaml,sha256=WojlqzuWao84MJxRE9K6a-1D-Jtu78-0h6laODhdKw8,975
|
|
85
|
-
followthemoney/schema/Organization.yaml,sha256=
|
|
85
|
+
followthemoney/schema/Organization.yaml,sha256=F_01GsdSsS2cUjJ732R6UfsiodylMITq23EiXdEuoDo,1136
|
|
86
86
|
followthemoney/schema/Ownership.yaml,sha256=tLWESE9VX0aUuhe6C1pToq2-auPVZBdE3xvBmTRfmPc,1057
|
|
87
87
|
followthemoney/schema/Package.yaml,sha256=gPr-P3lcg7OOAav_KVa8baK4yK57JwfcXwxXheD96UQ,310
|
|
88
88
|
followthemoney/schema/Page.yaml,sha256=YjYqaH2sOry0z4xh44CsX_eyuRClD6ZS0d2o2uQXFbo,1062
|
|
89
89
|
followthemoney/schema/Pages.yaml,sha256=KKPGZ06Ehp5mWIGnYfHUBN9jT03bk8nakw0pB5bA_7E,450
|
|
90
90
|
followthemoney/schema/Passport.yaml,sha256=rpuLC86sdXnHF-prFQM4mAqYzlSGWKvPE4Cphtn2KRw,805
|
|
91
91
|
followthemoney/schema/Payment.yaml,sha256=WRBJuj9ljsxLBs-0g9Z9UD87uR1RTtuUiYnWOnKr1qA,1757
|
|
92
|
-
followthemoney/schema/Person.yaml,sha256=
|
|
92
|
+
followthemoney/schema/Person.yaml,sha256=485xdaX3YYfJNtMkKuwRS78dD2l2bMYjGCmo-NqTvOY,2845
|
|
93
93
|
followthemoney/schema/PlainText.yaml,sha256=hfnVi-HmQeDbqDquSpkPJax9hNm86ioXGr4hzNzyPFE,278
|
|
94
94
|
followthemoney/schema/Position.yaml,sha256=ZpxjWOLxwva_on32r9WD5ys0Ty3YxCju41mg9HG-pe0,1308
|
|
95
95
|
followthemoney/schema/Project.yaml,sha256=2svtyGJopS0UrqPiuYGpBzj30V7k3LRDX4N1U56y4yY,462
|
|
96
96
|
followthemoney/schema/ProjectParticipant.yaml,sha256=xNehEu90uqUfboNouezhZQ8ZQLxzWq1yyNO4kua-Lyc,727
|
|
97
|
-
followthemoney/schema/PublicBody.yaml,sha256=
|
|
97
|
+
followthemoney/schema/PublicBody.yaml,sha256=uw4Ok36E8J5d7tyijHG9Sf_iNFOpUhGpo6UyKgTQp3Q,348
|
|
98
98
|
followthemoney/schema/RealEstate.yaml,sha256=NWFHXqEHskYQN-kvQESZpu74nztShqoYSZEjZAr-DHM,1363
|
|
99
99
|
followthemoney/schema/Representation.yaml,sha256=sCvFnUDQaElq2cqSB0rILcMYb2gaMZqlzxlHxyX9IGg,792
|
|
100
100
|
followthemoney/schema/Risk.yaml,sha256=2BRVBqb6wiLHxb_V50P-YMAOhjC64UVHDyh5PASpCIA,728
|
|
@@ -104,7 +104,7 @@ followthemoney/schema/Similar.yaml,sha256=gD8rZEaPQWzU-rEfsKdn62uEucF3KxYBcPMoSd
|
|
|
104
104
|
followthemoney/schema/Succession.yaml,sha256=RMJQqZ4Fv88N1RvWTAgjYg9BB5cELSj5CCAjM681Fpg,749
|
|
105
105
|
followthemoney/schema/Table.yaml,sha256=GcsIAgSO9t2tvObA9zU2HhxlSqTe9CePmUnagu1Z0vI,641
|
|
106
106
|
followthemoney/schema/TaxRoll.yaml,sha256=ugMzaaS7uyq2OLD50eGLcfvd6Cg0cSt65-T9GVqpRSA,746
|
|
107
|
-
followthemoney/schema/Thing.yaml,sha256=
|
|
107
|
+
followthemoney/schema/Thing.yaml,sha256=iUaGvGRBqhsVUg-Cx_CNw9WewlWsl9vylU2wiFDQ1qg,2975
|
|
108
108
|
followthemoney/schema/Trip.yaml,sha256=nLQD_ApmVJ8D56Czl7K700hhNZjzFV9FOQ3NBSQDLiM,771
|
|
109
109
|
followthemoney/schema/UnknownLink.yaml,sha256=lneS_HZNgeLyJxwzWnLx0ZoyY3MXt99I_K2X_o9z5g8,682
|
|
110
110
|
followthemoney/schema/UserAccount.yaml,sha256=2bbPKNtt1R3zWSSkaq_SVzRPfFzX74kAxwtIxTymHA8,840
|
|
@@ -114,10 +114,10 @@ followthemoney/schema/Vessel.yaml,sha256=zWHUfSK8g6Pz58ZyCaK0AFJ4u_UHjEIUGC4c_7o
|
|
|
114
114
|
followthemoney/schema/Video.yaml,sha256=LY3DYMWTHXiAhL0hxBCNCz50cp2sPbUlEhhig5Fbjos,327
|
|
115
115
|
followthemoney/schema/Workbook.yaml,sha256=iikWPElz4klA7SkWH7eae6xqhbkMCIP_3zdeXzFEMU0,354
|
|
116
116
|
followthemoney/statement/__init__.py,sha256=7m2VUCAuqNZXIY0WFJRFkw5UG14QuxATL4f_xbqKwhw,633
|
|
117
|
-
followthemoney/statement/entity.py,sha256=
|
|
117
|
+
followthemoney/statement/entity.py,sha256=oeudwhqfYLJKqbzxEydasMHqevkDASNyYN6s0yddW6I,18755
|
|
118
118
|
followthemoney/statement/serialize.py,sha256=9eXzQ1biR2mSxWRID5C7xDdku4b4ZImHeRJ53yLZ0yo,7225
|
|
119
119
|
followthemoney/statement/statement.py,sha256=Ae-EYuzS8S12BkaRqrvMuI1C7YwlRKa5C_pTBELyNMM,8029
|
|
120
|
-
followthemoney/statement/util.py,sha256=
|
|
120
|
+
followthemoney/statement/util.py,sha256=QMYSwAcnh2fCM1LtH_-v8Z5GdwOZfUTT1UkQ_ZMQ470,797
|
|
121
121
|
followthemoney/translations/messages.pot,sha256=JhtY9NJ9wP_EAX4APxOqMyvKcX53oIC9kAxBsliJkf4,107703
|
|
122
122
|
followthemoney/translations/ar/LC_MESSAGES/followthemoney.mo,sha256=uhb2crSNh8K2ts_QUeD2wvgWgzzpLJWRzXok-Uyx3Zk,38795
|
|
123
123
|
followthemoney/translations/ar/LC_MESSAGES/followthemoney.po,sha256=DuIfvR5v0sPGwFbeg3y6_jCbeglvHWXQ2LDH6prfwLc,121326
|
|
@@ -142,27 +142,27 @@ followthemoney/translations/ru/LC_MESSAGES/followthemoney.po,sha256=7SQWytOTvoAQ
|
|
|
142
142
|
followthemoney/translations/tr/LC_MESSAGES/followthemoney.mo,sha256=SC84e_ZF_oFJG1NKdyZY_W6Kb6POORZB6wdeAcEWmnE,487
|
|
143
143
|
followthemoney/translations/tr/LC_MESSAGES/followthemoney.po,sha256=AZC3marhtVVq8Ck1FOgnt4sbDMz548nX48O9GDwImbQ,89826
|
|
144
144
|
followthemoney/types/__init__.py,sha256=rWwQeiuMh2BNIuvhpMfJ4bPADDvt9Axu1eedvNFi0qY,3350
|
|
145
|
-
followthemoney/types/address.py,sha256=
|
|
146
|
-
followthemoney/types/checksum.py,sha256=
|
|
145
|
+
followthemoney/types/address.py,sha256=Gc-hqz00dRRkeANqkyPD2wtt7ksR9wMf4CX-U-5XvMo,2214
|
|
146
|
+
followthemoney/types/checksum.py,sha256=_0ev2Wwtd4iX_bLz0Lu-xcJIxNfH_V9kBKKtuZhoAwg,802
|
|
147
147
|
followthemoney/types/common.py,sha256=4ks7zPT8rknrGSd4JFc1zRkS-TL4SX-25_ZbjcVDos0,10081
|
|
148
|
-
followthemoney/types/country.py,sha256=
|
|
149
|
-
followthemoney/types/date.py,sha256=
|
|
148
|
+
followthemoney/types/country.py,sha256=X3Z1j6rIiCITpLtpFXwjTIh9uJwI99_gmPMJx8Jsq2w,1512
|
|
149
|
+
followthemoney/types/date.py,sha256=O3Xav9QNBqjy7LuUWiZrUdGrOvwwOdk6ea5qQEStIwQ,3084
|
|
150
150
|
followthemoney/types/email.py,sha256=L3RTYrMABlNQF7hCynXGfzoj6YNEHW5JAY_BwuhoZdA,3375
|
|
151
|
-
followthemoney/types/entity.py,sha256=
|
|
152
|
-
followthemoney/types/gender.py,sha256=
|
|
153
|
-
followthemoney/types/identifier.py,sha256=
|
|
154
|
-
followthemoney/types/ip.py,sha256=
|
|
155
|
-
followthemoney/types/json.py,sha256=
|
|
156
|
-
followthemoney/types/language.py,sha256=
|
|
157
|
-
followthemoney/types/mimetype.py,sha256=
|
|
158
|
-
followthemoney/types/name.py,sha256=
|
|
159
|
-
followthemoney/types/number.py,sha256=
|
|
160
|
-
followthemoney/types/phone.py,sha256=
|
|
161
|
-
followthemoney/types/string.py,sha256=
|
|
162
|
-
followthemoney/types/topic.py,sha256=
|
|
163
|
-
followthemoney/types/url.py,sha256=
|
|
164
|
-
followthemoney-4.
|
|
165
|
-
followthemoney-4.
|
|
166
|
-
followthemoney-4.
|
|
167
|
-
followthemoney-4.
|
|
168
|
-
followthemoney-4.
|
|
151
|
+
followthemoney/types/entity.py,sha256=56h6x8Ct7hWZIC3BjZHmRKGy9Ff2vuULNWH3xDRsKiU,2317
|
|
152
|
+
followthemoney/types/gender.py,sha256=XY9us98Sk25O1xnHN-88tbv9pHy6Mn7SR8GRYi6v5gI,1683
|
|
153
|
+
followthemoney/types/identifier.py,sha256=TYJwE7urjHFxEcDuiZMxGoCN6n34rAIdCt5_96Y7vI0,2198
|
|
154
|
+
followthemoney/types/ip.py,sha256=rCXkRrh_jDeWAhswCgSe6Z4uhIW7yvLAxIEw4x1SM3A,1279
|
|
155
|
+
followthemoney/types/json.py,sha256=Hefwns1-ziJf310MWvdfX5ICkOgj9cnnMJuqq1e6qKY,1676
|
|
156
|
+
followthemoney/types/language.py,sha256=JDFCO9g9lvgKihhYTz6e7TbJd3V9RTGJlS8kDn6aSCY,2726
|
|
157
|
+
followthemoney/types/mimetype.py,sha256=oqVP8EfGckPAI3WAziHomp6oUN7KXdIPWzGZPsRtIA8,1242
|
|
158
|
+
followthemoney/types/name.py,sha256=zd0aC4VGp1SYUI8Rj0-ZXlrpUI7ZcnJIljZqsEsV-CY,2363
|
|
159
|
+
followthemoney/types/number.py,sha256=vpAyhmc7UQlIm8h7Z5k8k4cTk37ykRF-AgYA1r_g1QQ,3934
|
|
160
|
+
followthemoney/types/phone.py,sha256=_HanfxxTV7jp75gZO2evBc9HWwQTxEMQRaoVDcoXDIQ,3790
|
|
161
|
+
followthemoney/types/string.py,sha256=SEh3xqQCnm377PGvwfR6ao85pHJCNeCUWBKnvccrJ7I,1216
|
|
162
|
+
followthemoney/types/topic.py,sha256=9FIH_WmwVOFg1CJRBF4KeE6vNTn-QQkzsKU5XaMqNJ0,4604
|
|
163
|
+
followthemoney/types/url.py,sha256=sSHKtzvm4kc-VTvNCPIDykOG1hUoawhORj6Bklo0a2A,1434
|
|
164
|
+
followthemoney-4.3.4.dist-info/METADATA,sha256=H3K0seI3SN6axQwxgjGK0zErE7ySmoYtOxDom-waNDU,6747
|
|
165
|
+
followthemoney-4.3.4.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
166
|
+
followthemoney-4.3.4.dist-info/entry_points.txt,sha256=caoFTlf213jhg5sz3TNSofutjUTzaKtWATuSIdd9Cps,653
|
|
167
|
+
followthemoney-4.3.4.dist-info/licenses/LICENSE,sha256=H6_EVXisnJC0-18CjXIaqrBSFq_VH3OnS7u3dccOv6g,1148
|
|
168
|
+
followthemoney-4.3.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|