Repository navigation
Conversation
StructuredModel._calculate_object_level_metrics has no caller. The one call site is inside StructuredListComparator, which does not inherit from StructuredModel and calls its own copy. ConfusionMatrixBuilder's calculator property and _calculator slot are never read. Both had zero executed lines across the full suite. Remove them, the HungarianHelper import that only the first used, and the docstrings that describe the builder as coordinating a ConfusionMatrixCalculator. classify_field_for_confusion_matrix now returns only the six counts. Its one caller, StructuredListComparator._handle_non_hierarchical_field, adds up those counts for every matched pair and primitive sub-field and never read the derived metrics that came with them. It is now a staticmethod that takes the ground truth model, so the StructuredModel wrapper no longer builds a ConfusionMatrixCalculator on every call. The StructuredModel branch in classify_field_for_confusion_matrix stays. is_structured_field_type does not recognise a list element sub-field annotated Union[A, B] or Any, so model values on such a field reach the classifier. Without the branch the default LevenshteinComparator would score the text of the two models, and a pair that scores 0.0 as models would count as a TP. A new test pins it. Also correct the StructuredModel size in the refactoring notes (2029 lines now, not ~1486), and name the shims awslabs#390 removed and what does their work now. Refs awslabs#395
sromoam
left a comment
There was a problem hiding this comment.
Thanks, this is a clean removal. I checked it against 623a408: the 6 new tests pass, and on dev the two TestVerdictShape tests fail, as the description says.
The only change I'm asking for is one sentence of docs. In docs/structured_model_REFACTORING.md, line 159 still says the private wrappers "were not removed", and the new paragraph on line 161 lists four that were. Rewording 159 to match is all that stands between this and an approval. Everything else below is optional.
Optional, outside the diff: comparison_engine.py:25, aggregate_metrics_calculator.py:21 and derived_metrics_calculator.py:21 still list ConfusionMatrixCalculator as a component that calculates the matrix, the same claim this PR corrects in the builder and StructuredModel docstrings.
Not for this PR: while testing I found that a dict in an Any sub-field of a list item crashes compare_with(). It fails the same way on dev and this PR doesn't touch that path, so I filed it as #400, with a direction that would also cover #395's remaining items 3 and 5.
|
|
||
| Older callers (and many tests) reach for private methods on `StructuredModel` such as `_dispatch_field_comparison`, `compare_recursive`, `_collect_enhanced_non_matches`, `_add_derived_metrics_to_result`, `_format_for_evaluator`, etc. These were **not removed** — they now exist as one-line wrappers that build the appropriate component and forward the call. They are retained for backward compatibility; new code should call the component directly. | ||
|
|
||
| Three wrappers that nothing called were removed in [#390](https://github.com/awslabs/stickler/pull/390): `_calculate_list_confusion_matrix`, `_calculate_nested_field_metrics` and `_calculate_single_nested_field_metrics`. Their work is done by `StructuredListComparator` for a list of models, including the fields inside matched items, and by `FieldComparator.compare_structured_field` for a single nested model. [#395](https://github.com/awslabs/stickler/issues/395) removed `_calculate_object_level_metrics`, an uncalled copy of `StructuredListComparator._calculate_object_level_metrics`. |
There was a problem hiding this comment.
The one requested change. Line 159 still says these wrappers "were not removed" and that each one "build[s] the appropriate component". This paragraph now lists four that were removed, and _classify_field_for_confusion_matrix no longer builds a component. Could you reword 159 to match? While you're here, the removal is in this PR, so the link should point to #398 rather than issue #395.
There was a problem hiding this comment.
|
|
||
| This method orchestrates the calculation of a complete confusion matrix | ||
| by coordinating between the three calculator classes: | ||
| in three steps: |
There was a problem hiding this comment.
Optional: the docstring now counts three steps, but the inline comments below number aggregate and derived as Step 1 and Step 2.
There was a problem hiding this comment.
Done in fc7e762: the inline comments are now Step 1 to Step 3, matching the docstring.
| ``ConfigurationHelper.is_structured_field_type`` recognises neither | ||
| annotation, so the pair takes the primitive route. The field's default | ||
| comparator is ``LevenshteinComparator``, which scores the text of | ||
| ``_Inner(text="p")`` against ``_Inner(text="q")`` at about 0.95. Compared as |
There was a problem hiding this comment.
Optional: measured at 0.9583, so "about 0.96" (the PR body says 0.96).
There was a problem hiding this comment.
Thanks, changed to "about 0.96" in fc7e762.
| """ | ||
| self.model = model | ||
|
|
||
| @staticmethod |
There was a problem hiding this comment.
Nit, take it or leave it: this is now one static method behind a forwarding wrapper. A module-level function called from StructuredListComparator would do the same job.
There was a problem hiding this comment.
I'd leave this one as is, if that's OK: keeping the class keeps the ConfusionMatrixCalculator import path and the component list in the refactoring notes unchanged. Happy to switch it to a module-level function if you prefer.
The refactoring notes still said the private wrappers "were not removed" right above the paragraph listing the four that were. Say the ones that still have callers are kept as forwarding wrappers, and link the _calculate_object_level_metrics removal to awslabs#398 instead of the issue. Also stop listing ConfusionMatrixCalculator as the component that calculates the matrix in the ComparisonEngine, AggregateMetricsCalculator and DerivedMetricsCalculator docstrings, number the builder's inline step comments to match its docstring, and give the Levenshtein score in the model-valued sub-field test as about 0.96 (measured 0.9583). Refs awslabs#395
|
Thanks for the review, @sromoam. fc7e762 rewords line 159 and fixes the link, and also takes the optional items: the three docstrings outside the diff ( |
sromoam
left a comment
There was a problem hiding this comment.
Thanks @Thanatos9404, fc7e762 covers what I asked for, and thanks for taking the optional items too. Approving. One small wording fix if you're touching it again: the ConfusionMatrixCalculator docstring bullet (structured_model.py:325) and the test module docstring say "primitive" sub-fields, but this PR keeps the model-valued branch on purpose, so "non-list sub-fields" is closer. Separately, that kept branch scores with compare_with() while the dispatcher uses compare(). That's older than this PR and is filed as #461.
Model-valued sub-fields annotated Union or Any also reach the classifier, so "non-list" describes what it handles; simple list sub-fields still go through the element-level dispatch.
Security Scan ResultsASH Security Scan Report
Scan Metadata
SummaryScanner ResultsThe table below shows findings by scanner, with status based on severity thresholds and dependencies:
Top 3 HotspotsFiles with the highest number of security findings:
Detailed FindingsShow 5 actionable findingsFinding 1: SECRET-HEX-HIGH-ENTROPY-STRING
Description: Code Snippet: Finding 2: B108
Description: Code Snippet: Finding 3: B307
Description: Code Snippet: Finding 4: B307
Description: Code Snippet: Finding 5: B102
Description: Code Snippet: Report generated by Automated Security Helper (ASH) at 2026-10-28T19:51:22+00:00 |
Pull Request
Issue Reference
Part of #395. Items 1, 2 and 4 and both doc fixes are here. Item 3 turned out to be reachable, and item 5 needs a decision first; both are explained below.
Description of Changes
Follow-up to #390. Removes the two dead paths from #395, makes the per-pair classifier return only the six counts, and fixes the two points in the refactoring notes. No
compare()orcompare_with()result changes: the full suite gives the same results before and after, plus 6 new tests.Changes Made
structured_model.py: delete_calculate_object_level_metrics(:1281, 55 lines) and theHungarianHelperimport, which only it used. In the class docstring, theConfusionMatrixCalculatorandConfusionMatrixBuilderbullets no longer describe list-level metrics or a third calculator.confusion_matrix_builder.py: delete thecalculatorproperty, the_calculatorslot, and the docstring lines that listedConfusionMatrixCalculatoras a dependency.confusion_matrix_calculator.py:classify_field_for_confusion_matrixreturns the six counts with noderiveddict, and drops theMetricsHelperimport. It is now a@staticmethodthat takes the ground truth model, soStructuredModel._classify_field_for_confusion_matrixno longer builds aConfusionMatrixCalculatorper pair. With the builder property gone, nothing else constructed one.docs/structured_model_REFACTORING.md: the size is now ~2030 lines (2029 after this PR; ~1486 was right when 47c25d0 wrote it in May). The shims paragraph names the three wrappers refactor(296): remove the dead ConfusionMatrixCalculator methods #390 removed and what does their work now. The builder's tree entry and the summary line no longer describe removed pieces.tests/structured_object_evaluator/test_list_subfield_classification.py, 6 tests: the classifier returns exactly the six counts (fails before this change, on thederivedkey), and a model-valued sub-field is compared as a model (passes before and after; see item 3).Why These Changes Are Needed
Same kind of leftover as #296: a second copy of
_calculate_object_level_metricsthat could drift from the live one, a builder dependency that does not exist, and a derived-metrics dict built for every matched pair and primitive sub-field and then thrown away (N*K per list comparison).Reachability check
Line numbers are at
ad19ac8, as in the issue;src/is identical ondev6abbf26. Each name was searched withgit grepoversrc/,tests/,examples/(including the 11 notebooks) anddocs/. A search for indirect access (getattrwith built names,mock.patch, the names as strings) found none. Executed statements come fromcoverage jsonafter a fullcoverage run -m pytest tests/ondev6abbf26.StructuredModel._calculate_object_level_metricsstructured_list_comparator.py:70callsself._calculate_object_level_metrics, andStructuredListComparator(:21) does not inherit fromStructuredModel, so it reaches its own copy at :102 (15/15)ConfusionMatrixBuilder.calculator,_calculatorbuild_confusion_matrixreads onlyaggregate_calculatorandderived_calculatorStructuredModelbranch inclassify_field_for_confusion_matrix(:80-87)classify_field_for_confusion_matrixvia_classify_field_for_confusion_matrixstructured_list_comparator.py:511, which copies only the six counts (:515)Item 3: kept
_handle_non_hierarchical_fieldgets every sub-field thatConfigurationHelper.is_structured_field_typedoes not recognise. That coversList[M],Optional[List[M]],MandOptional[M], but notUnion[A, B]withoutNone,Anyorobject, whichclassification-logic.mdnames as annotations that can carry model values. On such a sub-field this branch is what compares two models as models. Without it, the field's defaultLevenshteinComparatorscores the text ofInner(text="p")againstInner(text="q")at 0.96, so the confusion matrix counts the pair as a TP whilenon_matchesandfield_comparisonsin the same result reportitems[0].valueas a mismatch.TestModelValuedSubFieldpins this forUnion[A, B]andAny; with theisinstancetest replaced byFalse, its two mismatch cases fail. The import inside the branch is the lazy-import pattern the refactoring notes call intentional, so it stays as well.Item 5: not in this PR
ThresholdHelper.is_above_thresholdhere makes the confusion matrix agree with aList[str]element, but not with the rest of the same result. I tried it: for one list item whose sub-field scores0.7 + 0.1against0.8, onecompare_withthen reportsitems[0].namenon_matchesfield_comparisonsnon_matchesandfield_comparisonscome from the item's owncompare_with, which goes throughFieldComparator.compare_primitive_with_scoresand its bare>=(field_comparator.py:68). That comparison also clips the score feeding the pair's similarity, and it is what a top-level or nested-model field gets, so the same score is an FD there too. Today the tolerant test is used byComparisonHelper(primitive list elements) and the bare one byFieldComparatorand everything that reads it. Inside aList[StructuredModel], the matched objects themselves are also counted with a bare>=(structured_list_comparator.py:127).So there are three ways to go:
ThresholdHelpertogether (FieldComparator, thefield_comparisonscollectors and this classifier), as its own PR with a CHANGELOG entry, since it changes verdicts and clipped scores at the margin.I would keep the bare test. Which do you prefer?
Testing
signal.SIGALRM; details below)Windows, Python 3.12.5,
uv sync --frozen --extra llm --no-group docs(CI also installs thebertextra).inspect.getfile(stickler)pointed at this checkout'ssrc/stickler/__init__.pyfor every run.-iskips a temporary file thattest_the_canary_is_wired_upcreates and deletes.dev6abbf26confusion_matrix_calculator.pyconfusion_matrix_builder.pystructured_model.pystructured_list_comparator.pysrc/totalMissed statements in
src/drop by 21 (1010 to 989): the 19 of the two dead paths, plus the two lines of the item 3 branch, which the new test now runs.The 6 failures are the same on both trees: 5 tests in
TestRealisticDeepNestingandtest_integrationintest_realistic_deep_nesting.py, allAttributeError: module 'signal' has no attribute 'SIGALRM'. Linux CI runs them.On the changed Python files,
uv run ruff checkpasses, anduv run ruff format --checkpasses for all butconfusion_matrix_builder.py, which it already flagged ondev(the same 22 diff lines of whitespace and one trailing comma, none on lines this PR touches).Reviewers
@sromoam
Checklist
CHANGELOG.mdentry added under[Unreleased], or not needed becausenothing user-visible changed (see RELEASING.md): dead-code removal and an internal return shape, no entry
Additional Notes
classify_field_for_confusion_matrixhas no caller outsideStructuredModel._classify_field_for_confusion_matrix, andConfusionMatrixCalculatoris not exported, so making it a@staticmethodchanges no public signature. The wrapper and its row in the refactoring notes stay.By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.