From fba9979bc486689909fe4830cc33064c07b513bd Mon Sep 17 00:00:00 2001 From: "Ed (ODSC)" Date: Thu, 12 Dec 2024 14:30:48 +0000 Subject: [PATCH] schema.py: Allow passing as Python object Allow the calling application to load the schema rather than flatten-tool Partial work around for: https://github.com/OpenDataServices/flatten-tool/issues/447 --- CHANGELOG.md | 1 + docs/getting-started.rst | 21 + flattentool/schema.py | 4 +- .../tests/fixtures/basic_1.csv/addresses.csv | 2 + .../fixtures/basic_1.csv/annotations.csv | 1 + .../fixtures/basic_1.csv/identifiers.csv | 2 + .../tests/fixtures/basic_1.csv/interests.csv | 2 + .../tests/fixtures/basic_1.csv/main.csv | 4 + .../tests/fixtures/basic_1.csv/names.csv | 3 + .../fixtures/basic_1.csv/nationalities.csv | 2 + .../basic_1.csv/pol_det_sou_assertedBy.csv | 1 + .../fixtures/basic_1.csv/pol_details.csv | 1 + .../basic_1.csv/pub_securitiesListings.csv | 1 + .../fixtures/basic_1.csv/sou_assertedBy.csv | 1 + .../fixtures/basic_1.csv/taxResidencies.csv | 1 + flattentool/tests/fixtures/basic_1.json | 102 + flattentool/tests/fixtures/schema-0-3-0.json | 1945 +++++++++++++++++ flattentool/tests/test_library.py | 124 ++ 18 files changed, 2217 insertions(+), 1 deletion(-) create mode 100644 flattentool/tests/fixtures/basic_1.csv/addresses.csv create mode 100644 flattentool/tests/fixtures/basic_1.csv/annotations.csv create mode 100644 flattentool/tests/fixtures/basic_1.csv/identifiers.csv create mode 100644 flattentool/tests/fixtures/basic_1.csv/interests.csv create mode 100644 flattentool/tests/fixtures/basic_1.csv/main.csv create mode 100644 flattentool/tests/fixtures/basic_1.csv/names.csv create mode 100644 flattentool/tests/fixtures/basic_1.csv/nationalities.csv create mode 100644 flattentool/tests/fixtures/basic_1.csv/pol_det_sou_assertedBy.csv create mode 100644 flattentool/tests/fixtures/basic_1.csv/pol_details.csv create mode 100644 flattentool/tests/fixtures/basic_1.csv/pub_securitiesListings.csv create mode 100644 flattentool/tests/fixtures/basic_1.csv/sou_assertedBy.csv create mode 100644 flattentool/tests/fixtures/basic_1.csv/taxResidencies.csv create mode 100644 flattentool/tests/fixtures/basic_1.json create mode 100644 flattentool/tests/fixtures/schema-0-3-0.json create mode 100644 flattentool/tests/test_library.py diff --git a/CHANGELOG.md b/CHANGELOG.md index a58e247..13076e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Put back some translations that were removed - Minor python tidyups - Avoid deprecation warning from the ijson package. https://github.com/OpenDataServices/flatten-tool/pull/458 +- Allow passing of schema as Python object, and update docs ### Removed diff --git a/docs/getting-started.rst b/docs/getting-started.rst index 1c4803f..0d2af37 100644 --- a/docs/getting-started.rst +++ b/docs/getting-started.rst @@ -38,6 +38,27 @@ will print general help information. will print help information specific to that sub-command. +Library +------- + +The package can also be used as a Python library, e.g. to flatten +data: + +.. code-block:: python + + flattentool.flatten(input_filename, ...) + +or unflatten data: + +.. code-block:: python + + flattentool.unflatten(input_filename, ...) + +In addition to the functionality available from the command-line, +Python calls to the library functions also accept a Python object +as a schema rather than a filename. This allows schema objects to +be loaded/constructed by the calling application. + Python Version Support ---------------------- diff --git a/flattentool/schema.py b/flattentool/schema.py index 4f01722..6c99212 100644 --- a/flattentool/schema.py +++ b/flattentool/schema.py @@ -152,7 +152,9 @@ def __init__( _("Only one of schema_filename or root_schema_dict should be supplied") ) if schema_filename: - if schema_filename.startswith("http"): + if isinstance(schema_filename, dict): + self.root_schema_dict = schema_filename + elif schema_filename.startswith("http"): import requests r = requests.get(schema_filename) diff --git a/flattentool/tests/fixtures/basic_1.csv/addresses.csv b/flattentool/tests/fixtures/basic_1.csv/addresses.csv new file mode 100644 index 0000000..3d02a02 --- /dev/null +++ b/flattentool/tests/fixtures/basic_1.csv/addresses.csv @@ -0,0 +1,2 @@ +addresses/0/type,addresses/0/address,addresses/0/postCode,addresses/0/country,statementID +service,"Aston House, Cornwall Avenue, London",N3 1LF,GB,019a93f1-e470-42e9-957b-03559861b2e2 diff --git a/flattentool/tests/fixtures/basic_1.csv/annotations.csv b/flattentool/tests/fixtures/basic_1.csv/annotations.csv new file mode 100644 index 0000000..fbf9133 --- /dev/null +++ b/flattentool/tests/fixtures/basic_1.csv/annotations.csv @@ -0,0 +1 @@ +annotations/0/statementPointerTarget,annotations/0/creationDate,annotations/0/createdBy/name,annotations/0/createdBy/uri,annotations/0/motivation,annotations/0/description,annotations/0/transformedContent,annotations/0/url diff --git a/flattentool/tests/fixtures/basic_1.csv/identifiers.csv b/flattentool/tests/fixtures/basic_1.csv/identifiers.csv new file mode 100644 index 0000000..e0b3c04 --- /dev/null +++ b/flattentool/tests/fixtures/basic_1.csv/identifiers.csv @@ -0,0 +1,2 @@ +identifiers/0/id,identifiers/0/scheme,identifiers/0/schemeName,identifiers/0/uri,statementID +07444723,GB-COH,,,1dc0e987-5c57-4a1c-b3ad-61353b66a9b7 diff --git a/flattentool/tests/fixtures/basic_1.csv/interests.csv b/flattentool/tests/fixtures/basic_1.csv/interests.csv new file mode 100644 index 0000000..af2f7c8 --- /dev/null +++ b/flattentool/tests/fixtures/basic_1.csv/interests.csv @@ -0,0 +1,2 @@ +interests/0/type,interests/0/directOrIndirect,interests/0/beneficialOwnershipOrControl,interests/0/details,interests/0/share/exact,interests/0/share/maximum,interests/0/share/minimum,interests/0/share/exclusiveMinimum,interests/0/share/exclusiveMaximum,interests/0/startDate,interests/0/endDate,statementID +shareholding,direct,True,,100,100,100,,,2016-04-06,,fbfd0547-d0c6-4a00-b559-5c5e91c34f5c diff --git a/flattentool/tests/fixtures/basic_1.csv/main.csv b/flattentool/tests/fixtures/basic_1.csv/main.csv new file mode 100644 index 0000000..2b4e398 --- /dev/null +++ b/flattentool/tests/fixtures/basic_1.csv/main.csv @@ -0,0 +1,4 @@ +statementID,statementType,statementDate,isComponent,entityType,unspecifiedEntityDetails/reason,unspecifiedEntityDetails/description,name,alternateNames,jurisdiction/name,jurisdiction/code,foundingDate,dissolutionDate,uri,replacesStatements,publicationDetails/publicationDate,publicationDetails/bodsVersion,publicationDetails/license,publicationDetails/publisher/name,publicationDetails/publisher/url,source/type,source/description,source/url,source/retrievedAt,publicListing/hasPublicListing,publicListing/companyFilingsURLs,entitySubtype/generalCategory,entitySubtype/localTerm,formedByStatute/name,formedByStatute/date,personType,unspecifiedPersonDetails/reason,unspecifiedPersonDetails/description,placeOfBirth/type,placeOfBirth/address,placeOfBirth/postCode,placeOfBirth/country,birthDate,deathDate,placeOfResidence/type,placeOfResidence/address,placeOfResidence/postCode,placeOfResidence/country,politicalExposure/status,componentStatementIDs,subject/describedByEntityStatement,interestedParty/describedByEntityStatement,interestedParty/describedByPersonStatement,interestedParty/unspecified/reason,interestedParty/unspecified/description,publicListing/securitiesListings +1dc0e987-5c57-4a1c-b3ad-61353b66a9b7,entityStatement,2017-11-18,False,registeredEntity,,,CHRINON LTD,,,,2010-11-18,,,,2018-02-13,0.3,,CHRINON LTD,,,,,,True,http://example.com/,,,,,,,,,,,,,,,,,,,,,,,,, +019a93f1-e470-42e9-957b-03559861b2e2,personStatement,2017-11-18,False,,,,,,,,,,,,2018-02-13,0.3,,CHRINON LTD,,,,,,,,,,,,knownPerson,,,,,,,1964-04,,,,,,,,,,,,, +fbfd0547-d0c6-4a00-b559-5c5e91c34f5c,ownershipOrControlStatement,2017-11-18,False,,,,,,,,,,,,2018-02-13,0.3,,CHRINON LTD,,,,,,,,,,,,,,,,,,,,,,,,,,,1dc0e987-5c57-4a1c-b3ad-61353b66a9b7,,019a93f1-e470-42e9-957b-03559861b2e2,,, diff --git a/flattentool/tests/fixtures/basic_1.csv/names.csv b/flattentool/tests/fixtures/basic_1.csv/names.csv new file mode 100644 index 0000000..3478262 --- /dev/null +++ b/flattentool/tests/fixtures/basic_1.csv/names.csv @@ -0,0 +1,3 @@ +names/0/type,names/0/fullName,names/0/familyName,names/0/givenName,names/0/patronymicName,statementID +individual,Christopher Taggart,Taggart,Christopher,,019a93f1-e470-42e9-957b-03559861b2e2 +alternative,Chris Taggart,,,,019a93f1-e470-42e9-957b-03559861b2e2 diff --git a/flattentool/tests/fixtures/basic_1.csv/nationalities.csv b/flattentool/tests/fixtures/basic_1.csv/nationalities.csv new file mode 100644 index 0000000..2538661 --- /dev/null +++ b/flattentool/tests/fixtures/basic_1.csv/nationalities.csv @@ -0,0 +1,2 @@ +nationalities/0/name,nationalities/0/code,statementID +United Kingdom of Great Britain and Northern Ireland (the),GB,019a93f1-e470-42e9-957b-03559861b2e2 diff --git a/flattentool/tests/fixtures/basic_1.csv/pol_det_sou_assertedBy.csv b/flattentool/tests/fixtures/basic_1.csv/pol_det_sou_assertedBy.csv new file mode 100644 index 0000000..31a88a4 --- /dev/null +++ b/flattentool/tests/fixtures/basic_1.csv/pol_det_sou_assertedBy.csv @@ -0,0 +1 @@ +politicalExposure/details/0/source/assertedBy/0/name,politicalExposure/details/0/source/assertedBy/0/uri diff --git a/flattentool/tests/fixtures/basic_1.csv/pol_details.csv b/flattentool/tests/fixtures/basic_1.csv/pol_details.csv new file mode 100644 index 0000000..5cc0dbe --- /dev/null +++ b/flattentool/tests/fixtures/basic_1.csv/pol_details.csv @@ -0,0 +1 @@ +politicalExposure/details/0/reason,politicalExposure/details/0/missingInfoReason,politicalExposure/details/0/jurisdiction/name,politicalExposure/details/0/jurisdiction/code,politicalExposure/details/0/startDate,politicalExposure/details/0/endDate,politicalExposure/details/0/source/type,politicalExposure/details/0/source/description,politicalExposure/details/0/source/url,politicalExposure/details/0/source/retrievedAt diff --git a/flattentool/tests/fixtures/basic_1.csv/pub_securitiesListings.csv b/flattentool/tests/fixtures/basic_1.csv/pub_securitiesListings.csv new file mode 100644 index 0000000..9fd0e10 --- /dev/null +++ b/flattentool/tests/fixtures/basic_1.csv/pub_securitiesListings.csv @@ -0,0 +1 @@ +publicListing/securitiesListings/0/marketIdentifierCode,publicListing/securitiesListings/0/operatingMarketIdentifierCode,publicListing/securitiesListings/0/stockExchangeJurisdiction,publicListing/securitiesListings/0/stockExchangeName,publicListing/securitiesListings/0/security/idScheme,publicListing/securitiesListings/0/security/id,publicListing/securitiesListings/0/security/ticker diff --git a/flattentool/tests/fixtures/basic_1.csv/sou_assertedBy.csv b/flattentool/tests/fixtures/basic_1.csv/sou_assertedBy.csv new file mode 100644 index 0000000..73bcef0 --- /dev/null +++ b/flattentool/tests/fixtures/basic_1.csv/sou_assertedBy.csv @@ -0,0 +1 @@ +source/assertedBy/0/name,source/assertedBy/0/uri diff --git a/flattentool/tests/fixtures/basic_1.csv/taxResidencies.csv b/flattentool/tests/fixtures/basic_1.csv/taxResidencies.csv new file mode 100644 index 0000000..d149ebc --- /dev/null +++ b/flattentool/tests/fixtures/basic_1.csv/taxResidencies.csv @@ -0,0 +1 @@ +taxResidencies/0/name,taxResidencies/0/code diff --git a/flattentool/tests/fixtures/basic_1.json b/flattentool/tests/fixtures/basic_1.json new file mode 100644 index 0000000..8fddefc --- /dev/null +++ b/flattentool/tests/fixtures/basic_1.json @@ -0,0 +1,102 @@ +[ + { + "statementID": "1dc0e987-5c57-4a1c-b3ad-61353b66a9b7", + "statementType": "entityStatement", + "isComponent": false, + "statementDate": "2017-11-18", + "entityType": "registeredEntity", + "name": "CHRINON LTD", + "foundingDate": "2010-11-18", + "identifiers": [ + { + "scheme": "GB-COH", + "id": "07444723" + } + ], + "publicListing": { + "hasPublicListing": true, + "companyFilingsURLs": ["http://example.com/"], + "securitiesListings": [] + }, + "publicationDetails": { + "publicationDate": "2018-02-13", + "bodsVersion": "0.3", + "publisher": { + "name": "CHRINON LTD" + } + } + }, + { + "statementID": "019a93f1-e470-42e9-957b-03559861b2e2", + "statementType": "personStatement", + "isComponent": false, + "statementDate": "2017-11-18", + "personType": "knownPerson", + "nationalities": [ + { + "code": "GB", + "name": "United Kingdom of Great Britain and Northern Ireland (the)" + } + ], + "names": [ + { + "type": "individual", + "fullName": "Christopher Taggart", + "givenName": "Christopher", + "familyName": "Taggart" + }, + { + "type": "alternative", + "fullName": "Chris Taggart" + } + ], + "birthDate": "1964-04", + "addresses": [ + { + "type": "service", + "address": "Aston House, Cornwall Avenue, London", + "country": "GB", + "postCode": "N3 1LF" + } + ], + "publicationDetails": { + "publicationDate": "2018-02-13", + "bodsVersion": "0.3", + "publisher": { + "name": "CHRINON LTD" + } + } + }, + { + "statementID": "fbfd0547-d0c6-4a00-b559-5c5e91c34f5c", + "statementType": "ownershipOrControlStatement", + "isComponent": false, + "statementDate": "2017-11-18", + "subject": { + "describedByEntityStatement": "1dc0e987-5c57-4a1c-b3ad-61353b66a9b7" + }, + "interestedParty": { + "describedByPersonStatement": "019a93f1-e470-42e9-957b-03559861b2e2" + }, + "interests": [ + { + "type": "shareholding", + "directOrIndirect": "direct", + "beneficialOwnershipOrControl": true, + "startDate": "2016-04-06", + "share": { + "exact": 100, + "minimum": 100, + "maximum": 100 + } + } + ], + "publicationDetails": { + "publicationDate": "2018-02-13", + "bodsVersion": "0.3", + "publisher": { + "name": "CHRINON LTD" + } + } + } +] \ No newline at end of file diff --git a/flattentool/tests/fixtures/schema-0-3-0.json b/flattentool/tests/fixtures/schema-0-3-0.json new file mode 100644 index 0000000..7dbf9e9 --- /dev/null +++ b/flattentool/tests/fixtures/schema-0-3-0.json @@ -0,0 +1,1945 @@ +{ + "id": "bods-package.json", + "$schema": "http://json-schema.org/draft-04/schema#", + "version": "0.3", + "type": "array", + "items": { + "oneOf": [ + { + "id": "entity-statement.json", + "$schema": "http://json-schema.org/draft-04/schema#", + "version": "0.3", + "title": "Entity statement", + "description": "A statement identifying and describing the entity that is the subject of the ownership or control described in an ownership or control statement.", + "type": "object", + "properties": { + "statementID": { + "title": "Statement Identifier", + "description": "A persistent globally unique identifier for this statement.", + "type": "string", + "minLength": 32, + "maxLength": 64, + "propertyOrder": 1 + }, + "statementType": { + "title": "Statement type", + "description": "This MUST be 'entityStatement'.", + "type": "string", + "enum": [ + "entityStatement" + ], + "propertyOrder": 2 + }, + "statementDate": { + "title": "Statement date", + "description": "The date on which this statement was made.", + "type": "string", + "format": "date", + "propertyOrder": 3 + }, + "isComponent": { + "title": "Is component", + "description": "Does this Entity Statement represent a component of an indirect ownership-or-control relationship? Where `isComponent` is 'true': (1) the `statementID` of this Entity Statement MUST be an element in the `componentStatementIDs` array of that primary Ownership-or-control Statement, (2) this Entity Statement MUST come before that primary Ownership-or-control Statement in a BODS package or stream, (3) the replacement of this Entity Statement SHOULD be considered when replacing the primary Ownership-or-control Statement. The primary Ownership-or-control Statement MUST have a `isComponent` value of 'false'.", + "type": "boolean" + }, + "entityType": { + "title": "Type", + "description": "From the entityType codelist. What kind of entity is this? The 'registeredEntity' code covers any legal entity created through an act of official registration, usually resulting in an identifier being assigned to the entity. The \u2018legalEntity\u2019 code covers other bodies with distinct legal personality (international institutions, statutory corporations etc.). The 'arrangement' code covers artificial entities, described in the data model for the purpose of associating one or more natural or legal persons together in an ownership or control relationship, but without implying that the parties to this arrangement have any other form of collective legal identity.", + "type": "string", + "enum": [ + "registeredEntity", + "legalEntity", + "arrangement", + "anonymousEntity", + "unknownEntity", + "state", + "stateBody" + ], + "codelist": "entityType.csv", + "openCodelist": false, + "propertyOrder": 4 + }, + "unspecifiedEntityDetails": { + "title": "Unspecified entity details", + "description": "An explanation of why this entity has an `entityType` of 'anonymousEntity' or 'unknownEntity'. A `reason` MUST be specified.", + "type": "object", + "properties": { + "reason": { + "title": "Reason", + "description": "The reason that an entity cannot be specified. From the unspecifiedReason codelist.", + "type": "string", + "enum": [ + "noBeneficialOwners", + "subjectUnableToConfirmOrIdentifyBeneficialOwner", + "interestedPartyHasNotProvidedInformation", + "subjectExemptFromDisclosure", + "interestedPartyExemptFromDisclosure", + "unknown", + "informationUnknownToPublisher" + ], + "codelist": "unspecifiedReason.csv", + "openCodelist": false + }, + "description": { + "title": "Description", + "description": "Any supporting information about the absence of a specific entity. This field may be used to provide set phrases from a source system, or for a free-text explanation.", + "type": "string" + } + }, + "required": [ + "reason" + ], + "propertyOrder": 8 + }, + "name": { + "title": "Entity name", + "description": "The declared name of this entity.", + "type": "string", + "propertyOrder": 10 + }, + "alternateNames": { + "title": "Alternative names", + "description": "An array of other names this entity is known by.", + "type": "array", + "items": { + "type": "string", + "title": "Name", + "description": "A name this entity is known by." + }, + "propertyOrder": 12 + }, + "jurisdiction": { + "title": "Jurisdiction", + "description": "The jurisdiction in which this entity was registered (for legal and registered entities, and arrangements). Or the state's jurisdiction (for states and state bodies).", + "type": "object", + "properties": { + "name": { + "title": "Name", + "description": "The name of the jurisdiction", + "type": "string" + }, + "code": { + "title": "Country or subdivision code", + "description": "The 2-letter country code (ISO 3166-1) or the subdivision code (ISO 3166-2) for the jurisdiction.", + "type": "string", + "maxLength": 6, + "minLength": 2 + } + }, + "required": [ + "name" + ], + "propertyOrder": 15 + }, + "identifiers": { + "title": "Identifiers", + "description": "One or more official identifiers for this entity. Where available, official registration numbers should be provided.", + "type": "array", + "items": { + "title": "Identifier", + "description": "An identifier that has been assigned to this person or entity. The scheme or list from which the identifier is drawn should be declared.", + "type": "object", + "properties": { + "id": { + "title": "ID", + "description": "The identifier for this person or entity as provided in the declared scheme.", + "type": "string" + }, + "scheme": { + "title": "Scheme", + "description": "For entity statements, the scheme should be a entry from the org-id.guide (https://www.org-id.guide) codelist. For person statements, the scheme should have the pattern {JURISDICTION}-{TYPE} where JURISDICTION is an ISO 3-digit country code and TYPE is one of PASSPORT, TAXID or IDCARD. `scheme` or `schemeName` (or both) MUST be included in an Identifier object.", + "type": "string" + }, + "schemeName": { + "title": "Scheme name", + "description": "The name of this scheme, where the org-id code is unknown or only an unvalidated string is provided. `scheme` or `schemeName` (or both) MUST be included in an Identifier object.", + "type": "string" + }, + "uri": { + "title": "URI", + "description": "Where this identifier has a canonical URI (https://en.wikipedia.org/wiki/Uniform_Resource_Identifier) this may be included", + "type": "string", + "format": "uri" + } + }, + "anyOf": [ + { + "required": [ + "scheme" + ] + }, + { + "required": [ + "schemeName" + ] + }, + { + "required": [ + "scheme", + "schemeName" + ] + } + ] + }, + "propertyOrder": 20 + }, + "foundingDate": { + "title": "Founding date", + "description": "When was this entity founded, created or registered. Please provide as precise a date as possible in ISO 8601 format. When only the year or year and month is known, these can be given as YYYY or YYYY-MM.", + "type": "string", + "pattern": "^([\\+-]?\\d{4}(?!\\d{2}\b))((-?)((0[1-9]|1[0-2])(\\3([12]\\d|0[1-9]|3[01]))?|W([0-4]\\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\\d|[12]\\d{2}|3([0-5]\\d|6[1-6])))([T\\s]((([01]\\d|2[0-3])((:?)[0-5]\\d)?|24\\:?00)([\\.,]\\d+(?!:))?)?(\\17[0-5]\\d([\\.,]\\d+)?)?([zZ]|([\\+-])([01]\\d|2[0-3]):?([0-5]\\d)?)?)?)?$", + "propertyOrder": 30 + }, + "dissolutionDate": { + "title": "Dissolution date", + "description": "If this entity is no longer active, provide the date on which it was disolved or ceased. Please provide as precise a date as possible in ISO 8601 format. When only the year or year and month is known, these can be given as YYYY or YYYY-MM.", + "type": "string", + "pattern": "^([\\+-]?\\d{4}(?!\\d{2}\b))((-?)((0[1-9]|1[0-2])(\\3([12]\\d|0[1-9]|3[01]))?|W([0-4]\\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\\d|[12]\\d{2}|3([0-5]\\d|6[1-6])))([T\\s]((([01]\\d|2[0-3])((:?)[0-5]\\d)?|24\\:?00)([\\.,]\\d+(?!:))?)?(\\17[0-5]\\d([\\.,]\\d+)?)?([zZ]|([\\+-])([01]\\d|2[0-3]):?([0-5]\\d)?)?)?)?$", + "propertyOrder": 35 + }, + "addresses": { + "title": "Addresses", + "description": "One or more addresses for this entity.", + "type": "array", + "items": { + "title": "Address", + "description": "A free text address string, providing as much address data as is relevant, suitable for processing using address parsing algorithms. For some uses (for example, Place of Birth) only a town and country are required.", + "type": "object", + "properties": { + "type": { + "title": "Type", + "description": "What type of address is this? See the addressType codelist.", + "type": "string", + "enum": [ + "placeOfBirth", + "residence", + "registered", + "service", + "alternative", + "business" + ], + "codelist": "addressType.csv", + "openCodelist": false + }, + "address": { + "title": "Address", + "description": "The address, with each line or component of the address separated by a line-break or comma. Where postal codes are isolated fields in source systems, this `address` field SHOULD NOT include the postal code.", + "type": "string" + }, + "postCode": { + "title": "Postcode", + "description": "The postal code for this address.", + "type": "string" + }, + "country": { + "title": "Country", + "description": "The 2-letter country code (ISO 3166-1) for this address.", + "type": "string", + "minLength": 2, + "maxLength": 2 + } + } + }, + "propertyOrder": 40 + }, + "uri": { + "title": "URI", + "description": "Where a persistent URI (https://en.wikipedia.org/wiki/Uniform_Resource_Identifier) is available for this entity this should be included.", + "type": "string", + "format": "uri", + "propertyOrder": 21 + }, + "replacesStatements": { + "title": "Replaces statement(s)", + "description": "If this statement replaces a previous statement or statements, provide the identifier(s) for the previous statement(s) here. Consuming applications are advised to mark the identified statements as no longer active.", + "type": "array", + "items": { + "title": "Statement identifier", + "description": "The identifier of a statement that is no longer active.", + "type": "string", + "minLength": 32, + "maxLength": 64 + }, + "propertyOrder": 100 + }, + "publicationDetails": { + "title": "Publication details", + "description": "Information concerning the original publication of this statement.", + "type": "object", + "properties": { + "publicationDate": { + "title": "Publication date", + "description": "The date on which this statement was published.", + "type": "string", + "format": "date" + }, + "bodsVersion": { + "title": "BODS version", + "description": "The version of the Beneficial Ownership Data Standard to which this statement conforms, expressed as major.minor. For example: 0.2 or 1.0.", + "type": "string", + "pattern": "^(\\d+\\.)(\\d+)$" + }, + "license": { + "title": "License URL", + "description": "A link to the license that applies to this statement. The canonical URI of the license SHOULD be used. Publishers are encouraged to use a Public Domain Dedication or Open Definition Conformant (http://opendefinition.org/licenses/) license.", + "type": "string", + "format": "uri" + }, + "publisher": { + "type": "object", + "title": "Publisher", + "description": "Details of the organisation or individual publishing this statement.", + "properties": { + "name": { + "title": "Name", + "description": "The name of the publisher", + "type": "string" + }, + "url": { + "title": "URL", + "description": "The URL of the parent dataset or of the publisher's website homepage", + "type": "string", + "format": "uri" + } + }, + "anyOf": [ + { + "required": [ + "name" + ] + }, + { + "required": [ + "url" + ] + } + ] + } + }, + "required": [ + "publicationDate", + "bodsVersion", + "publisher" + ], + "propertyOrder": 85 + }, + "source": { + "title": "Source", + "description": "The source of information about this entity, or of information that supports an anonymous or unknown entity statement.", + "type": "object", + "properties": { + "type": { + "title": "Source type", + "description": "What type of source is this? Multiple tags can be combined. Values should come from the source type codelist.", + "type": "array", + "items": { + "type": "string", + "enum": [ + "selfDeclaration", + "officialRegister", + "thirdParty", + "primaryResearch", + "verified" + ], + "codelist": "sourceType.csv", + "openCodelist": false + } + }, + "description": { + "title": "Description", + "description": "Where required, additional free-text information about the source of this statement can be provided here.", + "type": "string" + }, + "url": { + "title": "Source URL", + "description": "If this information was fetched from an external URL, or a machine or human readable web page is available that provides additional information on how this statement was sourced, provide the URL.", + "type": "string", + "format": "uri" + }, + "retrievedAt": { + "title": "Retrieved at", + "description": "If this statement was imported from some external system, include a timestamp indicating when this took place. The statement's own date should be set based on the source information. ", + "type": "string", + "format": "date-time" + }, + "assertedBy": { + "title": "Asserted by", + "description": "Who is making this statement? This may be the name of the person or organisation making a self-declaration (in which case, please make sure the name field matches the organisation or person name field), or the name or description of some other party. If this statement has been verified, this may also include the name of the organisation providing verification.", + "type": "array", + "items": { + "type": "object", + "title": "Agent", + "description": "An individual, organisation or other responsible agent making, or supporting, a given statement or annotation.", + "properties": { + "name": { + "title": "Name", + "description": "The name of the agent", + "type": "string" + }, + "uri": { + "title": "URI", + "description": "An optional URI to identify the agent.", + "type": "string", + "format": "uri" + } + } + } + } + }, + "propertyOrder": 89 + }, + "annotations": { + "title": "Annotations", + "description": "Annotations about this statement or parts of this statement", + "type": "array", + "items": { + "title": "Annotation", + "description": "An annotation provides additional information about ownership or control data being provided. Annotations can be provided in free-text, and can apply to a whole statement, an object or a single field. Additional extended properties can be included on the annotation object to provide structured data where required.", + "type": "object", + "properties": { + "statementPointerTarget": { + "title": "Statement Fragment Pointer", + "description": "An RFC6901 JSON Pointer (https://tools.ietf.org/html/rfc6901) describing the target fragment of the statement that this annotation applies to, starting from the root of the statement. A value of '/' indicates that the annotation applies to the whole statement.", + "type": "string" + }, + "creationDate": { + "title": "Creation Date", + "description": "The date this annotation was created.", + "type": "string", + "format": "date" + }, + "createdBy": { + "title": "Created By", + "description": "The person, organisation or agent that created this annotation.", + "type": "object", + "properties": { + "name": { + "title": "Name", + "description": "The name of the person, organisation or agent that created this annotation.", + "type": "string" + }, + "uri": { + "title": "URI", + "description": "An optional URI to identify person, organisation or agent that created this annotation.", + "type": "string", + "format": "uri" + } + } + }, + "motivation": { + "title": "Motivation", + "description": "The motivation for this annotation, chosen from a codelist. See the annotationMotivation codelist.", + "type": "string", + "enum": [ + "commenting", + "correcting", + "identifying", + "linking", + "transformation" + ], + "codelist": "annotationMotivation.csv", + "openCodelist": false + }, + "description": { + "title": "Description", + "description": "A free-text description to annotate this statement or field.", + "type": "string" + }, + "transformedContent": { + "type": "string", + "title": "Transformed content", + "description": "A representation of the annotation target after the transformation in the description field has been applied. This field SHOULD only be used when the motivation is transformation." + }, + "url": { + "title": "URL", + "description": "A linked resource that annotates, provides context for or enhances this statement. The content of the resource, or the relationship to the statement, MAY be described in the `description` field. This field is REQUIRED if the value of `motivation` is 'linking'.", + "type": "string", + "format": "uri" + } + }, + "required": [ + "statementPointerTarget", + "motivation" + ], + "oneOfEnumSelectorField": "motivation", + "oneOf": [ + { + "properties": { + "motivation": { + "enum": [ + "linking" + ] + } + }, + "required": [ + "statementPointerTarget", + "motivation", + "url" + ] + }, + { + "properties": { + "motivation": { + "enum": [ + "identifying", + "commenting", + "correcting", + "transformation" + ] + } + } + } + ] + }, + "propertyOrder": 90 + }, + "publicListing": { + "type": "object", + "title": "Public listing", + "description": "Details of a publicly listed company, its securities (shares and other tradable financial instruments related to the entity), and related regulatory filings.", + "required": [ + "securitiesListings", + "hasPublicListing" + ], + "minProperties": 1, + "properties": { + "hasPublicListing": { + "type": "boolean", + "title": "Has public listing", + "description": "This value MUST be true if the entity is known to be a publicly listed company." + }, + "companyFilingsURLs": { + "type": "array", + "title": "Company filings URLs", + "description": "URL or URLs where regulatory filings related to major holdings can be retrieved. URLs may point to pages maintained by regulatory bodies, stock exchanges or by the company itself.", + "items": { + "type": "string" + }, + "format": "uri" + }, + "securitiesListings": { + "type": "array", + "title": "Securities listings", + "description": "Details of the entity's securities and the public exchanges and markets on which they are traded. All equity securities SHOULD BE listed here, plus any other securities from which beneficial ownership might be derived. Where a security is traded on more than one market, there SHOULD BE an entry for each market (or market segment).", + "items": { + "type": "object", + "title": "Securities listing", + "description": "Details of a security and the market on which it is traded.", + "required": [ + "stockExchangeJurisdiction", + "security", + "stockExchangeName" + ], + "properties": { + "marketIdentifierCode": { + "type": "string", + "title": "Market Identifier Code (MIC)", + "description": "The Market Identifier Code (MIC) of the market on which the security is traded. Where the security is traded on a segment of an exchange, this is the MIC of the segment. Where it is traded on the main exchange, this is the MIC of the main exchange and MUST match the `operatingMarketIdentifierCode`. MICs are allocated and managed under ISO standard 10383." + }, + "operatingMarketIdentifierCode": { + "type": "string", + "title": "Operating Market Identifier Code (Operating MIC)", + "description": "The Market Identifier Code (MIC) of the main exchange or trading platform handling trades in this security. Where the security is traded on a segment of an exchange, this is the MIC of the parent exchange or trading platform. Where it is traded on the main exchange, this is the MIC of that main exchange and MUST match the `marketIdentifierCode`. MICs are allocated and managed under ISO standard 10383." + }, + "stockExchangeJurisdiction": { + "type": "string", + "title": "Stock exchange jurisdiction", + "description": "The 2-letter country code (ISO 3166-1) or the subdivision code (ISO 3166-2) for the jurisdiction under which the exchange, market or trading platform is regulated.", + "maxLength": 6, + "minLength": 2 + }, + "stockExchangeName": { + "type": "string", + "title": "Stock exchange name", + "description": "The name of the exchange, market or trading platform on which the security is traded. If the security is traded on a segment of the exchange, then the name SHOULD include both elements. For example, 'London Stock Exchange - MTF'." + }, + "security": { + "type": "object", + "title": "Security", + "description": "Identifying information of the stock or other security.", + "required": [ + "ticker" + ], + "properties": { + "idScheme": { + "type": "string", + "title": "Identifier scheme", + "description": "The scheme under which the security has been issued a unique, persistent identifier. For accepted schemas, see the securitiesIdentifierSchemes codelist.", + "enum": [ + "isin", + "figi", + "cusip", + "cins" + ], + "codelist": "securitiesIdentifierSchemes.csv" + }, + "id": { + "type": "string", + "title": "Identifier", + "description": "The unique identifier of the security as issued under the `idScheme`." + }, + "ticker": { + "type": "string", + "title": "Stock ticker", + "description": "The stock ticker identifying this security on the named stock exchange." + } + } + } + } + } + } + } + }, + "entitySubtype": { + "type": "object", + "title": "Subtype", + "description": "Further information about the type of entity described in the statement.", + "required": [ + "generalCategory" + ], + "properties": { + "generalCategory": { + "type": "string", + "title": "General category", + "description": "The general category into which the entity fits. The category classification MUST align with the `entityType` classification.", + "codelist": "entitySubtypeCategory.csv", + "enum": [ + "stateBody-governmentDepartment", + "stateBody-stateAgency", + "stateBody-other" + ], + "openCodelist": false + }, + "localTerm": { + "type": "string", + "title": "Local term", + "description": "The local term for the category of entity. For example, in Finland 'ministeri\u00f6' for a government department." + } + }, + "propertyOrder": 5 + }, + "formedByStatute": { + "type": "object", + "title": "Formed by statute", + "description": "The law which mandated the formation of the entity described in the statement, where applicable. This information SHOULD be provided where a state has created an agency or other entity with specific legislation. ", + "properties": { + "name": { + "type": "string", + "title": "Statute name", + "description": "The name of the law. " + }, + "date": { + "type": "string", + "title": "Date", + "description": "The date on which the law was passed. The date SHOULD be in the form YYYY-MM-DD. When only the year or year and month is known, these can be given as YYYY or YYYY-MM.", + "pattern": "^([\\+-]?\\d{4}(?!\\d{2}\b))((-?)((0[1-9]|1[0-2])(\\3([12]\\d|0[1-9]|3[01]))?|W([0-4]\\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\\d|[12]\\d{2}|3([0-5]\\d|6[1-6])))([T\\s]((([01]\\d|2[0-3])((:?)[0-5]\\d)?|24\\:?00)([\\.,]\\d+(?!:))?)?(\\17[0-5]\\d([\\.,]\\d+)?)?([zZ]|([\\+-])([01]\\d|2[0-3]):?([0-5]\\d)?)?)?)?$" + } + }, + "propertyOrder": 18 + } + }, + "required": [ + "statementID", + "statementType", + "isComponent", + "entityType", + "publicationDetails" + ] + }, + { + "id": "person-statement.json", + "$schema": "http://json-schema.org/draft-04/schema#", + "version": "0.3", + "type": "object", + "title": "Person statement", + "description": "A person statement describes the information known about a natural person at a particular point in time, or from a given submission of information", + "properties": { + "statementID": { + "title": "Statement Identifier", + "description": "A persistent globally unique identifier for this statement.", + "type": "string", + "minLength": 32, + "maxLength": 64, + "propertyOrder": 1 + }, + "statementType": { + "title": "Statement type", + "description": "This MUST be 'personStatement'.", + "type": "string", + "enum": [ + "personStatement" + ], + "propertyOrder": 2 + }, + "statementDate": { + "title": "Statement date", + "description": "The date on which this statement was made.", + "type": "string", + "format": "date", + "propertyOrder": 3 + }, + "isComponent": { + "title": "Is component", + "description": "Does this Person Statement represent a component of an indirect ownership-or-control relationship? Where `isComponent` is 'true': (1) the `statementID` of this Person Statement MUST be an element in the `componentStatementIDs` array of that primary Ownership-or-control Statement, (2) this Person Statement MUST come before that primary Ownership-or-control Statement in a BODS package or stream, (3) the replacement of this Person Statement SHOULD be considered when replacing the primary Ownership-or-control Statement. The primary Ownership-or-control Statement MUST have a `isComponent` value of 'false'.", + "type": "boolean" + }, + "personType": { + "title": "Person type", + "description": "Use the personType codelist. The ultimate beneficial owner of a legal entity is always a natural person. Where the beneficial owner has been identified, but information about them cannot be disclosed, use 'anonymousPerson'. Where the beneficial owner has not been clearly identified, use 'unknownPerson'. Where the beneficial owner has been identified use knownPerson. Where a person has the type 'anonymousPerson' or 'unknownPerson' a reason for the absence of information SHOULD be provided in 'unspecifiedPersonDetails')", + "type": "string", + "enum": [ + "knownPerson", + "anonymousPerson", + "unknownPerson" + ], + "propertyOrder": 4, + "codelist": "personType.csv", + "openCodelist": false + }, + "unspecifiedPersonDetails": { + "title": "Unspecified person details", + "description": "An explanation of why this person has a `personType` of 'anonymousPerson' or 'unknownPerson'. A `reason` MUST be specified.", + "type": "object", + "properties": { + "reason": { + "title": "Reason", + "description": "The reason that an interested party cannot be specified. From the unspecifiedReason codelist.", + "type": "string", + "enum": [ + "noBeneficialOwners", + "subjectUnableToConfirmOrIdentifyBeneficialOwner", + "interestedPartyHasNotProvidedInformation", + "subjectExemptFromDisclosure", + "interestedPartyExemptFromDisclosure", + "unknown", + "informationUnknownToPublisher" + ], + "codelist": "unspecifiedReason.csv", + "openCodelist": false + }, + "description": { + "title": "Description", + "description": "Any supporting information about the absence of a confirmed beneficial owner. This field may be used to provide set phrases from a source system, or for a free-text explanation.", + "type": "string" + } + }, + "required": [ + "reason" + ], + "propertyOrder": 5 + }, + "names": { + "title": "Names", + "description": "One or more known names for this individual.", + "type": "array", + "items": { + "title": "Name", + "description": "An name by which this individual is known. Names should be provided in `fullName`, and may optionally be broken down in the `familyName`, `givenName` and `patronymicName` fields, based on the EC ISA Core Person Vocabulary (https://joinup.ec.europa.eu/solution/e-government-core-vocabularies) definitions.", + "type": "object", + "properties": { + "type": { + "title": "Type", + "description": "What kind of name is this? See the nameType codelist.", + "type": "string", + "enum": [ + "individual", + "translation", + "transliteration", + "former", + "alternative", + "birth" + ], + "codelist": "nameType.csv", + "openCodelist": false + }, + "fullName": { + "title": "Full name", + "description": "The full name contains the complete name of a person as one string.", + "type": "string" + }, + "familyName": { + "title": "Family name", + "description": "A family name is usually shared by members of a family. This attribute also carries prefixes or suffixes which are part of the Family Name, e.g. 'de Boer', 'van de Putte', 'von und zu Orlow'. Multiple family names, such as are commonly found in Hispanic countries, are recorded in the single Family Name field so that, for example, Miguel de Cervantes Saavedra's Family Name would be recorded as 'Cervantes Saavedra.'", + "type": "string" + }, + "givenName": { + "title": "Given names", + "description": "A given name, or multiple given names, are the denominator(s) that identify an individual within a family. These are given to a person by his or her parents at birth or may be legally recognised as 'given names' through a formal process. All given names are ordered in one field so that, for example, the given name for Johann Sebastian Bach is 'Johann Sebastian'.", + "type": "string" + }, + "patronymicName": { + "title": "Patronymic Name", + "description": "Patronymic names are important in some countries. Iceland does not have a concept of family name in the way that many other European countries do, for example. In Bulgaria and Russia, patronymic names are in every day usage, for example, the 'Sergeyevich' in 'Mikhail Sergeyevich Gorbachev'.", + "type": "string" + } + } + }, + "propertyOrder": 10 + }, + "identifiers": { + "title": "Identifiers", + "description": "One or more official identifiers for this perrson. Where available, official registration numbers should be provided.", + "type": "array", + "items": { + "title": "Identifier", + "description": "An identifier that has been assigned to this person or entity. The scheme or list from which the identifier is drawn should be declared.", + "type": "object", + "properties": { + "id": { + "title": "ID", + "description": "The identifier for this person or entity as provided in the declared scheme.", + "type": "string" + }, + "scheme": { + "title": "Scheme", + "description": "For entity statements, the scheme should be a entry from the org-id.guide (https://www.org-id.guide) codelist. For person statements, the scheme should have the pattern {JURISDICTION}-{TYPE} where JURISDICTION is an ISO 3-digit country code and TYPE is one of PASSPORT, TAXID or IDCARD. `scheme` or `schemeName` (or both) MUST be included in an Identifier object.", + "type": "string" + }, + "schemeName": { + "title": "Scheme name", + "description": "The name of this scheme, where the org-id code is unknown or only an unvalidated string is provided. `scheme` or `schemeName` (or both) MUST be included in an Identifier object.", + "type": "string" + }, + "uri": { + "title": "URI", + "description": "Where this identifier has a canonical URI (https://en.wikipedia.org/wiki/Uniform_Resource_Identifier) this may be included", + "type": "string", + "format": "uri" + } + }, + "anyOf": [ + { + "required": [ + "scheme" + ] + }, + { + "required": [ + "schemeName" + ] + }, + { + "required": [ + "scheme", + "schemeName" + ] + } + ] + }, + "propertyOrder": 20 + }, + "nationalities": { + "title": "Nationality", + "description": "An array of ISO 2-Digit country codes representing nationalities held by this individual.", + "type": "array", + "items": { + "title": "Country", + "description": "A country MUST have a name. A country SHOULD have a 2-letter country code (ISO 3166-1)", + "type": "object", + "properties": { + "name": { + "title": "Name", + "description": "The name of the country", + "type": "string" + }, + "code": { + "title": "Country code", + "description": "The 2-letter country code (ISO 3166-1) for this country.", + "type": "string", + "maxLength": 2, + "minLength": 2 + } + }, + "required": [ + "name" + ] + }, + "propertyOrder": 30 + }, + "placeOfBirth": { + "title": "Place of birth", + "description": "A free text address string, providing as much address data as is relevant, suitable for processing using address parsing algorithms. For some uses (for example, Place of Birth) only a town and country are required.", + "type": "object", + "properties": { + "type": { + "title": "Type", + "description": "What type of address is this? See the addressType codelist.", + "type": "string", + "enum": [ + "placeOfBirth", + "residence", + "registered", + "service", + "alternative", + "business" + ], + "codelist": "addressType.csv", + "openCodelist": false + }, + "address": { + "title": "Address", + "description": "The address, with each line or component of the address separated by a line-break or comma. Where postal codes are isolated fields in source systems, this `address` field SHOULD NOT include the postal code.", + "type": "string" + }, + "postCode": { + "title": "Postcode", + "description": "The postal code for this address.", + "type": "string" + }, + "country": { + "title": "Country", + "description": "The 2-letter country code (ISO 3166-1) for this address.", + "type": "string", + "minLength": 2, + "maxLength": 2 + } + }, + "propertyOrder": 40 + }, + "birthDate": { + "title": "Date of birth", + "description": "The date of birth for this individual. Please provide as precise a date as possible in ISO 8601 format. When only the year or year and month is known, these can be given as YYYY or YYYY-MM.", + "type": "string", + "pattern": "^([\\+-]?\\d{4}(?!\\d{2}\b))((-?)((0[1-9]|1[0-2])(\\3([12]\\d|0[1-9]|3[01]))?|W([0-4]\\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\\d|[12]\\d{2}|3([0-5]\\d|6[1-6])))([T\\s]((([01]\\d|2[0-3])((:?)[0-5]\\d)?|24\\:?00)([\\.,]\\d+(?!:))?)?(\\17[0-5]\\d([\\.,]\\d+)?)?([zZ]|([\\+-])([01]\\d|2[0-3]):?([0-5]\\d)?)?)?)?$", + "propertyOrder": 35 + }, + "deathDate": { + "title": "Death date", + "description": "If this individual is no longer alive, provide their date of death. Please provide as precise a date as possible in ISO 8601 format. When only the year or year and month is known, these can be given as YYYY or YYYY-MM.", + "type": "string", + "pattern": "^([\\+-]?\\d{4}(?!\\d{2}\b))((-?)((0[1-9]|1[0-2])(\\3([12]\\d|0[1-9]|3[01]))?|W([0-4]\\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\\d|[12]\\d{2}|3([0-5]\\d|6[1-6])))([T\\s]((([01]\\d|2[0-3])((:?)[0-5]\\d)?|24\\:?00)([\\.,]\\d+(?!:))?)?(\\17[0-5]\\d([\\.,]\\d+)?)?([zZ]|([\\+-])([01]\\d|2[0-3]):?([0-5]\\d)?)?)?)?$", + "propertyOrder": 36 + }, + "placeOfResidence": { + "title": "Place of residence", + "description": "A free text address string, providing as much address data as is relevant, suitable for processing using address parsing algorithms. For some uses (for example, Place of Birth) only a town and country are required.", + "type": "object", + "properties": { + "type": { + "title": "Type", + "description": "What type of address is this? See the addressType codelist.", + "type": "string", + "enum": [ + "placeOfBirth", + "residence", + "registered", + "service", + "alternative", + "business" + ], + "codelist": "addressType.csv", + "openCodelist": false + }, + "address": { + "title": "Address", + "description": "The address, with each line or component of the address separated by a line-break or comma. Where postal codes are isolated fields in source systems, this `address` field SHOULD NOT include the postal code.", + "type": "string" + }, + "postCode": { + "title": "Postcode", + "description": "The postal code for this address.", + "type": "string" + }, + "country": { + "title": "Country", + "description": "The 2-letter country code (ISO 3166-1) for this address.", + "type": "string", + "minLength": 2, + "maxLength": 2 + } + }, + "propertyOrder": 50 + }, + "taxResidencies": { + "title": "Tax residency", + "description": "An array of ISO 2-Digit country codes representing the tax residencies held by this individual.", + "type": "array", + "items": { + "title": "Country", + "description": "A country MUST have a name. A country SHOULD have a 2-letter country code (ISO 3166-1)", + "type": "object", + "properties": { + "name": { + "title": "Name", + "description": "The name of the country", + "type": "string" + }, + "code": { + "title": "Country code", + "description": "The 2-letter country code (ISO 3166-1) for this country.", + "type": "string", + "maxLength": 2, + "minLength": 2 + } + }, + "required": [ + "name" + ] + }, + "propertyOrder": 55 + }, + "addresses": { + "title": "Addresses", + "description": "One or more addresses for this entity.", + "type": "array", + "items": { + "title": "Address", + "description": "A free text address string, providing as much address data as is relevant, suitable for processing using address parsing algorithms. For some uses (for example, Place of Birth) only a town and country are required.", + "type": "object", + "properties": { + "type": { + "title": "Type", + "description": "What type of address is this? See the addressType codelist.", + "type": "string", + "enum": [ + "placeOfBirth", + "residence", + "registered", + "service", + "alternative", + "business" + ], + "codelist": "addressType.csv", + "openCodelist": false + }, + "address": { + "title": "Address", + "description": "The address, with each line or component of the address separated by a line-break or comma. Where postal codes are isolated fields in source systems, this `address` field SHOULD NOT include the postal code.", + "type": "string" + }, + "postCode": { + "title": "Postcode", + "description": "The postal code for this address.", + "type": "string" + }, + "country": { + "title": "Country", + "description": "The 2-letter country code (ISO 3166-1) for this address.", + "type": "string", + "minLength": 2, + "maxLength": 2 + } + } + }, + "propertyOrder": 60 + }, + "publicationDetails": { + "title": "Publication details", + "description": "Information concerning the original publication of this statement.", + "type": "object", + "properties": { + "publicationDate": { + "title": "Publication date", + "description": "The date on which this statement was published.", + "type": "string", + "format": "date" + }, + "bodsVersion": { + "title": "BODS version", + "description": "The version of the Beneficial Ownership Data Standard to which this statement conforms, expressed as major.minor. For example: 0.2 or 1.0.", + "type": "string", + "pattern": "^(\\d+\\.)(\\d+)$" + }, + "license": { + "title": "License URL", + "description": "A link to the license that applies to this statement. The canonical URI of the license SHOULD be used. Publishers are encouraged to use a Public Domain Dedication or Open Definition Conformant (http://opendefinition.org/licenses/) license.", + "type": "string", + "format": "uri" + }, + "publisher": { + "type": "object", + "title": "Publisher", + "description": "Details of the organisation or individual publishing this statement.", + "properties": { + "name": { + "title": "Name", + "description": "The name of the publisher", + "type": "string" + }, + "url": { + "title": "URL", + "description": "The URL of the parent dataset or of the publisher's website homepage", + "type": "string", + "format": "uri" + } + }, + "anyOf": [ + { + "required": [ + "name" + ] + }, + { + "required": [ + "url" + ] + } + ] + } + }, + "required": [ + "publicationDate", + "bodsVersion", + "publisher" + ], + "propertyOrder": 85 + }, + "source": { + "title": "Source", + "description": "The source of information about this person, or of information that supports an unknown or anonymous person statement.", + "type": "object", + "properties": { + "type": { + "title": "Source type", + "description": "What type of source is this? Multiple tags can be combined. Values should come from the source type codelist.", + "type": "array", + "items": { + "type": "string", + "enum": [ + "selfDeclaration", + "officialRegister", + "thirdParty", + "primaryResearch", + "verified" + ], + "codelist": "sourceType.csv", + "openCodelist": false + } + }, + "description": { + "title": "Description", + "description": "Where required, additional free-text information about the source of this statement can be provided here.", + "type": "string" + }, + "url": { + "title": "Source URL", + "description": "If this information was fetched from an external URL, or a machine or human readable web page is available that provides additional information on how this statement was sourced, provide the URL.", + "type": "string", + "format": "uri" + }, + "retrievedAt": { + "title": "Retrieved at", + "description": "If this statement was imported from some external system, include a timestamp indicating when this took place. The statement's own date should be set based on the source information. ", + "type": "string", + "format": "date-time" + }, + "assertedBy": { + "title": "Asserted by", + "description": "Who is making this statement? This may be the name of the person or organisation making a self-declaration (in which case, please make sure the name field matches the organisation or person name field), or the name or description of some other party. If this statement has been verified, this may also include the name of the organisation providing verification.", + "type": "array", + "items": { + "type": "object", + "title": "Agent", + "description": "An individual, organisation or other responsible agent making, or supporting, a given statement or annotation.", + "properties": { + "name": { + "title": "Name", + "description": "The name of the agent", + "type": "string" + }, + "uri": { + "title": "URI", + "description": "An optional URI to identify the agent.", + "type": "string", + "format": "uri" + } + } + } + } + }, + "propertyOrder": 89 + }, + "annotations": { + "title": "Annotations", + "description": "Annotations about this statement or parts of this statement", + "type": "array", + "items": { + "title": "Annotation", + "description": "An annotation provides additional information about ownership or control data being provided. Annotations can be provided in free-text, and can apply to a whole statement, an object or a single field. Additional extended properties can be included on the annotation object to provide structured data where required.", + "type": "object", + "properties": { + "statementPointerTarget": { + "title": "Statement Fragment Pointer", + "description": "An RFC6901 JSON Pointer (https://tools.ietf.org/html/rfc6901) describing the target fragment of the statement that this annotation applies to, starting from the root of the statement. A value of '/' indicates that the annotation applies to the whole statement.", + "type": "string" + }, + "creationDate": { + "title": "Creation Date", + "description": "The date this annotation was created.", + "type": "string", + "format": "date" + }, + "createdBy": { + "title": "Created By", + "description": "The person, organisation or agent that created this annotation.", + "type": "object", + "properties": { + "name": { + "title": "Name", + "description": "The name of the person, organisation or agent that created this annotation.", + "type": "string" + }, + "uri": { + "title": "URI", + "description": "An optional URI to identify person, organisation or agent that created this annotation.", + "type": "string", + "format": "uri" + } + } + }, + "motivation": { + "title": "Motivation", + "description": "The motivation for this annotation, chosen from a codelist. See the annotationMotivation codelist.", + "type": "string", + "enum": [ + "commenting", + "correcting", + "identifying", + "linking", + "transformation" + ], + "codelist": "annotationMotivation.csv", + "openCodelist": false + }, + "description": { + "title": "Description", + "description": "A free-text description to annotate this statement or field.", + "type": "string" + }, + "transformedContent": { + "type": "string", + "title": "Transformed content", + "description": "A representation of the annotation target after the transformation in the description field has been applied. This field SHOULD only be used when the motivation is transformation." + }, + "url": { + "title": "URL", + "description": "A linked resource that annotates, provides context for or enhances this statement. The content of the resource, or the relationship to the statement, MAY be described in the `description` field. This field is REQUIRED if the value of `motivation` is 'linking'.", + "type": "string", + "format": "uri" + } + }, + "required": [ + "statementPointerTarget", + "motivation" + ], + "oneOfEnumSelectorField": "motivation", + "oneOf": [ + { + "properties": { + "motivation": { + "enum": [ + "linking" + ] + } + }, + "required": [ + "statementPointerTarget", + "motivation", + "url" + ] + }, + { + "properties": { + "motivation": { + "enum": [ + "identifying", + "commenting", + "correcting", + "transformation" + ] + } + } + } + ] + }, + "propertyOrder": 90 + }, + "replacesStatements": { + "title": "Replaces statement(s)", + "description": "If this statement replaces a previous statement or statements, provide the identifier(s) for the previous statement(s) here. Consuming applications are advised to mark the identified statements as no longer active.", + "type": "array", + "items": { + "title": "Statement identifier", + "description": "The identifier of a statement that is no longer active.", + "type": "string", + "minLength": 32, + "maxLength": 64 + } + }, + "politicalExposure": { + "type": "object", + "title": "Political exposure", + "description": "Information about whether, and how, the person described by this statement is politically exposed. Use this property only if politically exposed person (PEP) declarations are expected as part of beneficial ownership declarations.", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string", + "title": "Politically exposed person (PEP) status", + "description": "This value is 'isPep' or 'isNotPep' according to whether the person described by this statement has the status of politically exposed person (PEP). An 'unknown' value means a PEP status declaration is expected but missing; the reason for the missing data SHOULD be supplied in the details array.", + "enum": [ + "isPep", + "isNotPep", + "unknown" + ] + }, + "details": { + "type": "array", + "title": "Politically exposed person (PEP) details", + "description": "One or more descriptions of this person's Politically Exposed Person (PEP) status.", + "items": { + "title": "PEP Status", + "description": "A description of a politically-exposed person status.", + "type": "object", + "properties": { + "reason": { + "title": "Reason", + "description": "The reason for this person being declared a politically-exposed person.", + "type": "string" + }, + "missingInfoReason": { + "title": "Missing information reason(s)", + "description": "An explanation of the reason that PEP status for the person is not provided (i.e. `politicalExposure.status` is 'unknown'). This may be a standard descriptive phrase from the source system, or a free-text justification. Where this field is present it should be the only field except for `source`.", + "type": "string" + }, + "jurisdiction": { + "title": "Jurisdiction", + "description": "The jurisdiction where this person is a PEP.", + "type": "object", + "properties": { + "name": { + "title": "Name", + "description": "The name of the jurisdiction", + "type": "string" + }, + "code": { + "title": "Country or subdivision code", + "description": "The 2-letter country code (ISO 3166-1) or the subdivision code (ISO 3166-2) for the jurisdiction.", + "type": "string", + "maxLength": 6, + "minLength": 2 + } + }, + "required": [ + "name" + ] + }, + "startDate": { + "title": "State date", + "description": "When did this PEP status begin. Please provide as precise a date as possible in ISO 8601 format. When only the year or year and month is known, these can be given as YYYY or YYYY-MM.", + "type": "string", + "pattern": "^([\\+-]?\\d{4}(?!\\d{2}\b))((-?)((0[1-9]|1[0-2])(\\3([12]\\d|0[1-9]|3[01]))?|W([0-4]\\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\\d|[12]\\d{2}|3([0-5]\\d|6[1-6])))([T\\s]((([01]\\d|2[0-3])((:?)[0-5]\\d)?|24\\:?00)([\\.,]\\d+(?!:))?)?(\\17[0-5]\\d([\\.,]\\d+)?)?([zZ]|([\\+-])([01]\\d|2[0-3]):?([0-5]\\d)?)?)?)?$" + }, + "endDate": { + "title": "End date", + "description": "When did this PEP status end. Please provide as precise a date as possible in ISO 8601 format. When only the year or year and month is known, these can be given as YYYY or YYYY-MM.", + "type": "string", + "pattern": "^([\\+-]?\\d{4}(?!\\d{2}\b))((-?)((0[1-9]|1[0-2])(\\3([12]\\d|0[1-9]|3[01]))?|W([0-4]\\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\\d|[12]\\d{2}|3([0-5]\\d|6[1-6])))([T\\s]((([01]\\d|2[0-3])((:?)[0-5]\\d)?|24\\:?00)([\\.,]\\d+(?!:))?)?(\\17[0-5]\\d([\\.,]\\d+)?)?([zZ]|([\\+-])([01]\\d|2[0-3]):?([0-5]\\d)?)?)?)?$" + }, + "source": { + "title": "Source", + "description": "The source of this PEP information", + "type": "object", + "properties": { + "type": { + "title": "Source type", + "description": "What type of source is this? Multiple tags can be combined. Values should come from the source type codelist.", + "type": "array", + "items": { + "type": "string", + "enum": [ + "selfDeclaration", + "officialRegister", + "thirdParty", + "primaryResearch", + "verified" + ], + "codelist": "sourceType.csv", + "openCodelist": false + } + }, + "description": { + "title": "Description", + "description": "Where required, additional free-text information about the source of this statement can be provided here.", + "type": "string" + }, + "url": { + "title": "Source URL", + "description": "If this information was fetched from an external URL, or a machine or human readable web page is available that provides additional information on how this statement was sourced, provide the URL.", + "type": "string", + "format": "uri" + }, + "retrievedAt": { + "title": "Retrieved at", + "description": "If this statement was imported from some external system, include a timestamp indicating when this took place. The statement's own date should be set based on the source information. ", + "type": "string", + "format": "date-time" + }, + "assertedBy": { + "title": "Asserted by", + "description": "Who is making this statement? This may be the name of the person or organisation making a self-declaration (in which case, please make sure the name field matches the organisation or person name field), or the name or description of some other party. If this statement has been verified, this may also include the name of the organisation providing verification.", + "type": "array", + "items": { + "type": "object", + "title": "Agent", + "description": "An individual, organisation or other responsible agent making, or supporting, a given statement or annotation.", + "properties": { + "name": { + "title": "Name", + "description": "The name of the agent", + "type": "string" + }, + "uri": { + "title": "URI", + "description": "An optional URI to identify the agent.", + "type": "string", + "format": "uri" + } + } + } + } + } + } + } + } + } + } + } + }, + "required": [ + "statementID", + "statementType", + "personType", + "isComponent", + "publicationDetails" + ] + }, + { + "id": "ownership-or-control-statement.json", + "$schema": "http://json-schema.org/draft-04/schema#", + "version": "0.3", + "title": "Ownership or control Statement", + "description": "An ownership or control statement is made up of an entity, an interested party (a reference to an entity, natural person, arrangement or trust), details of the interest and provenance information for the statement.", + "type": "object", + "properties": { + "statementID": { + "title": "Statement Identifier", + "description": "A persistent globally unique identifier for this statement.", + "type": "string", + "minLength": 32, + "maxLength": 64 + }, + "statementType": { + "title": "Statement type", + "description": "This MUST be 'ownershipOrControlStatement'.", + "type": "string", + "enum": [ + "ownershipOrControlStatement" + ] + }, + "statementDate": { + "title": "Statement date", + "description": "The date on which this statement was made.", + "type": "string", + "format": "date" + }, + "isComponent": { + "title": "Is component", + "description": "Does this Ownership-or-control Statement represent a component of a wider indirect ownership-or-control relationship? Where `isComponent` is 'true': (1) the `statementID` of this secondary Ownership-or-control Statement MUST be an element in the `componentStatementIDs` array of that primary Ownership-or-control Statement, (2) this Ownership-or-control Statement MUST come before that primary Ownership-or-control Statement in a BODS package or stream, (3) the replacement of this Ownership-or-control Statement SHOULD be considered when replacing the primary Ownership-or-control Statement, and (4) the primary Ownership-or-control Statement MUST have a `isComponent` value of 'false'. Where `isComponent` is 'false', this Ownership-or-control Statement is the primary declaration of the relationship between the `subject` and the `interestedParty`.", + "type": "boolean" + }, + "componentStatementIDs": { + "title": "Component statement IDs", + "description": "The identifiers of all component statements that provide detail about the indirect relationship between this Statement's `subject` and `interestedParty`. If this Ownership-or-control Statement has components, it MUST itself have a `isComponent` value of 'false'.", + "type": "array", + "items": { + "title": "Statement Identifier", + "description": "A persistent globally unique identifier for this statement.", + "type": "string", + "minLength": 32, + "maxLength": 64 + } + }, + "subject": { + "title": "Subject", + "description": "The subject of an ownership or control relationship.", + "type": "object", + "properties": { + "describedByEntityStatement": { + "title": "Described by entity statement", + "description": "Provide the identifier of the statement which describes the entity that the subject of an ownership or control interest.", + "type": "string" + } + }, + "required": [ + "describedByEntityStatement" + ] + }, + "interestedParty": { + "title": "Interested party", + "description": "The interested party has some level of ownership or control over the entity referenced in this ownership or control statement. This should be described with reference to either an entity statement or person statement, or, where the interested party is unknown, details of why. ", + "type": "object", + "properties": { + "describedByEntityStatement": { + "title": "Described by entity statement", + "description": "A reference to a statement describing a registered entity, trust or arrangement that has an ownership or control interest in the subject of this statement. An entityStatement should be used when the direct interests to be described represents known control or ownership by anyone other than a natural person.", + "type": "string" + }, + "describedByPersonStatement": { + "title": "Described by person statement", + "description": "A reference to a statement describing a natural person who has an ownership or control interest in the subject of this statement.", + "type": "string" + }, + "unspecified": { + "title": "Unspecified or unknown ownership and control", + "description": "When confirmation has been provided that no interested party exists, where ownership and control information does not need to be provided, or where details of ownership and control are unknown, a `reason` MUST be given. Where an unknown entity is the `subject` of further ownershipOrControlStatements in the same structure, or where there is a natural person with ownership or control but their name or details are not known or cannot be disclosed for some reason, `unspecified` should not be used, but instead a reference to a `personStatement` or `entityStatement` should be provided but identifying details MAY be left blank.", + "type": "object", + "properties": { + "reason": { + "title": "Reason", + "description": "The reason that an interested party cannot be specified. From the unspecifiedReason codelist.", + "type": "string", + "enum": [ + "noBeneficialOwners", + "subjectUnableToConfirmOrIdentifyBeneficialOwner", + "interestedPartyHasNotProvidedInformation", + "subjectExemptFromDisclosure", + "interestedPartyExemptFromDisclosure", + "unknown", + "informationUnknownToPublisher" + ], + "codelist": "unspecifiedReason.csv", + "openCodelist": false + }, + "description": { + "title": "Description", + "description": "Any supporting information about the absence of a confirmed beneficial owner. This field may be used to provide set phrases from a source system, or for a free-text explanation.", + "type": "string" + } + }, + "required": [ + "reason" + ] + } + } + }, + "interests": { + "title": "Interests", + "description": "A description of the interests held by the interestedParty covered by this statement in the entity covered by this statement.", + "type": "array", + "items": { + "title": "Interest", + "description": "A description of the interest held by an interestedParty in another entity.", + "type": "object", + "properties": { + "type": { + "title": "Type of interest", + "description": "A codelist value indicating the nature of the interest. See the interestType codelist", + "type": "string", + "enum": [ + "shareholding", + "votingRights", + "appointmentOfBoard", + "otherInfluenceOrControl", + "seniorManagingOfficial", + "settlor", + "trustee", + "protector", + "beneficiaryOfLegalArrangement", + "rightsToSurplusAssetsOnDissolution", + "rightsToProfitOrIncome", + "rightsGrantedByContract", + "conditionalRightsGrantedByContract", + "controlViaCompanyRulesOrArticles", + "controlByLegalFramework", + "boardMember", + "boardChair", + "unknownInterest", + "unpublishedInterest", + "enjoymentAndUseOfAssets", + "rightToProfitOrIncomeFromAssets" + ], + "codelist": "interestType.csv", + "openCodelist": false + }, + "directOrIndirect": { + "title": "Direct or indirect", + "description": "How directly the interest is exercised by the interested party. The value MUST be 'indirect' if intermediate entities or agents are known to exist, and MUST be 'direct' if such intermediaries are known not to exist. Otherwise the value MUST be 'unknown'.", + "type": "string", + "enum": [ + "direct", + "indirect", + "unknown" + ], + "codelist": "directOrIndirect.csv", + "openCodelist": false + }, + "beneficialOwnershipOrControl": { + "title": "Beneficial ownership or control", + "description": "Does this statement assert this as a beneficial ownership or control interest? A beneficial ownership or control interest is always between a natural person and some entity, and exists where the person ultimately benefits from, or has a degree of control over, the entity. There may be cases where a person has an interest in an entity, but where there are arrangements or other conditions that mean this interest does not constitute beneficial ownership or control.", + "type": "boolean" + }, + "details": { + "title": "Details", + "description": "This field may be used to provide the local name given to this kind of interest, or any further semi-structured or unstructured information to clarify the nature of the interest held.", + "type": "string" + }, + "share": { + "title": "Percentage share", + "description": "Where an exact percentage is available, this should be given, and `maximum` and `minimum` values set to the same as the exact percentage. Otherwise, `maximum` and `minimum` can be used to record the range into which the share of this kind of interest falls. By default the `minimum` is inclusive and the `maximum` exclusive (minimum-value \u2264 share < maximum-value). If you wish to change these defaults, use the `exclusiveMinimum` and `exclusiveMaximum` properties.", + "type": "object", + "properties": { + "exact": { + "title": "Exact share", + "description": "The exact share of this interest held (where available).", + "type": "number", + "maximum": 100, + "minimum": 0 + }, + "maximum": { + "title": "Maximum share", + "description": "The upper bound of the share of this interest known to be held.", + "type": "number", + "maximum": 100, + "minimum": 0 + }, + "minimum": { + "title": "Minimum share", + "description": "The lower bound of the share of this interest known to be held.", + "type": "number", + "maximum": 100, + "minimum": 0 + }, + "exclusiveMinimum": { + "title": "Exclusive minimum", + "description": "If `exclusiveMinimum` is true, then the share is at least greater than the `minimum` value given. E.g. if `minimum` is '25', the share is at least 25.1, and not simply 25.", + "type": "boolean", + "default": false + }, + "exclusiveMaximum": { + "title": "Exclusive maximum", + "description": "If `exclusiveMaximum` is true, then the share is at least less than the `maximum` value given. E.g. if `maximum` is '50', the share is less than 49.999, and not simply 50.", + "type": "boolean", + "default": true + } + } + }, + "startDate": { + "title": "Start date", + "description": "When did this interest first occur. Please provide as precise a date as possible in ISO 8601 format. When only the year or year and month is known, these can be given as YYYY or YYYY-MM.", + "type": "string", + "pattern": "^([\\+-]?\\d{4}(?!\\d{2}\b))((-?)((0[1-9]|1[0-2])(\\3([12]\\d|0[1-9]|3[01]))?|W([0-4]\\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\\d|[12]\\d{2}|3([0-5]\\d|6[1-6])))([T\\s]((([01]\\d|2[0-3])((:?)[0-5]\\d)?|24\\:?00)([\\.,]\\d+(?!:))?)?(\\17[0-5]\\d([\\.,]\\d+)?)?([zZ]|([\\+-])([01]\\d|2[0-3]):?([0-5]\\d)?)?)?)?$" + }, + "endDate": { + "title": "End date", + "description": "When did this interest cease. Please provide as precise a date as possible in ISO 8601 format. When only the year or year and month is known, these can be given as YYYY or YYYY-MM.", + "type": "string", + "pattern": "^([\\+-]?\\d{4}(?!\\d{2}\b))((-?)((0[1-9]|1[0-2])(\\3([12]\\d|0[1-9]|3[01]))?|W([0-4]\\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\\d|[12]\\d{2}|3([0-5]\\d|6[1-6])))([T\\s]((([01]\\d|2[0-3])((:?)[0-5]\\d)?|24\\:?00)([\\.,]\\d+(?!:))?)?(\\17[0-5]\\d([\\.,]\\d+)?)?([zZ]|([\\+-])([01]\\d|2[0-3]):?([0-5]\\d)?)?)?)?$" + } + } + } + }, + "publicationDetails": { + "title": "Publication details", + "description": "Information concerning the original publication of this statement.", + "type": "object", + "properties": { + "publicationDate": { + "title": "Publication date", + "description": "The date on which this statement was published.", + "type": "string", + "format": "date" + }, + "bodsVersion": { + "title": "BODS version", + "description": "The version of the Beneficial Ownership Data Standard to which this statement conforms, expressed as major.minor. For example: 0.2 or 1.0.", + "type": "string", + "pattern": "^(\\d+\\.)(\\d+)$" + }, + "license": { + "title": "License URL", + "description": "A link to the license that applies to this statement. The canonical URI of the license SHOULD be used. Publishers are encouraged to use a Public Domain Dedication or Open Definition Conformant (http://opendefinition.org/licenses/) license.", + "type": "string", + "format": "uri" + }, + "publisher": { + "type": "object", + "title": "Publisher", + "description": "Details of the organisation or individual publishing this statement.", + "properties": { + "name": { + "title": "Name", + "description": "The name of the publisher", + "type": "string" + }, + "url": { + "title": "URL", + "description": "The URL of the parent dataset or of the publisher's website homepage", + "type": "string", + "format": "uri" + } + }, + "anyOf": [ + { + "required": [ + "name" + ] + }, + { + "required": [ + "url" + ] + } + ] + } + }, + "required": [ + "publicationDate", + "bodsVersion", + "publisher" + ] + }, + "source": { + "title": "Source", + "description": "The source of the information that links the entity and the interested party, or that supports a null statement.", + "type": "object", + "properties": { + "type": { + "title": "Source type", + "description": "What type of source is this? Multiple tags can be combined. Values should come from the source type codelist.", + "type": "array", + "items": { + "type": "string", + "enum": [ + "selfDeclaration", + "officialRegister", + "thirdParty", + "primaryResearch", + "verified" + ], + "codelist": "sourceType.csv", + "openCodelist": false + } + }, + "description": { + "title": "Description", + "description": "Where required, additional free-text information about the source of this statement can be provided here.", + "type": "string" + }, + "url": { + "title": "Source URL", + "description": "If this information was fetched from an external URL, or a machine or human readable web page is available that provides additional information on how this statement was sourced, provide the URL.", + "type": "string", + "format": "uri" + }, + "retrievedAt": { + "title": "Retrieved at", + "description": "If this statement was imported from some external system, include a timestamp indicating when this took place. The statement's own date should be set based on the source information. ", + "type": "string", + "format": "date-time" + }, + "assertedBy": { + "title": "Asserted by", + "description": "Who is making this statement? This may be the name of the person or organisation making a self-declaration (in which case, please make sure the name field matches the organisation or person name field), or the name or description of some other party. If this statement has been verified, this may also include the name of the organisation providing verification.", + "type": "array", + "items": { + "type": "object", + "title": "Agent", + "description": "An individual, organisation or other responsible agent making, or supporting, a given statement or annotation.", + "properties": { + "name": { + "title": "Name", + "description": "The name of the agent", + "type": "string" + }, + "uri": { + "title": "URI", + "description": "An optional URI to identify the agent.", + "type": "string", + "format": "uri" + } + } + } + } + } + }, + "annotations": { + "title": "Annotations", + "description": "Annotations about this statement or parts of this statement", + "type": "array", + "items": { + "title": "Annotation", + "description": "An annotation provides additional information about ownership or control data being provided. Annotations can be provided in free-text, and can apply to a whole statement, an object or a single field. Additional extended properties can be included on the annotation object to provide structured data where required.", + "type": "object", + "properties": { + "statementPointerTarget": { + "title": "Statement Fragment Pointer", + "description": "An RFC6901 JSON Pointer (https://tools.ietf.org/html/rfc6901) describing the target fragment of the statement that this annotation applies to, starting from the root of the statement. A value of '/' indicates that the annotation applies to the whole statement.", + "type": "string" + }, + "creationDate": { + "title": "Creation Date", + "description": "The date this annotation was created.", + "type": "string", + "format": "date" + }, + "createdBy": { + "title": "Created By", + "description": "The person, organisation or agent that created this annotation.", + "type": "object", + "properties": { + "name": { + "title": "Name", + "description": "The name of the person, organisation or agent that created this annotation.", + "type": "string" + }, + "uri": { + "title": "URI", + "description": "An optional URI to identify person, organisation or agent that created this annotation.", + "type": "string", + "format": "uri" + } + } + }, + "motivation": { + "title": "Motivation", + "description": "The motivation for this annotation, chosen from a codelist. See the annotationMotivation codelist.", + "type": "string", + "enum": [ + "commenting", + "correcting", + "identifying", + "linking", + "transformation" + ], + "codelist": "annotationMotivation.csv", + "openCodelist": false + }, + "description": { + "title": "Description", + "description": "A free-text description to annotate this statement or field.", + "type": "string" + }, + "transformedContent": { + "type": "string", + "title": "Transformed content", + "description": "A representation of the annotation target after the transformation in the description field has been applied. This field SHOULD only be used when the motivation is transformation." + }, + "url": { + "title": "URL", + "description": "A linked resource that annotates, provides context for or enhances this statement. The content of the resource, or the relationship to the statement, MAY be described in the `description` field. This field is REQUIRED if the value of `motivation` is 'linking'.", + "type": "string", + "format": "uri" + } + }, + "required": [ + "statementPointerTarget", + "motivation" + ], + "oneOfEnumSelectorField": "motivation", + "oneOf": [ + { + "properties": { + "motivation": { + "enum": [ + "linking" + ] + } + }, + "required": [ + "statementPointerTarget", + "motivation", + "url" + ] + }, + { + "properties": { + "motivation": { + "enum": [ + "identifying", + "commenting", + "correcting", + "transformation" + ] + } + } + } + ] + } + }, + "replacesStatements": { + "title": "Replaces statement(s)", + "description": "If this statement replaces a previous statement or statements, provide the identifier(s) for the previous statement(s) here. Consuming applications are advised to mark the identified statements as no longer active.", + "type": "array", + "items": { + "title": "Statement identifier", + "description": "The identifier of a statement that is no longer active.", + "type": "string", + "minLength": 32, + "maxLength": 64 + } + } + }, + "required": [ + "statementID", + "statementType", + "isComponent", + "subject", + "interestedParty", + "publicationDetails" + ], + "definitions": { + "InterestedParty": { + "title": "Interested party", + "description": "The interested party has some level of ownership or control over the entity referenced in this ownership or control statement. This should be described with reference to either an entity statement or person statement, or, where the interested party is unknown, details of why. ", + "type": "object", + "properties": { + "describedByEntityStatement": { + "title": "Described by entity statement", + "description": "A reference to a statement describing a registered entity, trust or arrangement that has an ownership or control interest in the subject of this statement. An entityStatement should be used when the direct interests to be described represents known control or ownership by anyone other than a natural person.", + "type": "string" + }, + "describedByPersonStatement": { + "title": "Described by person statement", + "description": "A reference to a statement describing a natural person who has an ownership or control interest in the subject of this statement.", + "type": "string" + }, + "unspecified": { + "title": "Unspecified or unknown ownership and control", + "description": "When confirmation has been provided that no interested party exists, where ownership and control information does not need to be provided, or where details of ownership and control are unknown, a `reason` MUST be given. Where an unknown entity is the `subject` of further ownershipOrControlStatements in the same structure, or where there is a natural person with ownership or control but their name or details are not known or cannot be disclosed for some reason, `unspecified` should not be used, but instead a reference to a `personStatement` or `entityStatement` should be provided but identifying details MAY be left blank.", + "type": "object", + "properties": { + "reason": { + "title": "Reason", + "description": "The reason that an interested party cannot be specified. From the unspecifiedReason codelist.", + "type": "string", + "enum": [ + "noBeneficialOwners", + "subjectUnableToConfirmOrIdentifyBeneficialOwner", + "interestedPartyHasNotProvidedInformation", + "subjectExemptFromDisclosure", + "interestedPartyExemptFromDisclosure", + "unknown", + "informationUnknownToPublisher" + ], + "codelist": "unspecifiedReason.csv", + "openCodelist": false + }, + "description": { + "title": "Description", + "description": "Any supporting information about the absence of a confirmed beneficial owner. This field may be used to provide set phrases from a source system, or for a free-text explanation.", + "type": "string" + } + }, + "required": [ + "reason" + ] + } + } + } + } + } + ] + } +} diff --git a/flattentool/tests/test_library.py b/flattentool/tests/test_library.py new file mode 100644 index 0000000..042e9a5 --- /dev/null +++ b/flattentool/tests/test_library.py @@ -0,0 +1,124 @@ +import json + +import pytest + +import flattentool + + +@pytest.fixture +def basic_data_1(): + """Example JSON data""" + with open("flattentool/tests/fixtures/basic_1.json", "r") as read_file: + return json.load(read_file) + + +@pytest.fixture +def bods_schema_0_3(): + """Example JSON schema""" + with open("flattentool/tests/fixtures/schema-0-3-0.json", "r") as read_file: + return json.load(read_file) + + +def test_unflatten_schema_as_filename(tmpdir, basic_data_1): + output_file = "unflattened.json" + unflatten_kwargs = { + "output_name": tmpdir.join(output_file).strpath, + "root_list_path": "there-is-no-root-list-path", + "root_id": "statementID", + "id_name": "statementID", + "root_is_list": True, + "input_format": "csv", + "schema": "flattentool/tests/fixtures/schema-0-3-0.json", + } + + flattentool.unflatten("flattentool/tests/fixtures/basic_1.csv", **unflatten_kwargs) + + output_json = json.load(tmpdir.join(output_file)) + + for statement in output_json: + statement_id = statement["statementID"] + input_statement = [ + item for item in basic_data_1 if item["statementID"] == statement_id + ][0] + for property in statement: + # Skip publicListing property since contains 'securitiesListings': [] and there is a + # bug where empty lists are lost when flattening and then unflattening. This should + # be fixed, see: https://github.com/OpenDataServices/flatten-tool/issues/470 + if property != "publicListing": + assert statement[property] == input_statement[property] + + +def test_unflatten_schema_as_dict(tmpdir, basic_data_1, bods_schema_0_3): + output_file = "unflattened.json" + unflatten_kwargs = { + "output_name": tmpdir.join(output_file).strpath, + "root_list_path": "there-is-no-root-list-path", + "root_id": "statementID", + "id_name": "statementID", + "root_is_list": True, + "input_format": "csv", + "schema": bods_schema_0_3, + } + + flattentool.unflatten("flattentool/tests/fixtures/basic_1.csv", **unflatten_kwargs) + + output_json = json.load(tmpdir.join(output_file)) + + for statement in output_json: + statement_id = statement["statementID"] + input_statement = [ + item for item in basic_data_1 if item["statementID"] == statement_id + ][0] + for property in statement: + # Skip publicListing property since contains 'securitiesListings': [] and there is a + # bug where empty lists are lost when flattening and then unflattening. This should + # be fixed, see: https://github.com/OpenDataServices/flatten-tool/issues/470 + if property != "publicListing": + assert statement[property] == input_statement[property] + + +def test_unflatten_schema_as_empty_dict(tmpdir, basic_data_1, bods_schema_0_3): + output_file = "unflattened.json" + unflatten_kwargs = { + "output_name": tmpdir.join(output_file).strpath, + "root_list_path": "there-is-no-root-list-path", + "root_id": "statementID", + "id_name": "statementID", + "root_is_list": True, + "input_format": "csv", + "schema": {}, # Empty schema provided + } + + flattentool.unflatten("flattentool/tests/fixtures/basic_1.csv", **unflatten_kwargs) + + output_json = json.load(tmpdir.join(output_file)) + + for statement in output_json: + statement_id = statement["statementID"] + input_statement = [ + item for item in basic_data_1 if item["statementID"] == statement_id + ][0] + for property in statement: + # Booleans, and integers should end up as strings because of empty schema + if property in ("isComponent", "interests"): + if isinstance(statement[property], list): + for sub_property in statement[property][0]: + if isinstance(statement[property][0][sub_property], dict): + for sub_sub_property in statement[property][0][ + sub_property + ]: + assert isinstance( + statement[property][0][sub_property][ + sub_sub_property + ], + str, + ) + else: + assert isinstance(statement[property][0][sub_property], str) + else: + assert isinstance(statement[property], str) + # Skip publicListing property since contains 'securitiesListings': [] and there is a + # bug where empty lists are lost when flattening and then unflattening. This should + # be fixed, see: https://github.com/OpenDataServices/flatten-tool/issues/470 + elif property != "publicListing": + assert statement[property] == input_statement[property]