Skip to content

fix(harness): name a share's named members as its whole where the numerator holds others - #85

Merged
jm-rivera merged 12 commits into
devfrom
fix/share-wholes-kept-and-named
Oct 26, 2026
Merged

jm-rivera merged 12 commits into
devfrom
fix/share-wholes-kept-and-named

Conversation

@jm-rivera

@jm-rivera jm-rivera commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

A share whose denominator names its members gets no whole on its card. "What share of the United Kingdom's and the United States' ODA to Kenya came from the United States?" divides the US figure by the two donors together, yet the card title names only the period, so the reader cannot tell the share from the US share of all aid to Kenya. ai#79 names a whole only for named sets (DAC countries, G7) and for open axes; a column holding entities the question lists by name had no population and no entry in _populations.

This change gives such a column a population. _named_entities in services/harness/src/harness/plan_display.py resolves each named entity through the denominator's source (resolve_name) and identifies the column by its sorted graph refs, worded by entities_phrase. A name that a set of the source answers (resolve_group) stays the set's population, as before. If any name resolves to nothing, the dataset gives no named population, since the column is then unknown.

_divided_whole names a named column off the share axis only where the numerator is known to hold other entities there by identity. The numerator's own sources say what it holds. When it narrows one entity out of its frame (RequirementSide.entity), _narrowed_entity puts that entity on the column its requirement names it under (side_entities), and the frame's other columns stay as its sources hold them. A column both sides hold the same way is left out, so Kenya drops out, and the card reads "from United Kingdom and United States" in both plan forms: a separate US retrieval, or the US narrowed out of the two-donor frame. Both sides narrow alike (_side_populations), so a denominator taking the United Kingdom's row of a two-donor frame reads "from United Kingdom"; ai#79 gives such a share no whole. A side narrowed to an entity its sources do not resolve to one ref has no population: the denominator then names no whole, and the numerator shows nothing to compare. A list of names too long for the 120-character title goes to the card's note, as ai#79 does for any whole. On a pooled axis the existing rule holds, and ai#79's narrowing (_reads_whole) and ratio (_keeps_shares) rules apply to named columns as well.

Names keep the requirement's words with no article ("United Kingdom", not "the United Kingdom"). Neither the graph's labels nor planner.phrasing holds an article rule for place names, and adding one belongs with the phrasing helper, not with this change.

Frames kept from an earlier run of the plan call need no whole. _kept_asks (graph/execute.py) runs only on a stopped outcome and keeps presentable_steps, which drop every derived ops_ frame (graph/answer.py), so a kept entry holds retrieval frames only, and share_wholes gives a retrieval step no whole.

This builds on ai#81 and should merge after it.

Testing: test_a_share_of_named_donors_names_them_where_the_numerator_holds_others in tests/test_plan_display.py has seven rows over a new NAMED_DONORS source in tests/share_plan.py. named_donors and narrowed_out_of_the_frame cover both plan forms. narrowed_out_of_another_frame narrows the US out of UK and US ODA to Kenya and divides by the two donors' ODA to Kenya and Uganda, expecting the recipients named too; named_on_both_sides narrows out of a frame holding the United States as donor and recipient. Both fail when the numerator is assumed to hold the denominator's frame. same_donor and same_ref name the numerator's donor in the denominator, the second as "USA", and expect only the recipients named. value_filter filters donors to at least $1bn and expects no whole. test_a_denominator_narrowed_to_one_entity_names_that_entity expects "to every recipient and from United Kingdom" for the United Kingdom's row of a two-donor frame. ruff check, ruff format --check and ty check src are clean, and the full harness suite passes except test_server.py's zero-tools check, which fails locally only because a proxy listens on port 8083.

…s plan

"What share of ODA to Kenya came from the United States?" divides by
official donors (DAC) in some runs and by all providers in others, and
neither the card nor the approved plan said which. make_charts now
writes the whole into a share card's title from the denominator's
selected source, as it writes the period, and the plan's fetch label
and share sentence name the group or member count that source holds in
place of "every donor".
A denominator filtered by value or cut to its top members before its
total still read "of the total from all 140 donors"; a per-period rank
writing positions carried the share's whole; and the whole a numerator
also holds was matched by wording. share_wholes now names no whole past
a narrowing step, stops at a step whose output is not a ratio, and
compares populations by group ref or axis and member count. The plan's
share sentence names the narrowing step in place of the parties.
A plan said "Uganda and all 108 donors" and "summed over its 288
indicators" while the answer said 92 providers with data in 2022 and 13
indicators, with nothing to tie the two. The plan now says "the 108
donors the source holds in any year" and "summed over whichever of its
288 indicators hold data", a share card's whole names an open axis with
no count, and the orchestrator instruction quotes the new phrases.
…guess

The plan's donor count is already the providers with data in the plan's
years (and, with dfo-apis counting parties only, the donors the frame
holds), so "the N donors the source holds in any year" was false; it
reads "all N donors" again. The indicator clause states Search's exact
count of indicators with data where every entity axis is named
("summed over its 13 indicators"), and the family size otherwise. A
flow side's count comes from role_axis_facts over the scope list's
length, and the orchestrator instruction says what each count counts.
…re's whole

A numerator narrowed to one entity now holds what its own input's sources
hold, with that entity on the column its requirement names it under
(side_entities), in place of assuming the denominator's frame. A share of
the US out of UK and US ODA to Kenya, divided by the two donors' ODA to
Kenya and Uganda, now names the recipients too.
Two open populations are the same only when their sources are one dataset,
since sources record counts and no members; a different dataset of as many
donors keeps its clause. A denominator reduced by a mean, maximum or minimum
names no total, and neither does one narrowed to one entity. A card title
keeps its whole only where the whole subject fits beside it in 120
characters; otherwise the whole goes in the card's note.
… fix/plan-counts-say-what-they-count

# Conflicts:
#	services/harness/src/harness/plan_display.py
#	services/harness/tests/test_plan_display.py
…enominator's entity

ai#79 now gives no whole for a denominator narrowed to one entity. Here both
sides of a share narrow alike (_side_populations): the narrowed entity sits on
its column (_narrowed_entity) and the input's other populations stay, so the
United Kingdom's row of a two-donor frame reads "from United Kingdom". Named
populations take a string-tuple identity, as open axes now do.
@jm-rivera
jm-rivera changed the base branch from fix/plan-counts-say-what-they-count to dev October 26, 2026 14:53
…and-named

# Conflicts:
#	services/harness/src/harness/plan_display.py
#	services/harness/tests/share_plan.py
#	services/harness/tests/test_plan_display.py
@jm-rivera
jm-rivera merged commit a30b5d4 into dev Oct 26, 2026
3 checks passed
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