Skip to content

Correct label-count return types/docs and remove broken VideoLabels.counts - #430

Merged
gbeane merged 1 commit into
mainfrom
claude/quality-2026-08-09
Aug 9, 2026
Merged

Correct label-count return types/docs and remove broken VideoLabels.counts#430
gbeane merged 1 commit into
mainfrom
claude/quality-2026-08-09

Conversation

@gbeane

@gbeane gbeane commented Aug 9, 2026

Copy link
Copy Markdown
Collaborator

Reasoning

The label-count API (Project.load_countsProject.counts) is consumed in six
places (central_widget, training_strategy, session_tracker,
project_pruning, cross_validation CLI, MultiClassClassifier), and every one
of them treats the result as dict[identity_int, dict[str, tuple[int, int]]].
The declarations disagreed with that in three ways, all of which actively mislead
a reader:

  1. Project.load_counts was annotated
    -> dict[str, tuple[int, int]]. Both the key type and the value type are
    wrong — keys are int (the code does counts[int(identity)] = {...}) and
    values are dicts of four named count tuples. The docstring body below it
    already described the correct shape, so the annotation contradicted the
    docstring immediately above the code.
  2. Project.counts documented its values as
    "lists of (identity, 4 tuples)" — a shape that no longer exists anywhere.
  3. VideoLabels.counts is where that
    obsolete shape came from, and it is broken: it reads
    TrackLabels.counts, an attribute TrackLabels does not define. Any call
    with a labeled identity raises AttributeError: 'TrackLabels' object has no attribute 'counts' (verified against main). It has no callers in src/,
    tests/, or packages/.

While confirming the real shape I also found a latent crash in load_counts.
The loop iterates the union of identity keys from the labels and
unfragmented_labels sections, but defaults a missing identity to [] — and
count_labels immediately calls .get() on its argument. An identity present in
one section but not the other therefore raises
AttributeError: 'list' object has no attribute 'get'. Files written by
VideoLabels.as_dict currently keep both sections in sync (it seeds {} for
every identity in both), so this is unreachable today, but it is one asymmetric
annotation file away from a hard failure at project load. A wrong-typed default
constant is exactly the kind of thing that stops being latent when the writer
changes.

I picked this over the other candidates I looked at (a copy-paste "ignored"
docstring on psd_mean_band's freqs parameter, which is used; the
TrackLabels.downsample docstring's imprecise PAD bin description) because this
one is a genuine bug plus documentation that would send a reader down the wrong
path about a widely-consumed return type — not just cosmetics.

Why it's safe: net-zero behavior change on every reachable path.
count_labels({}) returns ((0, 0), (0, 0)), which is exactly what the removed
if labels else ((0, 0), (0, 0)) guard produced, so the guard is redundant, not
dropped semantics. Annotations and docstrings do not affect runtime. The removed
VideoLabels.counts could only ever raise. No FEATURE_VERSION bump — no
computed feature values are touched.

Change

Logic changes

  • src/jabs/project/project.py
    • load_counts: corrected return annotation to
      dict[int, dict[str, tuple[int, int]]]; added the missing Args section;
      changed the two missing-identity defaults from [] to {} and removed the
      now-redundant if labels guard (the AttributeError fix).
    • counts: added behavior: str and the return annotation; replaced the
      obsolete list-of-tuples Returns description with a pointer to
      load_counts.
  • src/jabs/project/video_labels.py — removed the unused, always-raising
    counts() method.
  • src/jabs/project/project_pruning.py — corrected the identity key type on
    the local check_label_counts helper (dict[str, ...]dict[int, ...]) to
    match what load_counts returns.
  • tests/project/test_project.py — two regression tests for load_counts:
    one asserting identities come back as int with both fragmented and
    unfragmented counts, one covering an identity present only in
    unfragmented_labels. The second fails on main with the AttributeError
    above.

Mechanical updates

None — no import paths, exports, or re-export shims were touched.

Verification

ruff check . and ruff format . clean; full root suite 827 passed, 200 skipped. Nothing under packages/ was modified.


This PR was produced by an automated analysis from Claude Code.


Generated by Claude Code

Project.load_counts returns a dict keyed by integer identity whose values are
dicts of named count tuples, but its annotation claimed
dict[str, tuple[int, int]] and Project.counts documented an obsolete list-of-
5-tuples shape. Both are corrected to match what every caller actually
consumes.

The per-identity loop unions the identity keys of the "labels" and
"unfragmented_labels" sections but defaulted a missing identity to [], which
count_labels then calls .get() on. Default to {} instead so an identity present
in only one section counts as zero frames and zero bouts rather than raising
AttributeError. The previous "if labels" guard becomes redundant and is
removed; it produced the same ((0, 0), (0, 0)) result.

VideoLabels.counts() described the obsolete shape and read a TrackLabels.counts
attribute that does not exist, so it raised AttributeError for any labeled
identity. It has no callers and is removed.

Also fixes the identity key type on project_pruning.check_label_counts and adds
regression tests for load_counts.
@gbeane
gbeane requested a lite review from Copilot August 9, 2026 00:11
@gbeane gbeane self-assigned this Aug 9, 2026

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

Pull request overview

This PR fixes mismatches between the documented/annotated label-count API and its actual runtime shape, and removes a dead/broken VideoLabels.counts() method that could only raise at runtime. It primarily improves correctness of type hints/docs around Project.load_counts() / Project.counts() and adds regression coverage for an AttributeError edge case in load_counts.

Changes:

  • Corrected Project.load_counts() and Project.counts() type annotations/docstrings to match the actual returned structure (per-video → per-identity → named count tuples).
  • Fixed a latent load_counts() crash by defaulting missing identity sections to {} (instead of []) when counting.
  • Removed unused/broken VideoLabels.counts() and added regression tests for load_counts() identity handling.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
src/jabs/project/project.py Fixes counts/load_counts signatures/docs and hardens load_counts defaults to avoid AttributeError.
src/jabs/project/video_labels.py Removes an unused counts() method that referenced a non-existent TrackLabels.counts.
src/jabs/project/project_pruning.py Aligns helper type hints with the corrected load_counts() return shape (int identity keys).
tests/project/test_project.py Adds regression tests ensuring identity keys are int and missing-section identities count as zero.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@gbeane
gbeane merged commit 3eed412 into main Aug 9, 2026
6 checks passed
@gbeane
gbeane deleted the claude/quality-2026-08-09 branch August 9, 2026 01:06
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.

3 participants