Repository navigation
fix(overview-metrics): a capped or timed-out count is not measured, never the cap - #555
Merged
Merged
Conversation
…ever the cap
Live on build 082ecd44 the native count of a supertype (Asset, Process)
times out at the client's 30 s limit; _element_count fell back to
len(find), which stops at DEFAULT_CAP, and non-strict mode returned that
500 as the total ("Total Cataloged Assets = 500", "Processes = 500",
Semantic Grounding "500 links / 100%").
- _element_count: a fallback page that reached DEFAULT_CAP is None in
either mode, never the cap. count_relationships does the same for a
full get_relationships page. Each such None records a note
{count, reason, at_least} for recording_unmeasured() (a ContextVar),
e.g. "the native count timed out, and the find fallback stopped at its
500-element page cap, so there are at least 500".
- exec_report_spec collects the notes around an analytic routine: the
output gains "unmeasured"; a scalar count that was not measured, and
any SERIES/BAR/PIE over one, become kind "absent" with the reason and
lower bound in content (no chart: a missing bar would read as 0).
- Count demo specs (Element Count by Type, Assets by Type Breakdown,
Count by Property, People and Community, Usage Context) ask for
strict=True; sum_type_counts fetches strictly; internal callers
(semantic_grounding, orphan_glossary_terms, stale_assets,
contextualised_coverage, solution_component_realisation, growth_series)
count strictly; sum_counts' total is None when any row is.
- Declared not_measured sentences for count_elements(_by_property),
counts_by_type and semantic_grounding name timeouts and the cap.
Measured results are byte-identical; only failure and capped results
change, from 0 or 500 to None with the reason.
Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
This was referenced Nov 8, 2026
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.
A count that timed out is never reported as a measured total. On the live platform (build 082ecd44) the native count of a supertype (
Asset,Process) times out at the client's 30 s limit.overview_metrics._element_countthen fell back tolen(find), which stops atDEFAULT_CAP(500), and in non-strict mode, the default forcount_elementsand the demo specs, returned that 500 as the count. The Portal showed it as a measurement: "Total Cataloged Assets = 500", "Assets by Type Breakdown: Processes = 500", and Semantic Grounding "500 links / 100%". Found in the Architect's post-deploy read-back of RS-W2-3 (READBACK-RS-W2-3-portal-deploy-2026-11-08.md§1–2).Branch
fix/capped-count-not-measuredoff upstream/main 556a2bb (6.2.26). No Egeria writes. No version bump.Design: None plus a recorded reason, never a structured
{"at_least": n}valueWhen the find fallback reaches the cap, the count is
Nonein both modes. The bound and the reason are recorded as a note,{"count", "reason", "at_least"}, and the report-spec executor puts the notes on its output.Why not return
{"at_least": 500}fromcount_elements:count_elementsis a public helper typedOptional[int]. The Overview handler and other routines do arithmetic on it (sums, percentages,term_total - referenced). A dict there is aTypeError. Anintsubclass marked as a bound would be worse: it serialises as500, which is the bug again.Noneis already "not measured" in strict mode and in fix(overview-metrics): report unmeasured as None, not 0 #531, and every routine's declaration already has anot_measuredsentence for it. Adding a second absence shape would give every renderer two things to handle.at_leastin the note and as words incontent.How the reason reaches the output:
overview_metrics.recording_unmeasured()is a context manager over aContextVar. It collects every note made while it is open.exec_report_specopens it around each analytic routine.asset_composition's worker threads record into their own cell's block. A cell'sreasonnow names the bound (e.g. "...so there are at least 500").What a reader sees, per renderer
type_name: Asset){"kind":"json","data":500}{"kind":"absent","content":"The elements were not counted: … Not measured: Asset: the native count timed out, and the find fallback stopped at its 500-element page cap, so there are at least 500.","absence":{state:"not_measured",…},"envelope":{state:"not_established",…},"unmeasured":[{"count":"Asset","at_least":500,"reason":…}]}null;"unmeasured": [...]names each one with its reason and boundkind: "absent", with no chart and the data underdetail. A missing bar reads as 0, and a pie without a slice changes every share, so the chart is withheld as ISSUE-140 already does for a non-established stateChanges
overview_metrics._element_count: a fallback page that reachedDEFAULT_CAPreturnsNonein either mode, never the cap. A failed native count is described in words: "timed out", "failed (X)", or "no native count is available". Non-strict failure still returns its historical0; strict returnsNone, as before.count_relationships: a fullget_relationshipspage returnsNoneplus a note. This is the Semantic Groundinglinks = 500case, and also covers License/Exception/feedback/DataFlow counts."strict": Trueinanalytic_spec_params):sum_type_countsfetches strictly.sum_counts'totalisNonewhen any row is (a sum of the measured rows only would read as the whole total).semantic_grounding,orphan_glossary_terms,stale_assets,contextualised_coverage,solution_component_realisation,growth_series(the fix(overview-metrics): report unmeasured as None, not 0 #531 rule).exec_report_spec:unmeasured.absent, notempty("No results." would read as "nothing found").measuredenvelope is overridden tonot_establishedwhen counts inside it were not measured.not_measuredsentences forcount_elements,count_elements_by_property,counts_by_typeandsemantic_groundingnow name timeouts and the page cap.outshapes are unchanged: every changed field was already anintthat can be absent, so no shape changed.completenessandpopulationare untouched.Demo spec outputs: before (6.2.26) vs after
Snapshot of all 23 demo
FormatSets, and 111exec_report_specruns (DICT plus every chart type each spec offers). The runs use fake clients in three scenarios: ok (native counts answer), fail (every query raises), and timeout_cap (native count times out; find and get_relationships return a full 500 page).'strict': True. The five are Element Count by Type, Assets by Type Breakdown, Count by Property, People and Community, and Usage Context: Supply Chains & Blueprints.okrun is byte-identical. All 42 changes are fail or timeout_cap runs.500→ absent, "at least 500"0→ absent, "find fallback failed"500→ absent, "at least 500"0→ absentunmeasuredunmeasuredunmeasured(Asset, SemanticAssignment)unmeasuredunmeasuredunmeasuredunmeasured(duplicateCount) / chart → absentunmeasured/ → absentRelease note (user-visible)
Live timing (iii), read-only, client timeout 120 s
Method:
MetadataExpert.count_metadata_elementswithtimeout=120, run one at a time on 2026-11-08.https://localhost:9443, view serverqs-view-server, userresourceexplorernpa(RE .env).FindRequestBody, type name,ACTIVE.push_down=True)What this tells the owner:
The default client timeout is unchanged (30 s) in this PR.
Tests
uv run -p 3.13 pytest tests/micro-tests/, without the library: 2274 passed, 3 skipped.Asset-Graph,Data-Classes).test_capped_count_not_measured.py, 20 tests, each executing the branch. They cover:Nonein both modes, with the "at least 500" note;None(strict);sum_countsnever adds a bound into a total;exec_report_specfor the tile (absent,at_least500), a measured tile unchanged, Breakdown rows, BAR/PIE withheld, series notes deduplicated;test_issue_123_strict_counts.py::test_strict_returns_none_for_a_capped_fallbackpinned the old non-strict behaviour (== DEFAULT_CAP). It is nowtest_a_capped_fallback_is_none_in_either_mode_never_the_cap.ruff --select F,E9is clean on the new code. The remaining findings in the touched modules pre-exist on upstream/main.Consumers (not changed here)
local-dashboards.htmlrenderExecResult(live checkout 3156a8d2, read only):kind: "absent", so such a result falls to its last branch: a<pre>dump of the whole JSON. That is honest (the sentence and the "at least 500" are visible) but not a tile, and it predates this PR: ISSUE-171'sabsentwas never rendered there.nullcells render blank (v == null ? ''), andunmeasuredis ignored, so a reader sees an unexplained gap rather than a 500.report_specs_handler.pypasses the dict through unchanged.kind: "absent"→ a KPI tile showing "Not measured" pluscontent, or the firstunmeasured[].at_leastas "≥ 500 (not measured)";kind: "json"withunmeasured→ "not measured" in null cells and a note listing the reasons.overview_handler.pycallscount_elementsnon-strict in two places:surveyReports, anddataStoreTotalwhenstores_capped. It also callssemantic_grounding. These can now getNonewhere they used to get 500. Check that they renderNoneas unmeasured.curation_coverageover RE's own ODS registry, not Egeria counts, andcurate-coverage.jsalready showsnot_established/not_applicableas the state plus the reason, with no table.Follow-ups (same defect family, not changed here)
Capped element scans (not counts) still report a page-cap value as a number:
*Cappedflag:governedCount,ownershipCount,assetTotalin Business Value Signals,totalin AI-Ready Assets;certifications_summary.active,term_definition_completeness.total,stale_assets.staleCount,contextualised_coverage.contextualisedCount,orphan_glossary_terms.referencedCount.🤖 Generated with Claude Code