From b05df90c00813fbc426bcd015c69ce4a8854de42 Mon Sep 17 00:00:00 2001 From: JD Bothma Date: Tue, 11 Jul 2023 14:39:48 +0200 Subject: [PATCH 01/12] First quick crack at more flexible position type --- followthemoney/schema/Position.yaml | 46 +++++++++++++++++++++++++ followthemoney/schema/PositionHeld.yaml | 26 ++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 followthemoney/schema/Position.yaml create mode 100644 followthemoney/schema/PositionHeld.yaml diff --git a/followthemoney/schema/Position.yaml b/followthemoney/schema/Position.yaml new file mode 100644 index 000000000..c70f6fc4f --- /dev/null +++ b/followthemoney/schema/Position.yaml @@ -0,0 +1,46 @@ +Position: + label: "Position" + plural: "Positions" + matchable: false + # cf. https://www.popoloproject.com/specs/post.html + description: > + A post, role or position within an organization or body. + This describes a position one or more people may occupy + and not the occupation of the post by a specific individual at a + specific point in time. + featured: + - organization + - name + - category + caption: + - name + properties: + organization: + label: "Organization" + type: entity + reverse: + name: positions + label: Positions + range: Organization + name: + label: "Name" + type: string + category: + label: "Category" + type: string + description: + label: "Description" + type: string + Summary: + label: "Summary" + type: text + country: + label: Country (Mandating and service) + type: country + jurisdiction: + label: Country or region of service + type: string + wikidataId: + label: "Wikidata ID" + hidden: true + type: identifier diff --git a/followthemoney/schema/PositionHeld.yaml b/followthemoney/schema/PositionHeld.yaml new file mode 100644 index 000000000..b9e6f8674 --- /dev/null +++ b/followthemoney/schema/PositionHeld.yaml @@ -0,0 +1,26 @@ +PositionHeld: + label: "Position held" + plural: "Positions held" + extends: Interval + matchable: false + description: > + The occupation of a position by a person at a specific point in time. + featured: + - holder + - position + properties: + holder: + label: "Holder" + reverse: + name: positionsHeld + label: "Positions held" + type: entity + range: Person + position: + label: "Position" + reverse: + name: occupations + label: "Occupations of this position" + type: entity + range: Position + From 5d64f47165f3eb820a61e90fae32f6c2fb52875f Mon Sep 17 00:00:00 2001 From: JD Bothma Date: Tue, 11 Jul 2023 23:33:42 +0200 Subject: [PATCH 02/12] Add occupancy status --- followthemoney/schema/PositionHeld.yaml | 8 +++- followthemoney/types/__init__.py | 2 + followthemoney/types/occupancy_status.py | 47 ++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 followthemoney/types/occupancy_status.py diff --git a/followthemoney/schema/PositionHeld.yaml b/followthemoney/schema/PositionHeld.yaml index b9e6f8674..35b9b1fd1 100644 --- a/followthemoney/schema/PositionHeld.yaml +++ b/followthemoney/schema/PositionHeld.yaml @@ -8,6 +8,9 @@ PositionHeld: featured: - holder - position + required: + - holder + - position properties: holder: label: "Holder" @@ -19,8 +22,11 @@ PositionHeld: position: label: "Position" reverse: - name: occupations + name: positions_held label: "Occupations of this position" type: entity range: Position + occupancy_status: + label: "Occupancy status" + type: occupancy_status diff --git a/followthemoney/types/__init__.py b/followthemoney/types/__init__.py index 73e460bc7..2d20f28fa 100644 --- a/followthemoney/types/__init__.py +++ b/followthemoney/types/__init__.py @@ -21,6 +21,7 @@ from followthemoney.types.string import StringType from followthemoney.types.number import NumberType from followthemoney.types.common import PropertyType +from followthemoney.types.occupancy_status import OccupancyStatus registry = Registry() @@ -45,3 +46,4 @@ registry.add(HTMLType) registry.add(StringType) registry.add(NumberType) +registry.add(OccupancyStatus) diff --git a/followthemoney/types/occupancy_status.py b/followthemoney/types/occupancy_status.py new file mode 100644 index 000000000..2384f8679 --- /dev/null +++ b/followthemoney/types/occupancy_status.py @@ -0,0 +1,47 @@ +from typing import Optional, TYPE_CHECKING +from babel.core import Locale + +from followthemoney.types.common import EnumType, EnumValues +from followthemoney.rdf import URIRef, Identifier +from followthemoney.util import gettext, defer as _ + +if TYPE_CHECKING: + from followthemoney.proxy import EntityProxy + + +class OccupancyStatus(EnumType): + """The status of occupation of the position as of the last update + of data.""" + + ACTIVE = "active" + ENDED = "ended" + + LOOKUP = {} + + name = "occupancy_status" + group = "occupancy_statuses" + label = _("Occupancy status") + plural = _("Occupancy statuses") + matchable = False + + def _locale_names(self, locale: Locale) -> EnumValues: + return { + self.ACTIVE: gettext("active"), + self.ENDED: gettext("ended"), + } + + def clean_text( + self, + text: str, + fuzzy: bool = False, + format: Optional[str] = None, + proxy: Optional["EntityProxy"] = None, + ) -> Optional[str]: + code = text.lower().strip() + code = self.LOOKUP.get(code, code) + if code not in self.codes: + return None + return code + + def rdf(self, value: str) -> Identifier: + return URIRef(f"occupancy_status:{value}") From 41af8de884c305ff42fb1898fd9c1adbbbe47cf2 Mon Sep 17 00:00:00 2001 From: JD Bothma Date: Tue, 11 Jul 2023 23:39:33 +0200 Subject: [PATCH 03/12] Refine Position fields --- followthemoney/schema/Position.yaml | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/followthemoney/schema/Position.yaml b/followthemoney/schema/Position.yaml index c70f6fc4f..26198ad5a 100644 --- a/followthemoney/schema/Position.yaml +++ b/followthemoney/schema/Position.yaml @@ -8,12 +8,18 @@ Position: This describes a position one or more people may occupy and not the occupation of the post by a specific individual at a specific point in time. + + - `jurisdiction` is not the constituency of a national member of + parliament - their legislative jurisdiction - the power or effect + of their position - is nationwide. featured: - - organization - name + - organization - category caption: - name + required: + - name properties: organization: label: "Organization" @@ -24,21 +30,24 @@ Position: range: Organization name: label: "Name" - type: string + type: name category: label: "Category" type: string description: label: "Description" - type: string - Summary: + type: text + summary: label: "Summary" type: text - country: - label: Country (Mandating and service) - type: country + sourceUrl: + label: "Source link" + type: url jurisdiction: - label: Country or region of service + label: Jurisdiction + type: country + jurisdiction_region: + label: Subnational jurisdiction name or code type: string wikidataId: label: "Wikidata ID" From e0f78525782e3e2c727baef58c0780c37ab71e32 Mon Sep 17 00:00:00 2001 From: JD Bothma Date: Tue, 11 Jul 2023 23:44:55 +0200 Subject: [PATCH 04/12] Add position inception/dissolution dates --- followthemoney/schema/Position.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/followthemoney/schema/Position.yaml b/followthemoney/schema/Position.yaml index 26198ad5a..d4081f45d 100644 --- a/followthemoney/schema/Position.yaml +++ b/followthemoney/schema/Position.yaml @@ -37,6 +37,12 @@ Position: description: label: "Description" type: text + inceptionDate: + label: Inception date + type: date + dissolutionDate: + label: Dissolution date + type: date summary: label: "Summary" type: text From d5d202bbf88cefea35920ace82b6461d43bab4fd Mon Sep 17 00:00:00 2001 From: JD Bothma Date: Wed, 12 Jul 2023 14:38:06 +0200 Subject: [PATCH 05/12] Property improvements --- followthemoney/schema/Position.yaml | 9 ++++++--- followthemoney/schema/PositionHeld.yaml | 18 +++++++++--------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/followthemoney/schema/Position.yaml b/followthemoney/schema/Position.yaml index d4081f45d..b7ff09e57 100644 --- a/followthemoney/schema/Position.yaml +++ b/followthemoney/schema/Position.yaml @@ -49,12 +49,15 @@ Position: sourceUrl: label: "Source link" type: url - jurisdiction: - label: Jurisdiction + country: + label: Country type: country - jurisdiction_region: + subnationalArea: label: Subnational jurisdiction name or code type: string + numberOfSeats: + label: Total number of seats + type: number wikidataId: label: "Wikidata ID" hidden: true diff --git a/followthemoney/schema/PositionHeld.yaml b/followthemoney/schema/PositionHeld.yaml index 35b9b1fd1..3e39000cb 100644 --- a/followthemoney/schema/PositionHeld.yaml +++ b/followthemoney/schema/PositionHeld.yaml @@ -4,29 +4,29 @@ PositionHeld: extends: Interval matchable: false description: > - The occupation of a position by a person at a specific point in time. + The occupation of a position by a person for a specific period of time. featured: - holder - - position + - post required: - holder - - position + - post properties: holder: label: "Holder" reverse: name: positionsHeld - label: "Positions held" + label: "Positions" type: entity range: Person - position: - label: "Position" + post: + label: "Position held" reverse: - name: positions_held - label: "Occupations of this position" + name: positionsHeld + label: "Positions held" type: entity range: Position - occupancy_status: + occupancyStatus: label: "Occupancy status" type: occupancy_status From 12f214fa6fbd35191adda20e637dd54f9b9cc92c Mon Sep 17 00:00:00 2001 From: JD Bothma Date: Wed, 12 Jul 2023 14:52:20 +0200 Subject: [PATCH 06/12] Fix diverging label and try more descriptive reverse names --- followthemoney/schema/PositionHeld.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/followthemoney/schema/PositionHeld.yaml b/followthemoney/schema/PositionHeld.yaml index 3e39000cb..0acc72dda 100644 --- a/followthemoney/schema/PositionHeld.yaml +++ b/followthemoney/schema/PositionHeld.yaml @@ -15,15 +15,15 @@ PositionHeld: holder: label: "Holder" reverse: - name: positionsHeld - label: "Positions" + name: positionsOccupied + label: "Positions occupied" type: entity range: Person post: - label: "Position held" + label: "Position occupied" reverse: - name: positionsHeld - label: "Positions held" + name: occupancies + label: "Occupancies" type: entity range: Position occupancyStatus: From cf79bc0a79c564581889bd9da90c85e389d453f3 Mon Sep 17 00:00:00 2001 From: JD Bothma Date: Wed, 12 Jul 2023 15:18:48 +0200 Subject: [PATCH 07/12] Clean up to fix type error and make type name camel case for now --- followthemoney/schema/PositionHeld.yaml | 2 +- followthemoney/types/occupancy_status.py | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/followthemoney/schema/PositionHeld.yaml b/followthemoney/schema/PositionHeld.yaml index 0acc72dda..95ab6fab4 100644 --- a/followthemoney/schema/PositionHeld.yaml +++ b/followthemoney/schema/PositionHeld.yaml @@ -28,5 +28,5 @@ PositionHeld: range: Position occupancyStatus: label: "Occupancy status" - type: occupancy_status + type: occupancyStatus diff --git a/followthemoney/types/occupancy_status.py b/followthemoney/types/occupancy_status.py index 2384f8679..a96b02e4c 100644 --- a/followthemoney/types/occupancy_status.py +++ b/followthemoney/types/occupancy_status.py @@ -16,10 +16,8 @@ class OccupancyStatus(EnumType): ACTIVE = "active" ENDED = "ended" - LOOKUP = {} - - name = "occupancy_status" - group = "occupancy_statuses" + name = "occupancyStatus" + group = "occupancyStatuses" label = _("Occupancy status") plural = _("Occupancy statuses") matchable = False @@ -38,10 +36,9 @@ def clean_text( proxy: Optional["EntityProxy"] = None, ) -> Optional[str]: code = text.lower().strip() - code = self.LOOKUP.get(code, code) if code not in self.codes: return None return code def rdf(self, value: str) -> Identifier: - return URIRef(f"occupancy_status:{value}") + return URIRef(f"occupancyStatus:{value}") From 6f6964c9f708489680c204a9ce2042c7b32986cd Mon Sep 17 00:00:00 2001 From: JD Bothma Date: Thu, 13 Jul 2023 14:46:35 +0200 Subject: [PATCH 08/12] Call the time the position is held Occupancy --- .../{PositionHeld.yaml => Occupancy.yaml} | 12 ++--- followthemoney/types/__init__.py | 2 - followthemoney/types/occupancy_status.py | 44 ------------------- 3 files changed, 6 insertions(+), 52 deletions(-) rename followthemoney/schema/{PositionHeld.yaml => Occupancy.yaml} (78%) delete mode 100644 followthemoney/types/occupancy_status.py diff --git a/followthemoney/schema/PositionHeld.yaml b/followthemoney/schema/Occupancy.yaml similarity index 78% rename from followthemoney/schema/PositionHeld.yaml rename to followthemoney/schema/Occupancy.yaml index 95ab6fab4..95a139d06 100644 --- a/followthemoney/schema/PositionHeld.yaml +++ b/followthemoney/schema/Occupancy.yaml @@ -1,6 +1,6 @@ -PositionHeld: - label: "Position held" - plural: "Positions held" +Occupancy: + label: "Occupancy" + plural: "Occupancies" extends: Interval matchable: false description: > @@ -26,7 +26,7 @@ PositionHeld: label: "Occupancies" type: entity range: Position - occupancyStatus: - label: "Occupancy status" - type: occupancyStatus + status: + label: "Status" + type: string diff --git a/followthemoney/types/__init__.py b/followthemoney/types/__init__.py index 2d20f28fa..73e460bc7 100644 --- a/followthemoney/types/__init__.py +++ b/followthemoney/types/__init__.py @@ -21,7 +21,6 @@ from followthemoney.types.string import StringType from followthemoney.types.number import NumberType from followthemoney.types.common import PropertyType -from followthemoney.types.occupancy_status import OccupancyStatus registry = Registry() @@ -46,4 +45,3 @@ registry.add(HTMLType) registry.add(StringType) registry.add(NumberType) -registry.add(OccupancyStatus) diff --git a/followthemoney/types/occupancy_status.py b/followthemoney/types/occupancy_status.py deleted file mode 100644 index a96b02e4c..000000000 --- a/followthemoney/types/occupancy_status.py +++ /dev/null @@ -1,44 +0,0 @@ -from typing import Optional, TYPE_CHECKING -from babel.core import Locale - -from followthemoney.types.common import EnumType, EnumValues -from followthemoney.rdf import URIRef, Identifier -from followthemoney.util import gettext, defer as _ - -if TYPE_CHECKING: - from followthemoney.proxy import EntityProxy - - -class OccupancyStatus(EnumType): - """The status of occupation of the position as of the last update - of data.""" - - ACTIVE = "active" - ENDED = "ended" - - name = "occupancyStatus" - group = "occupancyStatuses" - label = _("Occupancy status") - plural = _("Occupancy statuses") - matchable = False - - def _locale_names(self, locale: Locale) -> EnumValues: - return { - self.ACTIVE: gettext("active"), - self.ENDED: gettext("ended"), - } - - def clean_text( - self, - text: str, - fuzzy: bool = False, - format: Optional[str] = None, - proxy: Optional["EntityProxy"] = None, - ) -> Optional[str]: - code = text.lower().strip() - if code not in self.codes: - return None - return code - - def rdf(self, value: str) -> Identifier: - return URIRef(f"occupancyStatus:{value}") From 18a15d4fda33559b5baa69b98bf74e45eef45469 Mon Sep 17 00:00:00 2001 From: JD Bothma Date: Thu, 13 Jul 2023 15:20:46 +0200 Subject: [PATCH 09/12] Improve subnational area doc a bit --- followthemoney/schema/Position.yaml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/followthemoney/schema/Position.yaml b/followthemoney/schema/Position.yaml index b7ff09e57..4d7ea758c 100644 --- a/followthemoney/schema/Position.yaml +++ b/followthemoney/schema/Position.yaml @@ -9,9 +9,10 @@ Position: and not the occupation of the post by a specific individual at a specific point in time. - - `jurisdiction` is not the constituency of a national member of - parliament - their legislative jurisdiction - the power or effect - of their position - is nationwide. + 'subnationalArea' should be used to further restrict the scope of the + position. It should not simply represent some regional aspect of the + role - e.g. the constituency of a national member of parliament - when + their their legislative jurisdiction is nationwide. featured: - name - organization From ab84228cd42c03531fb292e5ba437e3b2f2de35a Mon Sep 17 00:00:00 2001 From: JD Bothma Date: Wed, 19 Jul 2023 10:34:21 +0200 Subject: [PATCH 10/12] Small review comments --- followthemoney/schema/Occupancy.yaml | 8 ++++++++ followthemoney/schema/Position.yaml | 9 +++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/followthemoney/schema/Occupancy.yaml b/followthemoney/schema/Occupancy.yaml index 95a139d06..4d150b588 100644 --- a/followthemoney/schema/Occupancy.yaml +++ b/followthemoney/schema/Occupancy.yaml @@ -11,6 +11,14 @@ Occupancy: required: - holder - post + edge: + source: holder + label: holds + target: post + directed: true + caption: + - startDate + - endDate properties: holder: label: "Holder" diff --git a/followthemoney/schema/Position.yaml b/followthemoney/schema/Position.yaml index 4d7ea758c..ae980565e 100644 --- a/followthemoney/schema/Position.yaml +++ b/followthemoney/schema/Position.yaml @@ -16,11 +16,15 @@ Position: featured: - name - organization - - category caption: - name required: - name + temporalExtent: + start: + - inceptionDate + end: + - dissolutionDate properties: organization: label: "Organization" @@ -32,9 +36,6 @@ Position: name: label: "Name" type: name - category: - label: "Category" - type: string description: label: "Description" type: text From 7237453e5f13629178eddef85db2843500bf5c5c Mon Sep 17 00:00:00 2001 From: JD Bothma Date: Wed, 19 Jul 2023 11:23:15 +0200 Subject: [PATCH 11/12] Inherit from Thing and remove duplicated properties --- followthemoney/schema/Position.yaml | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/followthemoney/schema/Position.yaml b/followthemoney/schema/Position.yaml index ae980565e..ae2154c35 100644 --- a/followthemoney/schema/Position.yaml +++ b/followthemoney/schema/Position.yaml @@ -1,6 +1,7 @@ Position: label: "Position" plural: "Positions" + extends: Thing matchable: false # cf. https://www.popoloproject.com/specs/post.html description: > @@ -12,7 +13,7 @@ Position: 'subnationalArea' should be used to further restrict the scope of the position. It should not simply represent some regional aspect of the role - e.g. the constituency of a national member of parliament - when - their their legislative jurisdiction is nationwide. + their legislative jurisdiction is nationwide. featured: - name - organization @@ -33,34 +34,15 @@ Position: name: positions label: Positions range: Organization - name: - label: "Name" - type: name - description: - label: "Description" - type: text inceptionDate: label: Inception date type: date dissolutionDate: label: Dissolution date type: date - summary: - label: "Summary" - type: text - sourceUrl: - label: "Source link" - type: url - country: - label: Country - type: country subnationalArea: label: Subnational jurisdiction name or code type: string numberOfSeats: label: Total number of seats type: number - wikidataId: - label: "Wikidata ID" - hidden: true - type: identifier From 3435fb73f5de09bced42a41e5b36c4d91d3992f2 Mon Sep 17 00:00:00 2001 From: JD Bothma Date: Fri, 21 Jul 2023 13:30:58 +0200 Subject: [PATCH 12/12] Rename occupancy holder reverse to positionOccupancies Make it clearer that it's not Position instances on this end, and keep it specific enough in case someone wants to model accommodation occupancies in future. --- followthemoney/schema/Occupancy.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/followthemoney/schema/Occupancy.yaml b/followthemoney/schema/Occupancy.yaml index 4d150b588..ac85dab7c 100644 --- a/followthemoney/schema/Occupancy.yaml +++ b/followthemoney/schema/Occupancy.yaml @@ -23,8 +23,8 @@ Occupancy: holder: label: "Holder" reverse: - name: positionsOccupied - label: "Positions occupied" + name: positionOccupancies + label: "Position Occupancies" type: entity range: Person post: