diff --git a/docs/history/changelog.md b/docs/history/changelog.md index a717f9dc2..f95815e6b 100644 --- a/docs/history/changelog.md +++ b/docs/history/changelog.md @@ -332,6 +332,8 @@ Per the [normative and non-normative content and changes policy](../governance/n * [#1450](https://github.com/open-contracting/standard/pull/1450) Replace a repeated example in schema/merging/ with a link to guidance/build/merging/. * [#1665](https://github.com/open-contracting/standard/pull/1665) Abandon in-file translations. +* [#1480](https://github.com/open-contracting/standard/pull/1480) Add `UnitValue` definition. + ### Documentation * [#1115](https://github.com/open-contracting/standard/pull/1115) Add guidance on when having multiple suppliers per award. diff --git a/manage.py b/manage.py index 9969859b9..7fb0d2143 100755 --- a/manage.py +++ b/manage.py @@ -23,7 +23,7 @@ from babel.messages.pofile import read_po from docutils.utils import relative_path from lxml import etree -from ocdskit.schema import get_schema_fields +from ocdskit.schema import add_validation_properties, get_schema_fields basedir = Path(__file__).resolve().parent schemadir = basedir / "schema" @@ -392,6 +392,126 @@ def get_versioned_release_schema(schema): return schema +def add_key_based_validation_properties(schema): + """ + Add validation properties based on key names. + + * "format": "email" if the key is "email" + * "minimum": 0 to "quantity", "durationInDays" and "numberOfTenderers fields + * "required": ["id", "name"] to "Organization" and "OrganizationReference" + * "required": ["id"] to "Amendment" and "RelatedProcess" + * "$ref": "#/definitions/UnitValue" to "Unit.value" + + Removes "integer" type from "id" and "projectID" fields. + + :param dict schema: a JSON schema + """ + if isinstance(schema, list): + for item in schema: + add_key_based_validation_properties(item) + elif isinstance(schema, dict): + for key, value in schema.items(): + if key == "email": + value["format"] = "email" + elif key in ["quantity", "durationInDays", "numberOfTenderers"]: + value["minimum"] = 0 + elif key in ["Organization", "OrganizationReference"]: + value["required"] = ["id", "name"] + value["properties"]["name"]["type"] = "string" + value["properties"]["id"]["type"] = "string" + elif key in ["Amendment", "RelatedProcess"]: + value["required"] = ["id"] + value["properties"]["id"]["type"] = "string" + elif key in ["id", "projectID"]: + if "type" in value and "integer" in value["type"]: + value["type"].remove("integer") + elif key == "Unit": + value["properties"]["value"]["$ref"] = "#/definitions/UnitValue" + + add_key_based_validation_properties(value) + + +def get_strict_schema(schema): + """ + Return the strict version of the schema. + """ + # Update schema metadata. + identifier = schema["id"].split("/") + identifier[-1] = f"strict/{identifier[-1]}" + schema["id"] = "/".join(identifier) + schema["title"] = f'Strict {schema["title"][0].lower()}{schema["title"][1:]}' + schema["description"] = ( + f'{schema["description"]} The strict schema adds additional validation rules planned for inclusion in OCDS 2.0. Use of the strict schema is a voluntary opportunity to improve data quality.' # noqa: E501 + ) + + # Update references to other schemas. + reference_strict_schemas(schema) + + # Reference compiled release schema. + if schema["id"].endswith("record-schema.json"): + schema["properties"]["compiledRelease"]["$ref"] = schema["properties"]["compiledRelease"]["$ref"].replace( + "release-schema.json", "compiled-release-schema.json" + ) + + # Add validation properties + add_validation_properties(schema) + + # Add key-based validation properties + add_key_based_validation_properties(schema) + + # Remove null types from package schemas + if "package" in schema["id"]: + return remove_nulls(schema) + + return schema + + +def get_compiled_release_schema(schema): + """ + Return the compiled release schema. + """ + # Update schema metadata. + schema["id"] = schema["id"].replace("release-schema", "compiled-release-schema") + schema["title"] = schema["title"].replace("Open Contracting Release", "Open Contracting Compiled Release") + + # Remove null types and enum values. + return remove_nulls(schema) + + +def remove_nulls(schema): + """ + Remove null types and enum values. + """ + if isinstance(schema, dict): + for key, value in schema.items(): + if key == "type" and isinstance(value, list) and "null" in value: + value.remove("null") + if key == "enum" and isinstance(value, list) and None in value: + value.remove(None) + + remove_nulls(value) + + return schema + + +def reference_strict_schemas(schema): + """ + Update $refs to reference strict schemas. + """ + if isinstance(schema, dict): + for key, value in schema.items(): + if key == "$ref" and value.startswith("https://standard.open-contracting.org/schema/"): + reference = value.split("/") + reference[-1] = f"strict/{reference[-1]}" + schema[key] = "/".join(reference) + + reference_strict_schemas(value) + + if isinstance(schema, list): + for subschema in schema: + reference_strict_schemas(subschema) + + @click.group() def cli(): pass @@ -494,6 +614,12 @@ def pre_commit(): - meta-schema.json - dereferenced-release-schema.json - versioned-release-validation-schema.json + - strict/release-schema.json + - strict/release-package.json + - strict/record-package.json + - strict/dereferenced-release-schema.json + - strict/versioned-release-validation-schema.json + - strict/compiled-release-schema.json """ nonmultilingual = { # Identifiers. @@ -516,6 +642,7 @@ def pre_commit(): } release_schema = json_load("release-schema.json") + strict_release_schema = get_strict_schema(deepcopy(release_schema)) jsonref_release_schema = json_load("release-schema.json", jsonref, merge_props=True) counts = defaultdict(list) @@ -568,6 +695,24 @@ def pre_commit(): json_dump("dereferenced-release-schema.json", jsonref_release_schema) json_dump("versioned-release-validation-schema.json", get_versioned_release_schema(release_schema)) + # Strict schemas. + directory = Path("strict") + json_dump(directory / "release-schema.json", strict_release_schema) + json_dump(directory / "record-schema.json", get_strict_schema(json_load("record-schema.json"))) + + strict_dereferenced_release_schema = json_load(directory / "release-schema.json", jsonref, merge_props=True) + json_dump(directory / "dereferenced-release-schema.json", strict_dereferenced_release_schema) + json_dump( + directory / "versioned-release-validation-schema.json", get_versioned_release_schema(strict_release_schema) + ) + + json_dump(directory / "release-package-schema.json", get_strict_schema(json_load("release-package-schema.json"))) + json_dump(directory / "record-package-schema.json", get_strict_schema(json_load("record-package-schema.json"))) + json_dump( + directory / "compiled-release-schema.json", + remove_nulls(get_compiled_release_schema(json_load(directory / "release-schema.json"))), + ) + @cli.command() @click.argument("file", type=click.File()) diff --git a/schema/dereferenced-release-schema.json b/schema/dereferenced-release-schema.json index f71c5592f..59df8c867 100644 --- a/schema/dereferenced-release-schema.json +++ b/schema/dereferenced-release-schema.json @@ -41482,6 +41482,338 @@ } } }, + "UnitValue": { + "title": "Unit value", + "description": "The monetary value of a single unit.", + "type": "object", + "properties": { + "amount": { + "title": "Amount", + "description": "Amount as a number.", + "type": [ + "number", + "null" + ], + "minimum": 0 + }, + "currency": { + "title": "Currency", + "description": "The currency of the amount, from the closed [currency](https://standard.open-contracting.org/{{version}}/{{lang}}/schema/codelists/#currency) codelist.", + "type": [ + "string", + "null" + ], + "codelist": "currency.csv", + "openCodelist": false, + "enum": [ + "ADP", + "AED", + "AFA", + "AFN", + "ALK", + "ALL", + "AMD", + "ANG", + "AOA", + "AOK", + "AON", + "AOR", + "ARA", + "ARP", + "ARS", + "ARY", + "ATS", + "AUD", + "AWG", + "AYM", + "AZM", + "AZN", + "BAD", + "BAM", + "BBD", + "BDT", + "BEC", + "BEF", + "BEL", + "BGJ", + "BGK", + "BGL", + "BGN", + "BHD", + "BIF", + "BMD", + "BND", + "BOB", + "BOP", + "BOV", + "BRB", + "BRC", + "BRE", + "BRL", + "BRN", + "BRR", + "BSD", + "BTN", + "BUK", + "BWP", + "BYB", + "BYN", + "BYR", + "BZD", + "CAD", + "CDF", + "CHC", + "CHE", + "CHF", + "CHW", + "CLF", + "CLP", + "CNY", + "COP", + "COU", + "CRC", + "CSD", + "CSJ", + "CSK", + "CUC", + "CUP", + "CVE", + "CYP", + "CZK", + "DDM", + "DEM", + "DJF", + "DKK", + "DOP", + "DZD", + "ECS", + "ECV", + "EEK", + "EGP", + "ERN", + "ESA", + "ESB", + "ESP", + "ETB", + "EUR", + "FIM", + "FJD", + "FKP", + "FRF", + "GBP", + "GEK", + "GEL", + "GHC", + "GHP", + "GHS", + "GIP", + "GMD", + "GNE", + "GNF", + "GNS", + "GQE", + "GRD", + "GTQ", + "GWE", + "GWP", + "GYD", + "HKD", + "HNL", + "HRD", + "HRK", + "HTG", + "HUF", + "IDR", + "IEP", + "ILP", + "ILR", + "ILS", + "INR", + "IQD", + "IRR", + "ISJ", + "ISK", + "ITL", + "JMD", + "JOD", + "JPY", + "KES", + "KGS", + "KHR", + "KMF", + "KPW", + "KRW", + "KWD", + "KYD", + "KZT", + "LAJ", + "LAK", + "LBP", + "LKR", + "LRD", + "LSL", + "LSM", + "LTL", + "LTT", + "LUC", + "LUF", + "LUL", + "LVL", + "LVR", + "LYD", + "MAD", + "MDL", + "MGA", + "MGF", + "MKD", + "MLF", + "MMK", + "MNT", + "MOP", + "MRO", + "MRU", + "MTL", + "MTP", + "MUR", + "MVQ", + "MVR", + "MWK", + "MXN", + "MXP", + "MXV", + "MYR", + "MZE", + "MZM", + "MZN", + "NAD", + "NGN", + "NIC", + "NIO", + "NLG", + "NOK", + "NPR", + "NZD", + "OMR", + "PAB", + "PEH", + "PEI", + "PEN", + "PES", + "PGK", + "PHP", + "PKR", + "PLN", + "PLZ", + "PTE", + "PYG", + "QAR", + "RHD", + "ROK", + "ROL", + "RON", + "RSD", + "RUB", + "RUR", + "RWF", + "SAR", + "SBD", + "SCR", + "SDD", + "SDG", + "SDP", + "SEK", + "SGD", + "SHP", + "SIT", + "SKK", + "SLL", + "SOS", + "SRD", + "SRG", + "SSP", + "STD", + "STN", + "SUR", + "SVC", + "SYP", + "SZL", + "THB", + "TJR", + "TJS", + "TMM", + "TMT", + "TND", + "TOP", + "TPE", + "TRL", + "TRY", + "TTD", + "TWD", + "TZS", + "UAH", + "UAK", + "UGS", + "UGW", + "UGX", + "USD", + "USN", + "USS", + "UYI", + "UYN", + "UYP", + "UYU", + "UYW", + "UZS", + "VEB", + "VEF", + "VES", + "VNC", + "VND", + "VUV", + "WST", + "XAF", + "XAG", + "XAU", + "XBA", + "XBB", + "XBC", + "XBD", + "XCD", + "XDR", + "XEU", + "XFO", + "XFU", + "XOF", + "XPD", + "XPF", + "XPT", + "XRE", + "XSU", + "XTS", + "XUA", + "XXX", + "YDD", + "YER", + "YUD", + "YUM", + "YUN", + "ZAL", + "ZAR", + "ZMK", + "ZMW", + "ZRN", + "ZRZ", + "ZWC", + "ZWD", + "ZWL", + "ZWN", + "ZWR", + null + ] + } + }, + "minProperties": 1 + }, "SimpleIdentifier": { "title": "Simple identifier", "description": "An unambiguous reference to a resource within a given context.", diff --git a/schema/release-schema.json b/schema/release-schema.json index 46f3ea6d8..3bd0049b7 100644 --- a/schema/release-schema.json +++ b/schema/release-schema.json @@ -2882,6 +2882,338 @@ } } }, + "UnitValue": { + "title": "Unit value", + "description": "The monetary value of a single unit.", + "type": "object", + "properties": { + "amount": { + "title": "Amount", + "description": "Amount as a number.", + "type": [ + "number", + "null" + ], + "minimum": 0 + }, + "currency": { + "title": "Currency", + "description": "The currency of the amount, from the closed [currency](https://standard.open-contracting.org/{{version}}/{{lang}}/schema/codelists/#currency) codelist.", + "type": [ + "string", + "null" + ], + "codelist": "currency.csv", + "openCodelist": false, + "enum": [ + "ADP", + "AED", + "AFA", + "AFN", + "ALK", + "ALL", + "AMD", + "ANG", + "AOA", + "AOK", + "AON", + "AOR", + "ARA", + "ARP", + "ARS", + "ARY", + "ATS", + "AUD", + "AWG", + "AYM", + "AZM", + "AZN", + "BAD", + "BAM", + "BBD", + "BDT", + "BEC", + "BEF", + "BEL", + "BGJ", + "BGK", + "BGL", + "BGN", + "BHD", + "BIF", + "BMD", + "BND", + "BOB", + "BOP", + "BOV", + "BRB", + "BRC", + "BRE", + "BRL", + "BRN", + "BRR", + "BSD", + "BTN", + "BUK", + "BWP", + "BYB", + "BYN", + "BYR", + "BZD", + "CAD", + "CDF", + "CHC", + "CHE", + "CHF", + "CHW", + "CLF", + "CLP", + "CNY", + "COP", + "COU", + "CRC", + "CSD", + "CSJ", + "CSK", + "CUC", + "CUP", + "CVE", + "CYP", + "CZK", + "DDM", + "DEM", + "DJF", + "DKK", + "DOP", + "DZD", + "ECS", + "ECV", + "EEK", + "EGP", + "ERN", + "ESA", + "ESB", + "ESP", + "ETB", + "EUR", + "FIM", + "FJD", + "FKP", + "FRF", + "GBP", + "GEK", + "GEL", + "GHC", + "GHP", + "GHS", + "GIP", + "GMD", + "GNE", + "GNF", + "GNS", + "GQE", + "GRD", + "GTQ", + "GWE", + "GWP", + "GYD", + "HKD", + "HNL", + "HRD", + "HRK", + "HTG", + "HUF", + "IDR", + "IEP", + "ILP", + "ILR", + "ILS", + "INR", + "IQD", + "IRR", + "ISJ", + "ISK", + "ITL", + "JMD", + "JOD", + "JPY", + "KES", + "KGS", + "KHR", + "KMF", + "KPW", + "KRW", + "KWD", + "KYD", + "KZT", + "LAJ", + "LAK", + "LBP", + "LKR", + "LRD", + "LSL", + "LSM", + "LTL", + "LTT", + "LUC", + "LUF", + "LUL", + "LVL", + "LVR", + "LYD", + "MAD", + "MDL", + "MGA", + "MGF", + "MKD", + "MLF", + "MMK", + "MNT", + "MOP", + "MRO", + "MRU", + "MTL", + "MTP", + "MUR", + "MVQ", + "MVR", + "MWK", + "MXN", + "MXP", + "MXV", + "MYR", + "MZE", + "MZM", + "MZN", + "NAD", + "NGN", + "NIC", + "NIO", + "NLG", + "NOK", + "NPR", + "NZD", + "OMR", + "PAB", + "PEH", + "PEI", + "PEN", + "PES", + "PGK", + "PHP", + "PKR", + "PLN", + "PLZ", + "PTE", + "PYG", + "QAR", + "RHD", + "ROK", + "ROL", + "RON", + "RSD", + "RUB", + "RUR", + "RWF", + "SAR", + "SBD", + "SCR", + "SDD", + "SDG", + "SDP", + "SEK", + "SGD", + "SHP", + "SIT", + "SKK", + "SLL", + "SOS", + "SRD", + "SRG", + "SSP", + "STD", + "STN", + "SUR", + "SVC", + "SYP", + "SZL", + "THB", + "TJR", + "TJS", + "TMM", + "TMT", + "TND", + "TOP", + "TPE", + "TRL", + "TRY", + "TTD", + "TWD", + "TZS", + "UAH", + "UAK", + "UGS", + "UGW", + "UGX", + "USD", + "USN", + "USS", + "UYI", + "UYN", + "UYP", + "UYU", + "UYW", + "UZS", + "VEB", + "VEF", + "VES", + "VNC", + "VND", + "VUV", + "WST", + "XAF", + "XAG", + "XAU", + "XBA", + "XBB", + "XBC", + "XBD", + "XCD", + "XDR", + "XEU", + "XFO", + "XFU", + "XOF", + "XPD", + "XPF", + "XPT", + "XRE", + "XSU", + "XTS", + "XUA", + "XXX", + "YDD", + "YER", + "YUD", + "YUM", + "YUN", + "ZAL", + "ZAR", + "ZMK", + "ZMW", + "ZRN", + "ZRZ", + "ZWC", + "ZWD", + "ZWL", + "ZWN", + "ZWR", + null + ] + } + }, + "minProperties": 1 + }, "SimpleIdentifier": { "title": "Simple identifier", "description": "An unambiguous reference to a resource within a given context.", diff --git a/schema/versioned-release-validation-schema.json b/schema/versioned-release-validation-schema.json index 55b8de369..9f3bfbbe6 100644 --- a/schema/versioned-release-validation-schema.json +++ b/schema/versioned-release-validation-schema.json @@ -3443,6 +3443,374 @@ } } }, + "UnitValue": { + "type": "object", + "properties": { + "amount": { + "type": "array", + "items": { + "type": "object", + "properties": { + "releaseDate": { + "format": "date-time", + "type": "string" + }, + "releaseID": { + "type": "string" + }, + "value": { + "type": [ + "number", + "null" + ], + "minimum": 0 + }, + "releaseTag": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "currency": { + "type": "array", + "items": { + "type": "object", + "properties": { + "releaseDate": { + "format": "date-time", + "type": "string" + }, + "releaseID": { + "type": "string" + }, + "value": { + "type": [ + "string", + "null" + ], + "codelist": "currency.csv", + "openCodelist": false, + "enum": [ + "ADP", + "AED", + "AFA", + "AFN", + "ALK", + "ALL", + "AMD", + "ANG", + "AOA", + "AOK", + "AON", + "AOR", + "ARA", + "ARP", + "ARS", + "ARY", + "ATS", + "AUD", + "AWG", + "AYM", + "AZM", + "AZN", + "BAD", + "BAM", + "BBD", + "BDT", + "BEC", + "BEF", + "BEL", + "BGJ", + "BGK", + "BGL", + "BGN", + "BHD", + "BIF", + "BMD", + "BND", + "BOB", + "BOP", + "BOV", + "BRB", + "BRC", + "BRE", + "BRL", + "BRN", + "BRR", + "BSD", + "BTN", + "BUK", + "BWP", + "BYB", + "BYN", + "BYR", + "BZD", + "CAD", + "CDF", + "CHC", + "CHE", + "CHF", + "CHW", + "CLF", + "CLP", + "CNY", + "COP", + "COU", + "CRC", + "CSD", + "CSJ", + "CSK", + "CUC", + "CUP", + "CVE", + "CYP", + "CZK", + "DDM", + "DEM", + "DJF", + "DKK", + "DOP", + "DZD", + "ECS", + "ECV", + "EEK", + "EGP", + "ERN", + "ESA", + "ESB", + "ESP", + "ETB", + "EUR", + "FIM", + "FJD", + "FKP", + "FRF", + "GBP", + "GEK", + "GEL", + "GHC", + "GHP", + "GHS", + "GIP", + "GMD", + "GNE", + "GNF", + "GNS", + "GQE", + "GRD", + "GTQ", + "GWE", + "GWP", + "GYD", + "HKD", + "HNL", + "HRD", + "HRK", + "HTG", + "HUF", + "IDR", + "IEP", + "ILP", + "ILR", + "ILS", + "INR", + "IQD", + "IRR", + "ISJ", + "ISK", + "ITL", + "JMD", + "JOD", + "JPY", + "KES", + "KGS", + "KHR", + "KMF", + "KPW", + "KRW", + "KWD", + "KYD", + "KZT", + "LAJ", + "LAK", + "LBP", + "LKR", + "LRD", + "LSL", + "LSM", + "LTL", + "LTT", + "LUC", + "LUF", + "LUL", + "LVL", + "LVR", + "LYD", + "MAD", + "MDL", + "MGA", + "MGF", + "MKD", + "MLF", + "MMK", + "MNT", + "MOP", + "MRO", + "MRU", + "MTL", + "MTP", + "MUR", + "MVQ", + "MVR", + "MWK", + "MXN", + "MXP", + "MXV", + "MYR", + "MZE", + "MZM", + "MZN", + "NAD", + "NGN", + "NIC", + "NIO", + "NLG", + "NOK", + "NPR", + "NZD", + "OMR", + "PAB", + "PEH", + "PEI", + "PEN", + "PES", + "PGK", + "PHP", + "PKR", + "PLN", + "PLZ", + "PTE", + "PYG", + "QAR", + "RHD", + "ROK", + "ROL", + "RON", + "RSD", + "RUB", + "RUR", + "RWF", + "SAR", + "SBD", + "SCR", + "SDD", + "SDG", + "SDP", + "SEK", + "SGD", + "SHP", + "SIT", + "SKK", + "SLL", + "SOS", + "SRD", + "SRG", + "SSP", + "STD", + "STN", + "SUR", + "SVC", + "SYP", + "SZL", + "THB", + "TJR", + "TJS", + "TMM", + "TMT", + "TND", + "TOP", + "TPE", + "TRL", + "TRY", + "TTD", + "TWD", + "TZS", + "UAH", + "UAK", + "UGS", + "UGW", + "UGX", + "USD", + "USN", + "USS", + "UYI", + "UYN", + "UYP", + "UYU", + "UYW", + "UZS", + "VEB", + "VEF", + "VES", + "VNC", + "VND", + "VUV", + "WST", + "XAF", + "XAG", + "XAU", + "XBA", + "XBB", + "XBC", + "XBD", + "XCD", + "XDR", + "XEU", + "XFO", + "XFU", + "XOF", + "XPD", + "XPF", + "XPT", + "XRE", + "XSU", + "XTS", + "XUA", + "XXX", + "YDD", + "YER", + "YUD", + "YUM", + "YUN", + "ZAL", + "ZAR", + "ZMK", + "ZMW", + "ZRN", + "ZRZ", + "ZWC", + "ZWD", + "ZWL", + "ZWN", + "ZWR", + null + ] + }, + "releaseTag": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + }, + "minProperties": 1 + }, "SimpleIdentifier": { "type": "object", "properties": {