10 KiB
10 KiB
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 — returningAnyfrom 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.2–0.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
- Lint Hygiene: 6.0 → 10.0 — All 89 ruff violations resolved (73 auto-fixed + 16 manual, including 8
F601duplicate dict keys). First perfect lint score. - 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. - 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).
Recommended Actions (Priority Order)
| # | 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 |
2–4 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 |
1–2 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 |
3–5 hr | Complexity +0.5 → 7.0 (+0.05 composite) | Complexity |
| 4 | Set up mkdocs-material + mkdocstrings to auto-generate API reference from existing docstrings | 1–2 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 |
2–3 hr | Security +0.5 → 9.0 (+0.05 composite) | Security |
Projected composite after actions 1–4: ~87 (B+), after all 5: ~88 (B+)
Notes
- Architecture qualitatively verified. All five newly-added
__init__.pyfiles were inspected and follow the pattern of explicit imports +__all__lists. No layer violations detected:io,transform,protocol, andchannelpackages contain no imports fromweborplot. - pytest-cov is now active. The test run enforces a 60% coverage floor (
--cov --cov-report=term-missingor equivalent inpyproject.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) andplot/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:68contains 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__.pyadditions and growth in existing files.