diff --git a/CHANGELOG.md b/CHANGELOG.md index 96f6dc2..11e9094 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,13 +7,23 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [0.13.0] - 2022-06-30 + ### Added - Schema version 0.3 - Schema class has new method is_schema_version_equal_to_or_greater_than() - New statistic: count of interest objects with different directOrIndirect values. For 0.3+ - Schema class has new method get_ownership_or_control_statement_interest_direct_or_indirect_list() for this - +- New check: has public listing information but has public listing is false. For 0.3+ +- New check: statement entityType and entitySubtype.generalCategory do not align. For 0.3+ +- New check: marketIdentifierCode / operatingMarketIdentifierCode - check one not missing. For 0.3+ +- New statistic: count ownership or control statement with at least one interest beneficial +- New statistic: count person statements have pep status statuses +- New check: has pep details without missing info but incorrect pep status. For 0.2 only +- New check: has pep details but incorrect pep status. For 0.3+ +- New check: has pep details with missing info but incorrect pep status. Different rules are used for 0.2 only and 0.3+ + ### Changed - Updated included schema files, and added instructions on how to do that to the README @@ -22,6 +32,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - New abstract class AdditionalCheck - can be extended by other classes to have better seperation in the future - New function process_additional_checks available to do work +## Removed + +- Removed statistic: count person statements have pep status and reason missing info + ## [0.12.0] - 2021-10-26 ### Added diff --git a/libcovebods/lib/common_checks.py b/libcovebods/lib/common_checks.py index 6c9716f..d00bb85 100644 --- a/libcovebods/lib/common_checks.py +++ b/libcovebods/lib/common_checks.py @@ -73,8 +73,6 @@ def __init__(self, lib_cove_bods_config, schema_object): self.count_person_statements_types = {} for value in schema_object.get_person_statement_types_list(): self.count_person_statements_types[value] = 0 - self.count_person_statements_have_pep_status = 0 - self.count_person_statements_have_pep_status_and_reason_missing_info = 0 # Ownership or control self.count_ownership_or_control_statement = 0 self.count_ownership_or_control_statement_interested_party_with_person = 0 @@ -165,20 +163,6 @@ def check_person_statement_first_pass(self, statement): and statement["personType"] in self.count_person_statements_types ): self.count_person_statements_types[statement["personType"]] += 1 - if self._schema_object.schema_version != "0.1": - if "hasPepStatus" in statement and statement["hasPepStatus"]: - self.count_person_statements_have_pep_status += 1 - if "pepStatusDetails" in statement and isinstance( - statement["pepStatusDetails"], list - ): - if [ - x - for x in statement["pepStatusDetails"] - if x.get("missingInfoReason") - ]: - self.count_person_statements_have_pep_status_and_reason_missing_info += ( - 1 - ) if "addresses" in statement and isinstance(statement["addresses"], list): for address in statement["addresses"]: self._process_address(address) @@ -296,13 +280,6 @@ def get_statistics(self): "count_addresses_with_country": self.count_addresses_with_country, "count_addresses_with_postcode_duplicated_in_address": self.count_addresses_with_postcode_duplicated_in_address, } - if self._schema_object.schema_version != "0.1": - data[ - "count_person_statements_have_pep_status" - ] = self.count_person_statements_have_pep_status - data[ - "count_person_statements_have_pep_status_and_reason_missing_info" - ] = self.count_person_statements_have_pep_status_and_reason_missing_info return data @@ -342,6 +319,27 @@ def get_statistics(self): } +class StatisticOwnershipOrControlWithAtLeastOneInterestBeneficial(AdditionalCheck): + def __init__(self, lib_cove_bods_config, schema_object): + super().__init__(lib_cove_bods_config, schema_object) + self.stat = 0 + + def check_ownership_or_control_statement_first_pass(self, statement): + if "interests" in statement and isinstance(statement["interests"], list): + interests_with_beneficialOwnershipOrControl = [ + i + for i in statement["interests"] + if isinstance(i, dict) and i.get("beneficialOwnershipOrControl") + ] + if interests_with_beneficialOwnershipOrControl: + self.stat += 1 + + def get_statistics(self): + return { + "count_ownership_or_control_statement_with_at_least_one_interest_beneficial": self.stat, + } + + class LegacyChecks(AdditionalCheck): """Before the AdditionalCheck system was implemented, all this code was together in one class. As we work on checks in this class, we should move them to seperate classes if possible.""" @@ -359,7 +357,7 @@ def __init__(self, lib_cove_bods_config, schema_object): self.statement_ids_counted = {} def check_entity_statement_first_pass(self, statement): - # Not doing any work if no statementID preserves the old behavoir of the code, but this should be evaluated. + # Not doing any work if no statementID preserves the old behaviour of the code, but this should be evaluated. if not statement.get("statementID"): return self.entity_statements_seen.append(statement.get("statementID")) @@ -426,7 +424,7 @@ def check_entity_statement_first_pass(self, statement): ) def check_person_statement_first_pass(self, statement): - # Not doing any work if no statementID preserves the old behavoir of the code, but this should be evaluated. + # Not doing any work if no statementID preserves the old behaviour of the code, but this should be evaluated. if not statement.get("statementID"): return self.person_statements_seen.append(statement.get("statementID")) @@ -508,7 +506,7 @@ def check_person_statement_first_pass(self, statement): ) def check_ownership_or_control_statement_first_pass(self, statement): - # Not doing any work if no statementID preserves the old behavoir of the code, but this should be evaluated. + # Not doing any work if no statementID preserves the old behaviour of the code, but this should be evaluated. if not statement.get("statementID"): return self.ownership_or_control_statements_seen.append(statement.get("statementID")) @@ -652,7 +650,7 @@ def check_ownership_or_control_statement_first_pass(self, statement): ) def check_entity_statement_second_pass(self, statement): - # Not doing any work if no statementID preserves the old behavoir of the code, but this should be evaluated. + # Not doing any work if no statementID preserves the old behaviour of the code, but this should be evaluated. if not statement.get("statementID"): return if ( @@ -681,7 +679,7 @@ def check_entity_statement_second_pass(self, statement): ) def check_person_statement_second_pass(self, statement): - # Not doing any work if no statementID preserves the old behavoir of the code, but this should be evaluated. + # Not doing any work if no statementID preserves the old behaviour of the code, but this should be evaluated. if not statement.get("statementID"): return if ( @@ -710,7 +708,7 @@ def check_person_statement_second_pass(self, statement): ) def check_ownership_or_control_statement_second_pass(self, statement): - # Not doing any work if no statementID preserves the old behavoir of the code, but this should be evaluated. + # Not doing any work if no statementID preserves the old behaviour of the code, but this should be evaluated. if not statement.get("statementID"): return interested_party = statement.get("interestedParty") @@ -884,10 +882,205 @@ def _check_addresses_list_for_alternatives(self, statement): ) +class CheckHasPublicListing(AdditionalCheck): + def does_apply_to_schema(self): + return self._schema_object.is_schema_version_equal_to_or_greater_than("0.3") + + def check_entity_statement_first_pass(self, statement): + if isinstance(statement.get("publicListing"), dict): + pl = statement.get("publicListing") + if pl.get("companyFilingsURLs") or pl.get("securitiesListings"): + if not pl.get("hasPublicListing"): + self._additional_check_results.append( + { + "type": "has_public_listing_information_but_has_public_listing_is_false", + "statement_type": "entity", + "statement": statement.get("statementID"), + } + ) + + +class CheckEntityTypeAndEntitySubtypeAlign(AdditionalCheck): + def does_apply_to_schema(self): + return self._schema_object.is_schema_version_equal_to_or_greater_than("0.3") + + def check_entity_statement_first_pass(self, statement): + if isinstance(statement.get("entitySubtype"), dict): + entitySubtype = statement["entitySubtype"].get("generalCategory") + if entitySubtype and isinstance(entitySubtype, str): + entityType = statement.get("entityType") + entitySubtypeFirstBit = entitySubtype.split("-").pop(0) + if entityType != entitySubtypeFirstBit: + self._additional_check_results.append( + { + "type": "statement_entity_type_and_entity_sub_type_do_not_align", + "statement_type": "entity", + "statement": statement.get("statementID"), + } + ) + + +class CheckEntitySecurityListingsMICSCodes(AdditionalCheck): + def __init__(self, lib_cove_bods_config, schema_object): + super().__init__(lib_cove_bods_config, schema_object) + self.mics_data = None + + def does_apply_to_schema(self): + return self._schema_object.is_schema_version_equal_to_or_greater_than("0.3") + + def check_entity_statement_first_pass(self, statement): + if isinstance(statement.get("publicListing"), dict) and isinstance( + statement["publicListing"].get("securitiesListings"), list + ): + for securitiesListing in statement["publicListing"].get( + "securitiesListings" + ): + if isinstance(securitiesListing, dict): + marketIdentifierCode = securitiesListing.get("marketIdentifierCode") + operatingMarketIdentifierCode = securitiesListing.get( + "operatingMarketIdentifierCode" + ) + if marketIdentifierCode and not operatingMarketIdentifierCode: + self._additional_check_results.append( + { + "type": "entity_security_listing_market_identifier_code_set_but_not_operating_market_identifier_code", + "statement_type": "entity", + "statement": statement.get("statementID"), + } + ) + elif operatingMarketIdentifierCode and not marketIdentifierCode: + self._additional_check_results.append( + { + "type": "entity_security_listing_operating_market_identifier_code_set_but_not_market_identifier_code", + "statement_type": "entity", + "statement": statement.get("statementID"), + } + ) + + +class PEPForSchema02Only(AdditionalCheck): + def __init__(self, lib_cove_bods_config, schema_object): + super().__init__(lib_cove_bods_config, schema_object) + self.count_person_statements_have_pep_status = 0 + # Schema 0.2 only has a boolean, but we are going to map them to these 2 values taken from schema 0.3 + self.count_person_statements_have_pep_status_statuses = { + "isPep": 0, + "isNotPep": 0, + } + + self.count_person_statements_have_pep_status_and_reason_missing_info = 0 + + def does_apply_to_schema(self): + return self._schema_object.schema_version == "0.2" + + def check_person_statement_first_pass(self, statement): + if "hasPepStatus" in statement: + self.count_person_statements_have_pep_status += 1 + if statement["hasPepStatus"]: + self.count_person_statements_have_pep_status_statuses["isPep"] += 1 + else: + self.count_person_statements_have_pep_status_statuses["isNotPep"] += 1 + if isinstance(statement.get("pepStatusDetails"), list): + details_no_missing_info = [ + x + for x in statement.get("pepStatusDetails") + if not x.get("missingInfoReason") + ] + if details_no_missing_info and not statement["hasPepStatus"]: + self._additional_check_results.append( + { + "type": "has_pep_details_without_missing_info_but_incorrect_pep_status", + "statement_type": "person", + "statement": statement.get("statementID"), + } + ) + details_with_missing_info = [ + x + for x in statement.get("pepStatusDetails") + if x.get("missingInfoReason") + ] + if details_with_missing_info and statement["hasPepStatus"]: + self._additional_check_results.append( + { + "type": "has_pep_details_with_missing_info_but_incorrect_pep_status", + "statement_type": "person", + "statement": statement.get("statementID"), + } + ) + + def get_statistics(self): + return { + "count_person_statements_have_pep_status": self.count_person_statements_have_pep_status, + "count_person_statements_have_pep_status_statuses": self.count_person_statements_have_pep_status_statuses, + } + + +class PEPForSchema03AndAbove(AdditionalCheck): + def __init__(self, lib_cove_bods_config, schema_object): + super().__init__(lib_cove_bods_config, schema_object) + self.count_person_statements_have_pep_status = 0 + self.count_person_statements_have_pep_status_statuses = {} + for ( + value + ) in schema_object.get_person_statement_political_exposure_status_list(): + self.count_person_statements_have_pep_status_statuses[value] = 0 + + def does_apply_to_schema(self): + return self._schema_object.is_schema_version_equal_to_or_greater_than("0.3") + + def check_person_statement_first_pass(self, statement): + if isinstance(statement.get("politicalExposure"), dict): + status = statement["politicalExposure"].get("status") + if status in self.count_person_statements_have_pep_status_statuses.keys(): + self.count_person_statements_have_pep_status += 1 + self.count_person_statements_have_pep_status_statuses[status] += 1 + has_pep_details_with_missing_info_but_incorrect_pep_status = False + details = statement["politicalExposure"].get("details") + if isinstance(details, list): + details_with_missing_info = [ + x for x in details if x.get("missingInfoReason") + ] + if details_with_missing_info and status != "unknown": + has_pep_details_with_missing_info_but_incorrect_pep_status = True + self._additional_check_results.append( + { + "type": "has_pep_details_with_missing_info_but_incorrect_pep_status", + "statement_type": "person", + "statement": statement.get("statementID"), + } + ) + if ( + details + and (not status or status == "isNotPep") + and not has_pep_details_with_missing_info_but_incorrect_pep_status + ): + # This check is a less specific version of has_pep_details_with_missing_info_but_incorrect_pep_status + # so if that one has already been issued then we want to skip this one. + self._additional_check_results.append( + { + "type": "has_pep_details_but_incorrect_pep_status", + "statement_type": "person", + "statement": statement.get("statementID"), + } + ) + + def get_statistics(self): + return { + "count_person_statements_have_pep_status": self.count_person_statements_have_pep_status, + "count_person_statements_have_pep_status_statuses": self.count_person_statements_have_pep_status_statuses, + } + + ADDITIONAL_CHECK_CLASSES = [ LegacyChecks, + CheckHasPublicListing, + CheckEntityTypeAndEntitySubtypeAlign, + CheckEntitySecurityListingsMICSCodes, LegacyStatistics, StatisticOwnershipOrControlInterestDirectOrIndirect, + StatisticOwnershipOrControlWithAtLeastOneInterestBeneficial, + PEPForSchema02Only, + PEPForSchema03AndAbove, ] diff --git a/libcovebods/schema.py b/libcovebods/schema.py index 54dbf79..e0be412 100644 --- a/libcovebods/schema.py +++ b/libcovebods/schema.py @@ -129,6 +129,21 @@ def get_ownership_or_control_statement_interest_direct_or_indirect_list(self): else: return [] + def get_person_statement_political_exposure_status_list(self): + for statement_schema in self._pkg_schema_obj["items"]["oneOf"]: + if ( + statement_schema["properties"]["statementType"]["enum"][0] + == "personStatement" + ): + political_exposure_schema = statement_schema["properties"].get( + "politicalExposure" + ) + # This is only available in 0.3 and above. + if isinstance(political_exposure_schema, dict): + return political_exposure_schema["properties"]["status"]["enum"] + else: + return [] + def get_inconsistent_schema_version_used_for_statement(self, statement): # If version is not set at all, then we assume it's the default version if ( diff --git a/setup.py b/setup.py index 43d22d7..c3522a1 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name="libcovebods", - version="0.12.0", + version="0.13.0", author="Open Data Services", author_email="code@opendataservices.coop", url="https://github.com/openownership/lib-cove-bods", @@ -16,6 +16,7 @@ "flattentool>=0.5.0", "libcove>=0.22.0", "libcoveweb>=0.21.0", + "packaging", ], extras_require={"dev": ["pytest", "flake8", "black==22.3.0", "isort"]}, classifiers=[ diff --git a/tests/fixtures/0.2/pep_details_missing_info_but_incorrect_status_1.json b/tests/fixtures/0.2/pep_details_missing_info_but_incorrect_status_1.json new file mode 100644 index 0000000..199deb7 --- /dev/null +++ b/tests/fixtures/0.2/pep_details_missing_info_but_incorrect_status_1.json @@ -0,0 +1,93 @@ +[ + { + "statementID": "0d2a7a15-3291-4ac7-9663-e3659056dadc", + "statementType": "entityStatement", + "isComponent": false, + "statementDate": "2019-03-18", + "entityType": "registeredEntity", + "name": "Platinum Emerald and Plutonim Mining Limited", + "identifiers": [ + { + "scheme": "GB-COH", + "id": "XE-000079054" + } + ], + "publicationDetails": { + "publicationDate": "2019-06-07", + "bodsVersion": "0.2", + "license": "https://example.com/document/ei-open-data-policy-2015", + "publisher": { + "name": "ETI International" + } + } + }, + { + "statementID": "a5680770-899f-4f7a-b795-e266f1ce8668", + "statementType": "personStatement", + "isComponent": false, + "personType": "knownPerson", + "statementDate": "2019-03-18", + "names": [ + { + "fullName": "Michael Hubbard" + } + ], + "hasPepStatus": true, + "pepStatusDetails": + [ + { + "missingInfoReason": "They won't tell us" + } + ], + "publicationDetails": { + "publicationDate": "2019-06-07", + "bodsVersion": "0.2", + "license": "https://example.com/document/ei-open-data-policy-2015", + "publisher": { + "name": "ETI International" + } + } + }, + { + "statementID": "106271aa-10ff-48ad-a5ba-f3f8132b59ff", + "statementType": "ownershipOrControlStatement", + "isComponent": false, + "statementDate": "2019-03-18", + "subject": { + "describedByEntityStatement": "0d2a7a15-3291-4ac7-9663-e3659056dadc" + }, + "interestedParty": { + "describedByPersonStatement": "a5680770-899f-4f7a-b795-e266f1ce8668" + }, + "interests": [ + { + "type": "shareholding", + "interestLevel": "direct", + "share": { + "minimum": 25, + "maximum": 50 + }, + "startDate": "2016-07-07", + "beneficialOwnershipOrControl": true + }, + { + "type": "voting-rights", + "interestLevel": "direct", + "share": { + "minimum": 25, + "maximum": 50 + }, + "startDate": "2016-07-07", + "beneficialOwnershipOrControl": true + } + ], + "publicationDetails": { + "publicationDate": "2019-06-07", + "bodsVersion": "0.2", + "license": "https://example.com/document/ei-open-data-policy-2015", + "publisher": { + "name": "ETI International" + } + } + } +] diff --git a/tests/fixtures/0.2/pep_details_no_missing_info_but_incorrect_status_1.json b/tests/fixtures/0.2/pep_details_no_missing_info_but_incorrect_status_1.json new file mode 100644 index 0000000..cebc7f3 --- /dev/null +++ b/tests/fixtures/0.2/pep_details_no_missing_info_but_incorrect_status_1.json @@ -0,0 +1,105 @@ +[ + { + "statementID": "0d2a7a15-3291-4ac7-9663-e3659056dadc", + "statementType": "entityStatement", + "isComponent": false, + "statementDate": "2019-03-18", + "entityType": "registeredEntity", + "name": "Platinum Emerald and Plutonim Mining Limited", + "identifiers": [ + { + "scheme": "GB-COH", + "id": "XE-000079054" + } + ], + "publicationDetails": { + "publicationDate": "2019-06-07", + "bodsVersion": "0.2", + "license": "https://example.com/document/ei-open-data-policy-2015", + "publisher": { + "name": "ETI International" + } + } + }, + { + "statementID": "a5680770-899f-4f7a-b795-e266f1ce8668", + "statementType": "personStatement", + "isComponent": false, + "personType": "knownPerson", + "statementDate": "2019-03-18", + "names": [ + { + "fullName": "Michael Hubbard" + } + ], + "hasPepStatus": false, + "pepStatusDetails": + [ + { + "jurisdiction": { + "code": "gb" + }, + "reason": "Member of Parliament", + "startDate": "2017-10-15", + "source": { + "type": ["selfDeclaration"], + "assertedBy": [ + { + "name": "Michael Hubbard" + } + ] + } + } + ], + "publicationDetails": { + "publicationDate": "2019-06-07", + "bodsVersion": "0.2", + "license": "https://example.com/document/ei-open-data-policy-2015", + "publisher": { + "name": "ETI International" + } + } + }, + { + "statementID": "106271aa-10ff-48ad-a5ba-f3f8132b59ff", + "statementType": "ownershipOrControlStatement", + "isComponent": false, + "statementDate": "2019-03-18", + "subject": { + "describedByEntityStatement": "0d2a7a15-3291-4ac7-9663-e3659056dadc" + }, + "interestedParty": { + "describedByPersonStatement": "a5680770-899f-4f7a-b795-e266f1ce8668" + }, + "interests": [ + { + "type": "shareholding", + "interestLevel": "direct", + "share": { + "minimum": 25, + "maximum": 50 + }, + "startDate": "2016-07-07", + "beneficialOwnershipOrControl": true + }, + { + "type": "voting-rights", + "interestLevel": "direct", + "share": { + "minimum": 25, + "maximum": 50 + }, + "startDate": "2016-07-07", + "beneficialOwnershipOrControl": true + } + ], + "publicationDetails": { + "publicationDate": "2019-06-07", + "bodsVersion": "0.2", + "license": "https://example.com/document/ei-open-data-policy-2015", + "publisher": { + "name": "ETI International" + } + } + } +] diff --git a/tests/fixtures/0.2/pep_status_missing_info_1.json b/tests/fixtures/0.2/pep_status_missing_info_1.json index 7f50860..faf2877 100644 --- a/tests/fixtures/0.2/pep_status_missing_info_1.json +++ b/tests/fixtures/0.2/pep_status_missing_info_1.json @@ -32,7 +32,7 @@ "fullName": "Michael Hubbard" } ], - "hasPepStatus": true, + "hasPepStatus": false, "pepStatusDetails": [ { diff --git a/tests/fixtures/0.3/basic_1.json b/tests/fixtures/0.3/basic_1.json index 40cd2f2..8fddefc 100644 --- a/tests/fixtures/0.3/basic_1.json +++ b/tests/fixtures/0.3/basic_1.json @@ -13,6 +13,11 @@ "id": "07444723" } ], + "publicListing": { + "hasPublicListing": true, + "companyFilingsURLs": ["http://example.com/"], + "securitiesListings": [] + }, "publicationDetails": { "publicationDate": "2018-02-13", "bodsVersion": "0.3", diff --git a/tests/fixtures/0.3/basic_with_correct_mic_codes_1.json b/tests/fixtures/0.3/basic_with_correct_mic_codes_1.json new file mode 100644 index 0000000..4a570b0 --- /dev/null +++ b/tests/fixtures/0.3/basic_with_correct_mic_codes_1.json @@ -0,0 +1,112 @@ +[ + { + "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": [ + { + "marketIdentifierCode": "PURE", + "operatingMarketIdentifierCode": "XCNQ", + "stockExchangeJurisdiction": "CA", + "stockExchangeName": "CANADIAN SECURITIES EXCHANGE - PURE", + "security": { + "ticker": "DEMO" + } + } + ] + }, + "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/tests/fixtures/0.3/basic_with_operating_mic_code_missing_1.json b/tests/fixtures/0.3/basic_with_operating_mic_code_missing_1.json new file mode 100644 index 0000000..9c5aecc --- /dev/null +++ b/tests/fixtures/0.3/basic_with_operating_mic_code_missing_1.json @@ -0,0 +1,111 @@ +[ + { + "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": [ + { + "marketIdentifierCode": "PURE", + "stockExchangeJurisdiction": "CA", + "stockExchangeName": "CANADIAN SECURITIES EXCHANGE - PURE", + "security": { + "ticker": "DEMO" + } + } + ] + }, + "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/tests/fixtures/0.3/entity_sub_type_does_align_1.json b/tests/fixtures/0.3/entity_sub_type_does_align_1.json new file mode 100644 index 0000000..bee4b75 --- /dev/null +++ b/tests/fixtures/0.3/entity_sub_type_does_align_1.json @@ -0,0 +1,100 @@ +[ + { + "statementID": "1dc0e987-5c57-4a1c-b3ad-61353b66a9b7", + "statementType": "entityStatement", + "isComponent": false, + "statementDate": "2017-11-18", + "entityType": "stateBody", + "name": "CHRINON LTD", + "foundingDate": "2010-11-18", + "identifiers": [ + { + "scheme": "GB-COH", + "id": "07444723" + } + ], + "entitySubtype": { + "generalCategory": "stateBody-governmentDepartment" + }, + "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/tests/fixtures/0.3/entity_sub_type_does_not_align_1.json b/tests/fixtures/0.3/entity_sub_type_does_not_align_1.json new file mode 100644 index 0000000..ceeb787 --- /dev/null +++ b/tests/fixtures/0.3/entity_sub_type_does_not_align_1.json @@ -0,0 +1,105 @@ +[ + { + "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": [] + }, + "entitySubtype": { + "generalCategory": "stateBody-governmentDepartment" + }, + "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/tests/fixtures/0.3/has_public_listing_information_but_has_public_listing_is_false_1.json b/tests/fixtures/0.3/has_public_listing_information_but_has_public_listing_is_false_1.json new file mode 100644 index 0000000..4264148 --- /dev/null +++ b/tests/fixtures/0.3/has_public_listing_information_but_has_public_listing_is_false_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": false, + "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/tests/fixtures/0.3/pep_details_but_incorrect_status_1.json b/tests/fixtures/0.3/pep_details_but_incorrect_status_1.json new file mode 100644 index 0000000..7fe8125 --- /dev/null +++ b/tests/fixtures/0.3/pep_details_but_incorrect_status_1.json @@ -0,0 +1,108 @@ +[ + { + "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" + } + ], + "politicalExposure": { + "status": "isNotPep", + "details": [ + {"reason": "They stood for election but lost."} + ] + }, + "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/tests/fixtures/0.3/pep_details_but_incorrect_status_2.json b/tests/fixtures/0.3/pep_details_but_incorrect_status_2.json new file mode 100644 index 0000000..93c54e7 --- /dev/null +++ b/tests/fixtures/0.3/pep_details_but_incorrect_status_2.json @@ -0,0 +1,107 @@ +[ + { + "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" + } + ], + "politicalExposure": { + "details": [ + {"reason": "They stood for election but lost."} + ] + }, + "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/tests/fixtures/0.3/pep_details_missing_info_but_incorrect_status_1.json b/tests/fixtures/0.3/pep_details_missing_info_but_incorrect_status_1.json new file mode 100644 index 0000000..77875e6 --- /dev/null +++ b/tests/fixtures/0.3/pep_details_missing_info_but_incorrect_status_1.json @@ -0,0 +1,110 @@ +[ + { + "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" + } + ], + "politicalExposure": { + "status": "isPep", + "details": [ + { + "missingInfoReason": "They won't tell us." + } + ] + }, + "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/tests/fixtures/0.3/pep_details_missing_info_but_no_status_1.json b/tests/fixtures/0.3/pep_details_missing_info_but_no_status_1.json new file mode 100644 index 0000000..1c3f3e5 --- /dev/null +++ b/tests/fixtures/0.3/pep_details_missing_info_but_no_status_1.json @@ -0,0 +1,109 @@ +[ + { + "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" + } + ], + "politicalExposure": { + "details": [ + { + "missingInfoReason": "They won't tell us." + } + ] + }, + "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/tests/fixtures/0.3/pep_status_1.json b/tests/fixtures/0.3/pep_status_1.json new file mode 100644 index 0000000..0b8a37d --- /dev/null +++ b/tests/fixtures/0.3/pep_status_1.json @@ -0,0 +1,105 @@ +[ + { + "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" + } + ], + "politicalExposure": { + "status": "isPep" + }, + "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/tests/test_api_0_3.py b/tests/test_api_0_3.py index b62c0e7..160bbdf 100644 --- a/tests/test_api_0_3.py +++ b/tests/test_api_0_3.py @@ -19,3 +19,131 @@ def test_basic_1(): assert results["validation_errors_count"] == 0 assert results["additional_fields_count"] == 0 assert results["additional_checks_count"] == 0 + + +def test_has_public_listing_information_but_has_public_listing_is_false_1(): + + cove_temp_folder = tempfile.mkdtemp( + prefix="lib-cove-bods-tests-", dir=tempfile.gettempdir() + ) + json_filename = os.path.join( + os.path.dirname(os.path.realpath(__file__)), + "fixtures", + "0.3", + "has_public_listing_information_but_has_public_listing_is_false_1.json", + ) + + results = bods_json_output(cove_temp_folder, json_filename) + + assert results["schema_version"] == "0.3" + assert results["validation_errors_count"] == 0 + assert results["additional_fields_count"] == 0 + + assert results["additional_checks_count"] == 1 + assert ( + results["additional_checks"][0]["type"] + == "has_public_listing_information_but_has_public_listing_is_false" + ) + assert ( + results["additional_checks"][0]["statement"] + == "1dc0e987-5c57-4a1c-b3ad-61353b66a9b7" + ) + + +def test_entity_sub_type_does_not_align_1(): + + cove_temp_folder = tempfile.mkdtemp( + prefix="lib-cove-bods-tests-", dir=tempfile.gettempdir() + ) + json_filename = os.path.join( + os.path.dirname(os.path.realpath(__file__)), + "fixtures", + "0.3", + "entity_sub_type_does_not_align_1.json", + ) + + results = bods_json_output(cove_temp_folder, json_filename) + + assert results["schema_version"] == "0.3" + assert results["validation_errors_count"] == 0 + assert results["additional_fields_count"] == 0 + + assert results["additional_checks_count"] == 1 + assert ( + results["additional_checks"][0]["type"] + == "statement_entity_type_and_entity_sub_type_do_not_align" + ) + assert ( + results["additional_checks"][0]["statement"] + == "1dc0e987-5c57-4a1c-b3ad-61353b66a9b7" + ) + + +def test_entity_sub_type_does_align_1(): + + cove_temp_folder = tempfile.mkdtemp( + prefix="lib-cove-bods-tests-", dir=tempfile.gettempdir() + ) + json_filename = os.path.join( + os.path.dirname(os.path.realpath(__file__)), + "fixtures", + "0.3", + "entity_sub_type_does_align_1.json", + ) + + results = bods_json_output(cove_temp_folder, json_filename) + + assert results["schema_version"] == "0.3" + assert results["validation_errors_count"] == 0 + assert results["additional_fields_count"] == 0 + assert results["additional_checks_count"] == 0 + + +def test_basic_with_correct_mic_codes_1(): + + cove_temp_folder = tempfile.mkdtemp( + prefix="lib-cove-bods-tests-", dir=tempfile.gettempdir() + ) + json_filename = os.path.join( + os.path.dirname(os.path.realpath(__file__)), + "fixtures", + "0.3", + "basic_with_correct_mic_codes_1.json", + ) + + results = bods_json_output(cove_temp_folder, json_filename) + + assert results["schema_version"] == "0.3" + assert results["validation_errors_count"] == 0 + assert results["additional_fields_count"] == 0 + assert results["additional_checks_count"] == 0 + + +def test_basic_with_operating_mic_code_missing_1(): + + cove_temp_folder = tempfile.mkdtemp( + prefix="lib-cove-bods-tests-", dir=tempfile.gettempdir() + ) + json_filename = os.path.join( + os.path.dirname(os.path.realpath(__file__)), + "fixtures", + "0.3", + "basic_with_operating_mic_code_missing_1.json", + ) + + results = bods_json_output(cove_temp_folder, json_filename) + + assert results["schema_version"] == "0.3" + assert results["validation_errors_count"] == 0 + assert results["additional_fields_count"] == 0 + + assert results["additional_checks_count"] == 1 + assert ( + results["additional_checks"][0]["type"] + == "entity_security_listing_market_identifier_code_set_but_not_operating_market_identifier_code" + ) + assert results["additional_checks"][0]["statement_type"] == "entity" + assert ( + results["additional_checks"][0]["statement"] + == "1dc0e987-5c57-4a1c-b3ad-61353b66a9b7" + ) diff --git a/tests/test_pep.py b/tests/test_pep.py new file mode 100644 index 0000000..402f9fa --- /dev/null +++ b/tests/test_pep.py @@ -0,0 +1,438 @@ +import os +import tempfile + +from libcovebods.api import bods_json_output + + +def test_not_in_old_schema(): + + cove_temp_folder = tempfile.mkdtemp( + prefix="lib-cove-bods-tests-", dir=tempfile.gettempdir() + ) + json_filename = os.path.join( + os.path.dirname(os.path.realpath(__file__)), "fixtures", "0.1", "basic_1.json" + ) + + results = bods_json_output(cove_temp_folder, json_filename) + + assert results["schema_version"] == "0.1" + assert "count_person_statements_have_pep_status" not in results["statistics"] + assert results["additional_checks_count"] == 0 + + +def test_schema_0_2_basic_1(): + + cove_temp_folder = tempfile.mkdtemp( + prefix="lib-cove-bods-tests-", dir=tempfile.gettempdir() + ) + json_filename = os.path.join( + os.path.dirname(os.path.realpath(__file__)), + "fixtures", + "0.2", + "pep_status_1.json", + ) + + results = bods_json_output(cove_temp_folder, json_filename) + + assert results["schema_version"] == "0.2" + assert results["statistics"]["count_person_statements_have_pep_status"] == 1 + # We want to test the dict has the correct keys! + # So these tests are deliberately written so they will error if the specified key is not in that dict + assert ( + results["statistics"]["count_person_statements_have_pep_status_statuses"][ + "isPep" + ] + == 1 + ) + assert ( + results["statistics"]["count_person_statements_have_pep_status_statuses"][ + "isNotPep" + ] + == 0 + ) + + assert results["additional_checks_count"] == 0 + + +def test_schema_0_2_missing_1(): + + cove_temp_folder = tempfile.mkdtemp( + prefix="lib-cove-bods-tests-", dir=tempfile.gettempdir() + ) + json_filename = os.path.join( + os.path.dirname(os.path.realpath(__file__)), + "fixtures", + "0.2", + "pep_status_missing_info_1.json", + ) + + results = bods_json_output(cove_temp_folder, json_filename) + + assert results["schema_version"] == "0.2" + assert results["statistics"]["count_person_statements_have_pep_status"] == 1 + # We want to test the dict has the correct keys! + # So these tests are deliberately written so they will error if the specified key is not in that dict + assert ( + results["statistics"]["count_person_statements_have_pep_status_statuses"][ + "isPep" + ] + == 0 + ) + assert ( + results["statistics"]["count_person_statements_have_pep_status_statuses"][ + "isNotPep" + ] + == 1 + ) + + assert results["additional_checks_count"] == 0 + + +def test_schema_0_2_pep_details_no_missing_info_but_incorrect_status_1(): + + cove_temp_folder = tempfile.mkdtemp( + prefix="lib-cove-bods-tests-", dir=tempfile.gettempdir() + ) + json_filename = os.path.join( + os.path.dirname(os.path.realpath(__file__)), + "fixtures", + "0.2", + "pep_details_no_missing_info_but_incorrect_status_1.json", + ) + + results = bods_json_output(cove_temp_folder, json_filename) + + assert results["schema_version"] == "0.2" + assert results["statistics"]["count_person_statements_have_pep_status"] == 1 + # We want to test the dict has the correct keys! + # So these tests are deliberately written so they will error if the specified key is not in that dict + assert ( + results["statistics"]["count_person_statements_have_pep_status_statuses"][ + "isPep" + ] + == 0 + ) + assert ( + results["statistics"]["count_person_statements_have_pep_status_statuses"][ + "isNotPep" + ] + == 1 + ) + + assert results["additional_checks_count"] == 1 + assert ( + results["additional_checks"][0]["type"] + == "has_pep_details_without_missing_info_but_incorrect_pep_status" + ) + assert results["additional_checks"][0]["statement_type"] == "person" + assert ( + results["additional_checks"][0]["statement"] + == "a5680770-899f-4f7a-b795-e266f1ce8668" + ) + + +def test_schema_0_2_pep_details_missing_info_but_incorrect_status_1(): + + cove_temp_folder = tempfile.mkdtemp( + prefix="lib-cove-bods-tests-", dir=tempfile.gettempdir() + ) + json_filename = os.path.join( + os.path.dirname(os.path.realpath(__file__)), + "fixtures", + "0.2", + "pep_details_missing_info_but_incorrect_status_1.json", + ) + + results = bods_json_output(cove_temp_folder, json_filename) + + assert results["schema_version"] == "0.2" + assert results["statistics"]["count_person_statements_have_pep_status"] == 1 + # We want to test the dict has the correct keys! + # So these tests are deliberately written so they will error if the specified key is not in that dict + assert ( + results["statistics"]["count_person_statements_have_pep_status_statuses"][ + "isPep" + ] + == 1 + ) + assert ( + results["statistics"]["count_person_statements_have_pep_status_statuses"][ + "isNotPep" + ] + == 0 + ) + + assert results["additional_checks_count"] == 1 + assert ( + results["additional_checks"][0]["type"] + == "has_pep_details_with_missing_info_but_incorrect_pep_status" + ) + assert results["additional_checks"][0]["statement_type"] == "person" + assert ( + results["additional_checks"][0]["statement"] + == "a5680770-899f-4f7a-b795-e266f1ce8668" + ) + + +def test_schema_0_3_basic_1(): + cove_temp_folder = tempfile.mkdtemp( + prefix="lib-cove-bods-tests-", dir=tempfile.gettempdir() + ) + json_filename = os.path.join( + os.path.dirname(os.path.realpath(__file__)), + "fixtures", + "0.3", + "basic_1.json", + ) + + results = bods_json_output(cove_temp_folder, json_filename) + + assert results["schema_version"] == "0.3" + assert results["statistics"]["count_person_statements_have_pep_status"] == 0 + # We want to test the dict has the correct keys! + # So these tests are deliberately written so they will error if the specified key is not in that dict + assert ( + results["statistics"]["count_person_statements_have_pep_status_statuses"][ + "isPep" + ] + == 0 + ) + assert ( + results["statistics"]["count_person_statements_have_pep_status_statuses"][ + "isNotPep" + ] + == 0 + ) + + assert results["additional_checks_count"] == 0 + + +def test_schema_0_3_pep_status_1(): + cove_temp_folder = tempfile.mkdtemp( + prefix="lib-cove-bods-tests-", dir=tempfile.gettempdir() + ) + json_filename = os.path.join( + os.path.dirname(os.path.realpath(__file__)), + "fixtures", + "0.3", + "pep_status_1.json", + ) + + results = bods_json_output(cove_temp_folder, json_filename) + + assert results["schema_version"] == "0.3" + assert results["statistics"]["count_person_statements_have_pep_status"] == 1 + # We want to test the dict has the correct keys! + # So these tests are deliberately written so they will error if the specified key is not in that dict + assert ( + results["statistics"]["count_person_statements_have_pep_status_statuses"][ + "isPep" + ] + == 1 + ) + assert ( + results["statistics"]["count_person_statements_have_pep_status_statuses"][ + "isNotPep" + ] + == 0 + ) + assert ( + results["statistics"]["count_person_statements_have_pep_status_statuses"][ + "unknown" + ] + == 0 + ) + + assert results["additional_checks_count"] == 0 + + +def test_schema_0_3_pep_details_but_incorrect_status_1(): + cove_temp_folder = tempfile.mkdtemp( + prefix="lib-cove-bods-tests-", dir=tempfile.gettempdir() + ) + json_filename = os.path.join( + os.path.dirname(os.path.realpath(__file__)), + "fixtures", + "0.3", + "pep_details_but_incorrect_status_1.json", + ) + + results = bods_json_output(cove_temp_folder, json_filename) + + assert results["schema_version"] == "0.3" + assert results["statistics"]["count_person_statements_have_pep_status"] == 1 + # We want to test the dict has the correct keys! + # So these tests are deliberately written so they will error if the specified key is not in that dict + assert ( + results["statistics"]["count_person_statements_have_pep_status_statuses"][ + "isPep" + ] + == 0 + ) + assert ( + results["statistics"]["count_person_statements_have_pep_status_statuses"][ + "isNotPep" + ] + == 1 + ) + assert ( + results["statistics"]["count_person_statements_have_pep_status_statuses"][ + "unknown" + ] + == 0 + ) + + assert results["additional_checks_count"] == 1 + assert ( + results["additional_checks"][0]["type"] + == "has_pep_details_but_incorrect_pep_status" + ) + assert results["additional_checks"][0]["statement_type"] == "person" + assert ( + results["additional_checks"][0]["statement"] + == "019a93f1-e470-42e9-957b-03559861b2e2" + ) + + +def test_schema_0_3_pep_details_but_incorrect_status_2(): + cove_temp_folder = tempfile.mkdtemp( + prefix="lib-cove-bods-tests-", dir=tempfile.gettempdir() + ) + json_filename = os.path.join( + os.path.dirname(os.path.realpath(__file__)), + "fixtures", + "0.3", + "pep_details_but_incorrect_status_2.json", + ) + + results = bods_json_output(cove_temp_folder, json_filename) + + assert results["schema_version"] == "0.3" + assert results["statistics"]["count_person_statements_have_pep_status"] == 0 + # We want to test the dict has the correct keys! + # So these tests are deliberately written so they will error if the specified key is not in that dict + assert ( + results["statistics"]["count_person_statements_have_pep_status_statuses"][ + "isPep" + ] + == 0 + ) + assert ( + results["statistics"]["count_person_statements_have_pep_status_statuses"][ + "isNotPep" + ] + == 0 + ) + assert ( + results["statistics"]["count_person_statements_have_pep_status_statuses"][ + "unknown" + ] + == 0 + ) + + assert results["additional_checks_count"] == 1 + assert ( + results["additional_checks"][0]["type"] + == "has_pep_details_but_incorrect_pep_status" + ) + assert results["additional_checks"][0]["statement_type"] == "person" + assert ( + results["additional_checks"][0]["statement"] + == "019a93f1-e470-42e9-957b-03559861b2e2" + ) + + +def test_schema_0_3_pep_details_missing_info_but_incorrect_status_1(): + cove_temp_folder = tempfile.mkdtemp( + prefix="lib-cove-bods-tests-", dir=tempfile.gettempdir() + ) + json_filename = os.path.join( + os.path.dirname(os.path.realpath(__file__)), + "fixtures", + "0.3", + "pep_details_missing_info_but_incorrect_status_1.json", + ) + + results = bods_json_output(cove_temp_folder, json_filename) + + assert results["schema_version"] == "0.3" + assert results["statistics"]["count_person_statements_have_pep_status"] == 1 + # We want to test the dict has the correct keys! + # So these tests are deliberately written so they will error if the specified key is not in that dict + assert ( + results["statistics"]["count_person_statements_have_pep_status_statuses"][ + "isPep" + ] + == 1 + ) + assert ( + results["statistics"]["count_person_statements_have_pep_status_statuses"][ + "isNotPep" + ] + == 0 + ) + assert ( + results["statistics"]["count_person_statements_have_pep_status_statuses"][ + "unknown" + ] + == 0 + ) + + assert results["additional_checks_count"] == 1 + assert ( + results["additional_checks"][0]["type"] + == "has_pep_details_with_missing_info_but_incorrect_pep_status" + ) + assert results["additional_checks"][0]["statement_type"] == "person" + assert ( + results["additional_checks"][0]["statement"] + == "019a93f1-e470-42e9-957b-03559861b2e2" + ) + + +def test_schema_0_3_pep_details_missing_info_but_no_status_1(): + cove_temp_folder = tempfile.mkdtemp( + prefix="lib-cove-bods-tests-", dir=tempfile.gettempdir() + ) + json_filename = os.path.join( + os.path.dirname(os.path.realpath(__file__)), + "fixtures", + "0.3", + "pep_details_missing_info_but_no_status_1.json", + ) + + results = bods_json_output(cove_temp_folder, json_filename) + + assert results["schema_version"] == "0.3" + assert results["statistics"]["count_person_statements_have_pep_status"] == 0 + # We want to test the dict has the correct keys! + # So these tests are deliberately written so they will error if the specified key is not in that dict + assert ( + results["statistics"]["count_person_statements_have_pep_status_statuses"][ + "isPep" + ] + == 0 + ) + assert ( + results["statistics"]["count_person_statements_have_pep_status_statuses"][ + "isNotPep" + ] + == 0 + ) + assert ( + results["statistics"]["count_person_statements_have_pep_status_statuses"][ + "unknown" + ] + == 0 + ) + + assert results["additional_checks_count"] == 1 + assert ( + results["additional_checks"][0]["type"] + == "has_pep_details_with_missing_info_but_incorrect_pep_status" + ) + assert results["additional_checks"][0]["statement_type"] == "person" + assert ( + results["additional_checks"][0]["statement"] + == "019a93f1-e470-42e9-957b-03559861b2e2" + ) diff --git a/tests/test_stat_count_person_statements_have_pep_status.py b/tests/test_stat_count_ownership_or_control_statement_with_at_least_one_interest_beneficial.py similarity index 57% rename from tests/test_stat_count_person_statements_have_pep_status.py rename to tests/test_stat_count_ownership_or_control_statement_with_at_least_one_interest_beneficial.py index c9a590a..44e220a 100644 --- a/tests/test_stat_count_person_statements_have_pep_status.py +++ b/tests/test_stat_count_ownership_or_control_statement_with_at_least_one_interest_beneficial.py @@ -4,22 +4,28 @@ from libcovebods.api import bods_json_output -def test_not_in_old_schema(): +def test_schema_0_2(): cove_temp_folder = tempfile.mkdtemp( prefix="lib-cove-bods-tests-", dir=tempfile.gettempdir() ) json_filename = os.path.join( - os.path.dirname(os.path.realpath(__file__)), "fixtures", "0.1", "basic_1.json" + os.path.dirname(os.path.realpath(__file__)), "fixtures", "0.2", "basic_1.json" ) results = bods_json_output(cove_temp_folder, json_filename) - assert results["schema_version"] == "0.1" - assert "count_person_statements_have_pep_status" not in results["statistics"] + assert results["schema_version"] == "0.2" + + assert ( + 1 + == results["statistics"][ + "count_ownership_or_control_statement_with_at_least_one_interest_beneficial" + ] + ) -def test_1(): +def test_schema_0_2_statement_is_component_but_is_not_used_1(): cove_temp_folder = tempfile.mkdtemp( prefix="lib-cove-bods-tests-", dir=tempfile.gettempdir() @@ -28,40 +34,37 @@ def test_1(): os.path.dirname(os.path.realpath(__file__)), "fixtures", "0.2", - "pep_status_1.json", + "statement_is_component_but_is_not_used_1.json", ) results = bods_json_output(cove_temp_folder, json_filename) assert results["schema_version"] == "0.2" - assert results["statistics"]["count_person_statements_have_pep_status"] == 1 + assert ( - results["statistics"][ - "count_person_statements_have_pep_status_and_reason_missing_info" + 2 + == results["statistics"][ + "count_ownership_or_control_statement_with_at_least_one_interest_beneficial" ] - == 0 ) -def test_missing_1(): +def test_schema_0_3(): cove_temp_folder = tempfile.mkdtemp( prefix="lib-cove-bods-tests-", dir=tempfile.gettempdir() ) json_filename = os.path.join( - os.path.dirname(os.path.realpath(__file__)), - "fixtures", - "0.2", - "pep_status_missing_info_1.json", + os.path.dirname(os.path.realpath(__file__)), "fixtures", "0.3", "basic_1.json" ) results = bods_json_output(cove_temp_folder, json_filename) - assert results["schema_version"] == "0.2" - assert results["statistics"]["count_person_statements_have_pep_status"] == 1 + assert results["schema_version"] == "0.3" + assert ( - results["statistics"][ - "count_person_statements_have_pep_status_and_reason_missing_info" + 1 + == results["statistics"][ + "count_ownership_or_control_statement_with_at_least_one_interest_beneficial" ] - == 1 )