Repository navigation
fix(novelty): the dpnp heading-query 0.0 is a template artifact, not a finding #712
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
antonwolfy
merged 9 commits into
intel:main
from
antonwolfy:docs/heading-query-artifact
Nov 14, 2026
+220
−13
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
646c7e2
fix(novelty): the dpnp heading-query 0.0 is a template artifact, not …
antonwolfy 0ce0e09
docs(novelty): the changelog fragment names the command `novelty rout…
antonwolfy ab26e00
fix(novelty): shared-query warning no longer claims equal ranks, skip…
vchamarthi da5ab7a
Merge remote-tracking branch 'origin/main' into HEAD
vchamarthi bd594a7
fix(novelty): warn about shared queries from drop_queries_left_out to…
vchamarthi 793118c
feat(novelty): record the shared-query counts in the all-vs-all result
antonwolfy 1347abd
Merge main into docs/heading-query-artifact
antonwolfy 2e0f942
fix(novelty): count two queries as shared when the scorer reads them …
antonwolfy d206c17
fix(novelty): count two queries as shared when only their word order …
antonwolfy File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| ### Fixed | ||
|
|
||
| - **The dpnp 0.0 recall@1 is no longer presented as a finding** — the novelty docs, the `--competitors` help and the `note` of every lexical routing result said every `dpnp-*` skill scored 0.0 against its siblings. That figure came from queries drawn from the skills' section headings, which are one template across the family, so it measured the template, not the descriptions. They now say so and point to queries written independently of the skills. `catalog routing` warns on stderr when half or more of the queries in `--queries-by-skill` are listed for more than one skill, before the estimate and before any model call, since the same text is then the gold of several skills and the result reflects the queries, not the descriptions. |
3 changes: 3 additions & 0 deletions
3
dokimi/changelog.d/20261113_160000_shared-queries-in-result.md
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| ### Added | ||
|
|
||
| - **The `catalog routing` result records `shared_queries`** — `{"shared": 9, "total": 9}`: how many of the queries with a content word are listed for more than one skill. The shared-query warning went to stderr only, so a file saved with `--out` from a run on queries drawn from one template read later as a normal own-rate table. Files saved before this change lack the key. Two queries are the same when the lexical scorer reads them as the same content words: case, spacing, trailing punctuation and word order do not tell `Quick Start` from `Quick Start.` or `start quick`, while `Straße` and `STRASSE` stay two queries. |
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
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
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
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| # Copyright (C) 2026 Intel Corporation | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| """Queries that several skills share: a field-wide run on them says nothing about descriptions.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from dokimi.novelty.routing import tokenize | ||
|
|
||
| # Share of the given queries listed for more than one skill at which a run is warned. | ||
| SHARED_QUERY_WARN_SHARE = 0.5 | ||
|
|
||
|
|
||
| def _key(query: str) -> tuple[str, ...]: | ||
| return tuple(sorted(tokenize(query))) | ||
|
|
||
|
|
||
| def shared_query_counts(queries_by_skill: dict[str, list[str]]) -> tuple[int, int]: | ||
| """``(shared, total)``: the queries listed for two or more skills, and all the queries. | ||
|
|
||
| A query counts as shared when the lexical scorer reads it as the same content words (case, | ||
| spacing, punctuation and word order aside; a repeated word still counts) as a query of another skill. Queries with no content word are | ||
| not scored by either arm and are in neither count. | ||
| """ | ||
| scored = {name: [q for q in queries if tokenize(q)] for name, queries in queries_by_skill.items()} | ||
| owners: dict[tuple[str, ...], set[str]] = {} | ||
| for name, queries in scored.items(): | ||
| for query in queries: | ||
| owners.setdefault(_key(query), set()).add(name) | ||
| total = sum(len(queries) for queries in scored.values()) | ||
| shared = sum(1 for queries in scored.values() for query in queries | ||
| if len(owners[_key(query)]) > 1) | ||
| return shared, total | ||
|
|
||
|
|
||
| def shared_queries_warning(queries_by_skill: dict[str, list[str]]) -> str | None: | ||
| """Warning text when half or more of the queries are shared by several skills, else ``None``. | ||
|
|
||
| Sibling skills built from one template have identical heading queries, so the same text is | ||
| the gold of several skills and the result reflects the template, not the descriptions. | ||
| """ | ||
| shared, total = shared_query_counts(queries_by_skill) | ||
| if len(queries_by_skill) < 2 or not total: | ||
| return None | ||
| share = shared / total | ||
| if share < SHARED_QUERY_WARN_SHARE: | ||
| return None | ||
| return (f"{shared} of {total} queries ({share:.0%}) are listed for more than one skill, as " | ||
| "when sibling skills built from one template give the same heading queries: the same " | ||
| "text is the gold of several skills, so the result reflects the queries, not the " | ||
| "descriptions; use queries written independently for each skill") |
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,133 @@ | ||
| # Copyright (C) 2026 Intel Corporation | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| """Heading queries shared by sibling skills: the warning, and a lexical note that claims no finding.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import json | ||
|
|
||
| from dokimi.cli import build_parser | ||
| from dokimi.commands.novelty_routing_report import warn_shared_queries | ||
| from dokimi.novelty.all_vs_all import all_vs_all | ||
| from dokimi.novelty.query_overlap import ( | ||
| SHARED_QUERY_WARN_SHARE, | ||
| shared_queries_warning, | ||
| shared_query_counts, | ||
| ) | ||
| from dokimi.novelty.routing import Candidate, headings_as_queries, lexical_routing | ||
|
|
||
| TEMPLATE = "## Quick Start\n\n## Implementation Guide\n\n## Gotchas & Limitations\n" | ||
|
|
||
|
|
||
| def test_sibling_skills_on_one_template_give_identical_heading_queries(): | ||
| queries = headings_as_queries(TEMPLATE) | ||
| warning = shared_queries_warning({"dpnp-a": queries, "dpnp-b": queries, "dpnp-c": queries}) | ||
| assert warning is not None | ||
| assert "9 of 9 queries (100%)" in warning | ||
| assert "same rank" not in warning | ||
| assert "independent" in warning | ||
|
|
||
|
|
||
| def test_case_and_spacing_do_not_hide_a_shared_query(): | ||
| warning = shared_queries_warning({"a": ["Quick Start"], "b": ["quick start"]}) | ||
| assert warning is not None | ||
| assert "2 of 2" in warning | ||
|
|
||
|
|
||
| def test_trailing_punctuation_does_not_hide_a_shared_query(): | ||
| assert shared_query_counts({"a": ["Quick Start"], "b": ["Quick Start."]}) == (2, 2) | ||
|
|
||
|
|
||
| def test_word_order_does_not_hide_a_shared_query(): | ||
| assert shared_query_counts({"a": ["quick start"], "b": ["start quick"]}) == (2, 2) | ||
|
|
||
|
|
||
| def test_a_repeated_word_keeps_a_query_apart_from_the_single_word(): | ||
| assert shared_query_counts({"a": ["start start"], "b": ["start"]}) == (0, 2) | ||
|
|
||
|
|
||
| def test_queries_the_scorer_tokenizes_differently_are_not_shared(): | ||
| assert shared_query_counts({"a": ["Straße"], "b": ["STRASSE"]}) == (0, 2) | ||
|
|
||
|
|
||
| def test_the_threshold_is_half_of_the_queries_listed_for_several_skills(): | ||
| assert SHARED_QUERY_WARN_SHARE == 0.5 | ||
| at = {"a": ["shared one", "own a1"], "b": ["shared one", "own b1"]} | ||
| below = {"a": ["shared one", "own a1", "own a2"], "b": ["shared one", "own b1", "own b2"]} | ||
| assert shared_queries_warning(at) is not None # 2 of 4 | ||
| assert shared_queries_warning(below) is None # 2 of 6 | ||
|
|
||
|
|
||
| def test_queries_written_per_skill_draw_no_warning(): | ||
| assert shared_queries_warning({"a": ["profile a binary"], "b": ["slice an array"]}) is None | ||
|
|
||
|
|
||
| def test_one_skill_or_no_queries_draw_no_warning(): | ||
| assert shared_queries_warning({"a": ["x y", "x y"]}) is None | ||
| assert shared_queries_warning({"a": [], "b": []}) is None | ||
|
|
||
|
|
||
| def test_the_warning_does_not_claim_equal_ranks_the_run_does_not_give(): | ||
| cands = [Candidate("a", "alpha"), Candidate("b", "gamma")] | ||
| queries = {"a": ["alpha"], "b": ["alpha"]} | ||
| rates = all_vs_all(cands, queries)["lexical"]["own_rate"] | ||
| assert rates["a"] != rates["b"] | ||
| assert "same rank" not in shared_queries_warning(queries) | ||
|
|
||
|
|
||
| def test_queries_with_no_content_word_are_not_counted(): | ||
| queries = {"a": ["the", "and", "profile a binary"], "b": ["the", "and", "slice an array"]} | ||
| assert shared_queries_warning(queries) is None | ||
|
|
||
|
|
||
| def test_the_warning_goes_to_stderr(capsys): | ||
| shared = headings_as_queries(TEMPLATE) | ||
| warn_shared_queries({"dpnp-a": shared, "dpnp-b": shared}) | ||
| captured = capsys.readouterr() | ||
| assert captured.out == "" | ||
| assert captured.err.startswith("WARNING: 6 of 6 queries (100%)") | ||
|
|
||
|
|
||
| def test_the_lexical_note_does_not_present_the_family_zero_as_a_finding(): | ||
| result = lexical_routing([Candidate("a", "alpha beta"), Candidate("b", "gamma")], | ||
| ["alpha beta"]) | ||
| assert "0.0 for every" not in result["note"] | ||
| assert "independently" in result["note"] | ||
|
|
||
|
|
||
| def _run_all_vs_all(tmp_path, *extra): | ||
| root = tmp_path / "catalog" | ||
| for name in ("a", "b"): | ||
| skill_dir = root / "skills" / name | ||
| skill_dir.mkdir(parents=True) | ||
| (skill_dir / "SKILL.md").write_text( | ||
| f"---\nname: {name}\ndescription: tune {name} things\n---\n\n# B\n", encoding="utf-8") | ||
| queries = tmp_path / "q.json" | ||
| queries.write_text(json.dumps({"a": ["quick start"], "b": ["quick start"]}), encoding="utf-8") | ||
| args = build_parser().parse_args( | ||
| ["catalog", "routing", str(root), | ||
| "--queries-by-skill", str(queries), "--out", str(tmp_path / "out.json"), *extra]) | ||
| args.func(args) | ||
|
|
||
|
|
||
| def test_all_vs_all_result_records_the_shared_query_counts(): | ||
| cands = [Candidate("a", "alpha"), Candidate("b", "beta")] | ||
| queries = {"a": ["alpha one", "the", "own first"], "b": ["Alpha one", "own second"]} | ||
| assert all_vs_all(cands, queries)["shared_queries"] == {"shared": 2, "total": 4} | ||
|
|
||
|
|
||
| def test_a_saved_all_vs_all_file_carries_the_shared_query_counts(tmp_path): | ||
| _run_all_vs_all(tmp_path) | ||
| saved = json.loads((tmp_path / "out.json").read_text(encoding="utf-8")) | ||
| assert saved["result"]["shared_queries"] == {"shared": 2, "total": 2} | ||
|
|
||
|
|
||
| def test_all_vs_all_warns_of_shared_queries_on_stderr(tmp_path, capsys): | ||
| _run_all_vs_all(tmp_path) | ||
| assert "WARNING: 2 of 2 queries (100%)" in capsys.readouterr().err | ||
|
|
||
|
|
||
| def test_estimate_only_warns_of_shared_queries_before_returning(tmp_path, capsys): | ||
| _run_all_vs_all(tmp_path, "--estimate-only") | ||
| assert "WARNING: 2 of 2 queries (100%)" in capsys.readouterr().err |
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.