stroke-viewer-frontend / docs /TECHNICAL_DEBT.md
VibecoderMcSwaggins's picture
docs: add technical debt audit report
ed93dd8
|
raw
history blame
4.61 kB

Technical Debt and Known Issues

Last Audit: December 2025 Auditor: Claude Code Status: Clean - No blocking issues found

Summary

Full architectural review completed. The codebase is production-ready with only minor improvements possible.

Severity Count Description
P0 (Critical) 0 None
P1 (High) 0 None
P2 (Medium) 1 Silent empty dataset on missing directory
P3 (Low) 3 Type ignore comments (expected)

P2: Silent Empty Dataset on Missing Data Directory

Location: src/stroke_deepisles_demo/data/adapter.py:70

Issue: When build_local_dataset() is called with a non-existent directory, Path.glob() returns an empty iterator instead of raising an error. This results in an empty LocalDataset with 0 cases.

Example:

dataset = build_local_dataset(Path("/wrong/path"))
len(dataset)  # Returns 0, no error

Mitigation:

  • UI path (components.py:35-36) explicitly checks if not case_ids and raises RuntimeError
  • Pipeline path will fail with IndexError when accessing case by index
  • CLI shows "Found 0 cases:" which is visible but potentially confusing

Impact: User confusion if data path is misconfigured

Recommended Fix (optional):

def build_local_dataset(data_dir: Path) -> LocalDataset:
    dwi_dir = data_dir / "Images-DWI"
    if not dwi_dir.exists():
        raise FileNotFoundError(f"Data directory not found: {dwi_dir}")
    # ... rest of function

Status: Acceptable - downstream checks prevent silent failures in all user-facing paths


P3: Type Ignore Comments (Expected)

These # type: ignore comments are correct and expected due to library typing limitations:

1. nibabel typing (3 occurrences)

Location: src/stroke_deepisles_demo/metrics.py:26-28

img = nib.load(path)  # type: ignore[attr-defined]
data = img.get_fdata()  # type: ignore[attr-defined]
zooms = img.header.get_zooms()  # type: ignore[attr-defined]

Reason: nibabel has incomplete type stubs

2. numpy.ma typing (5 occurrences)

Location: src/stroke_deepisles_demo/ui/viewer.py

np.ma.masked_where(m_slice == 0, m_slice)  # type: ignore[no-untyped-call]

Reason: numpy masked array functions lack complete type annotations

3. pydantic computed_field (2 occurrences)

Location: src/stroke_deepisles_demo/core/config.py:100,106

@computed_field  # type: ignore[prop-decorator]
@property
def is_hf_spaces(self) -> bool:

Reason: pydantic-settings computed_field decorator typing quirk

Status: These are industry-standard workarounds, not technical debt


Good Patterns Observed

Error Handling

  • No bare except: or except: pass statements
  • All exceptions are re-raised with context using from e
  • logger.exception() used before re-raising for full traceback

Fail-Loud Design

  • UI components raise RuntimeError on missing data
  • CLI returns non-zero exit codes on failure
  • Index bounds are explicitly validated before access

Logging

  • Consistent use of module-level loggers via get_logger(__name__)
  • Warnings for skipped cases include counts and examples
  • Debug logging for Docker commands and inference paths

Type Safety

  • Proper use of Path vs str throughout
  • TypedDict for CaseFiles structure
  • Return types explicitly annotated

Architecture Decisions (Not Debt)

1. Dice Score Failure Handling

Location: pipeline.py:129-133

if compute_dice and ground_truth:
    try:
        dice_score = metrics.compute_dice(...)
    except Exception:
        logger.warning("Failed to compute Dice score", exc_info=True)

Decision: Pipeline continues if Dice computation fails. This is intentional - inference results are more valuable than failing the entire pipeline due to a metrics issue.

2. Direct Invocation Module

Location: inference/direct.py Decision: Separate module for HF Spaces direct Python invocation. Keeps Docker path clean and follows single-responsibility principle.

3. Lazy Demo Initialization

Location: ui/app.py:159-168

_demo: gr.Blocks | None = None

def get_demo() -> gr.Blocks:
    global _demo
    if _demo is None:
        _demo = create_app()
    return _demo

Decision: Avoids import-time side effects. Demo is only created when accessed.


Conclusion

The codebase is clean and well-architected. The single P2 issue is already mitigated by downstream checks. No action required before deployment.