Repository navigation
feat(search): offer each choice refusal's options as ready requests, and skip qualifiers the answer applies - #66
Merged
Merged
Conversation
…served at that basis
A single-variable raw-text question passes the whole query to the region, which reads "constant" as a RealValue requirement. The judge then found no measurementQualifier binding and raised query_qualifier_dropped ("The requested qualifier "constant" was not applied.") on answers served in constant 2022 euros.
The judge now takes each answer's served price basis, read from the Spec's unit request, and counts a measurementQualifier requirement as met when that basis reads as one of the rule's codes: constant prices meet RealValue and InflationAdjusted, current prices meet Nominal. A question for constant prices answered from a current-price series still warns.
`no_data.options` lists one `{mentions, label, data_query}` per reading the client can choose, on entity_ambiguous, source_ambiguous, source_code_ambiguous, role_not_resolved and unit_not_resolved. Each `data_query` is a complete parsed request that the client posts back unchanged. The list replaces `no_data.data_query`.
role_not_resolved offers each giving and receiving assignment of the named places, up to six, and drops a reading that its own role words contradict. A part whose directions were all read with none holding data offers nothing. unit_not_resolved offers each currency the words can name, so "francs" offers CHF, XAF and XOF.
…nswer leaves that role open
"aid from all donors to Ethiopia in 2022" dropped "all donors" with a `query_qualifier_dropped` warning and a `measure_not_applied` departure, though the answer totals across every provider. `role_text.every_member_axis` reads a dropped phrase such as "all donors", "every provider", "to all creditors" or "from all countries" as a request for one flow axis, and `region.result.every_member_phrases` admits it when the answer is role-bearing and binds no place on that axis. A phrase on a subject-only answer, on an axis a named place binds, or outside the role lexicon ("in all sectors") still warns.
The response cache states the named axis in its layer 2 key, so "from all countries" and "to all countries" never share an entry.
…ery-member words on every answer
A `unit_not_resolved` option names a currency whose code is also an English word ("COP") by its display name, which the unit binder now reads, so each of the 13 ambiguous codes offers words that bind back to it.
"each" and "every" ask for a breakdown, so they are met only when every answer declares a role on that axis; "all" is also met by a total. A candidate set meets an every-member phrase only when every candidate does. The `NoData.options` description lists when the list is empty. The price-basis judge reads `PRICES_BY_QUALIFIER` in place of its own copy.
…d post only unambiguous group names `_settle_direction` refuses `no_direction_holds` only when every direction was read and each read is empty; a read the coverage index cannot answer is unknown, so the role refusal still offers its readings. A group candidate's option posts a registered name only when that name resolves to the group alone, and has no option otherwise. The trace's query qualifiers, on the definite answer and on each candidate, are judged under the unit request the answer is served under, as the warnings are.
… meets A dropped every-member phrase that one part's region admits cleared the departure on every part of the request, so "aid to Kenya and GDP of Kenya from all donors" stated no departure on the GDP part, whose subject-only answer reads no donor. Each region now carries the request-wide qualifier warnings its own region leaves unapplied (`RegionResult.unapplied_qualifier_warnings`), and the part's departures, including late departures, read them beside its own and the envelope's warnings. The envelope's `query_qualifier_dropped` warning keeps its rule: one region admitting the phrase clears it.
…option round trip A role refusal's option mentions came from `unresolved_entity_names` while its readings came from `role_assignments`, so a name with no place set, or a set named by a pinned node's label, could appear in the mentions without any option settling it. `_role_options` now takes the mentions from the texts the readings map, in the request's words. `_source_options` keeps one group per wording in a dict. The choice-option round trip requires a 200 for every posted option. Conformance clauses C191 and C192 state the price-basis and every-member rules, and `src/search/README.md` states the price-basis rule.
… texts, in query order A `role_not_resolved` refusal named each untold place set once, by a graph label when the set's only text was a pinned key, while its options rewrote every text of those sets. A client saw `entity_terms` and the message say "Kenya" where every option's `mentions` said "KE", and the mentions came grouped by place set. `untold_direction_names` now returns every request text of each untold set in `part.entities` order, and `direction_readings` takes its untold sets from the same grouping (`place_roles._untold_groups`), so the option mentions, `entity_terms` and the message read one list. `NoData.entity_terms` names `role_not_resolved` among the reasons that carry it.
…ce's texts together
A `role_not_resolved` refusal now quotes every text of each untold place set, so one place named twice read as two parties: "the union" (pinned to the EU) and "European Union" with Kenya as destination gave "Could not tell who is giving and who is receiving among "the union" and "European Union"." `RegionResult.untold_groups` carries each untold set's texts (`place_roles.untold_direction_groups`), and the no-data renderer chooses its wording by the number of places, writing a place's further texts in parentheses: "Could not tell whether "the union" ("European Union") is giving or receiving." `entity_terms` and the option mentions stay flat, in query order.
…ocument the naming and departure rules
A role option's label named one place once per text: `Kenya`, `Norway` and a pinned `KEN` gave "Kenya and KEN to Norway" where the message names the place as "Kenya" ("KEN"). `render.no_data.untold_place_name` now writes a place for both the message and the labels, and `conjoin._untold_terms` gives `entity_terms` and the option mentions one list. The README and C192 state that the every-member warning is request-wide and stays while no part meets the phrase, while each part whose own answer leaves it unmet departs; C50e states the role-refusal naming rule.
…statements Docstrings and comments the choice options and the qualifier rule added read as present-tense sentences, and C50e says when options are offered in one sentence. No behaviour changes.
This was referenced Oct 21, 2026
Merged
jm-rivera
marked this pull request as ready for review
October 22, 2026 13:35
Validate ambiguous source candidates against the resolved include and exclude scope before offering ready requests. Preserve parent-source exclusions and reject conflicting choices for repeated mixed-polarity mentions.
`_option` in src/search/engine/conjoin.py compiles each option's sources against the bound catalogue's dictionary and drops an option the parsed door would refuse, so a choice-asking refusal offers only requests the server accepts. A source wording whose every reading empties the scope is refused with request_invalid at the excluded mention (`_refuse_every_reading_empty`), at one compile per candidate. The raw-text door still answers no_data. `blocked_choices` and `_source_choices` are removed.
Keep dev's measure-qualifier warnings, `_admitted_phrases` and `MeasureRule`, and the branch's unapplied-qualifier warnings, every-member phrases and price-basis check. A `MeasureRule` is also met by the served unit's price basis. The branch's conformance rows become C194 and C195, since dev took C191 to C193.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Every Search choice refusal supplies
no_data.options, each withmentions, a label, and a completedata_querythat callers can post unchanged. Entity, source, source-code, role, and unit choices pin the selected interpretation. Options beyond parsed-input bounds are omitted. Every option passes through_optioninsrc/search/engine/conjoin.py, which compiles its sources against the bound catalogue's dictionary and drops an option the parsed door would refuse, so a posted option is accepted. A source wording whose every reading empties the scope, such as one both included and excluded, is refused withrequest_invalidat the excluded mention before any choice is offered (_refuse_every_reading_emptyinsrc/search/engine/source_scope.py), at one compile per candidate. The raw-text door, whose mentions a model wrote, still answersno_data. A contradiction spread over two ambiguous wordings still answerssource_ambiguouswith no options; the refusal applies once one ambiguous wording remains.Dropped-qualifier warnings skip words the answer already applies. Constant-price requirements steer towards and recognise constant-price series. Phrases asking for every member of a flow role are met when the answer represents that scope. Candidate sets satisfy a phrase only when every reading does, and multi-part requests assess each part separately. Cache keys preserve the role axis.
The website consumes the ready requests in DevelopmentFinanceObservatory/website#42. Choice refusals remove
no_data.data_queryand supply each request atno_data.options[*].data_query.src/search/README.mdand the conformance document describe the behavior.Testing: all of
tests/enginepassed serially on this head (18,644 passed, 24 skipped, 1 xfailed), and again on #67's head after merging this branch (18,674 passed), with Ruff and ty clean. An earlier composition ofdev, #65, #66, #67, #68, and #72 passed 200 selected tests covering the contract gate, registry binding, source choices, source scope, and assumptions. The Search corpus evaluation needs its required result before merge.