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

211 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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).
---
## 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 | 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.