Skip to content

feat(search): draw an answer's assumptions and post choice options unchanged - #42

Merged
jm-rivera merged 13 commits into
devfrom
feat/answer-assumptions
Oct 23, 2026
Merged

jm-rivera merged 13 commits into
devfrom
feat/answer-assumptions

Conversation

@jm-rivera

Copy link
Copy Markdown
Contributor

The answer block states what Search assumed, and every edit, pick, share and permalink echoes those assumptions back. A part's assumptions close the block as one paragraph of clauses ("Assumed: official development assistance; every provider."), drawn from the plan's assumption tokens by assumptionClauses in src/lib/search/departures.ts. In a response of several parts a clause carries the variable text of the part that owns its lead token, and a clause naming several parts' items carries no label. A mixed answer's coverage tokens ("Answered 1 of 2 parts.") draw as a muted lead above the <h1>. planSentenceText reads heading tokens only, so the heading's accessible name, the run label and the Studio hand-off question leave the coverage line out. "About this answer" states each item as a sentence in the words of its text (scopeAssumptions in src/lib/search/view.ts), reading an open role from its origin: or where: ref.

Every resubmit posts the echo Search defines (src/lib/search/datasetEntries.ts). A definite part's identity entry posts its assumptions, [] included. A pick posts the chosen reading's reading_assumptions[spec_id], a candidates entry posts each reading's own list, and an edit of a no-data part posts none, so it reads as a fresh request. A permalink keeps each stored edit's own echo through replayableEntry, and an entry that carries an echo keeps it, so a replay posts what the live edit posted. A share stores the list in share_ref.assumptions (db/migrations/0018_share_ref_assumptions.sql), and the share and permalink validators refuse a list Search would refuse (isAssumption: at most 67 items, text of 1 to 200 code points).

The entity picker is built from no_data.options, grouped by mentions, and posts the chosen option's part unchanged. Nothing reads the removed no_data.data_query, and NoDataReason gains source_code_ambiguous. The source, role and unit refusals list options the page does not offer yet.

The types mirror DevelopmentFinanceObservatory/apis#66 and DevelopmentFinanceObservatory/apis#67. Merge this after both deploy to dev; against today's dev the refusals carry no options and no part carries assumptions.

Testing: pnpm lint, pnpm lint:css and pnpm typecheck pass at 85eea2b, and node --test over every suite except db/test passed 1,600 tests. The db/test suite, which holds the share round trip and migration 0018, needs a Postgres and did not run; CI runs it once the PR is marked ready.

…changed

The Search API now lists each part's assumptions and closes the sentence with an "Assumed: ..." clause in `assumption` tokens. The answer block draws that clause as its own paragraph after the departures, and "About this answer" states each assumption from `part.assumptions` in place of the place-scope and role-scope warnings, so each is said once. A dataset_resubmit identity entry echoes the part's assumptions back unchanged, so an edit keeps the defaults it did not touch.

`no_data.options` replaces `no_data.data_query`, and an entity pick posts the matching option's request. `source_code_not_held`, `source_code_ambiguous` and `source_code_shares_variable` get their own messages.
An empty list tells the engine the previous answer stated no assumptions, so the edited answer states none the echo lacks. Only a response without the field leaves the echo off.
…part sentence

A sentence written part by part labels each byline with its part, so its clauses take the same label even when only one part states a clause; otherwise that clause reads as covering the whole answer.
… by their lead, and keep a share's assumptions

The entity picker mapped a picked candidate to its option by dcid. A group candidate on a members row posts the group's registered name with no dcid, so the match failed and the fallback pinned the group with `members: true`, a request the API refuses. Both pickers (`SearchCards.tsx`, `SearchSentence.tsx`) now list `no_data.options` grouped by their mentions (`entityChoiceGroups`) and post the chosen option's `data_query` part unchanged; the restatement rewrite is gone. A parsed entry keeps the operations it was built with.

`assumptionClauses` labels a clause by the owner of its lead token whenever the answer has more than one part, so a definite part beside a candidates or no-data part names its clause, and a clause every folded part shares carries no single part's name.

A share replays through `dataset_resubmit`, where a missing echo states no declared default, so a shared ODA answer lost "official development assistance". `share_ref` keeps each part's `assumptions` (migration `0018_share_ref_assumptions.sql`), and replay echoes them; a share stored before the migration replays with none.
`isAssumption` checked an echoed assumption's shape but none of Search's bounds, so a share could store a text past 200 code points, two refs or 68 items, and every replay of it failed with `request_invalid`. The share request now applies Search's bounds on `Assumption` and on a part's list, as it does for `EntityGroup`.
…ing's on a candidates entry

A candidates part's `assumptions` holds only what its readings share, and a pick echoed that list, so the picked answer dropped the reading's own open role or place scope. A pick (`candidatePickEntry`) now echoes `reading_assumptions[spec_id]`, a candidates entry echoes each reading's list on its candidate, and a reading the response gives no list for posts no echo. Only a definite part's identity entry echoes the part's own list: an edit of a no_data part reads fresh. `types.ts` mirrors `CandidatesPart.reading_assumptions` and `ResubmitCandidate.assumptions`, and the `AssumptionKind` comment uses the API's wording for an open role.
A permalink rebuilt each identity entry from four fields and dropped its `assumptions`, so a reloaded candidates pick, which echoes its reading's own list, replayed with no echo and stated no declared default. `replayableEntry` keeps the echo, `[]` included, when it passes the share request's validator (`isAssumptions`, now shared), and drops an echo Search would refuse.
A permalink replay posts each stored edit with the echo it was posted with. `withAssumptions` overwrote that echo with the fresh part's list, so an edit that had narrowed the list (a flow set back to ODA after an edit to OOF posts `[]`) replayed with the default stated again. An identity entry that already carries `assumptions` now keeps them; a live edit's entry carries none and still takes its part's list.
Search sends "Answered N of M parts." as tokens of its own `coverage` segment (DevelopmentFinanceObservatory/apis `feat/search-answer-statements`). The answer block draws it as a muted lead above the `<h1>`, beside "Closest match", and `planSentenceText` reads heading tokens only, so the heading's accessible name, the run label and the Studio hand-off question no longer open with the coverage line. `planCoverageText` reads it.
…ut this answer

Search sends an open destination role as binding_ref "where:", the slot the destination shares with the subject. assumptionSentence branched on "destination:", so the sentence dropped the provider that limits the scope ("covers every recipient" for "covers every recipient from Germany"). The why and resultNotes fixtures now carry the served ref.
…faulted slots from the About summary comments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant