QA-Improver
This commit is contained in:
85
.claude/agents/qa-improver.md
Normal file
85
.claude/agents/qa-improver.md
Normal file
@@ -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.
|
||||||
@@ -62,6 +62,7 @@ packages = ["src/impakt"]
|
|||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
testpaths = ["tests"]
|
testpaths = ["tests"]
|
||||||
pythonpath = ["src"]
|
pythonpath = ["src"]
|
||||||
|
addopts = "--cov=impakt --cov-report=term-missing:skip-covered --cov-fail-under=60"
|
||||||
filterwarnings = [
|
filterwarnings = [
|
||||||
"ignore::pytest.PytestCollectionWarning",
|
"ignore::pytest.PytestCollectionWarning",
|
||||||
]
|
]
|
||||||
|
|||||||
136
scripts/qa-improve.sh
Executable file
136
scripts/qa-improve.sh
Executable file
@@ -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-<datetime>.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
|
||||||
Binary file not shown.
Binary file not shown.
@@ -60,10 +60,9 @@ MAIN_LOCATIONS: dict[str, str] = {
|
|||||||
"CLAV": "Clavicle",
|
"CLAV": "Clavicle",
|
||||||
"RIBS": "Ribs",
|
"RIBS": "Ribs",
|
||||||
"STRN": "Sternum",
|
"STRN": "Sternum",
|
||||||
"SHLR": "Shoulder",
|
"SHLR": "Shoulder Right",
|
||||||
"SHLD": "Shoulder",
|
"SHLD": "Shoulder",
|
||||||
"SHLL": "Shoulder Left",
|
"SHLL": "Shoulder Left",
|
||||||
"SHLR": "Shoulder Right",
|
|
||||||
# Spine
|
# Spine
|
||||||
"SPIN": "Spine",
|
"SPIN": "Spine",
|
||||||
"LUSP": "Lumbar Spine",
|
"LUSP": "Lumbar Spine",
|
||||||
@@ -74,7 +73,6 @@ MAIN_LOCATIONS: dict[str, str] = {
|
|||||||
"SACR": "Sacrum",
|
"SACR": "Sacrum",
|
||||||
"PUBC": "Pubic Symphysis",
|
"PUBC": "Pubic Symphysis",
|
||||||
# Upper extremities
|
# Upper extremities
|
||||||
"SHLD": "Shoulder",
|
|
||||||
"UPRA": "Upper Arm",
|
"UPRA": "Upper Arm",
|
||||||
"ELBO": "Elbow",
|
"ELBO": "Elbow",
|
||||||
"FORA": "Forearm",
|
"FORA": "Forearm",
|
||||||
@@ -122,7 +120,7 @@ MAIN_LOCATIONS: dict[str, str] = {
|
|||||||
# Wheel
|
# Wheel
|
||||||
"WHEL": "Wheel",
|
"WHEL": "Wheel",
|
||||||
# Structural — additional
|
# Structural — additional
|
||||||
"FORA": "Floor Rail",
|
"FRAL": "Floor Rail",
|
||||||
"FBAR": "Barrier Face",
|
"FBAR": "Barrier Face",
|
||||||
"KNSL": "Knee Slider",
|
"KNSL": "Knee Slider",
|
||||||
# Simulation / other
|
# Simulation / other
|
||||||
@@ -181,11 +179,6 @@ FINE_LOCATIONS: dict[str, str] = {
|
|||||||
"URXX": "Upper Right",
|
"URXX": "Upper Right",
|
||||||
"LLXX": "Lower Left",
|
"LLXX": "Lower Left",
|
||||||
"LRXX": "Lower Right",
|
"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
|
# Fine location with number suffix
|
||||||
"0001": "Secondary",
|
"0001": "Secondary",
|
||||||
"0100": "Lower Position",
|
"0100": "Lower Position",
|
||||||
|
|||||||
@@ -8,17 +8,17 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import fnmatch
|
import fnmatch
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
from collections.abc import Iterator
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from datetime import date
|
from datetime import date
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Iterator
|
from typing import Any
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from numpy.typing import NDArray
|
from numpy.typing import NDArray
|
||||||
|
|
||||||
from impakt.channel.code import ChannelCode
|
from impakt.channel.code import ChannelCode
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Metadata models
|
# Metadata models
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -6,14 +6,11 @@ Viscous Criterion (VC): V(t) * C(t) where V = deflection velocity, C = compressi
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from impakt.channel.model import Channel, DummyInfo
|
from impakt.channel.model import Channel, DummyInfo
|
||||||
from impakt.criteria.base import CriterionResult
|
from impakt.criteria.base import CriterionResult
|
||||||
|
|
||||||
|
|
||||||
# Initial chest depth by dummy type (mm)
|
# Initial chest depth by dummy type (mm)
|
||||||
CHEST_DEPTH: dict[str, float] = {
|
CHEST_DEPTH: dict[str, float] = {
|
||||||
"H3-50M": 229.0,
|
"H3-50M": 229.0,
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ where the total time the signal exceeds that level equals at least 3 ms.
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from impakt.channel.model import Channel, ChannelGroup, DummyInfo
|
from impakt.channel.model import Channel, ChannelGroup, DummyInfo
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ Evaluated separately for left and right femur.
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from impakt.channel.model import Channel, DummyInfo
|
from impakt.channel.model import Channel, DummyInfo
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ The reported Nij is the maximum across all four modes and all time steps.
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ where:
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1,17 @@
|
|||||||
"""Data I/O readers for crash test formats."""
|
"""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",
|
||||||
|
]
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -89,7 +89,6 @@ UNIT_MAP: dict[str, str] = {
|
|||||||
"Nm": "N·m",
|
"Nm": "N·m",
|
||||||
"N.m": "N·m",
|
"N.m": "N·m",
|
||||||
"N*m": "N·m",
|
"N*m": "N·m",
|
||||||
"Nm": "N·m",
|
|
||||||
"mm": "mm",
|
"mm": "mm",
|
||||||
"m": "m",
|
"m": "m",
|
||||||
"cm": "cm",
|
"cm": "cm",
|
||||||
@@ -626,7 +625,7 @@ class MMEReader:
|
|||||||
unit = _normalize_unit(header.get("unit", ""))
|
unit = _normalize_unit(header.get("unit", ""))
|
||||||
dt = _parse_float(header.get("sampling interval", "0"))
|
dt = _parse_float(header.get("sampling interval", "0"))
|
||||||
t_first = _parse_float(header.get("time of first sample", "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_str = header.get("channel frequency class", "")
|
||||||
cfc_class: int | None = None
|
cfc_class: int | None = None
|
||||||
if cfc_str and cfc_str.upper() != "NOVALUE":
|
if cfc_str and cfc_str.upper() != "NOVALUE":
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -6,8 +6,6 @@ across all plotted channels.
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
|
|||||||
@@ -11,11 +11,10 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
import plotly.graph_objects as go
|
import plotly.graph_objects as go
|
||||||
|
|
||||||
from impakt.channel.model import Channel
|
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 color palette (colorblind-friendly)
|
||||||
DEFAULT_COLORS = [
|
DEFAULT_COLORS = [
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from impakt.plot.engine import PlotEngine
|
from impakt.plot.engine import PlotEngine
|
||||||
from impakt.plot.spec import PlotSpec
|
from impakt.plot.spec import PlotSpec
|
||||||
|
|||||||
@@ -1 +1,19 @@
|
|||||||
"""Plugin system for extensibility."""
|
"""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",
|
||||||
|
]
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -10,7 +10,6 @@ Threshold values are versioned — this module supports multiple protocol years.
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from impakt.criteria.base import CriterionResult
|
from impakt.criteria.base import CriterionResult
|
||||||
from impakt.protocol.base import BodyRegionScore, Color, ProtocolResult
|
from impakt.protocol.base import BodyRegionScore, Color, ProtocolResult
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ The overall rating is determined by the worst sub-rating.
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from impakt.criteria.base import CriterionResult
|
from impakt.criteria.base import CriterionResult
|
||||||
from impakt.protocol.base import BodyRegionScore, ProtocolResult, Rating
|
from impakt.protocol.base import BodyRegionScore, ProtocolResult, Rating
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ injury probability, which are then combined.
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import math
|
import math
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from impakt.criteria.base import CriterionResult
|
from impakt.criteria.base import CriterionResult
|
||||||
from impakt.protocol.base import BodyRegionScore, ProtocolResult, Rating
|
from impakt.protocol.base import BodyRegionScore, ProtocolResult, Rating
|
||||||
|
|||||||
@@ -1 +1,13 @@
|
|||||||
"""PDF and report generation."""
|
"""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",
|
||||||
|
]
|
||||||
|
|||||||
@@ -155,7 +155,8 @@ def _fallback_protocol_html(
|
|||||||
test_info = f"""
|
test_info = f"""
|
||||||
<div class="test-info">
|
<div class="test-info">
|
||||||
<p><strong>Test:</strong> {metadata.test_number}</p>
|
<p><strong>Test:</strong> {metadata.test_number}</p>
|
||||||
<p><strong>Vehicle:</strong> {metadata.vehicle.year} {metadata.vehicle.make} {metadata.vehicle.model}</p>
|
<p><strong>Vehicle:</strong> {metadata.vehicle.year} {metadata.vehicle.make} \
|
||||||
|
{metadata.vehicle.model}</p>
|
||||||
<p><strong>Dummy:</strong> {metadata.dummy.dummy_type} ({metadata.dummy.position})</p>
|
<p><strong>Dummy:</strong> {metadata.dummy.dummy_type} ({metadata.dummy.position})</p>
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
@@ -178,7 +179,11 @@ def _fallback_protocol_html(
|
|||||||
color_badge = f'<span class="badge" style="background:{bg}">{rs.rating.value}</span>'
|
color_badge = f'<span class="badge" style="background:{bg}">{rs.rating.value}</span>'
|
||||||
|
|
||||||
points_str = f"{rs.points:.1f}/{rs.max_points:.1f}" if rs.max_points > 0 else ""
|
points_str = f"{rs.points:.1f}/{rs.max_points:.1f}" if rs.max_points > 0 else ""
|
||||||
rows += f"<tr><td>{rs.region}</td><td>{rs.criterion}</td><td>{rs.value:.2f} {rs.unit}</td><td>{color_badge}</td><td>{points_str}</td></tr>"
|
rows += (
|
||||||
|
f"<tr><td>{rs.region}</td><td>{rs.criterion}</td>"
|
||||||
|
f"<td>{rs.value:.2f} {rs.unit}</td>"
|
||||||
|
f"<td>{color_badge}</td><td>{points_str}</td></tr>"
|
||||||
|
)
|
||||||
|
|
||||||
stars_display = ""
|
stars_display = ""
|
||||||
if result.stars is not None:
|
if result.stars is not None:
|
||||||
@@ -207,10 +212,12 @@ def _fallback_protocol_html(
|
|||||||
<div class="summary">
|
<div class="summary">
|
||||||
<p><span class="stars">{stars_display}</span></p>
|
<p><span class="stars">{stars_display}</span></p>
|
||||||
<p><strong>Overall:</strong> {result.overall_rating}</p>
|
<p><strong>Overall:</strong> {result.overall_rating}</p>
|
||||||
<p><strong>Score:</strong> {result.total_points:.1f}/{result.max_points:.1f} ({result.percentage:.0f}%)</p>
|
<p><strong>Score:</strong> \
|
||||||
|
{result.total_points:.1f}/{result.max_points:.1f} ({result.percentage:.0f}%)</p>
|
||||||
</div>
|
</div>
|
||||||
<table>
|
<table>
|
||||||
<tr><th>Body Region</th><th>Criterion</th><th>Value</th><th>Rating</th><th>Points</th></tr>
|
<tr><th>Body Region</th><th>Criterion</th><th>Value</th>\
|
||||||
|
<th>Rating</th><th>Points</th></tr>
|
||||||
{rows}
|
{rows}
|
||||||
</table>
|
</table>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -1 +1,12 @@
|
|||||||
"""Scripting API and CLI."""
|
"""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",
|
||||||
|
]
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -32,12 +32,12 @@ import numpy as np
|
|||||||
from impakt.channel.model import Channel, ChannelGroup, TestData, TestMetadata
|
from impakt.channel.model import Channel, ChannelGroup, TestData, TestMetadata
|
||||||
from impakt.criteria.base import CriterionResult
|
from impakt.criteria.base import CriterionResult
|
||||||
from impakt.io.mme import MMEReader
|
from impakt.io.mme import MMEReader
|
||||||
from impakt.io.reader import ReaderRegistry, get_registry, register_reader
|
from impakt.io.reader import get_registry, register_reader
|
||||||
from impakt.plot.engine import PlotEngine, cursor_values
|
from impakt.plot.engine import PlotEngine
|
||||||
from impakt.plot.spec import ChannelRef, CursorValues, PlotSpec, PlotStyle
|
from impakt.plot.spec import ChannelRef, PlotSpec, PlotStyle
|
||||||
from impakt.protocol.base import ProtocolResult
|
from impakt.protocol.base import ProtocolResult
|
||||||
from impakt.template.library import TemplateLibrary
|
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.template.session import SessionManager
|
||||||
from impakt.transform.cfc import CFCFilter
|
from impakt.transform.cfc import CFCFilter
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
|
|
||||||
def main(argv: list[str] | None = None) -> None:
|
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:
|
def _cmd_evaluate(args: argparse.Namespace) -> None:
|
||||||
from impakt.script.api import Session
|
from impakt.script.api import Session
|
||||||
from impakt.criteria import hic, clip_3ms, nij, chest_deflection, femur_load
|
|
||||||
|
|
||||||
session = Session.open(args.path)
|
session = Session.open(args.path)
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1,13 @@
|
|||||||
"""Template and session management."""
|
"""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",
|
||||||
|
]
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -6,8 +6,8 @@ Manages the global template library at ``~/.impakt/templates/``.
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
from collections.abc import Iterator
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Iterator
|
|
||||||
|
|
||||||
from impakt.template.model import TemplateSpec
|
from impakt.template.model import TemplateSpec
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ Sessions bind templates to specific test data and track user overrides.
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import copy
|
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -94,7 +93,6 @@ class TemplateSpec:
|
|||||||
for plot_data in data.get("plots", []):
|
for plot_data in data.get("plots", []):
|
||||||
channel_patterns = []
|
channel_patterns = []
|
||||||
transforms = []
|
transforms = []
|
||||||
corridors = []
|
|
||||||
|
|
||||||
for ch in plot_data.get("channels", []):
|
for ch in plot_data.get("channels", []):
|
||||||
if isinstance(ch, str):
|
if isinstance(ch, str):
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -76,7 +76,7 @@ class XAlign:
|
|||||||
# Threshold never crossed — no shift
|
# Threshold never crossed — no shift
|
||||||
return channel.with_data(
|
return channel.with_data(
|
||||||
data=channel.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]])
|
crossing_time = float(channel.time[indices[0]])
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ TransformChain composes multiple transforms into a reproducible pipeline.
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass
|
||||||
from typing import Any, Protocol, runtime_checkable
|
from typing import Any, Protocol, runtime_checkable
|
||||||
|
|
||||||
from impakt.channel.model import Channel
|
from impakt.channel.model import Channel
|
||||||
|
|||||||
@@ -15,13 +15,11 @@ from __future__ import annotations
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
from scipy.signal import butter, filtfilt
|
from scipy.signal import butter, filtfilt
|
||||||
|
|
||||||
from impakt.channel.lookup import CFC_CLASSES, CFC_MINIMUM_SAMPLE_RATES
|
from impakt.channel.lookup import CFC_CLASSES, CFC_MINIMUM_SAMPLE_RATES
|
||||||
from impakt.channel.model import Channel
|
from impakt.channel.model import Channel
|
||||||
|
|
||||||
|
|
||||||
VALID_CFC_CLASSES = frozenset(CFC_CLASSES.keys())
|
VALID_CFC_CLASSES = frozenset(CFC_CLASSES.keys())
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ with numpy functions.
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import re
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
@@ -16,7 +15,6 @@ import numpy as np
|
|||||||
from impakt.channel.code import ChannelCode
|
from impakt.channel.code import ChannelCode
|
||||||
from impakt.channel.model import Channel
|
from impakt.channel.model import Channel
|
||||||
|
|
||||||
|
|
||||||
# Allowed numpy functions in expressions
|
# Allowed numpy functions in expressions
|
||||||
_SAFE_NUMPY = {
|
_SAFE_NUMPY = {
|
||||||
"abs": np.abs,
|
"abs": np.abs,
|
||||||
|
|||||||
@@ -6,9 +6,6 @@ The AppState holds Session objects server-side; Dash stores hold lightweight UI
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import TYPE_CHECKING
|
|
||||||
|
|
||||||
import dash
|
import dash
|
||||||
import dash_bootstrap_components as dbc
|
import dash_bootstrap_components as dbc
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,7 @@ from __future__ import annotations
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import dash
|
import dash
|
||||||
from dash import ALL, Input, Output, State, html, no_update
|
from dash import ALL, Input, Output, State
|
||||||
from dash.exceptions import PreventUpdate
|
|
||||||
|
|
||||||
from impakt.plot.engine import DEFAULT_COLORS
|
from impakt.plot.engine import DEFAULT_COLORS
|
||||||
from impakt.web.components.channel_grid import (
|
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:
|
if visible_data is None:
|
||||||
return prev_selected or []
|
return prev_selected or []
|
||||||
|
|
||||||
prev = set(prev_selected or [])
|
|
||||||
visible_keys = {row["key"] for row in visible_data}
|
visible_keys = {row["key"] for row in visible_data}
|
||||||
|
|
||||||
# Keys currently checked in the visible table
|
# 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"),
|
Output("selected-channels-badges", "children"),
|
||||||
[Input("selected-channels-store", "data")],
|
[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)
|
return build_selected_channels_badges(selected_keys or [], app_state)
|
||||||
|
|
||||||
@app.callback(
|
@app.callback(
|
||||||
@@ -104,7 +102,7 @@ def register_channel_callbacks(app: dash.Dash, app_state: AppState) -> None:
|
|||||||
direction: str | None,
|
direction: str | None,
|
||||||
selected_keys: list[str] | None,
|
selected_keys: list[str] | None,
|
||||||
all_rows: list[dict[str, Any]] | 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."""
|
"""Filter rows AND recompute selected_rows + color styling."""
|
||||||
if not all_rows:
|
if not all_rows:
|
||||||
return [], [], []
|
return [], [], []
|
||||||
@@ -135,7 +133,7 @@ def register_channel_callbacks(app: dash.Dash, app_state: AppState) -> None:
|
|||||||
selected_indices.append(idx)
|
selected_indices.append(idx)
|
||||||
|
|
||||||
# Build style_data_conditional for coloring selected rows
|
# Build style_data_conditional for coloring selected rows
|
||||||
style_cond: list[dict] = []
|
style_cond: list[dict[str, Any]] = []
|
||||||
for idx, row in enumerate(filtered):
|
for idx, row in enumerate(filtered):
|
||||||
if row["key"] in color_map:
|
if row["key"] in color_map:
|
||||||
ci = color_map[row["key"]]
|
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"),
|
Output("per-channel-overrides", "children"),
|
||||||
[Input("selected-channels-store", "data")],
|
[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."""
|
"""Show per-channel CFC override controls for selected channels."""
|
||||||
return build_per_channel_override_rows(
|
return build_per_channel_override_rows(
|
||||||
selected_keys or [],
|
selected_keys or [],
|
||||||
|
|||||||
@@ -9,11 +9,9 @@ Handles:
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
import io
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import dash
|
import dash
|
||||||
import numpy as np
|
|
||||||
from dash import Input, Output, State, html
|
from dash import Input, Output, State, html
|
||||||
from dash.exceptions import PreventUpdate
|
from dash.exceptions import PreventUpdate
|
||||||
|
|
||||||
@@ -42,7 +40,7 @@ def register_corridor_callbacks(app: dash.Dash, app_state: AppState) -> None:
|
|||||||
filename: str | None,
|
filename: str | None,
|
||||||
corridor_name: str | None,
|
corridor_name: str | None,
|
||||||
current_corridors: list[dict[str, Any]] | 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:
|
if contents is None:
|
||||||
raise PreventUpdate
|
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."""
|
"""Build the active corridors display list."""
|
||||||
if not corridors:
|
if not corridors:
|
||||||
return [html.Div("No corridors loaded", className="text-muted", style={"fontSize": "10px"})]
|
return [html.Div("No corridors loaded", className="text-muted", style={"fontSize": "10px"})]
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ from typing import Any
|
|||||||
|
|
||||||
import dash
|
import dash
|
||||||
from dash import Input, Output, State, html
|
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.components.criteria import build_criteria_results_display
|
||||||
from impakt.web.state import AppState
|
from impakt.web.state import AppState
|
||||||
|
|||||||
@@ -10,8 +10,7 @@ from __future__ import annotations
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import dash
|
import dash
|
||||||
from dash import Input, Output, State, html
|
from dash import Input, Output, State
|
||||||
from dash.exceptions import PreventUpdate
|
|
||||||
|
|
||||||
from impakt.plot.engine import DEFAULT_COLORS
|
from impakt.plot.engine import DEFAULT_COLORS
|
||||||
from impakt.web.callbacks.plot_callbacks import _resolve_channels
|
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,
|
selected_keys: list[str] | None,
|
||||||
cfc_value: str,
|
cfc_value: str,
|
||||||
y_align: bool,
|
y_align: bool,
|
||||||
channel_overrides: dict | None,
|
channel_overrides: dict[str, Any] | None,
|
||||||
x_align_method: str,
|
x_align_method: str,
|
||||||
x_align_value: float | None,
|
x_align_value: float | None,
|
||||||
show_resultant: bool,
|
show_resultant: bool,
|
||||||
@@ -105,7 +104,7 @@ def register_cursor_callbacks(app: dash.Dash, app_state: AppState) -> None:
|
|||||||
x_align_method: str,
|
x_align_method: str,
|
||||||
x_align_value: float | None,
|
x_align_value: float | None,
|
||||||
show_resultant: bool,
|
show_resultant: bool,
|
||||||
) -> list[dict]:
|
) -> list[dict[str, Any]]:
|
||||||
if not selected_keys:
|
if not selected_keys:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -119,7 +118,7 @@ def register_cursor_callbacks(app: dash.Dash, app_state: AppState) -> None:
|
|||||||
show_resultant,
|
show_resultant,
|
||||||
)
|
)
|
||||||
|
|
||||||
style_cond: list[dict] = []
|
style_cond: list[dict[str, Any]] = []
|
||||||
for i in range(len(channels)):
|
for i in range(len(channels)):
|
||||||
color = DEFAULT_COLORS[i % len(DEFAULT_COLORS)]
|
color = DEFAULT_COLORS[i % len(DEFAULT_COLORS)]
|
||||||
style_cond.append(
|
style_cond.append(
|
||||||
|
|||||||
@@ -8,14 +8,12 @@ Handles:
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import io
|
|
||||||
import tempfile
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import dash
|
import dash
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
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 dash.exceptions import PreventUpdate
|
||||||
|
|
||||||
from impakt.web.callbacks.plot_callbacks import _resolve_channels
|
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
|
# Generate report
|
||||||
try:
|
try:
|
||||||
from impakt.report.engine import generate_protocol_report
|
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
with tempfile.NamedTemporaryFile(suffix=".html", delete=False, mode="w") as f:
|
with tempfile.NamedTemporaryFile(suffix=".html", delete=False, mode="w") as f:
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from pathlib import Path
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import dash
|
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 dash.exceptions import PreventUpdate
|
||||||
|
|
||||||
from impakt.web.state import AppState
|
from impakt.web.state import AppState
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import dash
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
import plotly.graph_objects as go
|
import plotly.graph_objects as go
|
||||||
from dash import Input, Output, State
|
from dash import Input, Output, State
|
||||||
from dash.exceptions import PreventUpdate
|
|
||||||
|
|
||||||
from impakt.channel.model import Channel
|
from impakt.channel.model import Channel
|
||||||
from impakt.plot.engine import DEFAULT_COLORS, PlotEngine
|
from impakt.plot.engine import DEFAULT_COLORS, PlotEngine
|
||||||
@@ -148,7 +147,7 @@ def _build_plot_spec(
|
|||||||
channels: list[tuple[str, Channel]],
|
channels: list[tuple[str, Channel]],
|
||||||
cursor_x1: float | None,
|
cursor_x1: float | None,
|
||||||
cursor_x2: float | None,
|
cursor_x2: float | None,
|
||||||
corridors: list[dict] | None = None,
|
corridors: list[dict[str, Any]] | None = None,
|
||||||
) -> PlotSpec:
|
) -> PlotSpec:
|
||||||
"""Build a PlotSpec from resolved channels and UI state.
|
"""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,
|
x_align_method: str,
|
||||||
cursor_x1: float | None,
|
cursor_x1: float | None,
|
||||||
cursor_x2: float | None,
|
cursor_x2: float | None,
|
||||||
channel_overrides: dict | None,
|
channel_overrides: dict[str, Any] | None,
|
||||||
corridors_data: list[dict] | None,
|
corridors_data: list[dict[str, Any]] | None,
|
||||||
x_align_value: float | None,
|
x_align_value: float | None,
|
||||||
) -> go.Figure:
|
) -> go.Figure:
|
||||||
if not selected_keys:
|
if not selected_keys:
|
||||||
|
|||||||
@@ -14,13 +14,12 @@ without reordering.
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import fnmatch
|
import fnmatch
|
||||||
import re
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import dash_bootstrap_components as dbc
|
import dash_bootstrap_components as dbc
|
||||||
from dash import dash_table, dcc, html
|
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.plot.engine import DEFAULT_COLORS
|
||||||
from impakt.web.state import AppState
|
from impakt.web.state import AppState
|
||||||
|
|
||||||
@@ -277,7 +276,7 @@ def build_channel_grid(app_state: AppState) -> dbc.Card:
|
|||||||
def build_selected_channels_badges(
|
def build_selected_channels_badges(
|
||||||
selected_keys: list[str],
|
selected_keys: list[str],
|
||||||
app_state: AppState,
|
app_state: AppState,
|
||||||
) -> list:
|
) -> list[Any]:
|
||||||
"""Build badge pills showing currently selected channels."""
|
"""Build badge pills showing currently selected channels."""
|
||||||
if not selected_keys:
|
if not selected_keys:
|
||||||
return [
|
return [
|
||||||
|
|||||||
@@ -14,9 +14,8 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
import dash_bootstrap_components as dbc
|
import dash_bootstrap_components as dbc
|
||||||
|
import numpy as np
|
||||||
from dash import dash_table, dcc, html
|
from dash import dash_table, dcc, html
|
||||||
|
|
||||||
from impakt.channel.model import Channel
|
from impakt.channel.model import Channel
|
||||||
@@ -152,7 +151,7 @@ def build_channel_values_panel() -> dbc.Card:
|
|||||||
"whiteSpace": "nowrap",
|
"whiteSpace": "nowrap",
|
||||||
},
|
},
|
||||||
style_cell_conditional=[
|
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"},
|
"if": {"column_id": "ch_num"},
|
||||||
"width": "3%",
|
"width": "3%",
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ Provides:
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
import dash_bootstrap_components as dbc
|
import dash_bootstrap_components as dbc
|
||||||
from dash import dcc, html
|
from dash import dcc, html
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from typing import Any
|
|||||||
import dash_bootstrap_components as dbc
|
import dash_bootstrap_components as dbc
|
||||||
from dash import html
|
from dash import html
|
||||||
|
|
||||||
from impakt.channel.model import Channel, TestData
|
from impakt.channel.model import TestData
|
||||||
from impakt.criteria import (
|
from impakt.criteria import (
|
||||||
CriterionResult,
|
CriterionResult,
|
||||||
chest_deflection,
|
chest_deflection,
|
||||||
@@ -21,7 +21,6 @@ from impakt.criteria import (
|
|||||||
hic15,
|
hic15,
|
||||||
nij,
|
nij,
|
||||||
tibia_index,
|
tibia_index,
|
||||||
viscous_criterion,
|
|
||||||
)
|
)
|
||||||
from impakt.protocol.base import Color, ProtocolResult, Rating
|
from impakt.protocol.base import Color, ProtocolResult, Rating
|
||||||
|
|
||||||
@@ -229,7 +228,7 @@ def build_criteria_results_display(
|
|||||||
protocol_result: ProtocolResult | None = None,
|
protocol_result: ProtocolResult | None = None,
|
||||||
) -> html.Div:
|
) -> html.Div:
|
||||||
"""Build the criteria results display."""
|
"""Build the criteria results display."""
|
||||||
elements: list = []
|
elements: list[Any] = []
|
||||||
|
|
||||||
# Protocol summary
|
# Protocol summary
|
||||||
if protocol_result:
|
if protocol_result:
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ and template selector.
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import dash_bootstrap_components as dbc
|
import dash_bootstrap_components as dbc
|
||||||
from dash import dcc, html
|
from dash import html
|
||||||
|
|
||||||
from impakt.web.state import AppState
|
from impakt.web.state import AppState
|
||||||
|
|
||||||
|
|||||||
@@ -8,10 +8,8 @@ Example: sqrt(ax**2 + az**2) with ax=Head Accel X, az=Head Accel Z
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
import dash_bootstrap_components as dbc
|
import dash_bootstrap_components as dbc
|
||||||
from dash import dcc, html
|
from dash import html
|
||||||
|
|
||||||
from impakt.web.state import AppState
|
from impakt.web.state import AppState
|
||||||
|
|
||||||
|
|||||||
@@ -6,10 +6,11 @@ plot panes. Each pane has its own channel selection and axis labels.
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
import dash_bootstrap_components as dbc
|
import dash_bootstrap_components as dbc
|
||||||
from dash import dcc, html
|
from dash import dcc, html
|
||||||
|
|
||||||
|
|
||||||
# Layout presets: (rows, cols)
|
# Layout presets: (rows, cols)
|
||||||
LAYOUT_PRESETS: dict[str, tuple[int, int]] = {
|
LAYOUT_PRESETS: dict[str, tuple[int, int]] = {
|
||||||
"1x1": (1, 1),
|
"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."""
|
"""Build plot pane elements for a given layout."""
|
||||||
rows, cols = LAYOUT_PRESETS.get(layout, (1, 1))
|
rows, cols = LAYOUT_PRESETS.get(layout, (1, 1))
|
||||||
pane_count = rows * cols
|
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."""
|
"""Build an empty plot figure with instruction text."""
|
||||||
return {
|
return {
|
||||||
"data": [],
|
"data": [],
|
||||||
|
|||||||
@@ -10,10 +10,8 @@ Provides:
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
import dash_bootstrap_components as dbc
|
import dash_bootstrap_components as dbc
|
||||||
from dash import dcc, html
|
from dash import html
|
||||||
|
|
||||||
from impakt.web.state import AppState
|
from impakt.web.state import AppState
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ Provides global transform defaults and per-channel override capability.
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
import dash_bootstrap_components as dbc
|
import dash_bootstrap_components as dbc
|
||||||
from dash import dcc, html
|
from dash import dcc, html
|
||||||
|
|
||||||
@@ -92,7 +94,7 @@ def build_transform_panel() -> dbc.Card:
|
|||||||
def build_per_channel_override_rows(
|
def build_per_channel_override_rows(
|
||||||
selected_keys: list[str],
|
selected_keys: list[str],
|
||||||
overrides: dict[str, dict[str, str]],
|
overrides: dict[str, dict[str, str]],
|
||||||
) -> list:
|
) -> list[Any]:
|
||||||
"""Build per-channel override controls for selected channels.
|
"""Build per-channel override controls for selected channels.
|
||||||
|
|
||||||
Shows a compact row per selected channel with a CFC dropdown override.
|
Shows a compact row per selected channel with a CFC dropdown override.
|
||||||
|
|||||||
@@ -8,13 +8,12 @@ Two tabs:
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
import dash_bootstrap_components as dbc
|
import dash_bootstrap_components as dbc
|
||||||
from dash import dcc, html
|
from dash import dcc, html
|
||||||
|
|
||||||
from impakt.web.components.channel_grid import build_channel_grid
|
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.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.criteria import build_criteria_panel
|
||||||
from impakt.web.components.header import (
|
from impakt.web.components.header import (
|
||||||
build_header,
|
build_header,
|
||||||
@@ -22,9 +21,8 @@ from impakt.web.components.header import (
|
|||||||
build_overlay_modal,
|
build_overlay_modal,
|
||||||
build_test_info_panel,
|
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.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.report import build_report_panel
|
||||||
from impakt.web.components.templates import build_template_panel
|
from impakt.web.components.templates import build_template_panel
|
||||||
from impakt.web.components.transforms import build_transform_panel
|
from impakt.web.components.transforms import build_transform_panel
|
||||||
|
|||||||
@@ -15,12 +15,10 @@ import logging
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
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.script.api import Session
|
||||||
from impakt.template.library import TemplateLibrary
|
from impakt.template.library import TemplateLibrary
|
||||||
from impakt.template.model import PlotDefinition, TemplateSpec
|
from impakt.template.model import PlotDefinition, TemplateSpec
|
||||||
from impakt.transform.align import YAlign
|
|
||||||
from impakt.transform.cfc import CFCFilter
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user