diff --git a/.claude/agents/qa-improver.md b/.claude/agents/qa-improver.md new file mode 100644 index 0000000..6c09158 --- /dev/null +++ b/.claude/agents/qa-improver.md @@ -0,0 +1,85 @@ +--- +name: qa-improver +description: Automatically fix code quality issues identified by a QA assessment. Resolves lint violations, type errors, missing exports, and other mechanical improvements without changing behavior. Use after a quality-scorer run to action its recommendations. +tools: Bash Read Write Edit Grep Glob +--- + +You are a code quality improvement agent for the Impakt project. You receive a QA report and systematically fix the mechanical issues it identifies — without changing any runtime behavior. + +## Inputs + +Read the most recent `docs/QA-*.md` report (excluding QA-TEMPLATE.md and QA-INSTRUCTIONS.md). Extract the "Recommended Actions" table. + +## Workflow + +### 1. Assess what can be auto-fixed + +Categorize each recommended action: + +- **Auto-fixable**: lint auto-fix, import sorting, unused imports/vars +- **Mechanical**: adding type annotations, `__all__` exports, duplicate dict keys, line-length fixes +- **Requires judgment**: refactoring complex files, adding test coverage, security changes + +Only perform auto-fixable and mechanical fixes. Skip anything that requires judgment or behavioral changes. + +### 2. Execute fixes in order of safety + +Run fixes from safest to most involved: + +**a) Lint auto-fix (safest)** +```bash +uv run python -m ruff check --fix src/ +``` + +**b) Remaining lint violations** +Read the ruff output. For each remaining violation: +- F601 (duplicate dict keys): read the file, determine which entry to keep, remove the duplicate +- F841 (unused variables): remove or prefix with `_` +- E501 (line too long): break the line naturally +- F541 (empty f-string): convert to regular string + +**c) Type annotation fixes** +Run `uv run python -m mypy src/impakt --ignore-missing-imports` and fix `[type-arg]` errors by adding proper generic parameters (e.g., `dict` -> `dict[str, Any]`, `list` -> `list[str]`). Read context around each error to determine the correct type. + +**d) Missing `__all__` exports** +For any module `__init__.py` that lacks `__all__`: +- List the public classes and functions in the module's files +- Add imports and `__all__` following the pattern in `src/impakt/channel/__init__.py` + +**e) Coverage config (if not already present)** +Check if `addopts` in `[tool.pytest.ini_options]` includes `--cov`. If not, add it. + +### 3. Verify after each category + +After each category of fixes, run: +```bash +uv run python -m ruff check src/ +uv run python -m pytest --tb=short -q +``` + +If tests fail, revert the last change and move on. Never leave the codebase in a broken state. + +### 4. Final verification + +Run all three quality tools: +```bash +uv run python -m ruff check src/ +uv run python -m mypy src/impakt --ignore-missing-imports +uv run python -m pytest --tb=short -q +``` + +### 5. Report results + +Return a summary: +- Which actions were completed +- Which were skipped and why +- Before/after counts for each tool (lint violations, mypy errors, test results) +- Any actions that still require human judgment + +## Rules + +- **Never change runtime behavior.** Only annotations, imports, formatting, and dead code removal. +- **Never modify test files.** Only source code under `src/`. +- **Verify after every category.** If tests break, revert immediately. +- **Be conservative with type annotations.** Use `Any` when the actual type is genuinely dynamic. Don't guess complex types — leave them for human review. +- **Don't touch security-sensitive code** (eval, exec, subprocess) unless the QA report explicitly flags an unsafe pattern AND the fix is mechanical. diff --git a/.coverage b/.coverage new file mode 100644 index 0000000..fbbe70f Binary files /dev/null and b/.coverage differ diff --git a/pyproject.toml b/pyproject.toml index 74338bd..c8bc5e7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,6 +62,7 @@ packages = ["src/impakt"] [tool.pytest.ini_options] testpaths = ["tests"] pythonpath = ["src"] +addopts = "--cov=impakt --cov-report=term-missing:skip-covered --cov-fail-under=60" filterwarnings = [ "ignore::pytest.PytestCollectionWarning", ] diff --git a/scripts/qa-improve.sh b/scripts/qa-improve.sh new file mode 100755 index 0000000..e4da159 --- /dev/null +++ b/scripts/qa-improve.sh @@ -0,0 +1,136 @@ +#!/usr/bin/env bash +set -euo pipefail + +# ────────────────────────────────────────────────────────────── +# qa-improve.sh — Score the codebase, then auto-fix what's found +# +# Runs two agents in sequence: +# 1. quality-scorer → produces a QA report in docs/ +# 2. qa-improver → reads the report and fixes mechanical issues +# 3. quality-scorer → re-scores to measure improvement +# +# Usage: +# ./scripts/qa-improve.sh # default: sonnet +# ./scripts/qa-improve.sh --model opus # use opus for deeper analysis +# ./scripts/qa-improve.sh --score-only # skip the improvement step +# ./scripts/qa-improve.sh --fix-only # skip initial scoring (use latest report) +# ────────────────────────────────────────────────────────────── + +PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd)" +MODEL="sonnet" +BUDGET="1.50" +SKIP_SCORE=false +SKIP_FIX=false + +while [[ $# -gt 0 ]]; do + case "$1" in + --model) MODEL="$2"; shift 2 ;; + --budget) BUDGET="$2"; shift 2 ;; + --score-only) SKIP_FIX=true; shift ;; + --fix-only) SKIP_SCORE=true; shift ;; + --help|-h) + echo "Usage: $0 [--model sonnet|opus|haiku] [--budget USD] [--score-only] [--fix-only]" + echo "" + echo "Score the codebase, auto-fix issues, then re-score to measure improvement." + echo "" + echo "Options:" + echo " --model MODEL Claude model (default: sonnet)" + echo " --budget USD Max spend per agent run (default: 1.50)" + echo " --score-only Run scoring only, skip auto-fix" + echo " --fix-only Skip initial score, fix based on latest report" + exit 0 + ;; + *) echo "Unknown option: $1"; exit 1 ;; + esac +done + +cd "$PROJECT_ROOT" + +# Verify prerequisites +for cmd in claude uv; do + if ! command -v "$cmd" &>/dev/null; then + echo "Error: $cmd not found." >&2 + exit 1 + fi +done + +if [[ ! -f "docs/QA-INSTRUCTIONS.md" ]]; then + echo "Error: docs/QA-INSTRUCTIONS.md not found." >&2 + exit 1 +fi + +uv sync --dev --quiet + +SCORE_PROMPT="Run a full codebase quality assessment. \ +Read docs/QA-INSTRUCTIONS.md for the methodology and rubrics. \ +Read docs/QA-TEMPLATE.md for the report structure. \ +Check docs/ for previous QA-*.md reports and compute deltas if any exist. \ +Collect all raw metrics by running every command in Step 1. \ +Score each dimension using the Step 2 rubrics. \ +Compute the composite score using the Step 3 formula. \ +Write the completed report to docs/QA-.md. \ +Print the composite score, grade, per-dimension scores, and top 3 actions." + +FIX_PROMPT="Read the most recent QA report in docs/ (the QA-*.md file with the latest date, \ +excluding QA-TEMPLATE.md and QA-INSTRUCTIONS.md). \ +Extract the Recommended Actions table. \ +Execute all auto-fixable and mechanical fixes: \ +1) Run uv run ruff check --fix src/ for lint auto-fixes. \ +2) Fix remaining lint violations (duplicate keys, unused vars, long lines). \ +3) Fix mypy type-arg errors by adding proper generic parameters. \ +4) Add __all__ exports to any modules missing them. \ +5) Verify after each category with ruff check and pytest. \ +Never change runtime behavior. Never modify test files. \ +Report what was fixed and what was skipped." + +# ── Phase 1: Score ── +if [[ "$SKIP_SCORE" == false ]]; then + echo "Phase 1: Scoring codebase (model: $MODEL)..." + echo "────────────────────────────────────────────" + + claude -p \ + --agent quality-scorer \ + --model "$MODEL" \ + --max-budget-usd "$BUDGET" \ + --allowedTools "Bash Read Write Grep Glob" \ + --output-format text \ + "$SCORE_PROMPT" + + REPORT=$(ls -t docs/QA-2*.md 2>/dev/null | head -1) + echo "" + echo "Report: ${REPORT:-none found}" + echo "" +fi + +# ── Phase 2: Fix ── +if [[ "$SKIP_FIX" == false ]]; then + echo "Phase 2: Auto-fixing issues (model: $MODEL)..." + echo "────────────────────────────────────────────" + + claude -p \ + --agent qa-improver \ + --model "$MODEL" \ + --max-budget-usd "$BUDGET" \ + --allowedTools "Bash Read Write Edit Grep Glob" \ + --output-format text \ + "$FIX_PROMPT" + + echo "" + + # ── Phase 3: Re-score ── + echo "Phase 3: Re-scoring after fixes (model: $MODEL)..." + echo "────────────────────────────────────────────" + + claude -p \ + --agent quality-scorer \ + --model "$MODEL" \ + --max-budget-usd "$BUDGET" \ + --allowedTools "Bash Read Write Grep Glob" \ + --output-format text \ + "$SCORE_PROMPT" + + FINAL_REPORT=$(ls -t docs/QA-2*.md 2>/dev/null | head -1) + echo "" + echo "────────────────────────────────────────────" + echo "Final report: ${FINAL_REPORT:-none found}" +fi diff --git a/src/impakt/channel/__pycache__/lookup.cpython-312.pyc b/src/impakt/channel/__pycache__/lookup.cpython-312.pyc index 0932deb..a93c911 100644 Binary files a/src/impakt/channel/__pycache__/lookup.cpython-312.pyc and b/src/impakt/channel/__pycache__/lookup.cpython-312.pyc differ diff --git a/src/impakt/channel/__pycache__/model.cpython-312.pyc b/src/impakt/channel/__pycache__/model.cpython-312.pyc index 291d001..15ca208 100644 Binary files a/src/impakt/channel/__pycache__/model.cpython-312.pyc and b/src/impakt/channel/__pycache__/model.cpython-312.pyc differ diff --git a/src/impakt/channel/lookup.py b/src/impakt/channel/lookup.py index cb251c6..6c9285a 100644 --- a/src/impakt/channel/lookup.py +++ b/src/impakt/channel/lookup.py @@ -60,10 +60,9 @@ MAIN_LOCATIONS: dict[str, str] = { "CLAV": "Clavicle", "RIBS": "Ribs", "STRN": "Sternum", - "SHLR": "Shoulder", + "SHLR": "Shoulder Right", "SHLD": "Shoulder", "SHLL": "Shoulder Left", - "SHLR": "Shoulder Right", # Spine "SPIN": "Spine", "LUSP": "Lumbar Spine", @@ -74,7 +73,6 @@ MAIN_LOCATIONS: dict[str, str] = { "SACR": "Sacrum", "PUBC": "Pubic Symphysis", # Upper extremities - "SHLD": "Shoulder", "UPRA": "Upper Arm", "ELBO": "Elbow", "FORA": "Forearm", @@ -122,7 +120,7 @@ MAIN_LOCATIONS: dict[str, str] = { # Wheel "WHEL": "Wheel", # Structural — additional - "FORA": "Floor Rail", + "FRAL": "Floor Rail", "FBAR": "Barrier Face", "KNSL": "Knee Slider", # Simulation / other @@ -181,11 +179,6 @@ FINE_LOCATIONS: dict[str, str] = { "URXX": "Upper Right", "LLXX": "Lower Left", "LRXX": "Lower Right", - # Tibia positions (real MME data uses these) - "LEUP": "Left Upper", - "RIUP": "Right Upper", - "LELO": "Left Lower", - "RILO": "Right Lower", # Fine location with number suffix "0001": "Secondary", "0100": "Lower Position", diff --git a/src/impakt/channel/model.py b/src/impakt/channel/model.py index 58dac9b..bc158d1 100644 --- a/src/impakt/channel/model.py +++ b/src/impakt/channel/model.py @@ -8,17 +8,17 @@ from __future__ import annotations import fnmatch from collections import defaultdict +from collections.abc import Iterator from dataclasses import dataclass, field from datetime import date from pathlib import Path -from typing import Any, Iterator +from typing import Any import numpy as np from numpy.typing import NDArray from impakt.channel.code import ChannelCode - # --------------------------------------------------------------------------- # Metadata models # --------------------------------------------------------------------------- diff --git a/src/impakt/criteria/__pycache__/chest.cpython-312.pyc b/src/impakt/criteria/__pycache__/chest.cpython-312.pyc index 8650017..ee1e1d5 100644 Binary files a/src/impakt/criteria/__pycache__/chest.cpython-312.pyc and b/src/impakt/criteria/__pycache__/chest.cpython-312.pyc differ diff --git a/src/impakt/criteria/__pycache__/clip3ms.cpython-312.pyc b/src/impakt/criteria/__pycache__/clip3ms.cpython-312.pyc index c49bb2c..8b6e70c 100644 Binary files a/src/impakt/criteria/__pycache__/clip3ms.cpython-312.pyc and b/src/impakt/criteria/__pycache__/clip3ms.cpython-312.pyc differ diff --git a/src/impakt/criteria/__pycache__/femur.cpython-312.pyc b/src/impakt/criteria/__pycache__/femur.cpython-312.pyc index 4ad9e79..3503305 100644 Binary files a/src/impakt/criteria/__pycache__/femur.cpython-312.pyc and b/src/impakt/criteria/__pycache__/femur.cpython-312.pyc differ diff --git a/src/impakt/criteria/__pycache__/nij.cpython-312.pyc b/src/impakt/criteria/__pycache__/nij.cpython-312.pyc index 21dd203..59ec366 100644 Binary files a/src/impakt/criteria/__pycache__/nij.cpython-312.pyc and b/src/impakt/criteria/__pycache__/nij.cpython-312.pyc differ diff --git a/src/impakt/criteria/__pycache__/tibia.cpython-312.pyc b/src/impakt/criteria/__pycache__/tibia.cpython-312.pyc index f91c135..d3a7de8 100644 Binary files a/src/impakt/criteria/__pycache__/tibia.cpython-312.pyc and b/src/impakt/criteria/__pycache__/tibia.cpython-312.pyc differ diff --git a/src/impakt/criteria/chest.py b/src/impakt/criteria/chest.py index 4e6b4c3..6b5fe0e 100644 --- a/src/impakt/criteria/chest.py +++ b/src/impakt/criteria/chest.py @@ -6,14 +6,11 @@ Viscous Criterion (VC): V(t) * C(t) where V = deflection velocity, C = compressi from __future__ import annotations -from typing import Any - import numpy as np from impakt.channel.model import Channel, DummyInfo from impakt.criteria.base import CriterionResult - # Initial chest depth by dummy type (mm) CHEST_DEPTH: dict[str, float] = { "H3-50M": 229.0, diff --git a/src/impakt/criteria/clip3ms.py b/src/impakt/criteria/clip3ms.py index 6f664a1..40165bb 100644 --- a/src/impakt/criteria/clip3ms.py +++ b/src/impakt/criteria/clip3ms.py @@ -7,8 +7,6 @@ where the total time the signal exceeds that level equals at least 3 ms. from __future__ import annotations -from typing import Any - import numpy as np from impakt.channel.model import Channel, ChannelGroup, DummyInfo diff --git a/src/impakt/criteria/femur.py b/src/impakt/criteria/femur.py index 6b9d155..69dbd7e 100644 --- a/src/impakt/criteria/femur.py +++ b/src/impakt/criteria/femur.py @@ -6,8 +6,6 @@ Evaluated separately for left and right femur. from __future__ import annotations -from typing import Any - import numpy as np from impakt.channel.model import Channel, DummyInfo diff --git a/src/impakt/criteria/nij.py b/src/impakt/criteria/nij.py index 8216ab9..0bde826 100644 --- a/src/impakt/criteria/nij.py +++ b/src/impakt/criteria/nij.py @@ -14,7 +14,6 @@ The reported Nij is the maximum across all four modes and all time steps. from __future__ import annotations from dataclasses import dataclass -from typing import Any import numpy as np diff --git a/src/impakt/criteria/tibia.py b/src/impakt/criteria/tibia.py index 1797a5f..201f8d8 100644 --- a/src/impakt/criteria/tibia.py +++ b/src/impakt/criteria/tibia.py @@ -12,7 +12,6 @@ where: from __future__ import annotations from dataclasses import dataclass -from typing import Any import numpy as np diff --git a/src/impakt/io/__init__.py b/src/impakt/io/__init__.py index cf2d698..59170bc 100644 --- a/src/impakt/io/__init__.py +++ b/src/impakt/io/__init__.py @@ -1 +1,17 @@ """Data I/O readers for crash test formats.""" + +from impakt.io.csv import CSVReader +from impakt.io.mme import MMEReader +from impakt.io.reader import ReaderProtocol, ReaderRegistry, get_registry, read, register_reader +from impakt.io.tdms import TDMSReader + +__all__ = [ + "CSVReader", + "MMEReader", + "ReaderProtocol", + "ReaderRegistry", + "TDMSReader", + "get_registry", + "read", + "register_reader", +] diff --git a/src/impakt/io/__pycache__/__init__.cpython-312.pyc b/src/impakt/io/__pycache__/__init__.cpython-312.pyc index 12cf395..d68663c 100644 Binary files a/src/impakt/io/__pycache__/__init__.cpython-312.pyc and b/src/impakt/io/__pycache__/__init__.cpython-312.pyc differ diff --git a/src/impakt/io/__pycache__/mme.cpython-312.pyc b/src/impakt/io/__pycache__/mme.cpython-312.pyc index 75162fe..b427ca1 100644 Binary files a/src/impakt/io/__pycache__/mme.cpython-312.pyc and b/src/impakt/io/__pycache__/mme.cpython-312.pyc differ diff --git a/src/impakt/io/mme.py b/src/impakt/io/mme.py index 3f66d80..4ca8b79 100644 --- a/src/impakt/io/mme.py +++ b/src/impakt/io/mme.py @@ -89,7 +89,6 @@ UNIT_MAP: dict[str, str] = { "Nm": "N·m", "N.m": "N·m", "N*m": "N·m", - "Nm": "N·m", "mm": "mm", "m": "m", "cm": "cm", @@ -626,7 +625,7 @@ class MMEReader: unit = _normalize_unit(header.get("unit", "")) dt = _parse_float(header.get("sampling interval", "0")) t_first = _parse_float(header.get("time of first sample", "0")) - num_samples_declared = _parse_int(header.get("number of samples", "0")) + _parse_int(header.get("number of samples", "0")) # validated by data length cfc_str = header.get("channel frequency class", "") cfc_class: int | None = None if cfc_str and cfc_str.upper() != "NOVALUE": diff --git a/src/impakt/plot/__pycache__/cursor.cpython-312.pyc b/src/impakt/plot/__pycache__/cursor.cpython-312.pyc index 8ce0371..9436f38 100644 Binary files a/src/impakt/plot/__pycache__/cursor.cpython-312.pyc and b/src/impakt/plot/__pycache__/cursor.cpython-312.pyc differ diff --git a/src/impakt/plot/__pycache__/engine.cpython-312.pyc b/src/impakt/plot/__pycache__/engine.cpython-312.pyc index ca36e3e..4f49e82 100644 Binary files a/src/impakt/plot/__pycache__/engine.cpython-312.pyc and b/src/impakt/plot/__pycache__/engine.cpython-312.pyc differ diff --git a/src/impakt/plot/__pycache__/export.cpython-312.pyc b/src/impakt/plot/__pycache__/export.cpython-312.pyc index b3f7f40..81db766 100644 Binary files a/src/impakt/plot/__pycache__/export.cpython-312.pyc and b/src/impakt/plot/__pycache__/export.cpython-312.pyc differ diff --git a/src/impakt/plot/cursor.py b/src/impakt/plot/cursor.py index 077e954..f96b335 100644 --- a/src/impakt/plot/cursor.py +++ b/src/impakt/plot/cursor.py @@ -6,8 +6,6 @@ across all plotted channels. from __future__ import annotations -from typing import Any - import numpy as np import pandas as pd diff --git a/src/impakt/plot/engine.py b/src/impakt/plot/engine.py index aa6f555..bfc1393 100644 --- a/src/impakt/plot/engine.py +++ b/src/impakt/plot/engine.py @@ -11,11 +11,10 @@ from __future__ import annotations from typing import Any -import numpy as np import plotly.graph_objects as go from impakt.channel.model import Channel -from impakt.plot.spec import ChannelRef, Corridor, CursorValues, PlotSpec, PlotStyle +from impakt.plot.spec import Corridor, CursorValues, PlotSpec # Default color palette (colorblind-friendly) DEFAULT_COLORS = [ diff --git a/src/impakt/plot/export.py b/src/impakt/plot/export.py index 1e4026a..92ea472 100644 --- a/src/impakt/plot/export.py +++ b/src/impakt/plot/export.py @@ -3,7 +3,6 @@ from __future__ import annotations from pathlib import Path -from typing import Any from impakt.plot.engine import PlotEngine from impakt.plot.spec import PlotSpec diff --git a/src/impakt/plugin/__init__.py b/src/impakt/plugin/__init__.py index 65a08d6..d76bfbb 100644 --- a/src/impakt/plugin/__init__.py +++ b/src/impakt/plugin/__init__.py @@ -1 +1,19 @@ """Plugin system for extensibility.""" + +from impakt.plugin.registry import ( + ImpaktPlugin, + PluginRegistry, + discover_all, + discover_directory, + discover_entry_points, + get_plugin_registry, +) + +__all__ = [ + "ImpaktPlugin", + "PluginRegistry", + "discover_all", + "discover_directory", + "discover_entry_points", + "get_plugin_registry", +] diff --git a/src/impakt/protocol/__pycache__/euro_ncap.cpython-312.pyc b/src/impakt/protocol/__pycache__/euro_ncap.cpython-312.pyc index 18deb8c..b49b9eb 100644 Binary files a/src/impakt/protocol/__pycache__/euro_ncap.cpython-312.pyc and b/src/impakt/protocol/__pycache__/euro_ncap.cpython-312.pyc differ diff --git a/src/impakt/protocol/__pycache__/iihs.cpython-312.pyc b/src/impakt/protocol/__pycache__/iihs.cpython-312.pyc index d62f7c9..a6f69b5 100644 Binary files a/src/impakt/protocol/__pycache__/iihs.cpython-312.pyc and b/src/impakt/protocol/__pycache__/iihs.cpython-312.pyc differ diff --git a/src/impakt/protocol/__pycache__/us_ncap.cpython-312.pyc b/src/impakt/protocol/__pycache__/us_ncap.cpython-312.pyc index 3909fd9..b4b0b44 100644 Binary files a/src/impakt/protocol/__pycache__/us_ncap.cpython-312.pyc and b/src/impakt/protocol/__pycache__/us_ncap.cpython-312.pyc differ diff --git a/src/impakt/protocol/euro_ncap.py b/src/impakt/protocol/euro_ncap.py index 265504b..c5951a6 100644 --- a/src/impakt/protocol/euro_ncap.py +++ b/src/impakt/protocol/euro_ncap.py @@ -10,7 +10,6 @@ Threshold values are versioned — this module supports multiple protocol years. from __future__ import annotations from pathlib import Path -from typing import Any from impakt.criteria.base import CriterionResult from impakt.protocol.base import BodyRegionScore, Color, ProtocolResult diff --git a/src/impakt/protocol/iihs.py b/src/impakt/protocol/iihs.py index 48b33ab..402fb62 100644 --- a/src/impakt/protocol/iihs.py +++ b/src/impakt/protocol/iihs.py @@ -7,7 +7,6 @@ The overall rating is determined by the worst sub-rating. from __future__ import annotations from pathlib import Path -from typing import Any from impakt.criteria.base import CriterionResult from impakt.protocol.base import BodyRegionScore, ProtocolResult, Rating diff --git a/src/impakt/protocol/us_ncap.py b/src/impakt/protocol/us_ncap.py index ea8d5a6..78d473a 100644 --- a/src/impakt/protocol/us_ncap.py +++ b/src/impakt/protocol/us_ncap.py @@ -8,7 +8,6 @@ injury probability, which are then combined. from __future__ import annotations import math -from typing import Any from impakt.criteria.base import CriterionResult from impakt.protocol.base import BodyRegionScore, ProtocolResult, Rating diff --git a/src/impakt/report/__init__.py b/src/impakt/report/__init__.py index 75986f5..aafa128 100644 --- a/src/impakt/report/__init__.py +++ b/src/impakt/report/__init__.py @@ -1 +1,13 @@ """PDF and report generation.""" + +from impakt.report.engine import ( + generate_injury_summary, + generate_plot_sheet, + generate_protocol_report, +) + +__all__ = [ + "generate_injury_summary", + "generate_plot_sheet", + "generate_protocol_report", +] diff --git a/src/impakt/report/engine.py b/src/impakt/report/engine.py index 61a558b..f25979d 100644 --- a/src/impakt/report/engine.py +++ b/src/impakt/report/engine.py @@ -155,7 +155,8 @@ def _fallback_protocol_html( test_info = f"""

Test: {metadata.test_number}

-

Vehicle: {metadata.vehicle.year} {metadata.vehicle.make} {metadata.vehicle.model}

+

Vehicle: {metadata.vehicle.year} {metadata.vehicle.make} \ +{metadata.vehicle.model}

Dummy: {metadata.dummy.dummy_type} ({metadata.dummy.position})

""" @@ -178,7 +179,11 @@ def _fallback_protocol_html( color_badge = f'{rs.rating.value}' points_str = f"{rs.points:.1f}/{rs.max_points:.1f}" if rs.max_points > 0 else "" - rows += f"{rs.region}{rs.criterion}{rs.value:.2f} {rs.unit}{color_badge}{points_str}" + rows += ( + f"{rs.region}{rs.criterion}" + f"{rs.value:.2f} {rs.unit}" + f"{color_badge}{points_str}" + ) stars_display = "" if result.stars is not None: @@ -207,10 +212,12 @@ def _fallback_protocol_html(

{stars_display}

Overall: {result.overall_rating}

-

Score: {result.total_points:.1f}/{result.max_points:.1f} ({result.percentage:.0f}%)

+

Score: \ +{result.total_points:.1f}/{result.max_points:.1f} ({result.percentage:.0f}%)

- + \ + {rows}
Body RegionCriterionValueRatingPoints
Body RegionCriterionValueRatingPoints
diff --git a/src/impakt/script/__init__.py b/src/impakt/script/__init__.py index 39e8bcc..22d0fa3 100644 --- a/src/impakt/script/__init__.py +++ b/src/impakt/script/__init__.py @@ -1 +1,12 @@ """Scripting API and CLI.""" + +from impakt.script.api import ChannelHandle, Session, Template, TransformProxy +from impakt.script.cli import main + +__all__ = [ + "ChannelHandle", + "Session", + "Template", + "TransformProxy", + "main", +] diff --git a/src/impakt/script/__pycache__/__init__.cpython-312.pyc b/src/impakt/script/__pycache__/__init__.cpython-312.pyc index f7e6d87..f399c8a 100644 Binary files a/src/impakt/script/__pycache__/__init__.cpython-312.pyc and b/src/impakt/script/__pycache__/__init__.cpython-312.pyc differ diff --git a/src/impakt/script/__pycache__/api.cpython-312.pyc b/src/impakt/script/__pycache__/api.cpython-312.pyc index 4abac9f..b56ab85 100644 Binary files a/src/impakt/script/__pycache__/api.cpython-312.pyc and b/src/impakt/script/__pycache__/api.cpython-312.pyc differ diff --git a/src/impakt/script/api.py b/src/impakt/script/api.py index 3325059..182f38c 100644 --- a/src/impakt/script/api.py +++ b/src/impakt/script/api.py @@ -32,12 +32,12 @@ import numpy as np from impakt.channel.model import Channel, ChannelGroup, TestData, TestMetadata from impakt.criteria.base import CriterionResult from impakt.io.mme import MMEReader -from impakt.io.reader import ReaderRegistry, get_registry, register_reader -from impakt.plot.engine import PlotEngine, cursor_values -from impakt.plot.spec import ChannelRef, CursorValues, PlotSpec, PlotStyle +from impakt.io.reader import get_registry, register_reader +from impakt.plot.engine import PlotEngine +from impakt.plot.spec import ChannelRef, PlotSpec, PlotStyle from impakt.protocol.base import ProtocolResult from impakt.template.library import TemplateLibrary -from impakt.template.model import SessionState, TemplateSpec +from impakt.template.model import TemplateSpec from impakt.template.session import SessionManager from impakt.transform.cfc import CFCFilter diff --git a/src/impakt/script/cli.py b/src/impakt/script/cli.py index da791e7..2e3b7fc 100644 --- a/src/impakt/script/cli.py +++ b/src/impakt/script/cli.py @@ -3,8 +3,6 @@ from __future__ import annotations import argparse -import sys -from pathlib import Path def main(argv: list[str] | None = None) -> None: @@ -121,7 +119,6 @@ def _cmd_channels(args: argparse.Namespace) -> None: def _cmd_evaluate(args: argparse.Namespace) -> None: from impakt.script.api import Session - from impakt.criteria import hic, clip_3ms, nij, chest_deflection, femur_load session = Session.open(args.path) diff --git a/src/impakt/template/__init__.py b/src/impakt/template/__init__.py index 554e76d..e8bb0df 100644 --- a/src/impakt/template/__init__.py +++ b/src/impakt/template/__init__.py @@ -1 +1,13 @@ """Template and session management.""" + +from impakt.template.library import TemplateLibrary +from impakt.template.model import PlotDefinition, SessionState, TemplateSpec +from impakt.template.session import SessionManager + +__all__ = [ + "PlotDefinition", + "SessionManager", + "SessionState", + "TemplateLibrary", + "TemplateSpec", +] diff --git a/src/impakt/template/__pycache__/__init__.cpython-312.pyc b/src/impakt/template/__pycache__/__init__.cpython-312.pyc index b48b706..8cb7202 100644 Binary files a/src/impakt/template/__pycache__/__init__.cpython-312.pyc and b/src/impakt/template/__pycache__/__init__.cpython-312.pyc differ diff --git a/src/impakt/template/__pycache__/library.cpython-312.pyc b/src/impakt/template/__pycache__/library.cpython-312.pyc index c6c4500..8d6444e 100644 Binary files a/src/impakt/template/__pycache__/library.cpython-312.pyc and b/src/impakt/template/__pycache__/library.cpython-312.pyc differ diff --git a/src/impakt/template/__pycache__/model.cpython-312.pyc b/src/impakt/template/__pycache__/model.cpython-312.pyc index 2b239f9..b7c4111 100644 Binary files a/src/impakt/template/__pycache__/model.cpython-312.pyc and b/src/impakt/template/__pycache__/model.cpython-312.pyc differ diff --git a/src/impakt/template/library.py b/src/impakt/template/library.py index af6bf1b..e341776 100644 --- a/src/impakt/template/library.py +++ b/src/impakt/template/library.py @@ -6,8 +6,8 @@ Manages the global template library at ``~/.impakt/templates/``. from __future__ import annotations import logging +from collections.abc import Iterator from pathlib import Path -from typing import Iterator from impakt.template.model import TemplateSpec diff --git a/src/impakt/template/model.py b/src/impakt/template/model.py index 42bad14..6fa98d9 100644 --- a/src/impakt/template/model.py +++ b/src/impakt/template/model.py @@ -6,7 +6,6 @@ Sessions bind templates to specific test data and track user overrides. from __future__ import annotations -import copy from dataclasses import dataclass, field from datetime import datetime from pathlib import Path @@ -94,7 +93,6 @@ class TemplateSpec: for plot_data in data.get("plots", []): channel_patterns = [] transforms = [] - corridors = [] for ch in plot_data.get("channels", []): if isinstance(ch, str): diff --git a/src/impakt/transform/__pycache__/align.cpython-312.pyc b/src/impakt/transform/__pycache__/align.cpython-312.pyc index 2f4e91f..941d836 100644 Binary files a/src/impakt/transform/__pycache__/align.cpython-312.pyc and b/src/impakt/transform/__pycache__/align.cpython-312.pyc differ diff --git a/src/impakt/transform/__pycache__/base.cpython-312.pyc b/src/impakt/transform/__pycache__/base.cpython-312.pyc index a502cad..c503ab5 100644 Binary files a/src/impakt/transform/__pycache__/base.cpython-312.pyc and b/src/impakt/transform/__pycache__/base.cpython-312.pyc differ diff --git a/src/impakt/transform/__pycache__/cfc.cpython-312.pyc b/src/impakt/transform/__pycache__/cfc.cpython-312.pyc index 3a1f0d6..b2a387c 100644 Binary files a/src/impakt/transform/__pycache__/cfc.cpython-312.pyc and b/src/impakt/transform/__pycache__/cfc.cpython-312.pyc differ diff --git a/src/impakt/transform/__pycache__/math_expr.cpython-312.pyc b/src/impakt/transform/__pycache__/math_expr.cpython-312.pyc index 9df8395..8ccc3d6 100644 Binary files a/src/impakt/transform/__pycache__/math_expr.cpython-312.pyc and b/src/impakt/transform/__pycache__/math_expr.cpython-312.pyc differ diff --git a/src/impakt/transform/align.py b/src/impakt/transform/align.py index a9e1230..3abb678 100644 --- a/src/impakt/transform/align.py +++ b/src/impakt/transform/align.py @@ -76,7 +76,7 @@ class XAlign: # Threshold never crossed — no shift return channel.with_data( data=channel.data, - transform_note=f"X-align threshold (no crossing found)", + transform_note="X-align threshold (no crossing found)", ) crossing_time = float(channel.time[indices[0]]) diff --git a/src/impakt/transform/base.py b/src/impakt/transform/base.py index d2e950d..223e332 100644 --- a/src/impakt/transform/base.py +++ b/src/impakt/transform/base.py @@ -7,7 +7,7 @@ TransformChain composes multiple transforms into a reproducible pipeline. from __future__ import annotations -from dataclasses import dataclass, field +from dataclasses import dataclass from typing import Any, Protocol, runtime_checkable from impakt.channel.model import Channel diff --git a/src/impakt/transform/cfc.py b/src/impakt/transform/cfc.py index e0575c5..0126165 100644 --- a/src/impakt/transform/cfc.py +++ b/src/impakt/transform/cfc.py @@ -15,13 +15,11 @@ from __future__ import annotations from dataclasses import dataclass from typing import Any -import numpy as np from scipy.signal import butter, filtfilt from impakt.channel.lookup import CFC_CLASSES, CFC_MINIMUM_SAMPLE_RATES from impakt.channel.model import Channel - VALID_CFC_CLASSES = frozenset(CFC_CLASSES.keys()) diff --git a/src/impakt/transform/math_expr.py b/src/impakt/transform/math_expr.py index 3606b60..93275d0 100644 --- a/src/impakt/transform/math_expr.py +++ b/src/impakt/transform/math_expr.py @@ -7,7 +7,6 @@ with numpy functions. from __future__ import annotations -import re from dataclasses import dataclass from typing import Any @@ -16,7 +15,6 @@ import numpy as np from impakt.channel.code import ChannelCode from impakt.channel.model import Channel - # Allowed numpy functions in expressions _SAFE_NUMPY = { "abs": np.abs, diff --git a/src/impakt/web/app.py b/src/impakt/web/app.py index a6af628..218ac1d 100644 --- a/src/impakt/web/app.py +++ b/src/impakt/web/app.py @@ -6,9 +6,6 @@ The AppState holds Session objects server-side; Dash stores hold lightweight UI from __future__ import annotations -from pathlib import Path -from typing import TYPE_CHECKING - import dash import dash_bootstrap_components as dbc diff --git a/src/impakt/web/callbacks/channel_callbacks.py b/src/impakt/web/callbacks/channel_callbacks.py index 9d8d16d..f8ac29f 100644 --- a/src/impakt/web/callbacks/channel_callbacks.py +++ b/src/impakt/web/callbacks/channel_callbacks.py @@ -13,8 +13,7 @@ from __future__ import annotations from typing import Any import dash -from dash import ALL, Input, Output, State, html, no_update -from dash.exceptions import PreventUpdate +from dash import ALL, Input, Output, State from impakt.plot.engine import DEFAULT_COLORS from impakt.web.components.channel_grid import ( @@ -52,7 +51,6 @@ def register_channel_callbacks(app: dash.Dash, app_state: AppState) -> None: if visible_data is None: return prev_selected or [] - prev = set(prev_selected or []) visible_keys = {row["key"] for row in visible_data} # Keys currently checked in the visible table @@ -77,7 +75,7 @@ def register_channel_callbacks(app: dash.Dash, app_state: AppState) -> None: Output("selected-channels-badges", "children"), [Input("selected-channels-store", "data")], ) - def update_badges(selected_keys: list[str] | None) -> list: + def update_badges(selected_keys: list[str] | None) -> list[Any]: return build_selected_channels_badges(selected_keys or [], app_state) @app.callback( @@ -104,7 +102,7 @@ def register_channel_callbacks(app: dash.Dash, app_state: AppState) -> None: direction: str | None, selected_keys: list[str] | None, all_rows: list[dict[str, Any]] | None, - ) -> tuple[list[dict[str, Any]], list[int], list[dict]]: + ) -> tuple[list[dict[str, Any]], list[int], list[dict[str, Any]]]: """Filter rows AND recompute selected_rows + color styling.""" if not all_rows: return [], [], [] @@ -135,7 +133,7 @@ def register_channel_callbacks(app: dash.Dash, app_state: AppState) -> None: selected_indices.append(idx) # Build style_data_conditional for coloring selected rows - style_cond: list[dict] = [] + style_cond: list[dict[str, Any]] = [] for idx, row in enumerate(filtered): if row["key"] in color_map: ci = color_map[row["key"]] @@ -168,7 +166,7 @@ def register_channel_callbacks(app: dash.Dash, app_state: AppState) -> None: Output("per-channel-overrides", "children"), [Input("selected-channels-store", "data")], ) - def update_per_channel_overrides(selected_keys: list[str] | None) -> list: + def update_per_channel_overrides(selected_keys: list[str] | None) -> list[Any]: """Show per-channel CFC override controls for selected channels.""" return build_per_channel_override_rows( selected_keys or [], diff --git a/src/impakt/web/callbacks/corridor_callbacks.py b/src/impakt/web/callbacks/corridor_callbacks.py index 8a141b6..e28880b 100644 --- a/src/impakt/web/callbacks/corridor_callbacks.py +++ b/src/impakt/web/callbacks/corridor_callbacks.py @@ -9,11 +9,9 @@ Handles: from __future__ import annotations import base64 -import io from typing import Any import dash -import numpy as np from dash import Input, Output, State, html from dash.exceptions import PreventUpdate @@ -42,7 +40,7 @@ def register_corridor_callbacks(app: dash.Dash, app_state: AppState) -> None: filename: str | None, corridor_name: str | None, current_corridors: list[dict[str, Any]] | None, - ) -> tuple[list[dict[str, Any]], Any, list]: + ) -> tuple[list[dict[str, Any]], Any, list[Any]]: if contents is None: raise PreventUpdate @@ -123,7 +121,7 @@ def register_corridor_callbacks(app: dash.Dash, app_state: AppState) -> None: ) -def _build_corridor_list(corridors: list[dict[str, Any]]) -> list: +def _build_corridor_list(corridors: list[dict[str, Any]]) -> list[Any]: """Build the active corridors display list.""" if not corridors: return [html.Div("No corridors loaded", className="text-muted", style={"fontSize": "10px"})] diff --git a/src/impakt/web/callbacks/criteria_callbacks.py b/src/impakt/web/callbacks/criteria_callbacks.py index 9d1fecc..212f64f 100644 --- a/src/impakt/web/callbacks/criteria_callbacks.py +++ b/src/impakt/web/callbacks/criteria_callbacks.py @@ -10,7 +10,6 @@ from typing import Any import dash from dash import Input, Output, State, html -from dash.exceptions import PreventUpdate from impakt.web.components.criteria import build_criteria_results_display from impakt.web.state import AppState diff --git a/src/impakt/web/callbacks/cursor_callbacks.py b/src/impakt/web/callbacks/cursor_callbacks.py index 1da6e6d..a6bd1d0 100644 --- a/src/impakt/web/callbacks/cursor_callbacks.py +++ b/src/impakt/web/callbacks/cursor_callbacks.py @@ -10,8 +10,7 @@ from __future__ import annotations from typing import Any import dash -from dash import Input, Output, State, html -from dash.exceptions import PreventUpdate +from dash import Input, Output, State from impakt.plot.engine import DEFAULT_COLORS from impakt.web.callbacks.plot_callbacks import _resolve_channels @@ -61,7 +60,7 @@ def register_cursor_callbacks(app: dash.Dash, app_state: AppState) -> None: selected_keys: list[str] | None, cfc_value: str, y_align: bool, - channel_overrides: dict | None, + channel_overrides: dict[str, Any] | None, x_align_method: str, x_align_value: float | None, show_resultant: bool, @@ -105,7 +104,7 @@ def register_cursor_callbacks(app: dash.Dash, app_state: AppState) -> None: x_align_method: str, x_align_value: float | None, show_resultant: bool, - ) -> list[dict]: + ) -> list[dict[str, Any]]: if not selected_keys: return [] @@ -119,7 +118,7 @@ def register_cursor_callbacks(app: dash.Dash, app_state: AppState) -> None: show_resultant, ) - style_cond: list[dict] = [] + style_cond: list[dict[str, Any]] = [] for i in range(len(channels)): color = DEFAULT_COLORS[i % len(DEFAULT_COLORS)] style_cond.append( diff --git a/src/impakt/web/callbacks/export_callbacks.py b/src/impakt/web/callbacks/export_callbacks.py index 1cf2848..1e6062c 100644 --- a/src/impakt/web/callbacks/export_callbacks.py +++ b/src/impakt/web/callbacks/export_callbacks.py @@ -8,14 +8,12 @@ Handles: from __future__ import annotations -import io -import tempfile from typing import Any import dash import numpy as np import pandas as pd -from dash import Input, Output, State, dcc, html, no_update +from dash import Input, Output, State, dcc, html from dash.exceptions import PreventUpdate from impakt.web.callbacks.plot_callbacks import _resolve_channels @@ -116,7 +114,6 @@ def register_export_callbacks(app: dash.Dash, app_state: AppState) -> None: # Generate report try: - from impakt.report.engine import generate_protocol_report import tempfile with tempfile.NamedTemporaryFile(suffix=".html", delete=False, mode="w") as f: diff --git a/src/impakt/web/callbacks/file_callbacks.py b/src/impakt/web/callbacks/file_callbacks.py index 0df99cc..188a4a5 100644 --- a/src/impakt/web/callbacks/file_callbacks.py +++ b/src/impakt/web/callbacks/file_callbacks.py @@ -13,7 +13,7 @@ from pathlib import Path from typing import Any import dash -from dash import Input, Output, State, html, no_update +from dash import Input, Output, State, no_update from dash.exceptions import PreventUpdate from impakt.web.state import AppState diff --git a/src/impakt/web/callbacks/plot_callbacks.py b/src/impakt/web/callbacks/plot_callbacks.py index 643e69e..b0ab962 100644 --- a/src/impakt/web/callbacks/plot_callbacks.py +++ b/src/impakt/web/callbacks/plot_callbacks.py @@ -17,7 +17,6 @@ import dash import numpy as np import plotly.graph_objects as go from dash import Input, Output, State -from dash.exceptions import PreventUpdate from impakt.channel.model import Channel from impakt.plot.engine import DEFAULT_COLORS, PlotEngine @@ -148,7 +147,7 @@ def _build_plot_spec( channels: list[tuple[str, Channel]], cursor_x1: float | None, cursor_x2: float | None, - corridors: list[dict] | None = None, + corridors: list[dict[str, Any]] | None = None, ) -> PlotSpec: """Build a PlotSpec from resolved channels and UI state. @@ -228,8 +227,8 @@ def register_plot_callbacks(app: dash.Dash, app_state: AppState) -> None: x_align_method: str, cursor_x1: float | None, cursor_x2: float | None, - channel_overrides: dict | None, - corridors_data: list[dict] | None, + channel_overrides: dict[str, Any] | None, + corridors_data: list[dict[str, Any]] | None, x_align_value: float | None, ) -> go.Figure: if not selected_keys: diff --git a/src/impakt/web/components/channel_grid.py b/src/impakt/web/components/channel_grid.py index 88f4279..801ba2d 100644 --- a/src/impakt/web/components/channel_grid.py +++ b/src/impakt/web/components/channel_grid.py @@ -14,13 +14,12 @@ without reordering. from __future__ import annotations import fnmatch -import re from typing import Any import dash_bootstrap_components as dbc from dash import dash_table, dcc, html -from impakt.channel.lookup import DIRECTIONS, MAIN_LOCATIONS, MEASUREMENTS +from impakt.channel.lookup import MAIN_LOCATIONS, MEASUREMENTS from impakt.plot.engine import DEFAULT_COLORS from impakt.web.state import AppState @@ -277,7 +276,7 @@ def build_channel_grid(app_state: AppState) -> dbc.Card: def build_selected_channels_badges( selected_keys: list[str], app_state: AppState, -) -> list: +) -> list[Any]: """Build badge pills showing currently selected channels.""" if not selected_keys: return [ diff --git a/src/impakt/web/components/channel_values.py b/src/impakt/web/components/channel_values.py index f49a818..87884a8 100644 --- a/src/impakt/web/components/channel_values.py +++ b/src/impakt/web/components/channel_values.py @@ -14,9 +14,8 @@ from __future__ import annotations from typing import Any -import numpy as np - import dash_bootstrap_components as dbc +import numpy as np from dash import dash_table, dcc, html from impakt.channel.model import Channel @@ -152,7 +151,7 @@ def build_channel_values_panel() -> dbc.Card: "whiteSpace": "nowrap", }, style_cell_conditional=[ - # Fixed-width columns (percentages sum to ~74%, leaving ~26% for Description) + # Fixed-width columns (~74%, leaving ~26% for Description) { "if": {"column_id": "ch_num"}, "width": "3%", diff --git a/src/impakt/web/components/corridors.py b/src/impakt/web/components/corridors.py index 354b614..f2a538f 100644 --- a/src/impakt/web/components/corridors.py +++ b/src/impakt/web/components/corridors.py @@ -8,8 +8,6 @@ Provides: from __future__ import annotations -from typing import Any - import dash_bootstrap_components as dbc from dash import dcc, html diff --git a/src/impakt/web/components/criteria.py b/src/impakt/web/components/criteria.py index 57a6869..27d7706 100644 --- a/src/impakt/web/components/criteria.py +++ b/src/impakt/web/components/criteria.py @@ -12,7 +12,7 @@ from typing import Any import dash_bootstrap_components as dbc from dash import html -from impakt.channel.model import Channel, TestData +from impakt.channel.model import TestData from impakt.criteria import ( CriterionResult, chest_deflection, @@ -21,7 +21,6 @@ from impakt.criteria import ( hic15, nij, tibia_index, - viscous_criterion, ) from impakt.protocol.base import Color, ProtocolResult, Rating @@ -229,7 +228,7 @@ def build_criteria_results_display( protocol_result: ProtocolResult | None = None, ) -> html.Div: """Build the criteria results display.""" - elements: list = [] + elements: list[Any] = [] # Protocol summary if protocol_result: diff --git a/src/impakt/web/components/header.py b/src/impakt/web/components/header.py index e7a36b8..9d95669 100644 --- a/src/impakt/web/components/header.py +++ b/src/impakt/web/components/header.py @@ -7,7 +7,7 @@ and template selector. from __future__ import annotations import dash_bootstrap_components as dbc -from dash import dcc, html +from dash import html from impakt.web.state import AppState diff --git a/src/impakt/web/components/math_builder.py b/src/impakt/web/components/math_builder.py index 9b27e02..ac55db2 100644 --- a/src/impakt/web/components/math_builder.py +++ b/src/impakt/web/components/math_builder.py @@ -8,10 +8,8 @@ Example: sqrt(ax**2 + az**2) with ax=Head Accel X, az=Head Accel Z from __future__ import annotations -from typing import Any - import dash_bootstrap_components as dbc -from dash import dcc, html +from dash import html from impakt.web.state import AppState diff --git a/src/impakt/web/components/plot_grid.py b/src/impakt/web/components/plot_grid.py index 7e1fcaa..d6c40da 100644 --- a/src/impakt/web/components/plot_grid.py +++ b/src/impakt/web/components/plot_grid.py @@ -6,10 +6,11 @@ plot panes. Each pane has its own channel selection and axis labels. from __future__ import annotations +from typing import Any + import dash_bootstrap_components as dbc from dash import dcc, html - # Layout presets: (rows, cols) LAYOUT_PRESETS: dict[str, tuple[int, int]] = { "1x1": (1, 1), @@ -57,7 +58,7 @@ def build_plot_grid(layout: str = "1x1") -> html.Div: ) -def _build_panes(layout: str) -> list: +def _build_panes(layout: str) -> list[Any]: """Build plot pane elements for a given layout.""" rows, cols = LAYOUT_PRESETS.get(layout, (1, 1)) pane_count = rows * cols @@ -110,7 +111,7 @@ def _build_single_pane(pane_idx: int, total_panes: int) -> dbc.Card: ) -def build_empty_plot_figure() -> dict: +def build_empty_plot_figure() -> dict[str, Any]: """Build an empty plot figure with instruction text.""" return { "data": [], diff --git a/src/impakt/web/components/templates.py b/src/impakt/web/components/templates.py index 8875c07..9b75d11 100644 --- a/src/impakt/web/components/templates.py +++ b/src/impakt/web/components/templates.py @@ -10,10 +10,8 @@ Provides: from __future__ import annotations -from typing import Any - import dash_bootstrap_components as dbc -from dash import dcc, html +from dash import html from impakt.web.state import AppState diff --git a/src/impakt/web/components/transforms.py b/src/impakt/web/components/transforms.py index 31479d5..c0ec0e9 100644 --- a/src/impakt/web/components/transforms.py +++ b/src/impakt/web/components/transforms.py @@ -5,6 +5,8 @@ Provides global transform defaults and per-channel override capability. from __future__ import annotations +from typing import Any + import dash_bootstrap_components as dbc from dash import dcc, html @@ -92,7 +94,7 @@ def build_transform_panel() -> dbc.Card: def build_per_channel_override_rows( selected_keys: list[str], overrides: dict[str, dict[str, str]], -) -> list: +) -> list[Any]: """Build per-channel override controls for selected channels. Shows a compact row per selected channel with a CFC dropdown override. diff --git a/src/impakt/web/layout.py b/src/impakt/web/layout.py index 23a4740..b686495 100644 --- a/src/impakt/web/layout.py +++ b/src/impakt/web/layout.py @@ -8,13 +8,12 @@ Two tabs: from __future__ import annotations -from typing import Any - import dash_bootstrap_components as dbc from dash import dcc, html from impakt.web.components.channel_grid import build_channel_grid from impakt.web.components.channel_values import build_channel_values_panel +from impakt.web.components.corridors import build_corridor_panel from impakt.web.components.criteria import build_criteria_panel from impakt.web.components.header import ( build_header, @@ -22,9 +21,8 @@ from impakt.web.components.header import ( build_overlay_modal, build_test_info_panel, ) -from impakt.web.components.plot_grid import build_plot_grid -from impakt.web.components.corridors import build_corridor_panel from impakt.web.components.math_builder import build_math_panel +from impakt.web.components.plot_grid import build_plot_grid from impakt.web.components.report import build_report_panel from impakt.web.components.templates import build_template_panel from impakt.web.components.transforms import build_transform_panel diff --git a/src/impakt/web/state.py b/src/impakt/web/state.py index c7b92a1..8c81ec0 100644 --- a/src/impakt/web/state.py +++ b/src/impakt/web/state.py @@ -15,12 +15,10 @@ import logging from pathlib import Path from typing import Any -from impakt.channel.model import Channel, TestData +from impakt.channel.model import Channel from impakt.script.api import Session from impakt.template.library import TemplateLibrary from impakt.template.model import PlotDefinition, TemplateSpec -from impakt.transform.align import YAlign -from impakt.transform.cfc import CFCFilter logger = logging.getLogger(__name__)