Skip to content

refactor(296): remove the dead ConfusionMatrixCalculator methods - #390

Merged
sromoam merged 1 commit into
awslabs:devfrom
Thanatos9404:refactor/296-remove-dead-confusion-matrix-code
Oct 15, 2026
Merged

sromoam merged 1 commit into
awslabs:devfrom
Thanatos9404:refactor/296-remove-dead-confusion-matrix-code

Conversation

@Thanatos9404

Copy link
Copy Markdown
Contributor

Pull Request

Issue Reference

Closes #296

Description of Changes

Removes the three ConfusionMatrixCalculator methods that nothing reaches, and the three StructuredModel wrappers that were their only way in. Nothing that runs today changes: before this change all six had zero executed lines across the full suite.

Changes Made

  • confusion_matrix_calculator.py: delete calculate_list_confusion_matrix (lines 38-159), calculate_nested_field_metrics (244-556) and calculate_single_nested_field_metrics (557-650). With the blank line between them that is the 530 lines from the re-verification comment. Drop the HungarianHelper, NonMatchesHelper and List imports, which only those methods used, and the "and lists of structured models" clause from the class docstring. The file goes from 650 to 117 lines. ruff format also strips the trailing whitespace from 9 blank docstring lines in the kept code, the cleanup the refactor: remove deprecated ComparableField(aggregate=) parameter (#226) #293 review set aside for Delete the superseded ConfusionMatrixCalculator nested and list methods (329 dead lines) and their StructuredModel wrappers #296.
  • structured_model.py: delete the wrappers _calculate_list_confusion_matrix (:1504), _calculate_nested_field_metrics (:1526) and _calculate_single_nested_field_metrics (:1558), 80 lines.
  • docs/structured_model_REFACTORING.md: drop the three rows from the Code Location Mapping table (378-380), and the two removed names from the "Legacy private methods are kept as delegating shims" paragraph (:159), which said they still exist.
  • src/stickler/algorithms/README.md and the HungarianMatcher.calculate_metrics docstring: both said ConfusionMatrixCalculator accepts a score up to 1e-10 under the threshold. That tolerant test lived in the deleted calculate_nested_field_metrics (old line 324). The method that remains, classify_field_for_confusion_matrix, uses a bare similarity >= threshold, so both now name only ThresholdHelper and ComparisonHelper, which do use the tolerant test.

No test changes.

Why These Changes Are Needed

These methods are a superseded copy of the list and nested metrics that StructuredListComparator computes today. They were kept as backward-compatibility shims, and nothing in the repository uses them any more. Removing them takes about 610 lines of unexecuted code out of the metrics path, and leaves one _calculate_nested_field_metrics instead of two.

Reachability check

Line numbers are on dev at 70e9afb. Each name was searched with git grep -n over the whole checkout: src/, tests/, examples/ (including all 11 notebooks), docs/ and CHANGELOG.md. A search for indirect access (getattr/hasattr/mock.patch on these names, or the names as string literals) found none. Executed statements come from coverage json after a full coverage run -m pytest tests/ on unmodified dev.

Symbol Callers on dev Executed before
ConfusionMatrixCalculator.calculate_list_confusion_matrix calculate_nested_field_metrics (:343, :436, :490), calculate_single_nested_field_metrics (:614), wrapper StructuredModel._calculate_list_confusion_matrix (:1524) 0/29
ConfusionMatrixCalculator.calculate_nested_field_metrics calculate_list_confusion_matrix (:145), wrapper StructuredModel._calculate_nested_field_metrics (:1554) 0/100
ConfusionMatrixCalculator.calculate_single_nested_field_metrics itself (:641), wrapper StructuredModel._calculate_single_nested_field_metrics (:1580) 0/26
StructuredModel._calculate_list_confusion_matrix none 0/3
StructuredModel._calculate_nested_field_metrics none (see below) 0/3
StructuredModel._calculate_single_nested_field_metrics none 0/3

The three calculator methods only call each other, the three wrappers are their only entry points, and the wrappers have no callers.

The one call that looks like a caller is structured_list_comparator.py:82, self._calculate_nested_field_metrics(...). StructuredListComparator does not inherit from StructuredModel (class StructuredListComparator: at :21), so that call resolves to its own method at :209, which this PR keeps. It also passes five arguments, including matched_pairs; the wrapper took four.

None of the six is public API. None is exported from any __init__.py or __all__. The API reference renders StructuredModel through mkdocstrings with filters: ["!^_[^_]", "^_compare$"] (docs/mkdocs.yml), so the underscore wrappers never appeared there, and no ::: directive renders ConfusionMatrixCalculator. The only docs that listed them were the refactoring notes edited here.

Deliberately kept

  • StructuredListComparator._calculate_nested_field_metrics (structured_list_comparator.py:209): the live namesake, called from :82. 15/15 statements executed before and after.
  • ConfusionMatrixCalculator.__init__ and classify_field_for_confusion_matrix: live through structured_list_comparator.py:511 calling StructuredModel._classify_field_for_confusion_matrix (:1482), which calls classify_field_for_confusion_matrix. Statements executed, the same before and after: classify_field_for_confusion_matrix 26/29, __init__ 1/1. ConfusionMatrixBuilder.calculator also constructs a ConfusionMatrixCalculator and needs only __init__.
  • The _classify_field_for_confusion_matrix wrapper and its row in the refactoring notes.

Testing

  • Unit tests added/updated (none needed: the issue requires no test changes)
  • Integration tests added/updated
  • Manual testing completed
  • All existing tests pass (apart from 6 tests that need the POSIX-only signal.SIGALRM, plus two timing tests that fail only under coverage tracing; details below)

Run on Windows, Python 3.12.5, uv sync --frozen --extra llm --no-group docs (CI also installs the bert extra; its torch stack was not installed for this run). inspect.getfile(stickler) pointed at this checkout's src/stickler/__init__.py.

uv run coverage run -m pytest tests/
uv run coverage report
uv run coverage json
dev 70e9afb this branch (ad19ac8)
pytest 2780 passed, 8 failed, 10 skipped, 1 xfailed 2780 passed, 8 failed, 10 skipped, 1 xfailed
confusion_matrix_calculator.py 38/196 statements (19%) 33/36 (92%)
structured_model.py 430/487 (88%) 427/475 (90%)
structured_list_comparator.py 147/157 (94%) 147/157 (94%)
src/ total 6581/7755 (84.86%) 6573/7583 (86.68%)

Missed statements in src/ drop by exactly 164 (1174 to 1010), which is the body statement count of the six methods (29 + 100 + 26 + 3 x 3).

Failures, the same 8 on both sides:

  • 6 tests in test_realistic_deep_nesting.py: AttributeError: module 'signal' has no attribute 'SIGALRM' (Windows has no SIGALRM). Linux CI runs them.
  • Two timing tests that fail under coverage tracing: test_deep_nesting_6_levels.py::test_performance_with_deep_nesting (35 s budget, 78.8 s on this branch) and test_performance_benchmark.py::test_performance_simple_comparison (5 ms budget; 12.1 ms on dev, 5.3 ms on this branch). Without coverage both pass on this branch: uv run pytest tests/structured_object_evaluator/test_performance_benchmark.py tests/structured_object_evaluator/test_deep_nesting_6_levels.py gives 12 passed.

Lint:

uv run ruff check .

The same 16 findings before and after, all in examples/notebooks/*.ipynb and examples/scripts/llm_comparator_demo.py. None are in src/ or tests/. On the three changed Python files, uv run ruff check passes and uv run ruff format --check reports them already formatted. The repo configures no type checker.

Reviewers

@sromoam

Checklist

  • Code follows the project's coding standards
  • Self-review of code completed
  • Documentation updated (if applicable)
  • CHANGELOG.md entry added under [Unreleased], or not needed because
    nothing user-visible changed (see RELEASING.md): dead-code removal, no entry
  • Tests added/updated for new functionality (no new functionality)
  • All CI checks are passing
  • Ready for review

Additional Notes

  • Line numbers in structured_model.py have moved since the re-verification comment (:1203 and :1229 there). On current dev, the kept wrapper is at :1482 and the removed ones are at :1504, :1526 and :1558.
  • fix: report refused list elements with field comparison rules #364 edits src/stickler/algorithms/README.md a few lines below the sentence changed here. The two changes do not touch the same lines.

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

calculate_list_confusion_matrix, calculate_nested_field_metrics and
calculate_single_nested_field_metrics only called each other, and their
only way in was three StructuredModel wrappers that nothing calls. All
six had zero executed lines across the full suite. Remove them, the
HungarianHelper, NonMatchesHelper and List imports that only they used,
and the refactoring notes that listed the wrappers.

StructuredListComparator._calculate_nested_field_metrics is a separate,
live method with the same name and is untouched. So are
ConfusionMatrixCalculator.__init__, classify_field_for_confusion_matrix
and StructuredModel._classify_field_for_confusion_matrix, which
StructuredListComparator calls.

The 1e-10 threshold window that algorithms/README.md and the
HungarianMatcher.calculate_metrics docstring attribute to
ConfusionMatrixCalculator lived in the deleted
calculate_nested_field_metrics. The method that remains compares with a
bare >=, so both now name only ThresholdHelper and ComparisonHelper.

Also run ruff format on the calculator file, which only strips the
trailing whitespace from 9 blank docstring lines.

Closes awslabs#296
@github-actions

Copy link
Copy Markdown

Security Scan Results

ASH Security Scan Report

  • Report generated: 2026-10-15T15:14:12+00:00
  • Time since scan: 2 minutes

Scan Metadata

  • Project: ASH
  • Scan executed: 2026-10-15T15:12:00+00:00
  • ASH version: 3.1.2

Summary

Scanner Results

The table below shows findings by scanner, with status based on severity thresholds and dependencies:

  • Severity levels:
    • Suppressed (S): Findings that have been explicitly suppressed and don't affect scanner status
    • Critical (C): Highest severity findings that require immediate attention
    • High (H): Serious findings that should be addressed soon
    • Medium (M): Moderate risk findings
    • Low (L): Lower risk findings
    • Info (I): Informational findings with minimal risk
  • Duration (Time): Time taken by the scanner to complete its execution
  • Actionable: Number of findings at or above the threshold severity level that require attention
  • Result:
    • PASSED = No findings at or above threshold
    • FAILED = Findings at or above threshold
    • MISSING = Required dependencies not available
    • SKIPPED = Scanner explicitly disabled
    • ERROR = Scanner execution error
  • Threshold: The minimum severity level that will cause a scanner to fail
    • Thresholds: ALL, LOW, MEDIUM, HIGH, CRITICAL
    • Source: Values in parentheses indicate where the threshold is set:
      • global (global_settings section in the ASH_CONFIG used)
      • config (scanner config section in the ASH_CONFIG used)
      • scanner (default configuration in the plugin, if explicitly set)
  • Statistics calculation:
    • All statistics are calculated from the final aggregated SARIF report
    • Suppressed findings are counted separately and do not contribute to actionable findings
    • Scanner status is determined by comparing actionable findings to the threshold
Scanner Suppressed Critical High Medium Low Info Actionable Result Threshold
bandit 0 3 0 0 5755 0 3 FAILED MEDIUM (global)
cdk-nag 0 0 0 0 0 0 0 PASSED MEDIUM (global)
cfn-nag 0 0 0 0 0 0 0 MISSING MEDIUM (global)
checkov 0 0 0 0 0 0 0 PASSED MEDIUM (global)
detect-secrets 0 1 0 0 0 0 1 SKIPPED MEDIUM (global)
grype 0 0 0 0 0 0 0 MISSING MEDIUM (global)
npm-audit 0 0 0 0 0 0 0 PASSED MEDIUM (global)
opengrep 0 0 0 0 0 0 0 MISSING MEDIUM (global)
semgrep 3 0 0 0 0 0 0 PASSED MEDIUM (global)
syft 0 0 0 0 0 0 0 MISSING MEDIUM (global)

Top 2 Hotspots

Files with the highest number of security findings:

Finding Count File Location
2 tests/comparators/test_anls_leaf_semantics.py
2 tests/structured_object_evaluator/test_rollup_node_semantics.py

Detailed Findings

Show 4 actionable findings

Finding 1: SECRET-HEX-HIGH-ENTROPY-STRING

  • Severity: HIGH
  • Scanner: detect-secrets
  • Rule ID: SECRET-HEX-HIGH-ENTROPY-STRING
  • Location: tests/comparators/test_anls_leaf_semantics.py:89

Description:
Secret of type 'Hex High Entropy String' detected in file 'tests/comparators/test_anls_leaf_semantics.py' at line 89

Code Snippet:

Secret of type Hex High Entropy String detected

Finding 2: B108

  • Severity: HIGH
  • Scanner: bandit
  • Rule ID: B108
  • Location: tests/comparators/test_anls_leaf_semantics.py:210-212

Description:
Probable insecure usage of temp file/directory.

Code Snippet:

uuid.UUID(int=1),
            pathlib.Path("/tmp/x"),
            b"bytes",

Finding 3: B307

  • Severity: HIGH
  • Scanner: bandit
  • Rule ID: B307
  • Location: tests/structured_object_evaluator/test_rollup_node_semantics.py:1362-1368

Description:
Use of possibly insecure function - consider using safer ast.literal_eval.

Code Snippet:

def published_label(node: dict, section: str, object_lists: set) -> str:
            counts_objects = eval(  # noqa: S307
                cond.group(1),
                {},
                {"data": node, "section": section, "OBJECT_LISTS": object_lists},
            )
            return eval(unit.group(1), {}, {"counts_objects": counts_objects})  # noqa: S307

Finding 4: B307

  • Severity: HIGH
  • Scanner: bandit
  • Rule ID: B307
  • Location: tests/structured_object_evaluator/test_rollup_node_semantics.py:1367-1369

Description:
Use of possibly insecure function - consider using safer ast.literal_eval.

Code Snippet:

)
            return eval(unit.group(1), {}, {"counts_objects": counts_objects})  # noqa: S307

Report generated by Automated Security Helper (ASH) at 2026-10-15T15:14:13+00:00

@sromoam sromoam left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @Thanatos9404, this is a clean removal, and welcome to the project. Nothing on ad19ac8 still references the six removed methods or the dropped imports, and the edits to algorithms/README.md and the hungarian.py docstring are accurate. CI is green.

Reviewing it turned up a few more dead paths of the same kind nearby, plus a threshold check on the remaining live path. None of that blocks this PR, so I've written them up in #395. If you'd like to take that one on too, it's yours.

@sromoam
sromoam merged commit 9db859d into awslabs:dev Oct 15, 2026
7 checks passed
@Thanatos9404

Copy link
Copy Markdown
Contributor Author

Thanks for the review, @sromoam. Happy to take #395, I'll send it as a separate PR on top of this one.

sromoam pushed a commit that referenced this pull request Oct 28, 2026
* refactor(395): remove the remaining dead confusion-matrix paths

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 #390 removed and what does
their work now.

Refs #395

* docs(395): say which StructuredModel shims remain

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 #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 #395

* docs(395): call the classified list sub-fields non-list, not primitive

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.
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.

Delete the superseded ConfusionMatrixCalculator nested and list methods (329 dead lines) and their StructuredModel wrappers

2 participants