diff --git a/python/client-contract/CHANGELOG.md b/python/client-contract/CHANGELOG.md index 16dc069..9e1dfce 100644 --- a/python/client-contract/CHANGELOG.md +++ b/python/client-contract/CHANGELOG.md @@ -11,11 +11,12 @@ All notable changes to the `dfo-spec-kit-client-contract` Python package will be - `strict-fields.json` and its schema: 29 members that stay required and non-nullable, each one a client reads, and the answer kinds, part statuses and operation statuses the clients branch on. - `actions.json` and its schema: the caller's next step for each of the 102 active refusal codes on `problem_details` or `no_data_reason`, with the gap kind of each no_data reason. - The status fallback for a refusal whose code has no row: 400 and 422 to `InvalidRequestError`, 401 to `AuthenticationError`, 403 to `PermissionDeniedError`, 404 and 410 to `NotFoundError`, 408 to `DfoTimeoutError`, 429 to `RateLimitError`, and any other 4xx or 5xx to `InvalidRequestError` or `ServerError`. -- `scenario.schema.json`, the scenario record, and 121 scenarios: 32 recorded against the deployed dfo-apis dev app at `99e62038c`, and 89 synthetic, each derived from a recorded one by a one-line edit, with 19 binary bodies totalling 1,124,808 bytes and records and bodies together 13,186,279 bytes. `search-place-ambiguous-twice` pins two entity_ambiguous groups of one text, each option and slot token naming its group by `entity_choice`. The scenarios cover the several-part choice entries and the assumptions each identity entry echoes, a picked reading whose assumption Search states in its answer, the place choice through a no_data part's options, the choice refusals, a part without variable_text, an unknown status without a restatement, a shared label, a one-part option whose data_query holds two parts, the separate and operations echo, an unsupported part's placeholder, the timed_out, unsupported and unknown gaps, an unknown answer kind, the `SearchWarning` rule, the stacked dataset's `part` column, the frame layout, a strict fetch's `DataWarning`, a spec fetch that rebinds one of two entity roles and keeps the other, one that drops a role bound to null, a spec whose destination is a named group fetched with a window or with its origin rebound, whose sentence lookup posts the groups of the roles the build left bound, a twice cut-off build, a cut-off build retried after its Retry-After, a build refused 504 then 503 and one refused 503 then 504, a refused build, a build refused 501 `capability_unavailable`, a digest mismatch, a signed URL refused on both rounds, a relative files-route Location, an artifact url on a foreign origin, on the storage host and absolute on the API's origin, a manifest definition without identity or schema_version, a Search spec without identity or resolved, nested specs no rule reads, an empty parts list, a Search body that is not an object, a codeless 503 problem document that is not retryable, a 401, an identity resubmit answering another spec, a part whose status is not a string, a Search spec whose definition names another spec_id, a Search response with no JSON body, an option with no data_query, empty entities and facets, a Sunset header issued once, `x-dfo-warning` messages and a Deprecation and Sunset pair, an unreadable versions page, a mint and a spec fetch whose build answers 202 and is polled to done, a resumed operation, a rate-limited poll, 503 polls retried within the budget an answered poll restores, a 202 that reads running, polls whose `retry_after_ms` changes from one to the next, a 404 poll that raises, a failed operation posted again, a failed operation with no error, and a ref whose stored file expired, read through the file selector, rebuilt at the same version, at a newer one, or refused with the newest version read from the versions route: the first version it lists with no `base_content_version`, the newest plain build, so a derived version listed before it is skipped. +- `scenario.schema.json`, the scenario record, and 122 scenarios: 32 recorded against the deployed dfo-apis dev app at `99e62038c`, and 90 synthetic, each derived from a recorded one by a one-line edit, with 19 binary bodies totalling 1,124,808 bytes and records and bodies together 13,324,483 bytes. `search-place-ambiguous-twice` pins two entity_ambiguous groups of one text, each option and slot token naming its group by `entity_choice`. The scenarios cover the several-part choice entries and the assumptions each identity entry echoes, a picked reading whose assumption Search states in its answer, the place choice through a no_data part's options, the choice refusals, a part without variable_text, an unknown status without a restatement, a shared label, a one-part option whose data_query holds two parts, the separate and operations echo, an unsupported part's placeholder, the timed_out, unsupported and unknown gaps, an unknown answer kind, the `SearchWarning` rule, the stacked dataset's `part` column, the frame layout, a strict fetch's `DataWarning`, a spec fetch that rebinds one of two entity roles and keeps the other, one that drops a role bound to null, a spec whose destination is a named group fetched with a window or with its origin rebound, whose sentence lookup posts the groups of the roles the build left bound, a twice cut-off build, a cut-off build retried after its Retry-After, a build refused 504 then 503 and one refused 503 then 504, a refused build, a build refused 501 `capability_unavailable`, a digest mismatch, a signed URL refused on both rounds, a relative files-route Location, an artifact url on a foreign origin, on the storage host and absolute on the API's origin, a manifest definition without identity or schema_version, a Search spec without identity or resolved, nested specs no rule reads, an empty parts list, a Search body that is not an object, a codeless 503 problem document that is not retryable, a 401, an identity resubmit answering another spec, a part whose status is not a string, a Search spec whose definition names another spec_id, a Search response with no JSON body, an option with no data_query, empty entities and facets, a Sunset header issued once, `x-dfo-warning` messages and a Deprecation and Sunset pair, an unreadable versions page, a mint and a spec fetch whose build answers 202 and is polled to done, a resumed operation, a rate-limited poll, 503 polls retried within the budget an answered poll restores, a 202 that reads running, polls whose `retry_after_ms` changes from one to the next, a 404 poll that raises, a failed operation posted again, a failed operation with no error, and a ref whose stored file expired, read through the file selector, rebuilt at the same version, at a newer one, or refused with the newest version read from the versions route: the first version it lists with no `base_content_version`, the newest plain build, so a derived version listed before it is skipped. - `client-codes.json` and its schema: the 23 codes a client sets on the errors it raises itself, each with its error class, retryable flag and condition, disjoint from `actions.json` and `registries/codes.json`, and the `client_codes()` and `client_codes_schema()` accessors. Every scenario error the client raises itself carries its code, and `refusal_without_code` names a refusal whose body carries no code. - `proxy-codes.json` and its schema, the website proxy's `rate_limited` and `transient_unavailable` codes with their statuses, which the proxy sends in problem documents of the shape every refusal has, both retryable, with `Retry-After` on the 429, so a client reads one refusal shape, and `warning-codes.json` and its schema, the 6 codes a client sets on the warnings it issues itself, with the `proxy_codes()` and `warning_codes()` accessors. - `ref_digest_changed` in `warning-codes.json`, a `DfoWarning` for a ref member whose `content_version` serves a file with another digest than the ref's, because the server rendered the same data again. The client returns the data, with no error under strict. - `timed_out` in `actions.json` and `registries/codes.json`, the no_data reason Search gives a part it left unresolved at the request's time limit, with action `ask_data_question` and gap kind `timed_out`, which `actions.schema.json` now admits. The client-side `timeout` code, a transport timeout, is unchanged. +- A no_data part's gap kind comes from its own reason, whatever the answer's `diagnostics.timed_out` says. `answer-timed-out` gives its part the reason `timed_out`, and `answer-timed-out-keeps-reasons` pins a part that keeps `variable_not_resolved` as a `not_understood` gap in a timed-out answer. - Error outcomes give the error's `trace_id`, the body's, else the `x-dfo-trace-id` header's, else null. Scenario outcomes give `retry_waits`, the waits before each retry, and `warnings`, the warning codes a step's responses issued. Scenario steps take no `timeout`. - `dataset-ref.schema.json`, the frozen `DatasetRef` format, with accepted and rejected examples. - `without_party_totals` in the ref's artifact rendering, `true` for the version rendered with party totals left out and absent for a plain rendering, with `ref-without-party-totals` and `ref-without-party-totals-false-rejected` as its examples. diff --git a/python/client-contract/README.md b/python/client-contract/README.md index 6b00aa1..46b29ef 100644 --- a/python/client-contract/README.md +++ b/python/client-contract/README.md @@ -73,7 +73,7 @@ uv run ty check src/dfo_client_contract ## Strict fields -`strict-fields.json` lists the members a client cannot act without. Each pointer names one property of one component schema, `/components/schemas//properties/`. A `required` entry stays in its schema's `required` list and admits no null, so a client raises `ContractError` when one is missing or null. Every entry is a member some client rule acts on, and a client checks a body only along them: from the body's root it goes down through strict members alone, so a member no client reads goes unchecked, however deep. A `Spec` where no rule reads it, in `resolved.equivalent_readings`, `no_data.flagged_match` or `no_data.alternate_match`, may lack `resolved` or its definition's `identity` and raises nothing, which `answer-nested-specs-unchecked` pins at all three. A strict member that holds a JSON type its schema does not admit raises `ContractError` `contract_violation` at its pointer, as a missing one does, which `answer-candidate-specs-not-a-list` pins with `candidates.specs` as an object. An integer is a JSON number with no fractional part that is not a boolean, as JSON Schema's `integer` reads it, since a JavaScript client cannot tell `1.0` from `1`: a strict member such as a manifest definition's `schema_version`, and a ref's `definition.schema_version`, admit `1.0` and refuse `1.5` and `true`. Each item of a strict list, `parts`, `candidates.specs` or `artifacts`, must be an object, and an item of another type raises `contract_violation` at its index pointer, such as `/parts/0/candidates/specs/0`. A member a client reads that no strict pointer names, and that holds a JSON type other than the list, object or string its rule reads, reads as absent, and so does every member of a holder that is not an object; an item of another type in such a list reads as absent too. So `no_data.options` as an object or null offers no option, `mentions` as a string mentions nothing, and a `diagnostics` that is not an object holds no `timed_out` and no warnings. `answer-operations-not-a-list` pins a definite part's `operations` as an object. An item of an `assumptions` or `reading_assumptions` list reads only when it is an object whose `kind`, `binding_ref` and `text` are strings, and an item of an `operations` list only when it is an object whose `verb` and `phrase` are strings; any other item reads as absent. A read item holds its schema's members alone: an assumption's `refs` as its string items when it is a list, and an operation's `over` when it is a string or null and its `target_year` when it is null or a number other than a boolean with no fractional part, read as that integer, so `2020.0` reads and echoes as `2020` and `2020.5` reads as absent. An optional member of another type reads as absent, and a client reads no member the schema does not name. An `enums` entry keeps every listed value, the values a client branches on: the answer kinds, the part statuses and the operation statuses. A client still accepts a value no entry lists. +`strict-fields.json` lists the members a client cannot act without. Each pointer names one property of one component schema, `/components/schemas//properties/`. A `required` entry stays in its schema's `required` list and admits no null, so a client raises `ContractError` when one is missing or null. Every entry is a member some client rule acts on, and a client checks a body only along them: from the body's root it goes down through strict members alone, so a member no client reads goes unchecked, however deep. A `Spec` where no rule reads it, in `resolved.equivalent_readings`, `no_data.flagged_match` or `no_data.alternate_match`, may lack `resolved` or its definition's `identity` and raises nothing, which `answer-nested-specs-unchecked` pins at all three. A strict member that holds a JSON type its schema does not admit raises `ContractError` `contract_violation` at its pointer, as a missing one does, which `answer-candidate-specs-not-a-list` pins with `candidates.specs` as an object. An integer is a JSON number with no fractional part that is not a boolean, as JSON Schema's `integer` reads it, since a JavaScript client cannot tell `1.0` from `1`: a strict member such as a manifest definition's `schema_version`, and a ref's `definition.schema_version`, admit `1.0` and refuse `1.5` and `true`. Each item of a strict list, `parts`, `candidates.specs` or `artifacts`, must be an object, and an item of another type raises `contract_violation` at its index pointer, such as `/parts/0/candidates/specs/0`. A member a client reads that no strict pointer names, and that holds a JSON type other than the list, object or string its rule reads, reads as absent, and so does every member of a holder that is not an object; an item of another type in such a list reads as absent too. So `no_data.options` as an object or null offers no option, `mentions` as a string mentions nothing, and a `diagnostics` that is not an object holds no warnings. `answer-operations-not-a-list` pins a definite part's `operations` as an object. An item of an `assumptions` or `reading_assumptions` list reads only when it is an object whose `kind`, `binding_ref` and `text` are strings, and an item of an `operations` list only when it is an object whose `verb` and `phrase` are strings; any other item reads as absent. A read item holds its schema's members alone: an assumption's `refs` as its string items when it is a list, and an operation's `over` when it is a string or null and its `target_year` when it is null or a number other than a boolean with no fractional part, read as that integer, so `2020.0` reads and echoes as `2020` and `2020.5` reads as absent. An optional member of another type reads as absent, and a client reads no member the schema does not name. An `enums` entry keeps every listed value, the values a client branches on: the answer kinds, the part statuses and the operation statuses. A client still accepts a value no entry lists. The answer's `kind` and each part's `status` are the discriminators of their unions in the served document. A search response with no `kind` raises `ContractError`, as a part with no `status` does, although the served document gives `kind` a default and leaves it out of `required`. A part `status` a client does not know raises nothing and makes the part an `unknown` gap. An answer `kind` a client does not know raises `ContractError`, because the client cannot read the answer. A Search response body that is not an object, a `resolution` answer whose `parts` is empty, at pointer `/parts`, since the served schema gives it at least one item, a part whose `status` is not a string, at pointer `/parts//status`, and a definite or candidate spec whose `definition.spec_id` differs from its `spec_id` raise `ContractError` `contract_violation`, in a Search response as in a ref; `search-body-not-an-object`, `answer-empty-parts`, `answer-part-status-not-a-string` and `answer-definition-spec-id-mismatch` pin the four. @@ -114,7 +114,7 @@ A client maps a refusal whose code has no row, or that carries no code, on its H A 408 is a timeout the caller retries, and a 410 names a resource that is gone, so the caller stops as it does on a 404. A client reads a refusal's body as a problem document when its content type is `application/problem+json` or `application/json`; a body of any other type reads as no body, so the refusal carries no code. A no_data reason with no row becomes a `no_data` gap. -An answer's gaps follow from its parts. A candidates part is a `choice` gap, its options the candidates. A no_data part is a gap of its reason's `gap_kind`, its options the no_data `options`, and of kind `timed_out` instead when the answer's `diagnostics.timed_out` is true. An unsupported part is an `unsupported` gap. A part whose status a client does not know is an `unknown` gap. +An answer's gaps follow from its parts. A candidates part is a `choice` gap, its options the candidates. A no_data part is a gap of its reason's `gap_kind`, its options the no_data `options`, whatever `diagnostics.timed_out` says. Search marks a part it left unresolved at its time limit with the reason `timed_out`. An unsupported part is an `unsupported` gap. A part whose status a client does not know is an `unknown` gap. ## Client codes diff --git a/python/client-contract/src/dfo_client_contract/scenarios/answer-timed-out-keeps-reasons.json b/python/client-contract/src/dfo_client_contract/scenarios/answer-timed-out-keeps-reasons.json new file mode 100644 index 0000000..c6897d5 --- /dev/null +++ b/python/client-contract/src/dfo_client_contract/scenarios/answer-timed-out-keeps-reasons.json @@ -0,0 +1,3659 @@ +{ + "format_version": 1, + "name": "answer-timed-out-keeps-reasons", + "description": "A no_data part's gap kind comes from its own reason, whatever diagnostics.timed_out says. A part Search resolved before its time limit keeps its reason's gap kind, and fetching the answer raises IncompleteAnswerError.", + "origin": { + "kind": "synthetic", + "derived_from": "answer-mixed-fetch-raises", + "edit": "The answer's diagnostics.timed_out becomes true, part 1 keeps its reason variable_not_resolved, and its response carries x-dfo-trace-id 55555555555555555555555555555555." + }, + "call": [ + { + "op": "search", + "question": "debt service of Kenya and the price of tea in Kenya" + }, + { + "op": "fetch" + } + ], + "exchanges": [ + { + "request": { + "method": "POST", + "path": "/search/resolve", + "json": { + "schema_version": 1, + "input": { + "kind": "raw_text", + "query": "debt service of Kenya and the price of tea in Kenya" + } + } + }, + "response": { + "status": 200, + "headers": { + "content-type": "application/json", + "x-dfo-trace-id": "55555555555555555555555555555555" + }, + "json": { + "schema_version": 1, + "query_echo": { + "entry_path": "raw_text", + "raw_query": "debt service of Kenya and the price of tea in Kenya", + "normalized_query": "debt service of Kenya and the price of tea in Kenya", + "extract_skipped": false + }, + "diagnostics": { + "engine_build": "99e62038ceb4b459c2ac699549076ff123c166aa", + "trace_id": "38ca03326c514418af8164d5740ec677", + "warnings": [], + "timing_ms": { + "total": 492, + "by_step": { + "coverage": 0, + "extract": 1, + "cache_lookup": 0 + } + }, + "llm_usage": { + "input_tokens": 0, + "output_tokens": 0, + "cached_tokens": 0, + "jev_input_tokens": 5250, + "jev_output_tokens": 1104 + }, + "timed_out": true + }, + "kind": "resolution", + "parts": [ + { + "variable_text": "debt service", + "warnings": [], + "timing_ms": { + "total": 486, + "by_step": { + "recall": 0, + "phase_resolve_places": 0, + "phase_check_entities": 0, + "shape_arcs": 2, + "shape_probe_coverage": 15, + "shape_probe": 105, + "shape_groups": 7, + "shape_labels": 11, + "shape_admission": 0, + "phase_derive_shapes": 0, + "shape": 219, + "phase_prune_and_rank": 0, + "bind": 112, + "phase_bind": 14, + "phase_settle": 5, + "phase_place_bindings": 12, + "phase_facet_requests": 0, + "materialise": 11, + "phase_materialise": 23, + "answer": 2, + "options": 8, + "phase_outcome": 80 + } + }, + "restatement": { + "variable_text": "debt service", + "entities": [ + { + "text": "Kenya" + } + ], + "sources": [], + "operations": [] + }, + "entity_refs": [ + { + "text": "Kenya", + "refs": [ + "country/KEN" + ] + } + ], + "operations": [], + "departures": [], + "assumptions": [ + { + "kind": "open_role", + "binding_ref": "origin:", + "text": "every creditor", + "refs": [] + } + ], + "status": "definite", + "spec": { + "spec_id": "spec_78d04fefc3b3f1ef71acd622957e4995", + "definition": { + "schema_version": 1, + "spec_id": "spec_78d04fefc3b3f1ef71acd622957e4995", + "identity": { + "identity_version": "spec1", + "shape": { + "kind": "statvar_schema", + "population_type": "FinancialFlow", + "measured_property": "amount", + "stat_type": "measuredValue", + "measurement_qualifier": "Nominal" + }, + "slots": [ + { + "property": "financialFlowType", + "binding": { + "state": "value", + "ref": "ServicingPayment" + } + }, + { + "property": "financingResource", + "binding": { + "state": "unbound" + } + }, + { + "property": "guaranteeStatus", + "binding": { + "state": "unbound" + } + }, + { + "property": "idsConcessionality", + "binding": { + "state": "unbound" + } + }, + { + "property": "idsCreditorType", + "binding": { + "state": "unbound" + } + }, + { + "property": "idsDebtorType", + "binding": { + "state": "unbound" + } + }, + { + "property": "includesIMFCredit", + "binding": { + "state": "unbound" + } + }, + { + "property": "maturityBand", + "binding": { + "state": "unbound" + } + } + ], + "entities": { + "destination": [ + "country/KEN" + ] + }, + "requested_window": { + "kind": "all" + } + } + }, + "resolved": { + "shape": { + "shape_id": "financialflow_amount_measuredvalue_nominal_by_role", + "label": "Debt service on external debt", + "population_type": { + "ref": "FinancialFlow", + "label": "financial flow" + }, + "measured_property": { + "ref": "amount", + "label": "amount" + }, + "stat_type": { + "ref": "measuredValue", + "label": "measuredValue" + }, + "measurement_qualifier": { + "ref": "Nominal", + "label": "Nominal" + }, + "measurement_denominator_unresolved": false, + "slot_keys": [ + { + "axis": "how", + "property": { + "ref": "financialFlowType", + "label": "financial flow type" + }, + "label": "financial flow type" + }, + { + "axis": "how", + "property": { + "ref": "financingResource", + "label": "financing resource" + }, + "label": "financing resource" + }, + { + "axis": "how", + "property": { + "ref": "guaranteeStatus", + "label": "guarantee status" + }, + "label": "guarantee status" + }, + { + "axis": "how", + "property": { + "ref": "idsDebtorType", + "label": "IDS debtor type" + }, + "label": "IDS debtor type" + }, + { + "axis": "how", + "property": { + "ref": "idsCreditorType", + "label": "IDS creditor type" + }, + "label": "IDS creditor type" + }, + { + "axis": "how", + "property": { + "ref": "idsConcessionality", + "label": "IDS concessionality" + }, + "label": "IDS concessionality" + }, + { + "axis": "how", + "property": { + "ref": "includesIMFCredit", + "label": "includes IMF credit" + }, + "label": "includes IMF credit" + }, + { + "axis": "how", + "property": { + "ref": "maturityBand", + "label": "maturity band" + }, + "label": "maturity band" + }, + { + "axis": "where", + "label": "place" + }, + { + "axis": "when", + "label": "time window" + }, + { + "axis": "source", + "label": "data source" + } + ], + "member_count": 65, + "member_digest": "b66541c53125ee32113e51b4f52a3a428043b5506e15f96277f37b89547fc1c4", + "refine_supported": true, + "data_commons": [ + { + "id": "dfo", + "label": "Development Finance Observatory" + } + ] + }, + "slots": [ + { + "key": { + "axis": "how", + "property": { + "ref": "financialFlowType", + "label": "financial flow type" + }, + "label": "financial flow type" + }, + "binding": { + "kind": "value", + "value": { + "ref": { + "ref": "ServicingPayment", + "label": "servicing payment" + }, + "value_kind": "enum_value" + } + }, + "summable": false + }, + { + "key": { + "axis": "how", + "property": { + "ref": "financingResource", + "label": "financing resource" + }, + "label": "financing resource" + }, + "binding": { + "kind": "unbound" + }, + "summable": false + }, + { + "key": { + "axis": "how", + "property": { + "ref": "guaranteeStatus", + "label": "guarantee status" + }, + "label": "guarantee status" + }, + "binding": { + "kind": "unbound" + }, + "summable": false + }, + { + "key": { + "axis": "how", + "property": { + "ref": "idsDebtorType", + "label": "IDS debtor type" + }, + "label": "IDS debtor type" + }, + "binding": { + "kind": "unbound" + }, + "summable": false + }, + { + "key": { + "axis": "how", + "property": { + "ref": "idsCreditorType", + "label": "IDS creditor type" + }, + "label": "IDS creditor type" + }, + "binding": { + "kind": "unbound" + }, + "summable": false + }, + { + "key": { + "axis": "how", + "property": { + "ref": "idsConcessionality", + "label": "IDS concessionality" + }, + "label": "IDS concessionality" + }, + "binding": { + "kind": "unbound" + }, + "summable": false + }, + { + "key": { + "axis": "how", + "property": { + "ref": "includesIMFCredit", + "label": "includes IMF credit" + }, + "label": "includes IMF credit" + }, + "binding": { + "kind": "unbound" + }, + "summable": false + }, + { + "key": { + "axis": "how", + "property": { + "ref": "maturityBand", + "label": "maturity band" + }, + "label": "maturity band" + }, + "binding": { + "kind": "unbound" + }, + "summable": false + }, + { + "key": { + "axis": "where", + "label": "place" + }, + "binding": { + "kind": "value", + "value": { + "ref": { + "ref": "country/KEN", + "label": "Kenya" + }, + "value_kind": "entity" + } + }, + "summable": false + }, + { + "key": { + "axis": "when", + "label": "time window" + }, + "binding": { + "kind": "unbound" + }, + "summable": false + }, + { + "key": { + "axis": "source", + "label": "data source" + }, + "binding": { + "kind": "unbound" + }, + "summable": false + } + ], + "inapplicable_slots": [ + "financingResource", + "guaranteeStatus", + "idsDebtorType", + "idsCreditorType", + "idsConcessionality", + "includesIMFCredit", + "maturityBand" + ], + "unresolved_slots": [], + "stat_vars": { + "member_count": 1, + "member_count_exact": false, + "served_count": 1, + "data_date_range": { + "start": "1970", + "end": "2024" + }, + "source_codes": [ + { + "provenance": "provenance/WorldBankInternationalDebtStatistics", + "code": "DT.TDS.DECT.CD", + "exact": true + } + ] + }, + "entities": [ + { + "ref": "country/KEN", + "label": "Kenya", + "entity_type": "Country", + "role": "destination" + } + ], + "coverage": { + "kind": "exact", + "has_data": true, + "observation_count": 55, + "evidence": "facet_metadata", + "dimensions": [ + { + "label": "sources", + "count": 1 + }, + { + "label": "observations", + "count": 55 + }, + { + "label": "variables", + "count": 1 + } + ] + }, + "headline": { + "year": 2024, + "year_named": false, + "observations": 1, + "series": 1, + "role_counts": { + "financingRecipient": 1 + }, + "recent_years": [ + { + "year": 2024, + "observations": 1, + "series": 1, + "role_counts": { + "financingRecipient": 1 + } + }, + { + "year": 2023, + "observations": 1, + "series": 1, + "role_counts": { + "financingRecipient": 1 + } + }, + { + "year": 2022, + "observations": 1, + "series": 1, + "role_counts": { + "financingRecipient": 1 + } + } + ] + }, + "download": { + "rows": 55, + "rows_exact": true, + "series": 1, + "window": { + "start_year": 1970, + "end_year": 2024 + }, + "licence": { + "label": "Creative Commons Attribution License" + } + }, + "coverage_stamp": "e6e717ab8c77e84d010915893ede5809e9ecec67929a07cfafbcc2210edfa1eb", + "facets": { + "provenance": { + "basis": "fallback", + "ref": "provenance/WorldBankInternationalDebtStatistics", + "label": "World Bank International Debt Statistics (IDS)", + "source": { + "ref": "dc/s/WorldBank", + "label": "World Bank" + }, + "data_commons": { + "id": "dfo", + "label": "Development Finance Observatory" + }, + "documentation": "/api/graph/documentation/provenance/WorldBankInternationalDebtStatistics" + }, + "unit": { + "basis": "fallback", + "ref": "CurrencyUSD", + "label": "US Dollar", + "currency": "USD" + } + }, + "slot_options": [ + { + "key": { + "axis": "how", + "property": { + "ref": "financialFlowType", + "label": "financial flow type" + }, + "label": "financial flow type" + }, + "options": [ + { + "value": { + "ref": { + "ref": "ServicingPayment", + "label": "servicing payment" + }, + "value_kind": "enum_value" + } + } + ], + "truncated": true, + "source": "observed" + }, + { + "key": { + "axis": "how", + "property": { + "ref": "guaranteeStatus", + "label": "guarantee status" + }, + "label": "guarantee status" + }, + "options": [ + { + "value": { + "ref": { + "ref": "PublicOrPubliclyGuaranteed", + "label": "public or publicly guaranteed" + }, + "value_kind": "enum_value" + }, + "served_rows": 63, + "served_rows_exact": false + }, + { + "value": { + "ref": { + "ref": "NotPubliclyGuaranteed", + "label": "not publicly guaranteed" + }, + "value_kind": "enum_value" + }, + "served_rows": 56, + "served_rows_exact": false + } + ], + "truncated": true, + "source": "observed" + }, + { + "key": { + "axis": "how", + "property": { + "ref": "idsDebtorType", + "label": "IDS debtor type" + }, + "label": "IDS debtor type" + }, + "options": [ + { + "value": { + "ref": { + "ref": "IDSDebtorTypePS", + "label": "public sector (IDS debtor type)" + }, + "value_kind": "enum_value" + }, + "served_rows": 63, + "served_rows_exact": false + }, + { + "value": { + "ref": { + "ref": "IDSDebtorTypeGG", + "label": "general government (IDS debtor type)" + }, + "value_kind": "enum_value" + }, + "served_rows": 63, + "served_rows_exact": false + }, + { + "value": { + "ref": { + "ref": "IDSDebtorTypeOPS", + "label": "other public sector (IDS debtor type)" + }, + "value_kind": "enum_value" + }, + "served_rows": 63, + "served_rows_exact": false + }, + { + "value": { + "ref": { + "ref": "IDSDebtorTypePrivateSector", + "label": "private sector (IDS debtor type)" + }, + "value_kind": "enum_value" + }, + "served_rows": 56, + "served_rows_exact": false + } + ], + "truncated": true, + "source": "observed" + }, + { + "key": { + "axis": "how", + "property": { + "ref": "idsCreditorType", + "label": "IDS creditor type" + }, + "label": "IDS creditor type" + }, + "options": [ + { + "value": { + "ref": { + "ref": "IDSCreditorTypePROP", + "label": "other private creditors (IDS creditor type)" + }, + "value_kind": "enum_value" + }, + "served_rows": 46, + "served_rows_exact": true + }, + { + "value": { + "ref": { + "ref": "IDSCreditorTypePBND", + "label": "bondholders (IDS creditor type)" + }, + "value_kind": "enum_value" + }, + "served_rows": 31, + "served_rows_exact": false + }, + { + "value": { + "ref": { + "ref": "IDSCreditorTypePRVT", + "label": "private creditors (IDS creditor type)" + }, + "value_kind": "enum_value" + }, + "served_rows": 63, + "served_rows_exact": false + }, + { + "value": { + "ref": { + "ref": "IDSCreditorTypeOFFT", + "label": "official creditors (IDS creditor type)" + }, + "value_kind": "enum_value" + }, + "served_rows": 55, + "served_rows_exact": true + }, + { + "value": { + "ref": { + "ref": "IDSCreditorTypeMLAT", + "label": "multilateral (IDS creditor type)" + }, + "value_kind": "enum_value" + }, + "served_rows": 63, + "served_rows_exact": false + } + ], + "truncated": true, + "source": "observed" + }, + { + "key": { + "axis": "how", + "property": { + "ref": "idsConcessionality", + "label": "IDS concessionality" + }, + "label": "IDS concessionality" + }, + "options": [ + { + "value": { + "ref": { + "ref": "IDSConcessional", + "label": "concessional (IDS concessionality)" + }, + "value_kind": "enum_value" + }, + "served_rows": 118, + "served_rows_exact": false + } + ], + "truncated": true, + "source": "observed" + }, + { + "key": { + "axis": "how", + "property": { + "ref": "includesIMFCredit", + "label": "includes IMF credit" + }, + "label": "includes IMF credit" + }, + "options": [ + { + "value": { + "ref": { + "ref": "WithoutIMFCredit", + "label": "without IMF credit" + }, + "value_kind": "enum_value" + }, + "served_rows": 63, + "served_rows_exact": false + } + ], + "truncated": true, + "source": "observed" + }, + { + "key": { + "axis": "how", + "property": { + "ref": "maturityBand", + "label": "maturity band" + }, + "label": "maturity band" + }, + "options": [ + { + "value": { + "ref": { + "ref": "LongTermMaturity", + "label": "long-term maturity" + }, + "value_kind": "enum_value" + }, + "served_rows": 63, + "served_rows_exact": false + } + ], + "truncated": true, + "source": "observed" + } + ], + "entity_options": [], + "facet_options": [ + { + "axis": "provenance", + "options": [ + { + "value": { + "ref": { + "ref": "provenance/WorldBankInternationalDebtStatistics", + "label": "World Bank International Debt Statistics (IDS)" + }, + "value_kind": "source" + } + } + ], + "truncated": false, + "source": "observed" + }, + { + "axis": "measurementMethod", + "options": [ + { + "value": { + "value_kind": "enum_value" + } + }, + { + "value": { + "ref": { + "ref": "Estimated", + "label": "estimated" + }, + "value_kind": "enum_value" + } + } + ], + "truncated": false, + "source": "observed" + }, + { + "axis": "unit", + "options": [ + { + "value": { + "ref": { + "ref": "CurrencyUSD", + "label": "US Dollar" + }, + "value_kind": "enum_value" + } + } + ], + "truncated": false, + "source": "observed" + } + ], + "rendered_sentence": "Kenya's external debt service owed to all creditors, 1970 to 2024. Current US dollars · World Bank International Debt Statistics (IDS).", + "sentence_plan": [ + { + "kind": "slot", + "text": "Kenya", + "axis": "where", + "segment": "heading", + "role": "destination", + "binding_ref": "where:", + "entity_refs": [ + "country/KEN" + ], + "states": [ + "where:" + ] + }, + { + "kind": "text", + "text": "'s", + "segment": "heading", + "entity_refs": [], + "states": [] + }, + { + "kind": "text", + "text": " ", + "segment": "heading", + "entity_refs": [], + "states": [] + }, + { + "kind": "slot", + "text": "external debt service", + "axis": "what", + "segment": "heading", + "entity_refs": [], + "states": [ + "how:financialFlowType", + "axis:populationType", + "axis:measuredProperty", + "axis:statType" + ] + }, + { + "kind": "text", + "text": " owed to ", + "segment": "heading", + "entity_refs": [], + "states": [] + }, + { + "kind": "slot", + "text": "all creditors", + "segment": "heading", + "role": "origin", + "binding_ref": "origin:", + "entity_refs": [], + "states": [ + "origin:" + ] + }, + { + "kind": "slot", + "text": "", + "axis": "how", + "segment": "heading", + "property_dcid": "financingResource", + "binding_ref": "how:financingResource", + "entity_refs": [], + "states": [] + }, + { + "kind": "slot", + "text": "", + "axis": "how", + "segment": "heading", + "property_dcid": "guaranteeStatus", + "binding_ref": "how:guaranteeStatus", + "entity_refs": [], + "states": [] + }, + { + "kind": "slot", + "text": "", + "axis": "how", + "segment": "heading", + "property_dcid": "idsConcessionality", + "binding_ref": "how:idsConcessionality", + "entity_refs": [], + "states": [] + }, + { + "kind": "slot", + "text": "", + "axis": "how", + "segment": "heading", + "property_dcid": "idsCreditorType", + "binding_ref": "how:idsCreditorType", + "entity_refs": [], + "states": [] + }, + { + "kind": "slot", + "text": "", + "axis": "how", + "segment": "heading", + "property_dcid": "idsDebtorType", + "binding_ref": "how:idsDebtorType", + "entity_refs": [], + "states": [] + }, + { + "kind": "slot", + "text": "", + "axis": "how", + "segment": "heading", + "property_dcid": "includesIMFCredit", + "binding_ref": "how:includesIMFCredit", + "entity_refs": [], + "states": [] + }, + { + "kind": "slot", + "text": "", + "axis": "how", + "segment": "heading", + "property_dcid": "maturityBand", + "binding_ref": "how:maturityBand", + "entity_refs": [], + "states": [] + }, + { + "kind": "slot", + "text": "", + "axis": "how", + "segment": "heading", + "property_dcid": "financialFlowType", + "binding_ref": "how:financialFlowType", + "entity_refs": [], + "states": [] + }, + { + "kind": "text", + "text": ", ", + "segment": "heading", + "entity_refs": [], + "states": [] + }, + { + "kind": "slot", + "text": "1970 to 2024", + "axis": "when", + "segment": "heading", + "binding_ref": "when:", + "entity_refs": [], + "states": [ + "when:" + ] + }, + { + "kind": "text", + "text": ".", + "segment": "heading", + "entity_refs": [], + "states": [] + }, + { + "kind": "text", + "text": " ", + "segment": "byline", + "entity_refs": [], + "states": [] + }, + { + "kind": "slot", + "text": "Current US dollars", + "axis": "unit", + "segment": "byline", + "binding_ref": "unit:", + "entity_refs": [], + "states": [ + "unit:", + "axis:measurementQualifier" + ] + }, + { + "kind": "text", + "text": " · ", + "segment": "byline", + "entity_refs": [], + "states": [] + }, + { + "kind": "slot", + "text": "World Bank International Debt Statistics (IDS)", + "axis": "source", + "segment": "byline", + "entity_refs": [], + "states": [ + "source:" + ] + }, + { + "kind": "text", + "text": ".", + "segment": "byline", + "entity_refs": [], + "states": [] + } + ], + "trace": { + "resolved_entities": [ + { + "ref": "country/KEN", + "label": "Kenya" + } + ], + "resolved_sources": [ + { + "ref": "provenance/WorldBankInternationalDebtStatistics", + "label": "World Bank International Debt Statistics (IDS)", + "source": { + "ref": "dc/s/WorldBank", + "label": "World Bank" + }, + "data_commons": { + "id": "dfo", + "label": "Development Finance Observatory" + }, + "documentation": "/api/graph/documentation/provenance/WorldBankInternationalDebtStatistics" + } + ], + "slot_filters": [ + { + "key": { + "axis": "how", + "property": { + "ref": "financialFlowType", + "label": "financial flow type" + }, + "label": "financial flow type" + }, + "binding_kind": "value", + "refs": [ + { + "ref": "ServicingPayment", + "label": "servicing payment" + } + ], + "provenance": "binder" + }, + { + "key": { + "axis": "how", + "property": { + "ref": "financingResource", + "label": "financing resource" + }, + "label": "financing resource" + }, + "binding_kind": "unbound", + "refs": [], + "provenance": "binder" + }, + { + "key": { + "axis": "how", + "property": { + "ref": "guaranteeStatus", + "label": "guarantee status" + }, + "label": "guarantee status" + }, + "binding_kind": "unbound", + "refs": [], + "provenance": "binder" + }, + { + "key": { + "axis": "how", + "property": { + "ref": "idsDebtorType", + "label": "IDS debtor type" + }, + "label": "IDS debtor type" + }, + "binding_kind": "unbound", + "refs": [], + "provenance": "binder" + }, + { + "key": { + "axis": "how", + "property": { + "ref": "idsCreditorType", + "label": "IDS creditor type" + }, + "label": "IDS creditor type" + }, + "binding_kind": "unbound", + "refs": [], + "provenance": "binder" + }, + { + "key": { + "axis": "how", + "property": { + "ref": "idsConcessionality", + "label": "IDS concessionality" + }, + "label": "IDS concessionality" + }, + "binding_kind": "unbound", + "refs": [], + "provenance": "binder" + }, + { + "key": { + "axis": "how", + "property": { + "ref": "includesIMFCredit", + "label": "includes IMF credit" + }, + "label": "includes IMF credit" + }, + "binding_kind": "unbound", + "refs": [], + "provenance": "binder" + }, + { + "key": { + "axis": "how", + "property": { + "ref": "maturityBand", + "label": "maturity band" + }, + "label": "maturity band" + }, + "binding_kind": "unbound", + "refs": [], + "provenance": "binder" + }, + { + "key": { + "axis": "where", + "label": "place" + }, + "binding_kind": "value", + "refs": [ + { + "ref": "country/KEN", + "label": "Kenya" + } + ], + "provenance": "binder" + }, + { + "key": { + "axis": "when", + "label": "time window" + }, + "binding_kind": "unbound", + "refs": [], + "provenance": "binder" + }, + { + "key": { + "axis": "source", + "label": "data source" + }, + "binding_kind": "unbound", + "refs": [], + "provenance": "binder" + } + ], + "query_qualifiers": [], + "capability_version": "cap_88defe77cc79e0c0", + "default_reading": { + "applied": true, + "removed_stat_vars": [] + } + }, + "confidence": "high", + "available_at_place": true, + "measure_label_axes": [], + "measure_axis_values": [], + "unit_conversion": { + "currencies": [ + { + "code": "AED", + "noun": "UAE dirhams", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "AFN", + "noun": "Afghan afghanis", + "current_years": { + "start_year": 2002, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 2002, + "base_year_max": 2025 + }, + { + "code": "ALL", + "noun": "Albanian leks", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "AMD", + "noun": "Armenian drams", + "current_years": { + "start_year": 1992, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1992, + "base_year_max": 2025 + }, + { + "code": "AOA", + "noun": "Angolan kwanzas", + "current_years": { + "start_year": 1991, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1991, + "base_year_max": 2025 + }, + { + "code": "ARS", + "noun": "Argentine pesos", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "AUD", + "noun": "Australian dollars", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "AWG", + "noun": "Aruban florins", + "current_years": { + "start_year": 1995, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1995, + "base_year_max": 2025 + }, + { + "code": "AZN", + "noun": "Azerbaijani manats", + "current_years": { + "start_year": 1992, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1992, + "base_year_max": 2025 + }, + { + "code": "BAM", + "noun": "Bosnia-Herzegovina convertible marks", + "current_years": { + "start_year": 1996, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1996, + "base_year_max": 2025 + }, + { + "code": "BBD", + "noun": "Barbadian dollars", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "BDT", + "noun": "Bangladeshi taka", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "BHD", + "noun": "Bahraini dinars", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "BIF", + "noun": "Burundian francs", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "BND", + "noun": "Brunei dollars", + "current_years": { + "start_year": 1985, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1985, + "base_year_max": 2025 + }, + { + "code": "BOB", + "noun": "Bolivian bolivianos", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "BRL", + "noun": "Brazilian reais", + "current_years": { + "start_year": 1986, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1986, + "base_year_max": 2025 + }, + { + "code": "BSD", + "noun": "Bahamian dollars", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "BTN", + "noun": "Bhutanese ngultrums", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "BWP", + "noun": "Botswana pula", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "BYN", + "noun": "Belarusian rubles", + "current_years": { + "start_year": 1992, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1992, + "base_year_max": 2025 + }, + { + "code": "BZD", + "noun": "Belize dollars", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "CAD", + "noun": "Canadian dollars", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "CDF", + "noun": "Congolese francs", + "current_years": { + "start_year": 1991, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1991, + "base_year_max": 2025 + }, + { + "code": "CHF", + "noun": "Swiss francs", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "CLP", + "noun": "Chilean pesos", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "CNY", + "noun": "Chinese yuan", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "COP", + "noun": "Colombian pesos", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "CRC", + "noun": "Costa Rican colones", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "CVE", + "noun": "Cape Verdean escudos", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "CZK", + "noun": "Czech korunas", + "current_years": { + "start_year": 1995, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1995, + "base_year_max": 2025 + }, + { + "code": "DJF", + "noun": "Djiboutian francs", + "current_years": { + "start_year": 1990, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1990, + "base_year_max": 2025 + }, + { + "code": "DKK", + "noun": "Danish kroner", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "DOP", + "noun": "Dominican pesos", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "DZD", + "noun": "Algerian dinars", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "EGP", + "noun": "Egyptian pounds", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "ERN", + "noun": "Eritrean nakfas", + "current_years": { + "start_year": 1992, + "end_year": 2019 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1992, + "base_year_max": 2019 + }, + { + "code": "ETB", + "noun": "Ethiopian birr", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "EUR", + "noun": "euros", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "FJD", + "noun": "Fijian dollars", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "GBP", + "noun": "British pounds", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "GEL", + "noun": "Georgian lari", + "current_years": { + "start_year": 1994, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1994, + "base_year_max": 2025 + }, + { + "code": "GHS", + "noun": "Ghanaian cedis", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "GMD", + "noun": "Gambian dalasis", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "GNF", + "noun": "Guinean francs", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "GTQ", + "noun": "Guatemalan quetzales", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "GYD", + "noun": "Guyanese dollars", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "HKD", + "noun": "Hong Kong dollars", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "HNL", + "noun": "Honduran lempiras", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "HTG", + "noun": "Haitian gourdes", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "HUF", + "noun": "Hungarian forints", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "IDR", + "noun": "Indonesian rupiahs", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "ILS", + "noun": "Israeli new shekels", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "INR", + "noun": "Indian rupees", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "IQD", + "noun": "Iraqi dinars", + "current_years": { + "start_year": 2000, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 2000, + "base_year_max": 2025 + }, + { + "code": "IRR", + "noun": "Iranian rials", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "ISK", + "noun": "Icelandic krónur", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "JMD", + "noun": "Jamaican dollars", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "JOD", + "noun": "Jordanian dinars", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "JPY", + "noun": "Japanese yen", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "KES", + "noun": "Kenyan shillings", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "KGS", + "noun": "Kyrgyz soms", + "current_years": { + "start_year": 1992, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1992, + "base_year_max": 2025 + }, + { + "code": "KHR", + "noun": "Cambodian riels", + "current_years": { + "start_year": 1986, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1986, + "base_year_max": 2025 + }, + { + "code": "KMF", + "noun": "Comorian francs", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "KRW", + "noun": "South Korean won", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "KWD", + "noun": "Kuwaiti dinars", + "current_years": { + "start_year": 1981, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1981, + "base_year_max": 2025 + }, + { + "code": "KZT", + "noun": "Kazakh tenge", + "current_years": { + "start_year": 1992, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1992, + "base_year_max": 2025 + }, + { + "code": "LAK", + "noun": "Lao kip", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "LBP", + "noun": "Lebanese pounds", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "LKR", + "noun": "Sri Lankan rupees", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "LSL", + "noun": "Lesotho maloti", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "LYD", + "noun": "Libyan dinars", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "MAD", + "noun": "Moroccan dirhams", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "MDL", + "noun": "Moldovan lei", + "current_years": { + "start_year": 1992, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1992, + "base_year_max": 2025 + }, + { + "code": "MGA", + "noun": "Malagasy ariary", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "MKD", + "noun": "Macedonian denars", + "current_years": { + "start_year": 1992, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1992, + "base_year_max": 2025 + }, + { + "code": "MMK", + "noun": "Myanmar kyats", + "current_years": { + "start_year": 1998, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1998, + "base_year_max": 2025 + }, + { + "code": "MNT", + "noun": "Mongolian tugriks", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "MOP", + "noun": "Macanese patacas", + "current_years": { + "start_year": 2001, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 2001, + "base_year_max": 2025 + }, + { + "code": "MRU", + "noun": "Mauritanian ouguiyas", + "current_years": { + "start_year": 1990, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1990, + "base_year_max": 2025 + }, + { + "code": "MUR", + "noun": "Mauritian rupees", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "MVR", + "noun": "Maldivian rufiyaa", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "MWK", + "noun": "Malawian kwachas", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "MXN", + "noun": "Mexican pesos", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "MYR", + "noun": "Malaysian ringgits", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "MZN", + "noun": "Mozambican meticais", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "NAD", + "noun": "Namibian dollars", + "current_years": { + "start_year": 1989, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1989, + "base_year_max": 2025 + }, + { + "code": "NGN", + "noun": "Nigerian naira", + "current_years": { + "start_year": 1990, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1990, + "base_year_max": 2025 + }, + { + "code": "NIO", + "noun": "Nicaraguan córdobas", + "current_years": { + "start_year": 1987, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1987, + "base_year_max": 2025 + }, + { + "code": "NOK", + "noun": "Norwegian kroner", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "NPR", + "noun": "Nepalese rupees", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "NZD", + "noun": "New Zealand dollars", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "OMR", + "noun": "Omani rials", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "PEN", + "noun": "Peruvian soles", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "PGK", + "noun": "Papua New Guinean kina", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "PHP", + "noun": "Philippine pesos", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "PKR", + "noun": "Pakistani rupees", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "PLN", + "noun": "Polish zlotys", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "PYG", + "noun": "Paraguayan guaranís", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "QAR", + "noun": "Qatari riyals", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "RON", + "noun": "Romanian lei", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "RSD", + "noun": "Serbian dinars", + "current_years": { + "start_year": 1997, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1997, + "base_year_max": 2025 + }, + { + "code": "RUB", + "noun": "Russian rubles", + "current_years": { + "start_year": 1992, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1992, + "base_year_max": 2025 + }, + { + "code": "RWF", + "noun": "Rwandan francs", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "SAR", + "noun": "Saudi riyals", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "SBD", + "noun": "Solomon Islands dollars", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "SCR", + "noun": "Seychellois rupees", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "SDG", + "noun": "Sudanese pounds", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "SEK", + "noun": "Swedish kronor", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "SGD", + "noun": "Singapore dollars", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "SLE", + "noun": "Sierra Leonean leones", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "SRD", + "noun": "Surinamese dollars", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "SSP", + "noun": "South Sudanese pounds", + "current_years": { + "start_year": 2011, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 2011, + "base_year_max": 2025 + }, + { + "code": "STN", + "noun": "São Tomé and Príncipe dobras", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "SYP", + "noun": "Syrian pounds", + "current_years": { + "start_year": 1980, + "end_year": 2010 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2010 + }, + { + "code": "SZL", + "noun": "Swazi emalangeni", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "THB", + "noun": "Thai baht", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "TJS", + "noun": "Tajikistani somonis", + "current_years": { + "start_year": 1992, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1992, + "base_year_max": 2025 + }, + { + "code": "TMT", + "noun": "Turkmenistani manat", + "current_years": { + "start_year": 1992, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1992, + "base_year_max": 2025 + }, + { + "code": "TND", + "noun": "Tunisian dinars", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "TOP", + "noun": "Tongan paʻanga", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "TRY", + "noun": "Turkish lira", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "TTD", + "noun": "Trinidad and Tobago dollars", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "TWD", + "noun": "New Taiwan dollars", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "TZS", + "noun": "Tanzanian shillings", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "UAH", + "noun": "Ukrainian hryvnias", + "current_years": { + "start_year": 1991, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1991, + "base_year_max": 2025 + }, + { + "code": "UGX", + "noun": "Ugandan shillings", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "USD", + "noun": "US dollars", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "UYU", + "noun": "Uruguayan pesos", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "UZS", + "noun": "Uzbekistani soms", + "current_years": { + "start_year": 1992, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1992, + "base_year_max": 2025 + }, + { + "code": "VES", + "noun": "Venezuelan bolívares", + "current_years": { + "start_year": 2017, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 2017, + "base_year_max": 2025 + }, + { + "code": "VND", + "noun": "Vietnamese dong", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "VUV", + "noun": "Vanuatu vatu", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "WST", + "noun": "Samoan tala", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "XAF", + "noun": "Central African CFA francs", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "XCD", + "noun": "East Caribbean dollars", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "XOF", + "noun": "West African CFA francs", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "YER", + "noun": "Yemeni rials", + "current_years": { + "start_year": 2014, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 2014, + "base_year_max": 2025 + }, + { + "code": "ZAR", + "noun": "South African rand", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + }, + { + "code": "ZMW", + "noun": "Zambian kwachas", + "current_years": { + "start_year": 1980, + "end_year": 2024 + }, + "constant_years": { + "start_year": 1980, + "end_year": 2024 + }, + "base_year_min": 1980, + "base_year_max": 2025 + } + ], + "default_base_year": 2024, + "role_currencies": { + "destination": "KES" + } + } + } + } + }, + { + "variable_text": "price of tea", + "warnings": [], + "timing_ms": { + "total": 100, + "by_step": { + "recall": 0, + "phase_resolve_places": 0, + "phase_check_entities": 0, + "shape_arcs": 2, + "shape_probe": 91, + "shape_groups": 1, + "shape_labels": 0, + "shape_admission": 1, + "phase_derive_shapes": 0, + "shape": 100, + "phase_prune_and_rank": 0 + } + }, + "rendered_sentence": "Could not match \"price of tea\" to a supported indicator. Try a different wording or a related indicator.", + "sentence_plan": [ + { + "kind": "text", + "text": "Could not match \"price of tea\" to a supported indicator. Try a different wording or a related indicator.", + "segment": "heading", + "entity_refs": [], + "states": [] + } + ], + "entity_refs": [], + "operations": [], + "departures": [], + "assumptions": [], + "status": "no_data", + "no_data": { + "reason": "variable_not_resolved", + "category": "unresolved", + "message": "Could not match \"price of tea\" to a supported indicator. Try a different wording or a related indicator.", + "entity_terms": [], + "ambiguous_entities": [], + "source_terms": [], + "ambiguous_sources": [], + "unbound_qualifiers": [], + "options": [] + } + } + ], + "rendered_sentence": "Answered 1 of 2 parts. Kenya's external debt service owed to all creditors, 1970 to 2024. Current US dollars · World Bank International Debt Statistics (IDS). Assumed: every creditor. No match for price of tea. Try a different wording or a related indicator.", + "sentence_plan": [ + { + "kind": "text", + "text": "Answered 1 of 2 parts.", + "segment": "coverage", + "entity_refs": [], + "states": [] + }, + { + "kind": "text", + "text": " ", + "segment": "coverage", + "entity_refs": [], + "states": [] + }, + { + "kind": "slot", + "text": "Kenya", + "axis": "where", + "segment": "heading", + "role": "destination", + "binding_ref": "where:", + "entity_refs": [ + "country/KEN" + ], + "states": [ + "where:" + ], + "part_index": 0 + }, + { + "kind": "text", + "text": "'s", + "segment": "heading", + "entity_refs": [], + "states": [], + "part_index": 0 + }, + { + "kind": "text", + "text": " ", + "segment": "heading", + "entity_refs": [], + "states": [], + "part_index": 0 + }, + { + "kind": "slot", + "text": "external debt service", + "axis": "what", + "segment": "heading", + "entity_refs": [], + "states": [ + "how:financialFlowType", + "axis:populationType", + "axis:measuredProperty", + "axis:statType" + ], + "part_index": 0 + }, + { + "kind": "text", + "text": " owed to ", + "segment": "heading", + "entity_refs": [], + "states": [], + "part_index": 0 + }, + { + "kind": "slot", + "text": "all creditors", + "segment": "heading", + "role": "origin", + "binding_ref": "origin:", + "entity_refs": [], + "states": [ + "origin:" + ], + "part_index": 0 + }, + { + "kind": "slot", + "text": "", + "axis": "how", + "segment": "heading", + "property_dcid": "financingResource", + "binding_ref": "how:financingResource", + "entity_refs": [], + "states": [], + "part_index": 0 + }, + { + "kind": "slot", + "text": "", + "axis": "how", + "segment": "heading", + "property_dcid": "guaranteeStatus", + "binding_ref": "how:guaranteeStatus", + "entity_refs": [], + "states": [], + "part_index": 0 + }, + { + "kind": "slot", + "text": "", + "axis": "how", + "segment": "heading", + "property_dcid": "idsConcessionality", + "binding_ref": "how:idsConcessionality", + "entity_refs": [], + "states": [], + "part_index": 0 + }, + { + "kind": "slot", + "text": "", + "axis": "how", + "segment": "heading", + "property_dcid": "idsCreditorType", + "binding_ref": "how:idsCreditorType", + "entity_refs": [], + "states": [], + "part_index": 0 + }, + { + "kind": "slot", + "text": "", + "axis": "how", + "segment": "heading", + "property_dcid": "idsDebtorType", + "binding_ref": "how:idsDebtorType", + "entity_refs": [], + "states": [], + "part_index": 0 + }, + { + "kind": "slot", + "text": "", + "axis": "how", + "segment": "heading", + "property_dcid": "includesIMFCredit", + "binding_ref": "how:includesIMFCredit", + "entity_refs": [], + "states": [], + "part_index": 0 + }, + { + "kind": "slot", + "text": "", + "axis": "how", + "segment": "heading", + "property_dcid": "maturityBand", + "binding_ref": "how:maturityBand", + "entity_refs": [], + "states": [], + "part_index": 0 + }, + { + "kind": "slot", + "text": "", + "axis": "how", + "segment": "heading", + "property_dcid": "financialFlowType", + "binding_ref": "how:financialFlowType", + "entity_refs": [], + "states": [], + "part_index": 0 + }, + { + "kind": "text", + "text": ", ", + "segment": "heading", + "entity_refs": [], + "states": [], + "part_index": 0 + }, + { + "kind": "slot", + "text": "1970 to 2024", + "axis": "when", + "segment": "heading", + "binding_ref": "when:", + "entity_refs": [], + "states": [ + "when:" + ], + "part_index": 0 + }, + { + "kind": "text", + "text": ".", + "segment": "heading", + "entity_refs": [], + "states": [], + "part_index": 0 + }, + { + "kind": "text", + "text": " ", + "segment": "byline", + "entity_refs": [], + "states": [], + "part_index": 0 + }, + { + "kind": "slot", + "text": "Current US dollars", + "axis": "unit", + "segment": "byline", + "binding_ref": "unit:", + "entity_refs": [], + "states": [ + "unit:", + "axis:measurementQualifier" + ], + "part_index": 0 + }, + { + "kind": "text", + "text": " · ", + "segment": "byline", + "entity_refs": [], + "states": [], + "part_index": 0 + }, + { + "kind": "slot", + "text": "World Bank International Debt Statistics (IDS)", + "axis": "source", + "segment": "byline", + "entity_refs": [], + "states": [ + "source:" + ], + "part_index": 0 + }, + { + "kind": "text", + "text": ".", + "segment": "byline", + "entity_refs": [], + "states": [], + "part_index": 0 + }, + { + "kind": "text", + "text": " ", + "segment": "assumption", + "entity_refs": [], + "states": [], + "part_index": 0 + }, + { + "kind": "text", + "text": "Assumed: ", + "segment": "assumption", + "entity_refs": [], + "states": [], + "part_index": 0 + }, + { + "kind": "text", + "text": "every creditor", + "segment": "assumption", + "entity_refs": [], + "states": [], + "part_index": 0 + }, + { + "kind": "text", + "text": ".", + "segment": "assumption", + "entity_refs": [], + "states": [], + "part_index": 0 + }, + { + "kind": "text", + "text": " ", + "segment": "heading", + "entity_refs": [], + "states": [] + }, + { + "kind": "text", + "text": "No match for ", + "segment": "heading", + "entity_refs": [], + "states": [], + "part_index": 1 + }, + { + "kind": "slot", + "text": "price of tea", + "axis": "what", + "segment": "heading", + "entity_refs": [], + "states": [], + "part_index": 1 + }, + { + "kind": "text", + "text": ". Try a different wording or a related indicator.", + "segment": "heading", + "entity_refs": [], + "states": [], + "part_index": 1 + } + ], + "status": "mixed" + } + } + } + ], + "expect": [ + { + "answer": { + "kind": "resolution", + "status": "mixed", + "sentence": "Answered 1 of 2 parts. Kenya's external debt service owed to all creditors, 1970 to 2024. Current US dollars · World Bank International Debt Statistics (IDS). Assumed: every creditor. No match for price of tea. Try a different wording or a related indicator.", + "complete": false, + "specs": [ + "spec_78d04fefc3b3f1ef71acd622957e4995" + ], + "gaps": [ + { + "part": 1, + "kind": "not_understood", + "options": 0 + } + ] + } + }, + { + "error": { + "class": "IncompleteAnswerError", + "code": "answer_incomplete", + "trace_id": "55555555555555555555555555555555" + } + } + ] +} diff --git a/python/client-contract/src/dfo_client_contract/scenarios/answer-timed-out.json b/python/client-contract/src/dfo_client_contract/scenarios/answer-timed-out.json index 051cdf2..8c0eb3c 100644 --- a/python/client-contract/src/dfo_client_contract/scenarios/answer-timed-out.json +++ b/python/client-contract/src/dfo_client_contract/scenarios/answer-timed-out.json @@ -1,11 +1,11 @@ { "format_version": 1, "name": "answer-timed-out", - "description": "A no_data part of an answer whose diagnostics say Search timed out is a timed_out gap, whatever its reason, and fetching the answer raises IncompleteAnswerError, which describes the answer and carries its response's trace id.", + "description": "A no_data part with reason timed_out, the reason Search gives a part it left unresolved at its time limit, is a timed_out gap, and fetching the answer raises IncompleteAnswerError, which describes the answer and carries its response's trace id.", "origin": { "kind": "synthetic", "derived_from": "answer-mixed-fetch-raises", - "edit": "The answer's diagnostics.timed_out becomes true, and its response carries x-dfo-trace-id 55555555555555555555555555555555." + "edit": "The answer's diagnostics.timed_out becomes true, part 1's no_data reason becomes timed_out with the message that reason renders, and its response carries x-dfo-trace-id 55555555555555555555555555555555." }, "call": [ { @@ -3286,11 +3286,11 @@ "phase_prune_and_rank": 0 } }, - "rendered_sentence": "Could not match \"price of tea\" to a supported indicator. Try a different wording or a related indicator.", + "rendered_sentence": "Search reached its time limit before answering this part. Try again, or ask a narrower question.", "sentence_plan": [ { "kind": "text", - "text": "Could not match \"price of tea\" to a supported indicator. Try a different wording or a related indicator.", + "text": "Search reached its time limit before answering this part. Try again, or ask a narrower question.", "segment": "heading", "entity_refs": [], "states": [] @@ -3302,9 +3302,9 @@ "assumptions": [], "status": "no_data", "no_data": { - "reason": "variable_not_resolved", + "reason": "timed_out", "category": "unresolved", - "message": "Could not match \"price of tea\" to a supported indicator. Try a different wording or a related indicator.", + "message": "Search reached its time limit before answering this part. Try again, or ask a narrower question.", "entity_terms": [], "ambiguous_entities": [], "source_terms": [], @@ -3314,7 +3314,7 @@ } } ], - "rendered_sentence": "Answered 1 of 2 parts. Kenya's external debt service owed to all creditors, 1970 to 2024. Current US dollars · World Bank International Debt Statistics (IDS). Assumed: every creditor. No match for price of tea. Try a different wording or a related indicator.", + "rendered_sentence": "Answered 1 of 2 parts. Kenya's external debt service owed to all creditors, 1970 to 2024. Current US dollars · World Bank International Debt Statistics (IDS). Assumed: every creditor. Search reached its time limit before answering this part. Try again, or ask a narrower question.", "sentence_plan": [ { "kind": "text", @@ -3600,24 +3600,7 @@ }, { "kind": "text", - "text": "No match for ", - "segment": "heading", - "entity_refs": [], - "states": [], - "part_index": 1 - }, - { - "kind": "slot", - "text": "price of tea", - "axis": "what", - "segment": "heading", - "entity_refs": [], - "states": [], - "part_index": 1 - }, - { - "kind": "text", - "text": ". Try a different wording or a related indicator.", + "text": "Search reached its time limit before answering this part. Try again, or ask a narrower question.", "segment": "heading", "entity_refs": [], "states": [], @@ -3634,7 +3617,7 @@ "answer": { "kind": "resolution", "status": "mixed", - "sentence": "Answered 1 of 2 parts. Kenya's external debt service owed to all creditors, 1970 to 2024. Current US dollars · World Bank International Debt Statistics (IDS). Assumed: every creditor. No match for price of tea. Try a different wording or a related indicator.", + "sentence": "Answered 1 of 2 parts. Kenya's external debt service owed to all creditors, 1970 to 2024. Current US dollars · World Bank International Debt Statistics (IDS). Assumed: every creditor. Search reached its time limit before answering this part. Try again, or ask a narrower question.", "complete": false, "specs": [ "spec_78d04fefc3b3f1ef71acd622957e4995" diff --git a/tools/capture_client_contract.py b/tools/capture_client_contract.py index b94d4c2..5b160cb 100644 --- a/tools/capture_client_contract.py +++ b/tools/capture_client_contract.py @@ -2832,10 +2832,10 @@ def derive(parent: dict[str, Any], ctx: Context) -> Derived: return derive -# The x-dfo-trace-id header the Search responses of `answer-timed-out` and -# `choose-refused-shared-label` carry. The first's answer_incomplete carries -# it, and the second's choice_invalid, which describes the caller's choice, -# does not. +# The x-dfo-trace-id header the Search responses of `answer-timed-out`, +# `answer-timed-out-keeps-reasons` and `choose-refused-shared-label` carry. The +# first two's answer_incomplete carries it, and the third's choice_invalid, +# which describes the caller's choice, does not. ANSWER_TRACE_ID = "5" * 32 @@ -2843,6 +2843,49 @@ def _timed_out(body: dict[str, Any]) -> None: body["diagnostics"]["timed_out"] = True +# The no_data message dfo-apis renders for the reason `timed_out`. +TIMED_OUT_MESSAGE = ( + "Search reached its time limit before answering this part. " + "Try again, or ask a narrower question." +) + + +def _part_timed_out(body: dict[str, Any]) -> None: + """Part 1 is the part Search left unresolved at its time limit: reason + timed_out and the message that reason renders, which is also its sentence + and its whole heading in the answer's composed sentence.""" + part = body["parts"][1] + part["no_data"]["reason"] = "timed_out" + part["no_data"]["message"] = TIMED_OUT_MESSAGE + part["rendered_sentence"] = TIMED_OUT_MESSAGE + (token,) = part["sentence_plan"] + token["text"] = TIMED_OUT_MESSAGE + plan = body["sentence_plan"] + indexes = [i for i, tok in enumerate(plan) if tok.get("part_index") == 1] + first, last = indexes[0], indexes[-1] + require(indexes == list(range(first, last + 1)), "part 1's tokens are adjacent") + heading = "".join(tok["text"] for tok in plan[first : last + 1]) + require(body["rendered_sentence"].endswith(heading), "part 1's tokens end it") + plan[first : last + 1] = [ + { + "kind": "text", + "text": TIMED_OUT_MESSAGE, + "segment": "heading", + "entity_refs": [], + "states": [], + "part_index": 1, + } + ] + body["rendered_sentence"] = ( + body["rendered_sentence"][: -len(heading)] + TIMED_OUT_MESSAGE + ) + + +def _timed_out_part(body: dict[str, Any]) -> None: + _timed_out(body) + _part_timed_out(body) + + def _unsupported(body: dict[str, Any]) -> None: part = body["parts"][1] del part["no_data"] @@ -4160,11 +4203,23 @@ def _empty_narrowing(parent: dict[str, Any], ctx: Context) -> Derived: Synthetic( "answer-timed-out", "answer-mixed-fetch-raises", - "The answer's diagnostics.timed_out becomes true, and its response carries " + "The answer's diagnostics.timed_out becomes true, part 1's no_data reason becomes " + "timed_out with the message that reason renders, and its response carries " f"x-dfo-trace-id {ANSWER_TRACE_ID}.", - "A no_data part of an answer whose diagnostics say Search timed out is a timed_out gap, " - "whatever its reason, and fetching the answer raises IncompleteAnswerError, which " - "describes the answer and carries its response's trace id.", + "A no_data part with reason timed_out, the reason Search gives a part it left " + "unresolved at its time limit, is a timed_out gap, and fetching the answer raises " + "IncompleteAnswerError, which describes the answer and carries its response's trace " + "id.", + _answer_edit(_timed_out_part, {"x-dfo-trace-id": ANSWER_TRACE_ID}), + ), + Synthetic( + "answer-timed-out-keeps-reasons", + "answer-mixed-fetch-raises", + "The answer's diagnostics.timed_out becomes true, part 1 keeps its reason " + f"variable_not_resolved, and its response carries x-dfo-trace-id {ANSWER_TRACE_ID}.", + "A no_data part's gap kind comes from its own reason, whatever diagnostics.timed_out " + "says. A part Search resolved before its time limit keeps its reason's gap kind, and " + "fetching the answer raises IncompleteAnswerError.", _answer_edit(_timed_out, {"x-dfo-trace-id": ANSWER_TRACE_ID}), ), Synthetic( diff --git a/tools/tests/test_capture_client_contract.py b/tools/tests/test_capture_client_contract.py index f51bc5d..9bad07f 100644 --- a/tools/tests/test_capture_client_contract.py +++ b/tools/tests/test_capture_client_contract.py @@ -379,22 +379,24 @@ def test_a_renamed_strict_field_ends_the_call_at_the_build() -> None: @pytest.mark.parametrize( - ("timed_out", "status", "expected"), + ("timed_out", "status", "reason", "expected"), [ - (False, "no_data", "not_understood"), - (True, "no_data", "timed_out"), - (False, "unsupported", "unsupported"), - (False, "deferred", "unknown"), + (False, "no_data", "variable_not_resolved", "not_understood"), + (True, "no_data", "variable_not_resolved", "not_understood"), + (False, "no_data", "timed_out", "timed_out"), + (False, "unsupported", "variable_not_resolved", "unsupported"), + (False, "deferred", "variable_not_resolved", "unknown"), ], ) def test_a_part_without_a_reading_is_a_gap_of_its_kind( - timed_out: bool, status: str, expected: str + timed_out: bool, status: str, reason: str, expected: str ) -> None: body = copy.deepcopy( _load("answer-mixed-fetch-raises")["exchanges"][0]["response"]["json"] ) body["diagnostics"]["timed_out"] = timed_out body["parts"][1]["status"] = status + body["parts"][1]["no_data"]["reason"] = reason gaps = capture.answer_gaps( capture.answer_from_body(body), capture.gap_kinds(ACTIONS) ) diff --git a/tools/tests/test_validate_client_contract.py b/tools/tests/test_validate_client_contract.py index 89a9859..d6418af 100644 --- a/tools/tests/test_validate_client_contract.py +++ b/tools/tests/test_validate_client_contract.py @@ -715,6 +715,11 @@ def change(doc: Any) -> None: _first_gap(kind="not_understood"), "step 0 expects", ), + ( + "scenarios/answer-timed-out-keeps-reasons.json", + _first_gap(kind="timed_out"), + "step 0 expects", + ), ( "scenarios/answer-unsupported-part.json", _first_gap(kind="unknown"), @@ -1648,11 +1653,6 @@ def change(doc: Any) -> None: _mentions_a_string, "the steps never make exchanges 1 to 1", ), - ( - "scenarios/answer-timed-out.json", - _set_body(0, lambda body: body.update(diagnostics="timed out")), - "step 0 expects", - ), ( "scenarios/answer-candidates-choose-fetch.json", _set_body( @@ -1703,6 +1703,7 @@ def change(doc: Any) -> None: "strict-fetch-made-lenient", "warn-notice-raises-on-strict", "expect-wrong-timed-out-gap", + "expect-timed-out-gap-over-the-reason", "expect-wrong-unsupported-gap", "known-kind-raises-nothing", "partial-fetch-nothing-answered", @@ -1900,7 +1901,6 @@ def change(doc: Any) -> None: "no-data-options-null-counted", "bound-entities-as-objects-compared", "mentions-as-a-string-matched", - "diagnostics-not-an-object-read", "candidate-spec-not-an-object-read", ], ) @@ -1949,6 +1949,36 @@ def change(doc: Any) -> None: assert not [p for p in problems if _CODELESS in p], problems +_DEGRADED = {"code": "engine_degraded", "severity": "degraded", "message": "slow"} + + +@pytest.mark.parametrize( + ("diagnostics", "warns"), + [ + ("timed out", False), + ([_DEGRADED], False), + ({"warnings": [_DEGRADED]}, True), + ], + ids=["a-string", "a-list-of-warnings", "an-object-as-control"], +) +def test_diagnostics_that_is_not_an_object_holds_no_warnings( + package_dir: Path, diagnostics: Any, warns: bool +) -> None: + """The served schema rejects the body, which is a separate problem. The + replay reads the answer, and a fetch that expects no SearchWarning holds + when diagnostics is a string or a list, even a list of warnings, and + differs from the expectation when it is an object whose warnings read.""" + target = "scenarios/answer-definite-fetch.json" + _edit( + package_dir / target, + _set_body(0, lambda body: body.update(diagnostics=diagnostics)), + ) + replayed = [ + p for p in validator.validate() if target in p and "response body:" not in p + ] + assert bool(replayed) is warns, replayed + + def test_one_record_that_crashes_its_derivation_is_its_own_problem( package_dir: Path, monkeypatch: pytest.MonkeyPatch ) -> None: diff --git a/tools/validate_client_contract.py b/tools/validate_client_contract.py index 604ba3b..296b5a3 100644 --- a/tools/validate_client_contract.py +++ b/tools/validate_client_contract.py @@ -1530,11 +1530,8 @@ def search_warning_code(answer: Answer) -> str | None: def answer_gaps(answer: Answer, kinds: Mapping[str, str]) -> list[dict[str, Any]]: """Each part without a reading. A candidates part is a choice among its - specs, a no_data part takes its reason's gap kind, or `timed_out` when the - answer timed out, an unsupported part is an `unsupported` gap, and a part - of unknown status is an `unknown` gap.""" - diagnostics = read_object(answer.body, "diagnostics") - timed_out = diagnostics is not None and diagnostics.get("timed_out") is True + specs, a no_data part takes its reason's gap kind, an unsupported part is + an `unsupported` gap, and a part of unknown status is an `unknown` gap.""" gaps = [] for index, part in enumerate(answer.parts): match part.get("status"): @@ -1544,11 +1541,7 @@ def answer_gaps(answer: Answer, kinds: Mapping[str, str]) -> list[dict[str, Any] kind, options = "choice", len(part["candidates"]["specs"]) case "no_data": no_data = part["no_data"] - kind = ( - "timed_out" - if timed_out - else kinds.get(no_data["reason"], "no_data") - ) + kind = kinds.get(no_data["reason"], "no_data") options = len(read_objects(no_data, "options")) case "unsupported": kind, options = "unsupported", 0