# 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.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 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). --- ## 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__.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.