Files
impakt/docs/QA-2026-04-11_1054.md
2026-04-11 11:00:55 -04:00

10 KiB
Raw Permalink Blame History

Quality Assessment -- 2026-04-11

Score: 83.7 / 100 — Grade: B+ Strong jump of +5.4 points from B (78.3) to B+ (83.7) driven by lint reaching perfection (0 violations) and architecture completing its public-API surface (all 11 modules now export __all__); type safety improved incrementally while complexity ticked slightly down as two files crossed the 300-line threshold.

Dimension Score
Test Health 8.0/10
Type Safety 6.8/10
Lint Hygiene 10.0/10
Architecture 10.0/10
Documentation 9.0/10
Complexity 6.5/10
Security 8.5/10
Maintainability 8.5/10

Version: 0.1.0 Assessed by: Claude Sonnet 4.6 Previous assessment: QA-2026-04-11_0619.md


Inventory

Metric Value
Source files 72
Source lines 10,563
Test files 30
Test lines 2,736
Test:source ratio 0.259
Direct dependencies 10 core + 1 optional + 4 dev

Raw Metrics

Test Suite

TOTAL                                             3746   1142    70%

20 files skipped due to complete coverage.
Required test coverage of 60% reached. Total coverage: 69.51%
240 passed, 7 warnings in 16.64s
  • Tests collected: 240
  • Tests passed: 240
  • Tests failed: 0
  • Test duration: 16.64s
  • Coverage: 69.51% (pytest-cov now configured; 60% floor enforced)

Type Safety (mypy --strict)

Found 34 errors in 16 files (checked 72 source files)
  • Total errors: 34
  • Files with errors: 16 / 72 (78% clean)
  • Top error categories:
    • [attr-defined] 9 — attribute access on loosely typed objects
    • [no-any-return] 6 — returning Any from typed functions
    • [return-value] 4 — incompatible return type
    • [import-untyped] 3 — third-party stubs missing
    • [assignment] 3 — incompatible assignment
    • [var-annotated] 2 — need type annotation
    • [valid-type] 2 — invalid type expression
    • [no-untyped-call] 2 — call to untyped function
    • [unused-ignore] 1
    • [no-untyped-def] 1
    • [comparison-overlap] 1

Lint (ruff)

All checks passed!
  • Total violations: 0
  • Auto-fixable: N/A
  • Top violation rules: none

Complexity

  • File size: min=1 / median=132 / mean=147 / max=692
  • Files >300 lines: 8 / 72
  • High-complexity files (branch density >15):
  80  src/impakt/io/mme.py (692 lines)                             -- ISO 13499 parser, justified
  44  src/impakt/web/components/criteria.py (342 lines)            -- UI assembly with protocol logic
  30  src/impakt/web/callbacks/plot_callbacks.py (324 lines)       -- transform pipeline orchestration
  30  src/impakt/channel/model.py (456 lines)                      -- core data model, multiple classes
  27  src/impakt/web/state.py (272 lines)                          -- app state with multi-test support
  27  src/impakt/protocol/euro_ncap.py (237 lines)                 -- sliding-scale scoring tables
  23  src/impakt/plot/engine.py (303 lines)                        -- Plotly rendering with corridors
  21  src/impakt/web/callbacks/channel_callbacks.py (236 lines)    -- selection/filter callbacks
  21  src/impakt/protocol/iihs.py (179 lines)                      -- G/A/M/P rating logic
  20  src/impakt/web/components/channel_grid.py (418 lines)        -- DataTable assembly
  19  src/impakt/script/cli.py (137 lines)                         -- CLI arg parsing

Documentation

  • Docstring coverage: 420 / 457 definitions (91.9%)
  • Modules with __all__: 11 / 11 (all modules)
    • channel: YES
    • criteria: YES
    • io: YES (new)
    • plot: YES
    • plugin: YES (new)
    • protocol: YES
    • report: YES (new)
    • script: YES (new)
    • template: YES (new)
    • transform: YES
    • web: YES
  • README: 1,266 lines with Mermaid diagrams
  • Architectural diagrams: yes

Security

  • eval/exec (sandboxed): 1 — math_expr.py, restricted builtins {} + 16-token blocklist; excluded from grep via # noqa: S307
  • eval/exec (unsandboxed): 0
  • subprocess: 1 confirmed false positive — src/impakt/transform/math_expr.py:68: "subprocess", is a forbidden-token blocklist string, not an invocation
  • Hardcoded secrets: 0
  • Bare except: 0

Maintainability

  • TODO: 0
  • FIXME: 0
  • HACK: 0
  • Logging calls: 50
  • try/except blocks: 53
  • Bare excepts: 0
  • Internal imports (coupling): 198

Scorecard

# Dimension Weight Score Weighted Justification
1 Test Health 20% 8.0/10 1.60 240/240 pass; test:source ratio 0.259 (within 0.20.5 band); integration tests with real datasets present; 69.51% coverage now measured (below 80% ceiling for a 10).
2 Type Safety 15% 6.8/10 1.02 mypy strict, 34 errors in 16 files; all 17 [type-arg] errors resolved since last run. Linear interpolation between 6 (<50) and 8 (<10): 6 + (50-34)/(50-10) × 2 = 6.8.
3 Lint Hygiene 10% 10.0/10 1.00 ruff check src/ reports 0 violations. Perfect score; all 89 prior violations cleared.
4 Architecture 15% 10.0/10 1.50 Clear 4-layer design (io → transform/channel → protocol → web/plot). Plugin system present. All 11 public modules export __all__. No layer violations confirmed (io/transform/protocol/channel do not import from web or plot).
5 Documentation 10% 9.0/10 0.90 91.9% docstring coverage exceeds 90% threshold. Comprehensive README with Mermaid diagrams. No generated API reference (Sphinx/mkdocs), so not a full 10.
6 Complexity 10% 6.5/10 0.65 Median 132 (<150, excellent). 8 files >300 lines (up from 6). plot_callbacks.py (324) and engine.py (303) newly crossed threshold. Interpolated between 8 (≤3 files >300) and 6 (≤10): 6 + (10-8)/(10-3) × 2 = 6.57 → 6.5.
7 Security 10% 8.5/10 0.85 Single eval sandboxed with {"__builtins__": {}} + 16-item token blocklist. Subprocess hit confirmed false positive (blocklist string, not call). No secrets, no bare excepts. Interpolated between 9 (fully sandboxed) and 7 (partially sandboxed).
8 Maintainability 10% 8.5/10 0.85 Zero debt markers. Zero bare excepts. 50 logging calls. Modern tooling (uv, hatchling, ruff, mypy). Between 10 (perfect) and 8 (<5 markers).

Composite Score: 83.7 / 100

Grade: B+

Calculation: (8.0×0.20 + 6.8×0.15 + 10.0×0.10 + 10.0×0.15 + 9.0×0.10 + 6.5×0.10 + 8.5×0.10 + 8.5×0.10) × 10 = (1.60 + 1.02 + 1.00 + 1.50 + 0.90 + 0.65 + 0.85 + 0.85) × 10 = 8.37 × 10 = 83.7


Delta from Previous Assessment

Dimension Previous Current Change
Test Health 8.0 8.0 0.0
Type Safety 6.5 6.8 +0.3
Lint Hygiene 6.0 10.0 +4.0
Architecture 9.0 10.0 +1.0
Documentation 9.0 9.0 0.0
Complexity 7.0 6.5 -0.5
Security 8.5 8.5 0.0
Maintainability 8.5 8.5 0.0
Composite 78.3 83.7 +5.4

Top Improvements Since Last Assessment

  1. Lint Hygiene: 6.0 → 10.0 — All 89 ruff violations resolved (73 auto-fixed + 16 manual, including 8 F601 duplicate dict keys). First perfect lint score.
  2. Architecture: 9.0 → 10.0 — Five modules (io, plugin, report, script, template) received proper __all__ exports, completing the public-API surface across all 11 packages.
  3. Type Safety: 6.5 → 6.8 — 15 fewer mypy errors (49 → 34); all 17 [type-arg] errors from generic parameters resolved; 4 fewer files with errors (20 → 16).

# Action Effort Impact Dimensions Affected
1 Increase test coverage from 69.51% to ≥80%: add unit tests for uncovered branches in web/state.py, plot/engine.py, and io/mme.py parser edge-cases 24 hr Test Health +1.0 → 9.0 (+0.20 composite) Test Health
2 Resolve 9 [attr-defined] + 6 [no-any-return] + 4 [return-value] mypy errors in the web layer to reach <10 total errors 12 hr Type Safety +1.2 → 8.0 (+0.18 composite) Type Safety
3 Decompose channel_grid.py (418 lines) and channel/model.py (456 lines) into focused sub-modules; extract sub-parsers from mme.py if it grows beyond 800 lines 35 hr Complexity +0.5 → 7.0 (+0.05 composite) Complexity
4 Set up mkdocs-material + mkdocstrings to auto-generate API reference from existing docstrings 12 hr Documentation +1.0 → 10.0 (+0.10 composite) Documentation
5 Replace eval-based math expression evaluator in math_expr.py with an AST-based parser (e.g., ast.parse + safe node visitor) to eliminate last eval usage 23 hr Security +0.5 → 9.0 (+0.05 composite) Security

Projected composite after actions 14: ~87 (B+), after all 5: ~88 (B+)


Notes

  • Architecture qualitatively verified. All five newly-added __init__.py files were inspected and follow the pattern of explicit imports + __all__ lists. No layer violations detected: io, transform, protocol, and channel packages contain no imports from web or plot.
  • pytest-cov is now active. The test run enforces a 60% coverage floor (--cov --cov-report=term-missing or equivalent in pyproject.toml). The overall 69.51% exceeds the floor but is below the 80% threshold required to push Test Health to 9.0. Note: the --co (collect-only) invocation showed only 30.81% because 11 files are skipped during collection; the full run gives 69.51%.
  • Complexity slight regression. Two files crossed the 300-line threshold since the last run: plot_callbacks.py (249 → 324 lines) and plot/engine.py (257 → 303 lines). Both contain justified density (pipeline orchestration and Plotly rendering respectively), but the increase in count from 6 to 8 files >300 reduces the score from 7.0 to 6.5 per the rubric interpolation.
  • Security subprocess false positive persists: math_expr.py:68 contains the string "subprocess" as an entry in its forbidden-token blocklist. No actual subprocess invocation exists in the codebase.
  • Type-arg errors fully resolved. The previous leading error category ([type-arg] = 17) is now zero. Remaining errors are concentrated in [attr-defined] (9) and [no-any-return] (6), both in the web callback layer where Dash's typing stubs are incomplete.
  • Source line growth (+238 lines, 10,325 → 10,563) is consistent with the five new __init__.py additions and growth in existing files.