diff --git a/docs/conformance-search-api-v1.md b/docs/conformance-search-api-v1.md index 01247d56..a5a4d801 100644 --- a/docs/conformance-search-api-v1.md +++ b/docs/conformance-search-api-v1.md @@ -84,7 +84,7 @@ Gap rows state what is needed to close the clause and name the open issue that t | C130 | When every contributing part is definite, their headings join into one sentence. A qualifier (where, origin, when, or source; origin is identified by role=='origin', since SentenceAxis has no 'origin' member) that every part states exactly once and alike, matched on text, connective, `binding_ref`, role, and `entity_refs`, is stated once after the joined phrases, in the first part's order and the family's wording ("USA's health ODA and Britain's education ODA to Ethiopia"), with `part_index` null; a client's edit to it applies to every part. With two or more parts, a possessive lead every part shares opens the sentence once ("Brazil's total population and nominal GDP"), and parts whose phrases differ only in their lead join the leads, each under its own `part_index`, before one shared noun with `part_index` null ("USA's and Britain's health ODA to Ethiopia"). Entity tokens keep their case; a later phrase's first word is lowered only when it is plain Title Case. The byline follows the joined heading once, unindexed; an empty byline placeholder every part shares appears once, unindexed, and otherwise under each part's own index. Any part that is not definite blocks the join outright (C131) | implemented | | | C130a | A qualifier that differs between definite parts stays inline in each part's own phrase, in that part's own plan order, under that part's own `part_index`, unless its connective opens with a comma (a period span, ", 1960 to 2024"): that would read as a list item inside the join, so the response composes one sentence per part (C131) | implemented | | | C131 | Otherwise (any contributing part not definite, a differing qualifier whose connective opens with a comma (C130a), or bylines whose text differs) the response composes one sentence per contributing part, in part order, joined by a shared, unindexed `" "` text token. A definite part's sentence is its own `spec.resolved.sentence_plan` verbatim followed by its departures and its assumptions clause (C193), every token carrying `part_index: i`. A mixed resolution opens with the coverage tokens C194 states. Nothing is parenthesised ("Answered 1 of 2 parts. Brazil's total population, 1960 to 2024. GDP has 2 readings. ..."). A part that is not definite composes as C133 states | implemented | | -| C194 | On a mixed resolution with at least one definite part, the envelope's `sentence_plan` opens with two text tokens, "Answered N of M parts." and " ", each with `segment="coverage"` and no `part_index`. No other token takes that segment. N counts the definite parts, folded ones included; candidates, no_data and unsupported parts are not answered. M is `len(parts)`. A mixed resolution with no definite part carries no opener, and a resolution whose parts share one status carries none | implemented | Pinned by `tests/render/test_compose_envelope.py` (`test_a_mixed_answer_opens_with_how_many_parts_it_answered`, whose `with-a-folded-part` row counts a folded part as answered, and `test_a_mixed_answer_with_no_definite_part_has_no_opener`) and the sentence golden (`weo-008`, `weo-044`) | +| C198 | On a mixed resolution with at least one definite part, the envelope's `sentence_plan` opens with two text tokens, "Answered N of M parts." and " ", each with `segment="coverage"` and no `part_index`. No other token takes that segment. N counts the definite parts, folded ones included; candidates, no_data and unsupported parts are not answered. M is `len(parts)`. A mixed resolution with no definite part carries no opener, and a resolution whose parts share one status carries none | implemented | Pinned by `tests/render/test_compose_envelope.py` (`test_a_mixed_answer_opens_with_how_many_parts_it_answered`, whose `with-a-folded-part` row counts a folded part as answered, and `test_a_mixed_answer_with_no_definite_part_has_no_opener`) and the sentence golden (`weo-008`, `weo-044`) | | C132 | The joined composition (C130) mints the Oxford connective(s) joining the phrases (`" and "`, or Oxford `", "` before a final `", and "` at three or more) and the sentence's own closing `"."`, minted once over the whole composition unless it already ends in `.`, `!`, or `?`. Both carry `part_index` null, as does the unindexed `" "` the per-part composition (C131) mints between sentences. Every other token in the joined sentence (each phrase's own words, a lead, a shared or inline qualifier's connective and slot token, the byline) has its text copied from a contributing part's own plan, never composer prose; its position relative to that part's own plan is not preserved (a shared counterpart moves after every phrase, "... for health and ... for education to Ethiopia") | implemented | | | C133 | Beside other parts (C131), a candidates part contributes its `variable_text` as an editable `what`-axis slot, then its own sentence with its count lowered into a predicate ("GDP has 2 readings. ..."); an unsupported part contributes the slot, then a text token stating its detail; a no_data part whose reason quotes the variable (variable_not_resolved, denominator_not_available, no_matching_slice, binding_mismatch, role_incompatible) contributes an optional prefix text token, the slot, a text token stating the outcome, then the rest of its own plan. Every other no_data part, and a part with no `variable_text`, contributes its own sentence verbatim, since it names what was checked itself. Every token carries that part's own part_index. A client derives the slot's styling and any affordance it hangs off it (a candidate menu, a retry prompt) from `parts[i].status`; no token carries a status field of its own | implemented | | | C141 | Every `Part`, whatever its status, carries `restatement`: a `ParsedPart` reproducing this part's own resolution inputs (entities, resolved time window, unit text, operations, open roles, and as `sources` the part's source mentions, each with the reference it resolved to, or for a part with none the declared dataset its identity binds as its source or its no-data run settled on, as one included mention), as if it were posted fresh to the `parsed` door. It is canonical, not verbatim: for a raw_text part, `time_text` is the phrase this door's own time grammar re-parses back into the identical window, not the user's original words, since date extraction keeps only the structured window. The guarantee is reproducing the same resolution inputs and `spec_id`, never the same wording. A part folded into an earlier part's spec still echoes its own inputs, never the representative's. It is null on a `dataset_resubmit` entry's `identity`/`candidates`/`placeholder` kinds, all of which post an identity or stub rather than a phrase; a `dataset_resubmit` `parsed` entry, genuinely re-resolved from its own posted `ParsedPart`, carries a real one like any parsed-door part. A leg bound on a directional role axis (origin/destination) echoes that axis, per entity, as the `role_text` a client can post straight back: `from` for the origin axis and `to` for the destination one, chosen from the same lexicon `role_axis_for_text` reads. A leg's own open roles echo as `open_roles` words, `origin` or `destination`, and the axes its operations open ride on the operations. It is null when the leg's direction came from its own wording alone and no door-supplied role named the axis, when a single name was recorded on both axes and so cannot be reproduced by one role_text, and when one of the leg's own inputs sits outside `ParsedPart`'s field bounds. One documented gap: an open-start/closed-end or reversed time window has no phrase in this grammar, so `time_text` comes back null rather than a wrong one. | implemented | | @@ -115,7 +115,7 @@ Gap rows state what is needed to close the clause and name the open issue that t | C50e | no_data.options lists one ready request per choice of a refusal that asks for one, in query then candidate order, each with the request words it settles (`mentions`), a label for a menu that clients must not parse, and a one-part parsed input (`data_query`) a client posts unchanged as the request's input or as a dataset_resubmit parsed entry's part. source_ambiguous: each option sets the candidate's ref on the ambiguous mention, keeping its text and polarity; a label for an excluded mention reads "Excluding" and the candidate. source_code_ambiguous: each option names the holding provenance as the part's only included source, keeping exclusions and the code, since included sources union. role_not_resolved: each option states a direction for every name the question left unstated, each untold set on an axis the elected shape declares a role on and the part does not ask open (open_roles, or an operation's over), with every such axis holding a place; options run over the sets in query order, origin first, labelled "France to Germany" or "To Kenya and Uganda". The refusal's entity_terms and every role option's mentions list each request text of every untold place set, in query order and in the request's own words, a pinned key included; the message and the option labels name each untold place once, by its first text with its further texts in parentheses (`Kenya ("KEN") to Norway`). They are offered while that gives at most six readings. None is offered in three cases: past six readings, when the single untold place was read in each direction and no reading holds data, and on the shape-level refusals. unit_not_resolved with unit_alternatives: each option replaces the ambiguous currency words of unit_text with words naming that alternative alone, its ISO code, a curated alias or its display name, or a fixed-role currency phrase for a role shape, read back through the unit binder before it is offered. An option whose part breaks the ParsedPart bounds, or whose stated directions the parsed door's role check contradicts, is left out, so options can list fewer entries than the candidates. Data for an option is not checked. Empty for every other reason. | implemented | Response field; no registry code changes. Pinned by `tests/engine/test_choice_options.py` (each reason's options posted back through the parsed door do not refuse with the same reason) and `tests/engine/test_unit_bind.py` (`unit_text_naming`). | | C51 | Within a part, when several reasons apply, the first in resolution order wins (entity, variable, role, unit, slice, denominator, observations, facet) | partial | The resolution order is documented (`NO_DATA_RESOLUTION_ORDER`), and every site that picks one reason from several elects through `elect_no_data_reason`: the entity check (`phase_intake._phase_check_entities`), the refusal of a part with no admissible interpretation (`InterpretationSet.refusal_reason`), and the materialise and candidate refusals (`codes._elect_no_data_reason`). The entity check refuses with the reason elected over the ones its unresolved names mint and names every such name: an ambiguous name with its candidates, an oversized group as too large, and the rest as unresolved, so a part naming an oversized group and an ambiguous name refuses entity_ambiguous. Pinned by `tests/models/test_no_data_category.py`, which also finds every reason the code mints and checks each has a rank, and `tests/engine/test_group_resolution.py` (`test_mixed_entity_refusals_elect_one_reason_and_name_every_entity`). No structural enforcement catches a phase check reordered against the order. Live verification of the role/unit/facet positions is blocked until role-declaring StatVars are loaded on the live graph. Issues: #11/#12 (live-graph half); none (enforcement) | | C51a | Parts sharing one five-tuple shape collapse onto one spec: every member answers with the same spec_id, and every member but the first carries a part-scoped binding_folded warning naming the fold. A parsed part posted with `separate: true`, or a raw_text variable its query compares with another (C42d), is compared with the others and does not fold. It answers its own spec in query order with no binding_folded, its response part and restatement state the mark, and the other parts of its shape fold among themselves (`same_shape.fold_key`, which `collapse_same_shape` and the combiner both group by). A fold whose members bind one slot to different values merges them into a set, then drops each value that descends from another merged value, since the fold reads its parts as a union (HIV/AIDS 13040 folded with reproductive health 130 binds 130); one value left binds a value. The merged trace keeps each member's qualifier record of a text that differs in offsets, operation or selected codes. The merged spec states a set binding's values in the order its identity holds the refs, and counts an open role over the union of the members' own populations, so it reads as the resubmit of its spec_id does | implemented | No gap. Pinned by `tests/engine/test_parts_shape.py` (`test_two_variables_collapse_onto_one_merged_spec_through_fanout`), `tests/engine/test_conjoin_five_tuple_and_slots.py` (`test_collapse_same_shape_states_its_set_in_identity_order`, `test_collapse_same_shape_drops_a_merged_value_nested_under_another`, `test_collapse_same_shape_keeps_every_union_record_of_one_text`), `tests/engine/test_conjunction.py` (`test_a_separate_part_never_folds_and_the_others_fold_as_before`), `tests/engine/test_parsed_pipeline.py` (`test_a_part_marked_separate_never_folds_into_a_same_shape_part`) and `tests/engine/test_same_shape.py` (`test_a_merged_answer_unions_each_members_own_known_origin_population`). | -| C195 | Parts whose specs share a `spec_id` are one reading of the data, the same series, entities and years. A part posted with `separate: true` never folds, so two separate parts that resolve to one reading carry the same `spec_id` and stay two parts. Parts sharing a `spec_id` can differ in `assumptions`, so a client keeps each part and never dedupes parts by `spec_id`. The envelope sentence states a folded part's own assumptions beside those of the part it folded into, an item only one of them carries under that part's `part_index` (C193) | implemented | Stated on `DefinitePart.spec`, `ParsedPart.separate` and `_PartBase.separate`. Pinned by `tests/render/test_compose_envelope.py` (`test_a_folded_part_keeps_its_items_beside_a_separate_part_of_the_same_spec`) and `tests/engine/test_conjunction.py` (`test_a_separate_part_never_folds_and_the_others_fold_as_before`) | +| C199 | Parts whose specs share a `spec_id` are one reading of the data, the same series, entities and years. A part posted with `separate: true` never folds, so two separate parts that resolve to one reading carry the same `spec_id` and stay two parts. Parts sharing a `spec_id` can differ in `assumptions`, so a client keeps each part and never dedupes parts by `spec_id`. The envelope sentence states a folded part's own assumptions beside those of the part it folded into, an item only one of them carries under that part's `part_index` (C193) | implemented | Stated on `DefinitePart.spec`, `ParsedPart.separate` and `_PartBase.separate`. Pinned by `tests/render/test_compose_envelope.py` (`test_a_folded_part_keeps_its_items_beside_a_separate_part_of_the_same_spec`) and `tests/engine/test_conjunction.py` (`test_a_separate_part_never_folds_and_the_others_fold_as_before`) | | C51b | A candidates part carries observations_omitted when the request set include_observations, since a candidates part has not committed to one spec to fetch observations against; max_candidates applies per part; the warning ships on the buffered and SSE transports alike, since it states the request and not a transport choice | implemented | No gap. Pinned by `tests/engine/test_parts_shape.py` (`test_candidates_part_carries_observations_omitted_alongside_definite_with_observations`). | | C51c | Each part carries its own timing_ms: by_step names the steps that variable's own leg ran and a phase_ entry for each phase it ran, holding the time no step booked, and total sums them except the stages inside shape (shape_arcs, shape_probe, shape_probe_coverage, shape_labels, shape_groups, shape_admission). diagnostics.timing_ms carries the whole request's wall-clock total plus by_step naming only the once-per-request steps: coverage; extract; cache_lookup on a raw-text request with the response cache active; entity_check on a request that runs the place check | implemented | No gap. Pinned by `tests/engine/test_parts_shape.py` (`test_per_part_timing_by_step_excludes_the_envelopes_shared_step`). | | C51d | A query in which extraction finds no variable is one part whose variable_text is the whole query text; the echoed variable list on query_echo is omitted, since it would duplicate the parts' own text and disagree with it in this case | implemented | Behaviour lives in `engine/entry/raw_text.py`'s zero-variable early return; no dedicated test names this path. Issue: none | @@ -126,7 +126,7 @@ Gap rows state what is needed to close the clause and name the open issue that t | C188 | An origin or destination `SlotOption` (the options door's items and `entity_options`) and an origin or destination `SeriesEntityRef` of an observations preview carry `party_kind` (`party`, `total` or `residual`), the kind the packaged code index lists for the party (`CodeIndex.parties`); a party the index does not list, and every subject or facet option, carries none, and reading it makes no request | implemented | Pinned by `tests/observations/test_preview_assembly.py` and `tests/engine/test_options_surface.py`. | | C52 | nearest_real carries up to two relaxed specs, each naming the relaxed part and dropped refs | implemented | | | C53 | flagged_match, when present, carries the elected variable's own Spec with a gate code from NoData.gate. A role_incompatible refusal before or during materialise, a qualifier_not_bound refusal and a denominator_not_available refusal are the three exceptions: each carries the graph-confirmed elected identity without a gate, role_incompatible because role incompatibility prevents an availability verdict, qualifier_not_bound because the qualifier guard refuses before an availability verdict exists, and denominator_not_available because the quantity gate refuses the series for its form or denominator, not for its data. On gate only_alternate_has_data flagged_match is absent and alternate_match carries a different, available alternate the request did not elect. NoData.elected_count counts the StatVars the request elected on every gate and on that role_incompatible exception, and is null when no graph-confirmed identity exists. On the two sibling gates (no_member_has_data_at_place, no_member_asserts_bound_properties) flagged_match and elected_count describe the same variable | implemented | | -| C188 | Before bind, every shape is held to every form the part's quantity requirement names (`quantity.serves_every_form`): per person, a numbered per-head base, per area, a rate or a share, read from the variable and its qualifier phrases; a shape that fails one is not electable, and the bind menu lists it as such (`admissibility.judge_pinned_parts`). A part none of whose readings is admissible refuses before bind with the reason the no-data order elects among their refusals, and flags nothing. The materialise gate holds the elected shape to the same forms, and to the form the bind model states when no phrase names one (`ResolutionTrace.stated_form`). When the question names a denominator, picked by the bind model from the StatVar denominators the menu's shapes divide by or "none of these", a shape with a resolved denominator arc must divide by it, and a shape whose arc does not resolve, or with no arc that classes as other, passes (`quantity.check_denominator`); a definite answer on such a series carries the `denominator_unverified` warning naming the denominator asked, judged on the series the answer serves. The alternate guard holds every alternate to the same rule (`quantity.serves_quantity`), a registry twin included, except an alternate of the elected shape's class beside an elected shape that fails it. When the elected shape fails and an alternate the fan-out admits passes, the fan-out runs as it would for any elected shape, and the part offers only the surviving readings that pass: one answers definite, as an only_alternate_has_data alternate, or as a substituted measure, and two or more are its candidates, so no candidate is a reading the gate refuses; it refuses when no surviving reading passes. A refusal is `denominator_not_available`, unless an earlier reason wins (C51), and carries as flagged_match the elected Spec, or the not-electable reading the bind model names as nearest when it declines every admissible one (`phase_place_materialise._declined_refusal`), its level reading as nearest_real (the recalled shape with the same population type, measured property, stat type and constraint properties and no denominator, under the same qualifier, or under the one qualifier its levels hold when the flagged match states none), none when the flagged shape has no such reading, as a level does, and `NoData.denominator` {asked, found}: the denominator the question named, null for none or one no shape divides by, and the one the flagged match divides by, null for none. A request that names no form reads the denominators for the bind list alone, in one batch, so a failed read of any of them leaves the list with no denominator, only "none of these"; when the bind model then states a form or names any denominator entry, "none of these" included, the read's infrastructure error fails the part | implemented | `tests/engine/test_per_capita_guard_unresolved_denominator.py` (`test_a_mismatched_denominator_refuses_with_both_denominators`, `test_none_of_these_refuses_a_shape_with_a_denominator_arc`, `test_a_series_stating_its_base_only_in_its_name_answers_unverified`, `test_an_unverified_series_without_data_raises_no_unverified_warning`, `test_a_stated_form_joins_only_when_no_phrase_names_one`, `test_a_failed_denominator_read_fails_a_request_bind_reads_a_quantity_in`, `test_an_alternate_dividing_by_another_base_is_not_offered`, `test_an_elected_share_answers_nothing_when_the_alternate_that_serves_holds_no_data`, `test_a_failed_denominator_read_leaves_a_formless_request_answering`, `test_a_failed_denominator_read_fails_a_request_naming_a_form`, `test_a_level_refused_on_its_form_is_flagged_when_bind_declines_and_names_it`, `test_a_density_no_reading_serves_refuses_before_bind`, `test_ghana_debt_to_gdp_flags_the_share_of_gni_with_the_debt_stock_nearest`), `tests/engine/test_alternate_guard.py` (`test_check_denominator_holds_the_shape_to_the_named_denominator`, `test_serves_every_form_holds_every_named_form`, `test_per_area_alternate_refused_when_the_phrase_also_names_a_per_person_form`), `tests/render/test_render_no_data_sentence_plan.py` (`test_a_denominator_refusal_names_the_denominators_and_offers_the_flagged_match`), `tests/engine/test_admissibility_table.py` (`test_each_rule_admits_or_refuses_a_reading`) | +| C200 | Before bind, every shape is held to every form the part's quantity requirement names (`quantity.serves_every_form`): per person, a numbered per-head base, per area, a rate or a share, read from the variable and its qualifier phrases; a shape that fails one is not electable, and the bind menu lists it as such (`admissibility.judge_pinned_parts`). A part none of whose readings is admissible refuses before bind with the reason the no-data order elects among their refusals, and flags nothing. The materialise gate holds the elected shape to the same forms, and to the form the bind model states when no phrase names one (`ResolutionTrace.stated_form`). When the question names a denominator, picked by the bind model from the StatVar denominators the menu's shapes divide by or "none of these", a shape with a resolved denominator arc must divide by it, and a shape whose arc does not resolve, or with no arc that classes as other, passes (`quantity.check_denominator`); a definite answer on such a series carries the `denominator_unverified` warning naming the denominator asked, judged on the series the answer serves. The alternate guard holds every alternate to the same rule (`quantity.serves_quantity`), a registry twin included, except an alternate of the elected shape's class beside an elected shape that fails it. When the elected shape fails and an alternate the fan-out admits passes, the fan-out runs as it would for any elected shape, and the part offers only the surviving readings that pass: one answers definite, as an only_alternate_has_data alternate, or as a substituted measure, and two or more are its candidates, so no candidate is a reading the gate refuses; it refuses when no surviving reading passes. A refusal is `denominator_not_available`, unless an earlier reason wins (C51), and carries as flagged_match the elected Spec, or the not-electable reading the bind model names as nearest when it declines every admissible one (`phase_place_materialise._declined_refusal`), its level reading as nearest_real (the recalled shape with the same population type, measured property, stat type and constraint properties and no denominator, under the same qualifier, or under the one qualifier its levels hold when the flagged match states none), none when the flagged shape has no such reading, as a level does, and `NoData.denominator` {asked, found}: the denominator the question named, null for none or one no shape divides by, and the one the flagged match divides by, null for none. A request that names no form reads the denominators for the bind list alone, in one batch, so a failed read of any of them leaves the list with no denominator, only "none of these"; when the bind model then states a form or names any denominator entry, "none of these" included, the read's infrastructure error fails the part | implemented | `tests/engine/test_per_capita_guard_unresolved_denominator.py` (`test_a_mismatched_denominator_refuses_with_both_denominators`, `test_none_of_these_refuses_a_shape_with_a_denominator_arc`, `test_a_series_stating_its_base_only_in_its_name_answers_unverified`, `test_an_unverified_series_without_data_raises_no_unverified_warning`, `test_a_stated_form_joins_only_when_no_phrase_names_one`, `test_a_failed_denominator_read_fails_a_request_bind_reads_a_quantity_in`, `test_an_alternate_dividing_by_another_base_is_not_offered`, `test_an_elected_share_answers_nothing_when_the_alternate_that_serves_holds_no_data`, `test_a_failed_denominator_read_leaves_a_formless_request_answering`, `test_a_failed_denominator_read_fails_a_request_naming_a_form`, `test_a_level_refused_on_its_form_is_flagged_when_bind_declines_and_names_it`, `test_a_density_no_reading_serves_refuses_before_bind`, `test_ghana_debt_to_gdp_flags_the_share_of_gni_with_the_debt_stock_nearest`), `tests/engine/test_alternate_guard.py` (`test_check_denominator_holds_the_shape_to_the_named_denominator`, `test_serves_every_form_holds_every_named_form`, `test_per_area_alternate_refused_when_the_phrase_also_names_a_per_person_form`), `tests/render/test_render_no_data_sentence_plan.py` (`test_a_denominator_refusal_names_the_denominators_and_offers_the_flagged_match`), `tests/engine/test_admissibility_table.py` (`test_each_rule_admits_or_refuses_a_reading`) | | C189 | A code the request names, or a resubmit posts, on a property the answering shape's slots hold is pinned: a definite read that does not apply it refuses `qualifier_not_bound` on every door (raw text, parsed, resubmit), with the read as flagged_match and the code named once as an unbound term, by its dcid when it has no name, and no departure beside it. A single surviving alternate is judged on its own shape, bindings and label, its slot-value words included, so one that binds the code, or a descendant of it, answers. A word of the variable that names, by its graph label, a value on a property the answering shape's slots hold ("female" for `gender` Female), as that shape or another on the bind menu offers it, is such a code whether or not the bind model lists it as unplaced, unless the answering shape's own label or population type names it; the shape answers only when its final bindings bind it or a value below it. A value word on a property the answering shape lacks is left to the bind model's judgement of the measure: it refuses when the model lists it as unplaced, and otherwise the answer departs `measure_not_applied` (`query_qualifier_dropped`) on it, unless the property is a code system the index declares, whose words the code reading judges. A value the bind model drafts on a property the elected shape has no slot for is required of every reading, judged by values alone (a word in a member's name does not meet it): a reading meets it when its bindings bind it, or every member it reads holds it or a value below it. The values drafted on one property are a set, met by membership as a set bound on a slot reads: every member the reading reads holds a value of the set or one below it. An alternate binds its slot on that property to the drafted values its members hold, a set when they hold several, so "male and female" on a shape with no sex slot offers each reading whose members hold either sex, a by-sex family serving both. A reading with a member holding a value outside the set, or none on the property, does not meet it. A reading that does not meet it is not offered, the elected one included (C45), and when none meets it the part refuses `qualifier_not_bound` naming the drafted values as one term, with the elected read as flagged_match and no departure beside it. Overlapping value labels are read longest first. The price-basis form of `measure_not_applied` (`binding_ref` `unit:`), a qualifier no one slot clears, a `measure_not_applied` code on a property the shape lacks, and `closest_match` stay departures | implemented | `tests/engine/test_pinned_code_refusal.py` (`test_education_share_refuses_the_health_code_it_cannot_bind`, `test_departure_the_shape_cannot_hold_stays_definite`, `test_unrealisable_term_refuses_once_and_candidate_sets_depart_it_once`, `test_an_alternate_survivor_is_judged_on_its_own_shape_and_bindings`, `test_an_alternate_survivor_is_judged_on_its_own_slot_value_records`), `tests/engine/test_standard_resolve.py` (`TestUnrealisableValuesUnbound`), `tests/engine/test_resubmit_measure_elected_provenance.py` (`test_a_posted_value_no_elected_member_carries_refuses`), `tests/engine/test_qualifier_guard.py` (`test_a_slot_value_the_phrase_names_is_recorded`, `test_a_value_word_on_a_property_the_elected_shape_lacks_departs`), `tests/engine/test_qualifier_grounding_fallback.py` (`test_a_reading_holding_no_member_of_the_named_purpose_refuses_before_bind`) | | C190 | A part's `source_code` (`ParsedPart.source_code`, stripped and upper-cased) is a publisher's series code. It names the StatVars the packaged code index records for it under each provenance holding it (`CodeIndex.source_code_members`, read from the `sourceIndicatorCode` arcs of its listed rows), and it compiles at the door after the part's sources, against the provenances they allow (`source_code.compile_part_code`, in `entry.parsed.interpret_parts` and `entry.raw_text._prepare_extraction`). A code no allowed provenance holds refuses source_code_not_held, its message naming the cause: the index holds no such code, naming every source whose codes it holds, or the part's sources leave out every provenance holding it, naming those; a code two allowed provenances hold refuses source_code_ambiguous, with the code and those provenances in `NoData.ambiguous_sources` and one option per provenance in `NoData.options` (C50e), and the part answers once its `sources` names one; a code whose StatVar its provenance gives another code too (`CodeIndex.shared_codes`: IDS gives one net-flow StatVar DT.NFL.IMFC.CD and DT.NFL.MIDA.CD) refuses source_code_shares_variable, its message naming those codes. Each of these door refusals carries the code in `NoData.source_terms` and runs no step. Otherwise the code's StatVars are the part's recall in place of the embedding resolve (`interpret.recall`), admitted by the request's catalogue as recalled hits are, and shape derivation, the twin election and bind run on them as on a recalled pool, so `open_roles: ["creditor"]` elects DT.TDS.DEGG.CD's by-creditor variable and no open role its aggregate. Bind binds every constraint value the code's StatVars all share as a sole option (`bound_values.bind_member_values`), so the minted identity elects those StatVars and no broader family, and a variable text that is only the code reaches the bind model as its StatVars' graph name. A code part refuses source_code_not_held, carrying the code in `NoData.source_terms` and the part's warnings, model usage and source scope, wherever it would refuse variable_not_resolved (`ResolveState.no_data`): its message names the cause, that the catalogue or graph serves none of the code's StatVars when no shape derives from them, and that the code's series cannot answer the part as asked otherwise, a bind that asks among them. So does a part one of whose Specs mints an identity whose Datasets mint serves StatVars outside the code's, or ones the code index does not resolve in memory (`pipeline._within_source_code`). Each entry of a Spec's `resolved.stat_vars.source_codes` states in `exact` whether the Spec is the series its provenance files under that code and no other (`source_code.stands_for_code`, called from `spec_build._resolved_stat_vars`): the Spec reads its observations from that provenance alone (`resolved.facets.provenance.ref`), the provenance gives none of the code's StatVars another code, every StatVar the Spec serves is the code's, and the StatVars a Datasets mint of its identity serves, its antichain-minimal members (`bound_values.served_members`), are known and lie within the code's StatVars (`bound_values.elects_within`). A Spec found by the measure's words gets the flag by the same computation as one a code part answers with, and a false `exact` is left off the wire. A code part gets no UN pass (C51e). The variable text leaves out the code's words (`source_code.without_code_words`), the restatement echoes the code, and posting it mints the same `spec_id`. On the raw_text door the extraction names no code: a query token naming a held code fills it (`source_code.scan_source_codes`), in any case when the token holds `.`, `_` or a digit and only in capitals otherwise, for the variable whose words hold the token, else the only variable; a token with the shape of a dotted series code, a two-letter topic and two or more segments of two to five capitals or digits in any case (NY.GDP.MKTP.KD), fills it the same way when the index holds no such code, so the part refuses source_code_not_held with the cause that the index holds no such code, naming the code; the raw-text response cache keys on each variable's code | implemented | Pinned by `tests/engine/test_source_code.py`, `tests/render/test_render_no_data_phrase.py::test_a_source_code_phrase_states_its_refusal`, `tests/engine/test_source_scan.py::test_the_raw_text_door_reads_a_series_code_the_query_writes`, `tests/engine/test_response_cache_keys.py` and `tests/engine/test_operations_echo.py`. | | C191 | A one-way UN series, one whose single `observationAbout` place sits on one end of a flow, carries the side the origin registry declares for it (`provider` or `recipient`, `series_declarations.declared_side`, read at runtime from `load_registry()`). When the question names a flow end and the declared side is not among the axes it needs (`place_roles.query_role_axes`), the series reads the other end of the flow, so admissibility refuses it `role_incompatible` (`place_roles.lacks_query_axis`, `admissibility.judge_measure_and_roles`): "climate finance to Bangladesh" refuses the provider-side series. An undeclared series, and a question naming no flow end, lack no axis by this rule. A no-data answer whose direction was discarded says the measure has no recipients (or providers) and that the place was checked itself | implemented | Pinned by `tests/engine/test_candidate_place_roles.py::test_a_one_way_series_lacks_the_flow_end_its_declared_side_opposes` and `tests/origin_catalogue/test_registry.py::test_un_agencies_declare_the_side_and_price_basis_of_their_series`. | @@ -196,7 +196,7 @@ Gap rows state what is needed to close the clause and name the open issue that t | C169 | Every sentence token carries `segment`: `heading` (actor, measure noun, counterpart, and period in possessive order, one slot token per axis, a unit that changes the magnitude included; the period is the span the answer serves, earliest start to latest end over its StatVars, resolved.stat_vars.data_date_range, "1973 to 2024" or "in 2024" for one date, whether or not the request named years), `byline` (after the heading's period, items joined by " · ", each opening with a capital: the text "Latest year with data" when the request asked for the latest year; every currency with its price basis, by its plural noun, "Current US dollars", "Constant 2024 euros" or "Current Guatemalan quetzales", or the role's currency for a request in each entity's own currency ("Current donor currency"), a slot token with axis `unit` and binding_ref `unit:`; the heading states a unit that changes the magnitude and is no currency (per capita, a share, percent, PPP international dollars); the source, a slot token; and, when the identity binds `measurementMethod` (a ref, a null ref, or a set), the method after the source, a slot token with axis `method` and binding_ref `method:` reading the method's label without a trailing full stop, or "No stated method" for a null ref), `departure`, `assumption` (the closing clause C193 states), or `coverage` (the opener C194 states). `spec.resolved.sentence_plan` holds heading and byline tokens only, so the spec sentence states what the answer is and reads the same after a resubmit. A role holding more than three entities renders as its count ("162 recipients") | implemented | | | C170 | Each part carries `departures`, an ordered list of `{code, binding_ref, asked, shown, text}`, `[]` when the answer is what was asked. `code` is a closed `DepartureCode` value, never a warning or refusal code; the list follows its precedence (closest_match, measure_not_applied, operation_not_applied, breakdown_not_applied, entity_unavailable, year_unavailable, modelled_values, group_read_as_members, whole_total_read, whole_total_unpublished, direction_discarded, direction_inferred, scope_limited) and the model refuses any other order. `binding_ref` names the identity binding whose edit clears the departure, null when no binding edit clears it; a client links the departure to the token carrying the same `binding_ref` when the plan has one, and a candidates or no_data plan, or a how slot the heading leaves silent, may have none. `text` is one or two short sentences for people, non-empty; on a no_data part no text claims the part shows anything. A `closest_match` departure tells a client to draw a muted "Closest match:" lead before the heading, a lead no spec token carries. `retrieval_score_weak` alone writes no departure. A candidates part whose readings were elected in place of a measure that holds no data, its `elected_shape_no_data` warning on the part, carries one `closest_match` departure, whose text speaks of the readings and whose `shown` is null, unless one reading names the measure asked by the test a definite part's reading takes (a content word of `variable_text` in its measure noun, in every one of its StatVar names, or in a name of a code it binds), as "GDP" answered with Kenya's GDP and its GDP per capita; a client draws the lead once, before the first reading. A part showing one member of a family whose total holds no data carries `closest_match` beside its `family_total_absent` warning, which holds that Spec's `spec_id`, with `shown` the member's measure noun; a candidates part leaves it out. A resubmit, whose request names no measure in words, raises it with `asked` null, and its text states only the measure shown ("This shows neonatal mortality rate, the closest measure the data holds."). A no_data part whose sentence shows an alternate reading (gate `only_alternate_has_data`) carries that reading's `closest_match` departure when the rule fires, and no other loose-match wording. A no_data part answering `no_observations` for years its elected series holds no point in carries `year_unavailable` with `shown` null, naming the years asked and the span the series runs, unless its sentence shows such an alternate. An `entity_unavailable` departure carries its role's token ref and `asked` naming the entities the request named that hold no values in the series the answer reads; for a role the request named as a group, `asked` is the request's own words for the group and the text counts the members of the group as the heading names it, naming none of them. A part states one `entity_unavailable` per role, naming every entity on the role the data holds no values for: those its read plan serves no point at, including entities whose rows a conversion into each entity's own currency left out (`currency_unmapped_omitted`); the places the UN front door refused (`un_system_unavailable`) when every StatVar the part reads is a UN one; and on a no_data part warned `withheld_by_registry` the places it names, its text then naming the sources the origin registry withholds that hold values there. A `modelled_values` departure states the served method when it is modelled or estimated, and fires with only some values modelled when a served series selects such a method or holds points converted on a projected exchange rate or deflator (`deflator_projected`). A `group_read_as_members` departure's `asked` is the request's own words for the group ("EU institutions"), the group's label when the request named it by no other words. On a flow role, its text also names the residuals the group's declared total counts beside the members and a dataset the Spec serves publishes (`PartyRow.residuals`, `PartyRow.datasets`), as "and leaves out what the data allocates to no single country: Africa, unallocated by country (DAC); Eastern Africa, unallocated by country (DAC)", each label once. For a group whose declared total is not its members' sum (`PartyRow.sums_members` false), the text names no residual and, when a dataset the Spec serves publishes that total on the role, adds "and the source publishes its own figure for the group, which can differ from one built from its members". A group read as its total writes no `group_read_as_members` departure. A resubmit's request holds no words, so every departure on it has `asked` null, `entity_unavailable` on a group or a named entity included. A text that quotes `asked` on another door states there only what the answer shows or left out: the members shown, as the heading names them ("This shows the 27 European Union members, each on its own."), the readings ("These readings are the closest measures the data holds."), and the span the data covers ("The data ends in 2024, so this shows 1980 to 2024."). Other texts stand. A `forecast` operation to a target year departs as `year_unavailable` when that year falls outside the served span, and as `operation_not_applied` otherwise. A phrase extraction dropped writes a `measure_not_applied` departure on the unit token when it names a price basis, and on no token when it holds a content word and no operation word ("Trend in", "compare the" write none). A qualifier whose content words one heading and byline the part shows already holds, source included, writes none, on a candidates part as on a definite one; and a price basis writes one only with `shown`, the byline's unit text. `direction_discarded` fires only when the request's own words gave the place its direction (a `from` or `to` before it, or a parsed role text). `direction_inferred` fires when the request named a place without a direction and only one direction holds data there, so the answer reads the place in that direction; its text names the place and the role it reads. Bound values that together match no member of the elected measure are unbound and the measure read again: values the engine chose (a declared default, the elected measure partition) go first and depart nothing, then the request's own values go. A request-named or posted value is a code pinned on a property the shape holds (C189), so on every door a definite read without it refuses `qualifier_not_bound`, naming the value once as an unbound term with the read as flagged_match and no departure beside it; a candidates part carries its `measure_not_applied` departure on its how token instead (`binding_ref` `how:`, `asked` the request's words for the value), except for a value the bind model drafted on a property the elected shape lacks, which every candidate applies (C189) and none departs. A set drafted on such a property is met by membership, and each value of it that neither an answer's bindings nor any member it reads holds departs `measure_not_applied` with `query_qualifier_dropped` once, on a definite part as on a candidates part, beside no refusal. An alternate reading's values come from its representative StatVar, so it unbinds them silently. A resubmit's values are all ones its request posted, so every one it unbinds refuses the same way, as does a single edited value no elected member carries. A no_data part whose check covered values the engine chose carries one `scope_limited` departure, its `binding_ref` the how token of the first such value and its text naming only what the check covered ("This check covered only ODA."), and its plan's departure tokens equal its `departures` texts | implemented | | | C171 | A sentence token's `axis` is a `SentenceAxis`: the request-slot `Axis` values plus `unit`, the byline's unit token. `Axis` itself (request slots, `/capabilities` `axes`) has no `unit` member | implemented | | -| C193 | Each part carries `assumptions`, an ordered list of `{kind, binding_ref, text, refs}` naming every value the engine filled that the request did not ask for, `[]` when it filled none. `kind` is a closed `AssumptionKind` value: `declared_default`, a how slot whose trace records `declared_default` provenance, `binding_ref` `how:`, `text` the bound code's full label in the code index ("official development assistance" for `CRSTypeOfFlow10`, whose short name is "ODA"), or for a value the index holds no row for its graph label without the property suffix, `refs` the slot's bound value dcids; a part a same-shape merge serves reads that provenance from its own request's Spec and states the values its slot fixed there that the merged slot still binds; `open_role`, a flow role no request word asked for: a role the role scope default filled (`resolved.scope` on that axis) whatever its member count, or a role the heading states with no entity of its own (a count, or "all creditors"; the destination shares the where slot, so a place that slot binds with no direction of its own names the destination), on an axis the identity neither binds in `entities` nor lists in `open_roles`, that none of the part's operations runs over (an `over` of `origin` or `destination`, as "top 5 donors" ranks over the providers; an `over` of `place` asks no flow axis), and that the request's words did not ask every member of ("all donors"), the operation axes and the identity's open roles read through one rule (`search.models.asked_open_axes`), which also opens those axes for resolution, `binding_ref` the role's token ref (`origin:` or `where:`), `text` "every" and the role's singular noun in the answer's flow family ("every provider", "every creditor"); `place_scope`, a `resolved.scope` on `observationAbout`, `binding_ref` `where:`, `text` "the world as a whole" for Earth alone, "every country" when every member's `entity_type` is `Country`, else "every place"; the place scope is the where slot's one item, so the destination, which shares that slot, states no `open_role` beside it. The order is declared defaults in `trace.slot_filters` order, open roles in the heading's role order, then the place scope. A measure election, the price basis and an open year are no assumptions. A definite part reads its list off its Spec, a candidates part carries the items every list in its `reading_assumptions` holds alike, compared on the whole item, `text` included, in the first reading's order, and no_data and unsupported parts carry `[]`. A candidates part's `reading_assumptions` maps each shown reading's `spec_id` to the list a definite part answering that Spec for the same request carries, read with the part's operations and every-member axes; a client that picks a reading posts its identity as a `dataset_resubmit` identity entry echoing `reading_assumptions[spec_id]`, and the rebuilt part states that list while it holds. The rules run on every door. `refs` is `[]` on `open_role` and `place_scope`. A `dataset_resubmit` identity entry records its posted slots as `binder`, and an axis its measure family election forces as `measure_elected`. When it carries the optional `assumptions` echo, the part states the echoed items that still hold, with `text` read from the rebuilt Spec: a `declared_default` whose slot (`binding_ref`) still binds the echoed `refs`, an `open_role` the rebuilt answer still reads open on that axis, and a `place_scope` the rebuilt answer still yields. An item the echo lacks is not stated, so an edit of the defaulted slot to another value drops it. The rebuilt trace keeps the posted provenance. An empty echo states none. An entry without `assumptions` states the open roles and place scope the rebuilt answer reads and no declared default, since the posted identity binds every slot. A `dataset_resubmit` candidates entry's `ResubmitCandidate` carries the same optional `assumptions` echo, its reading's `reading_assumptions[spec_id]`, with the same bounds and matching rule: the rebuilt part's `reading_assumptions` entry for that reading states the echoed items that still hold, and its `assumptions` are the items every such list holds alike. A candidate without `assumptions` states the open roles and place scope its rebuilt reading reads. Candidates that rebuild to one `spec_id` keep the first in posted order, with its own echo, as a fresh candidate set keeps one reading per `spec_id`. A set that keeps two or more readings drops a repeat with no warning. When the candidates that resolve rebuild to one `spec_id`, the part is that reading's definite part stating the first such candidate's echo, and its `candidates_collapsed_definite` warning says how many still resolve and, when more than one does, that they rebuild to one reading. The sentence carrying the part closes with them as one clause of `segment="assumption"` text tokens, after its departures: " ", "Assumed: ", the texts with "; " between, ".". A joined envelope sentence writes one clause naming each part's items in part order, each item under its part's `part_index`, an item two parts share under none, and the clause's leading space, "Assumed: ", separators and "." under the one part that owns every item, else under none. A part others fold into states the items of every part folded into it as well. An item every one of them carries has no `part_index`, an item one part alone carries has that part's, whether it is the part folded into or a folded one, and an item several but not all carry has none. A sentence written part by part closes each part's sentence with its own clause. A part whose sentence and clause both repeat an earlier part's is stated once with it, that sentence and clause under no `part_index`, followed by each part's own departures, and a part whose clause differs keeps its own sentence. A candidates part's own sentence ends with its clause. `resolved.sentence_plan` carries none. `slot_defaulted` and `role_scope_defaulted` fire beside the list | implemented | Pinned by `tests/render/test_render_assumptions.py`, `tests/render/test_compose_envelope.py` (`test_a_joined_sentence_closes_with_one_clause_naming_each_part_item`, `test_a_part_by_part_sentence_closes_each_part_with_its_own_clause`, `test_a_part_whose_sentence_repeats_keeps_it_when_its_clause_differs`, `test_a_part_others_fold_into_states_its_clause_for_all_of_them`, `test_a_folded_part_keeps_its_items_beside_a_separate_part_of_the_same_spec`), `tests/engine/test_answer_assumptions_e2e.py` (`test_a_role_an_operation_runs_over_is_asked`, `test_a_folded_part_states_only_its_own_assumptions`, `test_a_candidates_part_lists_each_reading_s_own_assumptions`, `test_a_picked_reading_echoing_its_own_list_states_it`, `test_an_edited_candidates_set_restates_each_reading_s_echo`, `test_an_edited_candidates_set_states_the_default_and_roles_its_readings_share`, `test_a_resubmit_states_the_echoed_assumptions_that_still_hold`, `test_a_resubmit_with_no_echo_states_the_roles_alone`, `test_an_edit_that_changes_the_defaulted_slot_drops_its_assumption`, `test_a_merged_part_keeps_the_default_its_own_request_left`, `test_an_unchanged_repost_of_a_merged_part_keeps_its_default`) and `tests/engine/test_first_answer_place_scenarios.py` (`test_a_placeless_one_role_question_discovers_its_recipients_from_coverage`) | +| C197 | Each part carries `assumptions`, an ordered list of `{kind, binding_ref, text, refs}` naming every value the engine filled that the request did not ask for, `[]` when it filled none. `kind` is a closed `AssumptionKind` value: `declared_default`, a how slot whose trace records `declared_default` provenance, `binding_ref` `how:`, `text` the bound code's full label in the code index ("official development assistance" for `CRSTypeOfFlow10`, whose short name is "ODA"), or for a value the index holds no row for its graph label without the property suffix, `refs` the slot's bound value dcids; a part a same-shape merge serves reads that provenance from its own request's Spec and states the values its slot fixed there that the merged slot still binds; `open_role`, a flow role no request word asked for: a role the role scope default filled (`resolved.scope` on that axis) whatever its member count, or a role the heading states with no entity of its own (a count, or "all creditors"; the destination shares the where slot, so a place that slot binds with no direction of its own names the destination), on an axis the identity neither binds in `entities` nor lists in `open_roles`, that none of the part's operations runs over (an `over` of `origin` or `destination`, as "top 5 donors" ranks over the providers; an `over` of `place` asks no flow axis), and that the request's words did not ask every member of ("all donors"), the operation axes and the identity's open roles read through one rule (`search.models.asked_open_axes`), which also opens those axes for resolution, `binding_ref` the role's token ref (`origin:` or `where:`), `text` "every" and the role's singular noun in the answer's flow family ("every provider", "every creditor"); `place_scope`, a `resolved.scope` on `observationAbout`, `binding_ref` `where:`, `text` "the world as a whole" for Earth alone, "every country" when every member's `entity_type` is `Country`, else "every place"; the place scope is the where slot's one item, so the destination, which shares that slot, states no `open_role` beside it. The order is declared defaults in `trace.slot_filters` order, open roles in the heading's role order, then the place scope. A measure election, the price basis and an open year are no assumptions. A definite part reads its list off its Spec, a candidates part carries the items every list in its `reading_assumptions` holds alike, compared on the whole item, `text` included, in the first reading's order, and no_data and unsupported parts carry `[]`. A candidates part's `reading_assumptions` maps each shown reading's `spec_id` to the list a definite part answering that Spec for the same request carries, read with the part's operations and every-member axes; a client that picks a reading posts its identity as a `dataset_resubmit` identity entry echoing `reading_assumptions[spec_id]`, and the rebuilt part states that list while it holds. The rules run on every door. `refs` is `[]` on `open_role` and `place_scope`. A `dataset_resubmit` identity entry records its posted slots as `binder`, and an axis its measure family election forces as `measure_elected`. When it carries the optional `assumptions` echo, the part states the echoed items that still hold, with `text` read from the rebuilt Spec: a `declared_default` whose slot (`binding_ref`) still binds the echoed `refs`, an `open_role` the rebuilt answer still reads open on that axis, and a `place_scope` the rebuilt answer still yields. An item the echo lacks is not stated, so an edit of the defaulted slot to another value drops it. The rebuilt trace keeps the posted provenance. An empty echo states none. An entry without `assumptions` states the open roles and place scope the rebuilt answer reads and no declared default, since the posted identity binds every slot. A `dataset_resubmit` candidates entry's `ResubmitCandidate` carries the same optional `assumptions` echo, its reading's `reading_assumptions[spec_id]`, with the same bounds and matching rule: the rebuilt part's `reading_assumptions` entry for that reading states the echoed items that still hold, and its `assumptions` are the items every such list holds alike. A candidate without `assumptions` states the open roles and place scope its rebuilt reading reads. Candidates that rebuild to one `spec_id` keep the first in posted order, with its own echo, as a fresh candidate set keeps one reading per `spec_id`. A set that keeps two or more readings drops a repeat with no warning. When the candidates that resolve rebuild to one `spec_id`, the part is that reading's definite part stating the first such candidate's echo, and its `candidates_collapsed_definite` warning says how many still resolve and, when more than one does, that they rebuild to one reading. The sentence carrying the part closes with them as one clause of `segment="assumption"` text tokens, after its departures: " ", "Assumed: ", the texts with "; " between, ".". A joined envelope sentence writes one clause naming each part's items in part order, each item under its part's `part_index`, an item two parts share under none, and the clause's leading space, "Assumed: ", separators and "." under the one part that owns every item, else under none. A part others fold into states the items of every part folded into it as well. An item every one of them carries has no `part_index`, an item one part alone carries has that part's, whether it is the part folded into or a folded one, and an item several but not all carry has none. A sentence written part by part closes each part's sentence with its own clause. A part whose sentence and clause both repeat an earlier part's is stated once with it, that sentence and clause under no `part_index`, followed by each part's own departures, and a part whose clause differs keeps its own sentence. A candidates part's own sentence ends with its clause. `resolved.sentence_plan` carries none. `slot_defaulted` and `role_scope_defaulted` fire beside the list | implemented | Pinned by `tests/render/test_render_assumptions.py`, `tests/render/test_compose_envelope.py` (`test_a_joined_sentence_closes_with_one_clause_naming_each_part_item`, `test_a_part_by_part_sentence_closes_each_part_with_its_own_clause`, `test_a_part_whose_sentence_repeats_keeps_it_when_its_clause_differs`, `test_a_part_others_fold_into_states_its_clause_for_all_of_them`, `test_a_folded_part_keeps_its_items_beside_a_separate_part_of_the_same_spec`), `tests/engine/test_answer_assumptions_e2e.py` (`test_a_role_an_operation_runs_over_is_asked`, `test_a_folded_part_states_only_its_own_assumptions`, `test_a_candidates_part_lists_each_reading_s_own_assumptions`, `test_a_picked_reading_echoing_its_own_list_states_it`, `test_an_edited_candidates_set_restates_each_reading_s_echo`, `test_an_edited_candidates_set_states_the_default_and_roles_its_readings_share`, `test_a_resubmit_states_the_echoed_assumptions_that_still_hold`, `test_a_resubmit_with_no_echo_states_the_roles_alone`, `test_an_edit_that_changes_the_defaulted_slot_drops_its_assumption`, `test_a_merged_part_keeps_the_default_its_own_request_left`, `test_an_unchanged_repost_of_a_merged_part_keeps_its_default`) and `tests/engine/test_first_answer_place_scenarios.py` (`test_a_placeless_one_role_question_discovers_its_recipients_from_coverage`) | | C172 | A role whose entities the request named as a group carries the group on the Spec's `resolved.entity_groups[role]` as `{ref, label, entity_type, total, group_id}`. `ref` is the group's graph node and `entity_type` that node's graph type (`Continent`, `UNGeoRegion`, `ContinentalUnion`), as each entry of `resolved.entities` carries its own. Both are null when the graph holds no node for the group. Such a group, read from the membership registry ("Asia (DAC)"), names the registry's id for it in `group_id` (`groups/OECD.DAC.Region.Asia`), and `group_id` is null whenever `ref` is set, so a group carries one id or neither. With a `ref`, `label` is the graph's label for that node on a first answer and on a resubmit, which ignores the posted `label` and the posted `entity_type` and reads both from the graph; without one, or where the graph gives no label, `label` is the request's own name for the group. On a `dataset_resubmit`, a client posts a Spec's `resolved.entity_groups` as `entity_groups` beside the `identity` it posts, on an identity entry and on each candidate. A resubmit keeps a posted group only when it posts a `ref` and the graph's members of that node are the role's entities, and otherwise drops it so the role reads as its members. The members of a continent or subregion that the membership policy holds or omits (`MembershipPolicy.holds_region`) are the countries the policy holds for it, which a first answer binds (`expand_places`), whatever its graph type, and those of any other node its `<-containedInPlace` subjects, so a first answer that bound a region's members resubmits to the same `spec_id` with its group. A posted group with no `ref` and a `group_id` is kept when the registry's members of that id, under the default projection, are the role's entities, with the total the code index declares for that id (`CodeIndex.totals_by_group`) kept aside, so the resubmit answers the same `spec_id` with the same group and the same `group_read_as_members` departure; a posted `group_id` the registry does not hold, or one a graph node retired (`groups/G7`), answers `request_invalid` at `/input/entity_groups//group_id`. Any other posted group with no `ref` drops, since nothing ties its label to the role's entities, and so does a group on a role the identity leaves unbound. A departure naming the group (`group_read_as_members`, `entity_unavailable`) asks for it in the request's own words. A group the code index declares a flow dataset's total for (`CodeIndex.totals_by_group`: Africa's `DACAfrica`, the G7's `GroupOfSeven`) binds that total party on a flow role in place of its members (resolvekit's "Africa (DAC)" and the other DAC regions whose members are a graph place's take that place's id, so "Africa (DAC)" reaches `DACAfrica`) when the Spec's own read holds data there and the request did not ask for the members, and the role then carries the group with `total` naming that party; any other role carries `total` null. A resubmit keeps a posted group with a `total` only when the identity binds exactly that party on the role and the code index declares it the total of the posted `ref`'s group, or of the posted `group_id`'s group, or of any group when the group posts neither. `definition.identity` carries no group and is hashed whole. A client that hashes it with RFC 8785 and SHA-256 gets `spec_id`. An identity posted with `entity_groups` inside it is refused. Search answers `request_invalid`, and the Datasets mint and register doors answer `request_invalid` too, with the kit's `unknown_member` in the detail text. `resolved.entity_groups` exposes the groups the spec still binds, and the heading names that role as the group with its member count ("the 27 EU member states"). A group whose `ref` is a continent or subregion that the membership policy holds or omits (`MembershipPolicy.holds_region`) reads as its countries ("the 54 countries in Africa"), whatever its graph type, in the heading and in a departure that counts the group. `entity_type` is informational, and the wording keys on `ref`. A flow role read as the whole population of its side carries `whole_of`, the classes of party that population covers (`official`, `private`, `residual`), and `whole_of` is absent on every other group: a DAC2a or DAC2b part that names no donor, names "official donors", or sums over no axis binds `DACOfficialDonors` (`total` naming it, `whole_of` `["official"]`), a DAC2a part asking 1984 to 2024 with no recipient named binds `DACAllRecipients` (`["official", "residual"]`), and "all donors", "every donor" and "all donors including private", phrases of one whole total set, bind `DACOfficialDonors` and `DACPrivateDonors` on DAC2a (`label` "all donors including private", `total` null, `whole_of` `["official", "private"]`). A total on a side the part totals binds only where the request's coverage snapshot holds a series at the total of the variables the part's final bindings elect, those settled with the total bound, the measure election's forced values and the dataset's canonical pins included. Those totals are tried in a fixed order, each settled on its own, and the first that holds binds: the donors' before the recipients', so a part naming neither side binds `DACAllRecipients` where `DACOfficialDonors` holds no series, and once a whole population a text names binds on one side, no total binds on the other; where none holds, each side keeps its members, and the part answers as a request offered no total does. An alternate, and the other direction of a place named without one, judges its own total under its own bindings. A side the part opens, or any operation runs over (a sum, count, rank, share or report), keeps its members: an operation over a side may filter its parties ("donors giving more than 100 million"), so "from all donors" with `open_roles: ["donor"]` lists the donors. A published total stands in for a side only for an unconditional sum over its whole population: a side no text names and no operation runs over, read alone or under a sum over no axis. A whole population posted with `members: true`, or named on a side an operation runs over ("sum aid from official donors giving more than 100 million"), asks for every party of its side: no total binds, and the part answers as `open_roles` naming that side does, warning `query_qualifier_dropped` where a named place fills the side, on a shape whose datasets hold on that side, totals aside, only parties of classes the population's markers cover. An open side lists every party its dataset holds, so every other shape is pruned: "official donors" lists on ONE Imputed Multilateral Sectors and on no CRS or DAC2a shape, which hold private donors, and "all donors" lists on the CRS and not on DAC2a, which also holds a residual donor the population leaves out. The side it opens was named, so it warns no `role_scope_defaulted`. With no shape left, the part refuses `entity_role_not_admitted`. Its text names each population no shape lists exactly, with the classes of party every one of those datasets holds beside it, and the total or named members as the ways to ask instead; where each population lists exactly on some shape and no shape lists them all, it names those some shape fails as populations no source lists together. A population every shape lists exactly is never named. A whole population whose stated direction is the other side ("from France to official donors") refuses `role_incompatible`, whether it is named, posted with `members: true`, or under an operation over a side, before any shape is derived, so no listing or variable refusal stands in front of it; the refusal names each such text in `no_data.entity_terms` and says it belongs on the other side. A whole population a text names ("all donors", "to all recipients", "France and official donors") binds its parties as named parties on any shape declaring its side, so role admission, the bind menu, availability and binding read it as they read any named party. Reading it whole is a label on those parties: the side carries `whole_of` where the population stands alone on it and every dataset of the shape marks it over the requested years, and otherwise binds the same parties with no `whole_of` ("all recipients" in 1983). Where no series holds it, the part departs `entity_unavailable` for it or refuses `no_observations`, as for any named party, and never answers the side's members in its place. On a side no place is named on, where no dataset of the shape marks any of the population's parties, the side reads as unnamed only where those datasets hold some party there and every one, totals aside, is of a class the population's markers cover: "all donors" (`official` and `private`) and "all recipients" (`official` and `residual`) on the CRS, whose donors and recipients are of those classes, and "official donors" on DAC1, whose donors are all official. The answer then covers every party the dataset holds on the side and departs `whole_total_unpublished`, whose `asked` is the request's words and whose text names the dataset ("You asked for all donors. OECD Creditor Reporting System (CRS) publishes no total of every donor, so this covers every donor it holds with data, private donors among them."), and warns no `role_scope_defaulted` saying nothing was named there. A candidate set states one such departure for each dataset its shown readings read the side so from, and a no-data answer states it for the alternate it shows in its place (`only_alternate_has_data`), whose text names that match ("so the closest match covers every donor it holds with data"). Beside a place named on the side ("France and all donors"), or beside a population that does not cover it ("official donors and all donors" on the CRS), the population binds as named parties, and departs `entity_unavailable` where no series holds them. "Official donors" names `DACOfficialDonors`, whose markers cover `official` alone, and the CRS holds private donors too, so on the CRS it binds as a party the CRS holds no series of: DAC2a answers where the menu offers it, and a part naming the CRS refuses `no_observations`. A resubmit derives `whole_of` from the identity's dataset and the code index, whatever group it posts on that role. A role read as its whole population also carries `side_classes`, the classes of party, totals aside, that the answer's dataset publishes on that side, sorted (`["official", "private", "residual"]` for donors in DAC2a, `["official", "residual"]` in DAC2b); a class in `side_classes` and absent from `whole_of` is outside the total, and a resubmit derives the field from the identity whatever it posts. A side read whole departs as `whole_total_read`, whose `asked` is the request's words for the population ("all donors"), null when the request named nothing on the side; its text says what the total holds ("Private donors are outside it." for an official donors' total where the answer's dataset holds private donors on that side, so on DAC2a and not on DAC2b; "which includes aid not allocated to a country") and how to list the members with `open_roles`. A Spec that lists a side member by member, opened by `open_roles` or defaulted, where the dataset elected for its identity publishes that side's whole total over the requested years and the coverage snapshot holds a series of the Spec's own variables at that total beside the places its other side binds, carries the info warning `whole_total_available` with `params` `{total, total_label, whole_of}`, `whole_of` comma-joined, stamped with that Spec's `spec_id`, on a first answer and a resubmit alike: a definite part's Spec, each candidate a candidate set shows, so the set states it for the readings it holds for alone, and a no-data part's flagged and alternate matches; none when the other side reads whole | implemented | | | C173 | The price basis (`measurementQualifier`: current or constant prices) belongs to the unit. The measure noun, `measure_label`, and every measure-menu label never name it ("gross disbursements", not "gross disbursements (nominal)"); the byline's unit token states it ("current US dollars", "constant 2024 euros"). That token has axis `unit` and `binding_ref` `unit:`, and a departure on the price basis carries the same ref. Its options are the `resolved.alternative_measures` entries with `axis` `unit`: each a partition that differs from the spec's only in price basis, labelled by its prices ("constant prices"), picked by posting its `definition` on a `dataset_resubmit` identity entry like any measure switch. The entries with `axis` `what` are the measure menu, which offers each measure once whatever its price basis, at the spec's own price basis where the graph holds it | implemented | | | C175 | A heading or byline slot token with text carries `states`, the refs it states: its own `binding_ref`, then each binding the renderer left silent because the token's text already says it, in the `binding_ref` grammar plus `axis:` for a measure axis no slot carries. The measure token states the measure axes and flow type its label or authored noun carries, the how values it names, the open axes whose members agree beside a label or authored noun, and the how values a StatVar or graph name folds. The byline's unit token states `unit:` and, when it names a price basis, the `measurementQualifier` axis, which the measure token then leaves out. The byline's source token states `source:`, which is also its `binding_ref` where the answer binds a source slot; a token naming the resolved provenance carries no `binding_ref`. The byline's method token states `method:`, its `binding_ref`, and while the identity binds no method no token states `method:`. A token with empty text states nothing. Within one part, on its own plan and on the envelope plan (where a token with no `part_index` counts for every part), no ref appears on two tokens. A bound slot, an unbound slot with options, or a role that no token states (a boolean member, an adjectival fold dropped whole, an open role with no count) is edited apart from the sentence with its current value. A heading unit token whose phrase names the unit ("in percent", "as a share of GDP", "in PPP international dollars") has `binding_ref` `unit:` and states it; "per capita" and "relative to" name only the denominator and state nothing | implemented | |