From 068622482439ac8a7ed475a25a4fbf3acc6402a3 Mon Sep 17 00:00:00 2001 From: noisedestroyers Date: Sat, 11 Apr 2026 06:42:24 -0400 Subject: [PATCH] QA-Improver --- .claude/agents/qa-improver.md | 85 +++++++++++ .coverage | Bin 0 -> 69632 bytes pyproject.toml | 1 + scripts/qa-improve.sh | 136 ++++++++++++++++++ .../__pycache__/lookup.cpython-312.pyc | Bin 9098 -> 8542 bytes .../channel/__pycache__/model.cpython-312.pyc | Bin 22302 -> 22332 bytes src/impakt/channel/lookup.py | 11 +- src/impakt/channel/model.py | 4 +- .../__pycache__/chest.cpython-312.pyc | Bin 5800 -> 5765 bytes .../__pycache__/clip3ms.cpython-312.pyc | Bin 3312 -> 3277 bytes .../__pycache__/femur.cpython-312.pyc | Bin 4003 -> 3968 bytes .../criteria/__pycache__/nij.cpython-312.pyc | Bin 6791 -> 6756 bytes .../__pycache__/tibia.cpython-312.pyc | Bin 5447 -> 5412 bytes src/impakt/criteria/chest.py | 3 - src/impakt/criteria/clip3ms.py | 2 - src/impakt/criteria/femur.py | 2 - src/impakt/criteria/nij.py | 1 - src/impakt/criteria/tibia.py | 1 - src/impakt/io/__init__.py | 16 +++ .../io/__pycache__/__init__.cpython-312.pyc | Bin 202 -> 567 bytes src/impakt/io/__pycache__/mme.cpython-312.pyc | Bin 25890 -> 25854 bytes src/impakt/io/mme.py | 3 +- .../plot/__pycache__/cursor.cpython-312.pyc | Bin 2043 -> 2008 bytes .../plot/__pycache__/engine.cpython-312.pyc | Bin 9208 -> 9145 bytes .../plot/__pycache__/export.cpython-312.pyc | Bin 1670 -> 1635 bytes src/impakt/plot/cursor.py | 2 - src/impakt/plot/engine.py | 3 +- src/impakt/plot/export.py | 1 - src/impakt/plugin/__init__.py | 18 +++ .../__pycache__/euro_ncap.cpython-312.pyc | Bin 8192 -> 8157 bytes .../protocol/__pycache__/iihs.cpython-312.pyc | Bin 6250 -> 6215 bytes .../__pycache__/us_ncap.cpython-312.pyc | Bin 5144 -> 5109 bytes src/impakt/protocol/euro_ncap.py | 1 - src/impakt/protocol/iihs.py | 1 - src/impakt/protocol/us_ncap.py | 1 - src/impakt/report/__init__.py | 12 ++ src/impakt/report/engine.py | 15 +- src/impakt/script/__init__.py | 11 ++ .../__pycache__/__init__.cpython-312.pyc | Bin 188 -> 412 bytes .../script/__pycache__/api.cpython-312.pyc | Bin 20163 -> 20065 bytes src/impakt/script/api.py | 8 +- src/impakt/script/cli.py | 3 - src/impakt/template/__init__.py | 12 ++ .../__pycache__/__init__.cpython-312.pyc | Bin 200 -> 493 bytes .../__pycache__/library.cpython-312.pyc | Bin 4493 -> 4502 bytes .../__pycache__/model.cpython-312.pyc | Bin 10235 -> 10203 bytes src/impakt/template/library.py | 2 +- src/impakt/template/model.py | 2 - .../__pycache__/align.cpython-312.pyc | Bin 7529 -> 7529 bytes .../__pycache__/base.cpython-312.pyc | Bin 6284 -> 6268 bytes .../transform/__pycache__/cfc.cpython-312.pyc | Bin 4205 -> 4181 bytes .../__pycache__/math_expr.cpython-312.pyc | Bin 7289 -> 7274 bytes src/impakt/transform/align.py | 2 +- src/impakt/transform/base.py | 2 +- src/impakt/transform/cfc.py | 2 - src/impakt/transform/math_expr.py | 2 - src/impakt/web/app.py | 3 - src/impakt/web/callbacks/channel_callbacks.py | 12 +- .../web/callbacks/corridor_callbacks.py | 6 +- .../web/callbacks/criteria_callbacks.py | 1 - src/impakt/web/callbacks/cursor_callbacks.py | 9 +- src/impakt/web/callbacks/export_callbacks.py | 5 +- src/impakt/web/callbacks/file_callbacks.py | 2 +- src/impakt/web/callbacks/plot_callbacks.py | 7 +- src/impakt/web/components/channel_grid.py | 5 +- src/impakt/web/components/channel_values.py | 5 +- src/impakt/web/components/corridors.py | 2 - src/impakt/web/components/criteria.py | 5 +- src/impakt/web/components/header.py | 2 +- src/impakt/web/components/math_builder.py | 4 +- src/impakt/web/components/plot_grid.py | 7 +- src/impakt/web/components/templates.py | 4 +- src/impakt/web/components/transforms.py | 4 +- src/impakt/web/layout.py | 6 +- src/impakt/web/state.py | 4 +- 75 files changed, 350 insertions(+), 108 deletions(-) create mode 100644 .claude/agents/qa-improver.md create mode 100644 .coverage create mode 100755 scripts/qa-improve.sh 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 0000000000000000000000000000000000000000..fbbe70f9c6bccdd5e5a1bca466656b968b3d839c GIT binary patch literal 69632 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/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 0932deb69294fd2f10e7e1b2fabc4fb94e594297..a93c91194a01d0eba059f517d875ed2c3ee84f0d 100644 GIT binary patch delta 3413 zcmY+EdrVvB6~KMXHDLSNKpX-j5FBX2BMI1$N88XOK(H}f+knj@PLtH(y8#bB*u|)e zwat~fKcY$7(z)G}ZSA&f>)sRUG^tzHY-^;c+JrQzVpmU`1Va#-N!y>=rt({8c6hO+ZhCqR1VUE5=wueXepBo<&n z$fGO-d5n!iR#~_tr7R_%)KrQ}gS9NpGO(Owxsr4f3r|XkBKsu!6fE9gkKrQwG%K;k z*%PddeTIFOeU5#eeF5l;Y>j=1eVKh_t>&hbu(57UW={^l9#R(e6#FV%|26h?$Tul} zgMAbDTa=$>-v<5;1^*d)4)V|0^N??`UqJpP z`xWFD*zJ;3df7VE16%w9dxhO$TkMbQRrVVD6MLP#!T!wNWN)o0u(?Xw+}mPvCAGp| ze2@JTZt*YnZ^-|-Au&ryf{bA$ssNISY(SMnRY285b|43l6G$mZ39Pv^8L}Hc1^(7R z)I!uj?1J#D$%$&*2k`Eq*;Eg*2ciL@ajha@HyfJF`d(Py2hj}C0O47YZOKCZ*;>V%api0&Iji5)iElychlzM+(3HDGYi>17&&u=rpbs*_j<=XMxUA;~>x=(Gbuu z(YZ2>lqmodplT2Z-f+mH7(^i;c;6w9VGxZ2!P^g6#V8dK!x%UW@Hc`%4N;&8qRBE% z0Zq|b3@Anv2Z|F-1I-Z4mT9I;b3k)cJr8uAC@}$;AiMx{f#^Y?i$oX8bV(4r!fx{c zUp5cv3RE8Yn4Eyu>{_yOetoO*6BWk_?$}~(HO(~LJC#~k%o}oSR6S=@@2rxm1=o&y zRqg8fQ;w_FVxewETTIQTHE(B7%W8Tu-DOmcWEWCd&8Qkm=JT36r-YH4rm+Dcx_ z6qKQ)uBYIW0?B#JkmEURrQjLT(s^&Enq2PP`G_vVHoK^4>4GDW&gEcV$yB=F9M5W+ zHcoz5<0a}Q^`Y7BX1WOUe&M< zpU-fI2Sbs|YIJxo9*Ra{`bXeZUaxh(Bozxv+~4P&(e%7_MNbx-s{j4+f0h3CSC0Qr z9Mj9o@UY;4=L=53bVLP*ntQlh5G>Bl9@FM?S(enVEfw6NFp*nH;bSV)2rqYZDe$Fz z{ysxieLg>B!TrR?4S8Cfn}a-sStVA9;fq7R&kx@pv%mD)+DD{Y>(M$xa=r-a^&&F)_1sEcPo}c^OQjd~{8*|keYhcGtC1|0fzsa^ z`aBAdQ2{MRrLN^y^{if3>Tf(T_ffpbGnn4M^cPHj#k7s-@0k99>0M0Ka1$Nx`Lso^ zMRwy|s|PYUwwl&X>kXjrYgrkzuit94OT|g4&AKH!t_Qdckyl!5%Jo@ZMN)0CIiX@F zaR_meC@3m-k+_A_knBPUau3OFkPW4o*OTqR(im?bX+#MWc@x=Qv=HA%(rjMULbhLA z)k@+;iSh#^_n-v%L6So#5#B-4jS|{EOmYNelpiJOK?(C-5}&0=B|ljols1$W-cNP{ z8^`%cl6z5t{63QVQ9}F_$$+RlKyn%-z|W8jNuo3?k)9(B@DZT_Q7!VI@KLinBy`N| z9v2!x#l1y^PY7@3lftJ=zZ(-Cr_0Cqw9pw-XNAt0dS03=;+ZGN1N?$0KS(;xFABY6 zRxb;Eh*afCp;yf2d7+Fn$Tgu?&1)BgE|QM%l+X`I`207NR=y+_(pcY6_VbMJEcK7` zoX};`F#n)XU39nd72$a*N48gmen_&oyqn7IO{IQQY1+cPcT3r|r5xB&JX=a5=%!MH zLd&M&*;HEZCari-kGOBR-?2J)BGZfnxC@y^B*1HsYsIy7$iG)6fL@N#vcGL8g%j@>9q=6N7xZh!+R>8I-ehenCDcp^143!@Qu0 zaS6k;pow7#Z->ZWR$Tkwh|9lpxS(*vaJ&thO7o`Da#zDQc=3gYEX6gX9o{oI8D#|qNIYf#`^LH6=G6gXFq4(>sL Zg9SOt>rvoj>6!+V_2<2}tup?n{x8t~0hRy& delta 4026 zcmZ9NS#VS56~}dD$&2I-9BejYVMwqYz?NA8fdIC!WZ74ev21M2D(k!00xjlR4nfmY zdFTV_LrW^U(>=6J)Aq%gb|!6Fnl@>grG?Hklip5gXi7=iK72r4I$i(geAlL_hjaA% zo%89g-x1e)KN@O#t)|A~z~39&f16sn-r;zMWZ|zyfp+(~@~7Hv?hTYQ(I02~k46C_ah&De-CK&xp?=e@?U_e_mWd{(|@-@|VPyk-s9oiaa5{hWs>l zFN?2(zahSf{EYY(^0VUGCGD~{;T9c+OMGWH?hTjntoUxJlyv&L#Sg>}vFZiRKN3F% zU*Y@{@gn%AoPQ=>0>4Z-|hcf_BNZ;C%7-xBZANpYh0 z#9wf?9cBLF&&4a~{6f5n{F-<)~#;>>vo{N6Y(%&7h*Tg<)!<7gs*9jy=*U9 z`w%^dUV9lmFtsXRuiB4FA0mhdA^H(v!~kLtp(7%QC?bX!x>P+;V_(n#I!21_e;835 z=7$s~Wf}otM34_I(imurYvZ7CrbD0!ro)SLWRZ@7j&k=H=or&+&~c^{pc71qA~3-? z37TX|f|5)EBxrZz)1ct$NzfG2^dhA|DXyIYonkr-I?a>@VaSoQiMm&K9&0OT4oBh5a6INqPA9Ti zBkjv3GO6tR?((x`Tdm6YbZ#y!4AXMO`}O^t^+L@;)u$UL^M%?)9ZpS6&)=##=`32V zgMIyh!jgl=bSjxPd~G2kYnX|2yXESOhN4z^Uv?^$H7r+e&&Y^X(VNKU4fCAk3Xb;a z1#fT;*AYKAn>RAFVt5!i*b^vJ_a;m;WthHTB8l&g#QNgMIb#-mJvLNm=rz)LUz?sN z&U7wZ6wWx--=|ydv1!9d7d*jqE@%3N6R9-LhGSczg}Qhu zGnY=}Q@N}!niDu-Y@~nKa>w%1^gz1sS6E`-m#@<|nmwJ(oy}Uck)E(OdR&hMdPc&r zXna0d^QMLeGScPmE>w^ByL|@@GjB|q3CpE-b@>Zk-T%Q-H%kAvY70tRKDcTdO56UY zG^&q}7d)dgGk8AqUM$z>Fe+ZI6n*q=P}6ypo;$m^O6OIoVlTp-`K_wBk<4X9f?i9P zANN|T)(qulQ}j%@>Mzu(>b=Fa!Em(i-tAk?o@MK@=VIx^-JsHJ5r zXEI~HL@n3pOy{)u3YE*ugfL&Fq8rpXTPWv`Zu51jRj}ajzv8u%3Da^8B`hsq{@UT_ za9A#3Oa;vAXqt3^=3A70M{B*ARMO1N=FLPZYyPIRvhjObMd?~&#$Ah93X}p(-3?xl zRfZ*2xoPC*%&b{oI^VQ?{Bb(SbChmU`YWZsQIeGIQhJ}#-zin#ASNC28HZU%Tti2# z+?~mZxwNszY=q*UWA;VQeB<&etr*u@oo~B5=Y!HsD9au0>hoi=f~C^o_M%h8;!)yd zsV3274NI+(I+mp*A=$ukALMHFYPpQ9ky-R4U&rPlCa#wvYDh`cCvIiim-_EvvsT17M87$tJTeN z8`}a#Z=j1s_vB&R-vphqQQ+-uZ;>hgJKCtw)s}V-3pV zN>8v3%7oHMyPH%hSaoSAJ!vnVQaa5Vk}0L9G=Ba;d0Kgz+XFJAG)sE5x<%%c&nRCb z&nP!}`JkLtnrDsNnN#}c!ne;Ve~jBvc~0qvRJ+(BA6JDBbH85}mF{z@ciH1qZ;v%1 z14@H-H>9+mH7dhOb=IJaC>6 z_Veb7$Ucos&3*)g+;w>vR&+`=?$}`5RO78vzU=)B>oyyjUa-N4(}iB(T-mKDU(SMi!1q{EnZ~y=R diff --git a/src/impakt/channel/__pycache__/model.cpython-312.pyc b/src/impakt/channel/__pycache__/model.cpython-312.pyc index 291d00133acc369948fe2ce61ca566fdca8ce416..15ca208ba7e3458e96677c8f751d51a7bd81b908 100644 GIT binary patch delta 615 zcmbQYj&aXAM&8rByj%=G@JRVqrt?HzNycdt)s5>J8B!Qi*m5{>IiompxuUqheD)mf zT%IT%FqTc1%QeL(G)X8Nnlkg1XL@Gp;i*B zS`naHQH0uhh%=(3uqqY~!{nl5E15NAO4k7O zJLearDx@TqBr4?Qr=;d6Og_(ekel%qhi6G@QDRAc(d2$6RW`<3%#L}Ln~yNfJHE#d%i*$Wa&GIBDLia0?$wvx(%%)Io;3qz}IxPfs6imu|%K;i>4 zBO~KSCKg7PuRIKl%8wcN?lLIfWl*`#pmCQ$@-BnKT?Vno3_^Dq6rM6@f8?7S8g>!@ D$kc-n delta 574 zcmZvZze~eF6vyvUOwzPz(;sP*Rw0O>MRCwo!QvqP0qt;GY@!X^HRTeA;!qH0p=5G# za%<_*N$Eczg1d#%LEQB}@RH(Udc(cljhO_n9V-qGHNQya)SydP{I z_KAo@2rOvvI7uWVMY(`eB#qKhE@GKvP=;htHqr^Kpd90qI8O?w5Y;4HBq~xN;D9Yy zsZn|e!%2YYGhoSgz{(7{Wo5Vw0F@%P{~yjWSc$>qm5p z6mFKNcX~7N+_$OeyL9r-JxGwBv}Ijo;db-o0B!`wjs25U*rtU|&$B(c#(GnpeKV1j zZQmg_6+$U=O}~wu3o3?+_j_HZb48O(1k=Wi#O@5j8+qSBy{VNKU}p~WInWls_yqYm NFuuUr;bqYBNJ1zCcA*n(2CyJy0osSKzeZiaFG+L)hZXdXk95fg~x zh$%#c|1xfp8GdPO_2eu%ECG80=Fpx;pd_#nS^jctiJ>B>AyPpjw67-7*1@G73u8Ei zK@i_J`z+!d;xb}MAQ_=`nB>dmG+E*I%z5iPMlJ|+P@nZ*1Cu{D zCzUl3THAYXek41n(#|v*4O>eOIV`ShxM9eAYujo>AxjTEoT6SUe$a_w!5V}g4&!*A zL*rUE2sT&4b~JD&DzLZ9=L2Pu><{LT?fSgM6OIzfdxDi`qu`0YWJ!L(DrE7zC;LUK$h1f$T40pfTinZA zC0cS6s;sa*sc4l89KKW=jfbQ0t?)8ja) zb-GRz+tk_eoKEb90bFL`ndF-^B(;N&g~$1$MO;`y=nN5Ir~@trBYC7P%yv;$5jDgF zViGZhm`>hl>trUG*Kf?#F)+tq)?fj(iNFHj6hcY{dUF^3!)Zije~ga2r_wgGsp|wW zoWV&I(LgLB<v_5`(G1In>LDCIT-5&NHNAbP0~25U8^gcT?qG@wV>+SxeR`3o92f z{}Mxle(L`{S^cSg$BjB+FS0w}#tz`=-DJrd2m6(m{1VR+QfLag`SGG zO~a0Dr?PEt_2M2lwhcL!kmlq5jvH(Pj~_zN^I3;rSEPoUT&)^;0e8tKR&^?~0}{@u zw#`h3aT}W>Vq2|Z7B)#%>(f8Co;8!S;)k^7Iak19^VlfS3x;8i5b|S;C;ZP6M^1d^ jYM;2u7q0n%TN=uoYP>0r<4>z1RpYxNCd2B0$MgFQr%lZ| diff --git a/src/impakt/criteria/__pycache__/clip3ms.cpython-312.pyc b/src/impakt/criteria/__pycache__/clip3ms.cpython-312.pyc index c49bb2c8141a3c2aed84aae2e124afa72372aa55..8b6e70ca8b05aca18e05c1b92467081c2f680fe2 100644 GIT binary patch delta 436 zcmYjMJxjwt7|u1hEht*nn8)Vh{0T1-XufC(LW$3b#NC7 zF49r|fE52gMpqH*;O<>p(0jS(ecq4f-uFhOw~{@y?Ytt+do2rwH+G30i{MV7ilb0O zb0K0mCx;|gL)FoQ*Fwy6M`wm(FyfGL4TmPnJ9(s-W245z|3^xwevKWgt2WH1UIh(a z(1X5-#k9->KTgBMjS|pgTn<>ry-1n~xKVu82G;y*F!?Q6L$myuY$1bxky^bdCB!>- zqsR~A4AYdcE-Z*Fn6iXj&+|j5@ICVp?}*II;KS^pdPC65`GbkBdR3jWLujWRkdMNu zgmvDxDrkehS}VFC5{&}?v=#}DQr79h5??NqUAZ;LuYfKfo15D>&^*bK+@Qvo8w^uZX_%!rp@du1;x@I%HIq3!P1As^@%71H{Exonr!Fyddq&0f zOhBl{JEa2_wo5CIw-b=6K%Crf_J#wvGbc?oP;7~y(`Av@j0BcPqy)CSAWM*!_oN0y zzLPR!udPC^sLVny6l1T^2$(3zW$OWNDJ3_2wfX?7vgOP=S?ycYuqOM?0<6m?XWk@A zsAOd9%oNP1*AAPO#4`O>7hhQ*YY86d#6#gA;KsSp;xVYoj~Z9 xg#LAa#J1*}+DmqL%kQ#aSL~@&?TtEh1*0SYd?hd#-x&nuPc$XZ(uaob`vt|5Typ>b diff --git a/src/impakt/criteria/__pycache__/femur.cpython-312.pyc b/src/impakt/criteria/__pycache__/femur.cpython-312.pyc index 4ad9e79512a331c5817908252b7788851c8b2c1e..35033052e66ad3a94a29589a102a06c4658d5eaa 100644 GIT binary patch delta 599 zcmY+AO>5Lp6ozy2F`0bLfViljHaad0OlK%ni&DE#EV?kFAVO3^I`@jfo0)PZS#%KU zqID^hEV?bY(WP|nA8_X~xDfjXTBti0o|AU`NXT>Ed*0l8&OPsE-^{kYc%H4`^P^Lx zNA7lOW<%m?N8w7OaK=^1GBr{eX-#U8j!~CJ)`%LJ8JTt7kj>1BET&i|FSn-ZZ{#*_ zY#Ncx9d2&wk;7fy#OSVTfj8M&5m^Dg93|Y}{`78dg?%-Q9`@1RJ7<2@*nIiTe#;ii zfwOSR#57z+1SVV{-7P;jciM9#XQsGS{&MzNzx?h#!_7Cn{*|j_T_Y?Kt`lw$8imcG8_u&zxKn?P{2hrCuev%$e)gL==vx2 z^QqrKztYfxOXd02y^Xqa9h1-{goFnKx?Z6EqrJ+EV?G+hab?Hxiy|*T#4%|2Uu6u7 zY;7HuNuN%$wd7Ua8yq**%SK#Cs7o0C`H<$~5u{0PH5rS#9?+a<6vk(EXS19A7Zj07m55QwDjS1}4Q;WMwufF;Z^GauI!m*-yM{>uMud0> z-g*c_k$Uwcc+0(akzRT;w^DlOtv&SKd27?wUHHv6@0)pV-a9E?6x|!gF_HYP{M*QO z`tF17q1>GzhA3i~DKSnIs$kG+tR{q#Op{onIT_PpJu#?}Omo^}0|7IW@v7@!k50E8>&ABQGyH%WLakJqA<(PXJE=y2K^o z70_9d8BhQw-Z}mwFgy|j9L5!RW>)mbeOa%iX&BEQ&jtr+$V=jjQ?%ygk)hgbalRld z_svrFK1~L>57-230iX#_`A-)_*L~Q{wK|O`eanfohg#ZB8Xf)|^hA_2YWuD7-d)vl za=^lvXPI;Nd>*wz9@WabwPu)A;V{O#!DNjEu>ITY(0C?Owc~JuZzD8~uymVsi61J) V_@_cJ`DG(q`i3UOLSdW0#ou)^b#wp# diff --git a/src/impakt/criteria/__pycache__/nij.cpython-312.pyc b/src/impakt/criteria/__pycache__/nij.cpython-312.pyc index 21dd20318f0d11e46533208ab0b3428f35e542a5..59ec366e1c8d065042363dc127e933be21e84812 100644 GIT binary patch delta 923 zcmaJ=PiWI%6wjBYY5FH^*LGdn&aHE5m!e}-dl04@?@_`Uaj@Aty*dm&HjpDw8fsu~mc z`8e)3U+$;{;_OuPEkgzZ;a0>}48@KbQ6lhKZpG}l5hsEuSQ13`lpeuJf!8+$h;9oI z>xsuq?>GoXf@>2HhXkmQ+)MSOeFhVK>><=Yzay* z45#|o5g36{DF0z$?3r@MP~o(dgz;%^*HYn3fkz_+6*#*s8R@8S^1#GN_ICsw&TWT{ zflW!Tni^jdTS%9@w`7EPU&MDLhAXEC8hayE<$O@GBdMBBV5%Y{5mMfT@D)M|-UInH zDWeUa)cw%RjX6IwRiE+2`V1{1D_*YC5}Q+sWSFff3#B0>Q;k*2Z8RO%qAS*h+o4mO zU1sl_IB<7IK>Flyb7;?dJ}~McjcdQ5^e` zzTUO%%~|JuyZod)-*zltrk2~KPOvjQJda0D&?OWnudSJmW3_%s={-);F2XWPYNNHl zNC#_l^C&mpT%Y~Dk&_`8`wArNI3Wt DXB@BGWykWnedsa?S&$gpxmX^ zKm#56y4Zf`hXEMuVuxS|Dloj8UF!4($6hIB21cwb9G{_0Ss6H?QET?V7@XV|&0L)8 zd|-6E|1FHe=(c3$Tas}qc!6e7%s`=V*=q%#_zx(Nq~$OJjl2=Z z%U`iQ-@>a_-EZJClwBt8ls!}mp2gE~q><;jDKts;a?T1%h9cDbS}3k=)LZyEBLvs> zLcy`TkoW3g%&k4J-KLAX`=nBAM0$E$DA({BRpL}s(zjg;D}>~yixW&Y8L?E?z-L)} zo_xvgXQGY;&tAt3O8UXu!U33prcM&j0`b diff --git a/src/impakt/criteria/__pycache__/tibia.cpython-312.pyc b/src/impakt/criteria/__pycache__/tibia.cpython-312.pyc index f91c1357d7c70849b13029ff53a53a7e40caa5a8..d3a7de8d8523de7dc5ce95e3b90dca50f19cb07d 100644 GIT binary patch delta 609 zcmZ9J&ubGw6vyZ7ZuUnuvzv6&%?~S$gwm#^py0tnsXtQbt=Nm=9xRo&&|r33rU_Um z2$FxG%vC&jOAku^2oD}S2qJqFL@$B}qV(jv4K3mf^L_7q-kaZVmtQUOcbpfQYMFhQ*gMY^OQ3FRBTZdUV9zE^IETrR)s2M|sk86)SMeB;ld zCZBj4up(c3^X{~es$H3Rb70Be-X<)_n||G+qh*kaOO(DWWB);ImXJH>J?`!9^iqtU zihGJIPCoc8eVz92SX+4$Z3@cbmNI8vcW*aM<9_S7wY8td;Cm}EZT*1m>e958!`);W`Wi*@>V=pbr`8Y5<0|*70?4`W4v_>xzWW8*V^Kvmy_40w~nLrYR=`?+knSdHYWV}OY zc1&n)l9-Xn%n;9#wHD1&^Te9xVq(+6e`I5U7XKpZJl8y%mRNz7H{~#9Q@19qw~!(K zezf`ws-gf!p4Mn(ob-yLWUD@1hIL4_Akwz@Xu6l^KsG0vQAtI-)T%}~4#gYkn##3A zr1haPy{?ZlbMoGNB4^*SwqZ@Yu$G#$LRFoY5@|vADAIf$Mn)G8RG^1`z%VU7TRX5K z*6gZPSB1dwjOtnx`}WO>t57cV@A=)kKBw#m_atdfPwj@*R>x|kcH@Q1Dk$KVG{3CQ zgD&T+*Lc*}J>(28Nr?MOI)A)vvFI$pWzlxrjVqEqbZ@9#uJVxKebx^L^X3(qjI=?B zk%256BFD5OEeDI@sdK(;grmOC4=}09l6)BSM|e@ZcIt*938qBk)NiYf_&u?zJV(g| zCF(Xw1MV#e81-smVv$&{(Lsk4WWQoTm*29aDBnowNcXt;Y{9BwI z42Xj`xQT8~Cf64A8y@fF`|{pL^3ZNK!PfP>Dee%!s}tk$M&xVTk{h6afg_px7bzpV2HZtwsHJj5Z7aKxLqS@r7W1aILMf2#jB{2wnyKHpVfvqp|MsTyJE|#H)e0^!D@#J|;mfW7-%IJf zvM-pPibAn;RZx}=ucb=nV_Jxmkm9t^gd}GJ6E1T>&r7x5 iAD@z+Jb6E(hZGx70Z4T*NaY7+Mn=Y440=T@Kn?)4-4XZz diff --git a/src/impakt/io/__pycache__/mme.cpython-312.pyc b/src/impakt/io/__pycache__/mme.cpython-312.pyc index 75162fef7b1ff6b83ca2545fd162c7b3ff422b65..b427ca13edd343d3e6aca7f464082c7008c443e3 100644 GIT binary patch delta 3858 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 delta 3856 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 E7YB}D6aWAK delta 508 zcmZ8cOH1TH5bjPokBNz+gUIgUBdC`For~9TK^*q7c$Zv4Gaa45P8%u{al8zO;87%? z?oaUS&+rdu?t9mRCp}mlJ=qOaU%kHi>aqFO3?72OBluM3X(+COpOXOwH$;$#2r3Lo zQzJ5{#-=n=7O@&LX+<{Xm~;9c>(U#64nbAMaDZ&sI%D;AKts>qmGkeKh(hV;BD6j41NzFEknNLcM zDjSkf0qS!#gB4xXGGGnOsh^2XkB|5tKzZ$E<;1Z#>c?^A#qoZDgGn5NS@Vp>*-#w- z(}AUmG#f*!E@WH@o)O;7svq>p^F&Jg9KB24s$$WY1beW#}07_?aYybcN diff --git a/src/impakt/plot/__pycache__/engine.cpython-312.pyc b/src/impakt/plot/__pycache__/engine.cpython-312.pyc index ca36e3edc080ee4575075e69bfabe907a58371ad..4f49e825d721f7b16e1d2f7943ba29f457c4b89f 100644 GIT binary patch delta 1303 zcmZXTO>7%Q6vuaL?|N-#6Q`+@II*R*T_;&uP(DO~LLz8F4Wd+3i7Le+aPh$`Z9JI8A~+eVVKwns-#*0o}oC}mPssa}s1BXX_LXgyYp)w_ya zb)#qyrJ|I>-;!c?Kq)9?{S&1exe>rzic;*^{$_y0clS;a5`N1AmR+OuL&8H4~4MR>M=Fm&4I<-pGXCtVXh*>F9r{XlqEC+i%H$`>L_+(6; zE6X?v?fA4f>t4J+efnWc%&WQ^rGvn?Q67OKz3xlz;WCb}1$dXw>=mc<{)Wve9y^Zu z3#h9XN==*cAYG;`hw3c++FK>Z+Rq<)Q=2#3Sg##RG*9OX5Ty1^qr+AznUJ^p3S;FJHC%1M%~a}i#|igXHxGI~lf`p& zmAbaiwop}>5OU|!3E9OTgt$F_W*{a=WICQB~{MW*#bE;fnGg%nJd)Za^ zDVv46**sZ*{@kncCA7)T;89PtjO(w|-SRqhX4tN<67lu05qE=xkb8kJ3Ee-Wklen~ YzprHWm9f8+>ED%U_#rnFY6-Xh00|&3!~g&Q delta 1556 zcmZ8hU1%It6rQ`gvwz8KHXECmrjcYre#SNxk_xS8Db^;|N-AwEvXV|FGdDA%^RwQW z&1Qp;w)mtK6EBLCzEltb`Xa%HJoLe5!3WWYUXVU$!8i3m5W(U(chfX+U_Q=0_nhzi z-FtUt|DM&p)3l-jU#D}=`(ab-CmWmNv zDAh_qSFI~3*UCY6t(z#0V&^_5wMt5vRqXtGie30Jh1`l#>)G7sBTsz2HA6I26wkV5 z7*hWdUFN@&YKJJi=10lpq~+7??$mF^!xeB5YSXh5S7dB6cJ(al1s&_-J=y}%`8(Py z>E%1x_93aB!o6vP69~`p>!s(iJt+3`Z%adiRS-qNjaDcNBAbe0>_$zWIyAJ|D1TUb z`9dFBVJ>1-U9Gkc9i z>!%TpA{;}&YSG@0nqV(-Nfgr6q&2VScZY#S2Yp0l+4Fr-K(rzsT8Sicy2cijb+Go( zIK_v8kjI{ew?>tHJKNo06|=}=v5vp13D1B9Ch7%Wx+ zFvM<*4`I31voFRYjvx8pz%gKdW#HBY$qvmk+$8WHr7p=o+T%T#`UcWg4$`9YJ^sv* zqw6j+pvdrF-sA&l^=gJW5_(YgKp;8u!Qe0pf~ybF7Jdfyq(!U=Cc23?})#a{7u7J12FKuJi>@&Itz%W<7rM#K!O{W$Hr zSCUwy8C_=YqQ@NsiQlofVZlB{ zz{}c}a3bvt4zb((cC}hm!$weViBwqUcdKLM3jeiwW>J=h?=&+JWY;jv|Jo_?&%^uH z@abvx4eS#5V{FAAX+p@KsT|4tRZ+;u1Eu#DrSh{f{6Lwwugw3X%=6~R=?o_OAODDo AF#rGn diff --git a/src/impakt/plot/__pycache__/export.cpython-312.pyc b/src/impakt/plot/__pycache__/export.cpython-312.pyc index b3f7f40e917cd7df92f502b1b1cb441deb8a4c0b..81db766066eab34d28421f27f508672de704cb88 100644 GIT binary patch delta 346 zcmZqUeaypqnwOW00SF!`-^$FM$ScVhF;U%8g_D7kA%z1(qQVr;6!sR@D9%bYO|FR( z%r~B!#U#ZE)P0LNt027yWKa<|h~NPcypx-ly%||2pJTRD5daAY0*PD9sTC!+xYF{A zauZA9a}x`0v8CpvXXd2>l^d`muqp!8a8I7fa@1W4q=gMcNCSx~uHup+g&Ku`#FC66 zkZCXhzkHxxu*s}xIr)htx7dnOOG=CKZn1!5G`S`(WYtU)%FHcD%r4O@$jL9!gWAUm zG~|{5vP5w~YBIz|kSkU)6e$6zA_b5-4x8Nkl+v73yCNMRml24I{ei>>W=2NFn+!5f R8I+zfNKAHMb7BGM0RRRbRv7>Q delta 364 zcmaFN)5gntnwOW00SMCcZe})3~1@1*ypp>p@Oh$xx&Wq>2Nsq2^HWN5QtgWLfLul(E)D_` mAD9^#8E-PkJY`UN${=x7hKhgU}c 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 18deb8cd9ac64995bd58ced58ee3e3de907d1e32..b49b9eb311f05ac95a292c918a4217fd822ba38b 100644 GIT binary patch delta 1604 zcmZ8hOK)366u#rPV>@>Ie#LPbiS||{LL?+@mBvX?l@LK1Xu7~WjGLJ{PU8pX-n4ZJ zN-EM7T_hSdpe{gSgDMq-H%LhQfx-qMP_K%hNJU7nC{-&&B?RZ(>zKe;I&&W1IdkTl zbG~G{WN4$zt{<+m}_VM5pT^~^wxTcJ#6Q4Y-iiGv+dqUao3ik=;LYb z#@%mIghK?bGs;zzm8`YzmE5!LQQ#7l7l?X4Pi#4hu~t&ewI^APwLi598*|H~ z^R_dIIpOx7&L0@-k+B}Ir<;E)4U>3c)d=MN# zLJowkFg7c1^!29?pk}*C>>KmE-_Ldb{nH>gAd+-v{=~PZG(;X%G!}t$eZ6#7i6{ z%YII@3_CuRc|j;rnG%Ut`2>bt(}5_~*Pw1V<{Nc^1Jt6EE?Oud=Q8<(>7grQ-g%0S zl4!}xnZt7ns2ZLoG>|5}WpQGT;0gu&NS9t+stJ9$!GTj6wF=aXIhZg<8i`G5&#w5) z0GUfRnGZ(g)$HpdR1W(xkt>!iaQp9gl7oV#_(eX^KeR!$=GBn()@IAZH9SQkOv3a< zx>~fW#kyL&tC=LY(Nta5vyg7SZikLtWQSZBCk`z(he*4C0&~sQFOkEf>0gjZa2z(J zH#d}sR%^}jIemC{qK40uH33e!kW0sCQo%gk97A)S2jxaC?xU|4tUm+!el9jMOLmvH u|50yvCTb17wkjs!ZN$;%sr90~XJ?Gvx4D_)VOU{9ca^04JNK7^*!%}nQf2u7 delta 1670 zcmZ8hO>7%Q6yEW_V>|x;|A|Q|$_5CiEySoLP7^^%BSNdD7bCHCGaJW=?KN*Vv^K5c zO6j3UsIo>JD7kP%mALp)i30~D5aPlCBxfMl{XBqgkcW79 z%T)^VAvMAyCosbSeiZppi*!oja%@xL@!OWc3ZO1_~nP zI>%DIRo9Ni8A8V*aJ9FC9ghTo2Sd}_P> zO|kOZ**m%Gzit=dB+i2wf(*hU%=S9NGAq!2;AI5a0tK?i5Oh*3p9k}}ex;%sim?K! zzM|E*4s#e>7u)`$c-Cm(I%9LLOT$|$0;Ca+zE_BR$hL)%Pz}3Z#(0Jfoa@Y|%(vSWWq{L^T7iV6@F)UJKmr7&W zemk(A{Vf&rs@CWd%Zots!#A(qxcap#1F7CjIAdiwp~RBOAV&MPb=)3|km(rI7pvEe zJm5#f$gqT8CO=7>!?4Wy*5;CNuso{RN{zDb#HXp5 zKnf`fA2E1^s5U5wU^?k*;{aL%&$5_I=i{aaPfwgn6n&1MEgI><>9c6|T}{wXF{3q5 z$86v=3V2aDqq5J@JB;&vr+qdHTmU(s;4rRxn3S^u0 z-Bn?aN&1mJNz#Ei?GRa)5pb@#^(*8sSr193t_ip%{?3jaau}^#dQDx diff --git a/src/impakt/protocol/__pycache__/iihs.cpython-312.pyc b/src/impakt/protocol/__pycache__/iihs.cpython-312.pyc index d62f7c9c55a2057fbe5897d511976330da0039c2..a6f69b5be95dd1130512966e83417c4dd8b56214 100644 GIT binary patch delta 1316 zcmZuwO>7%Q6y90yI_q7p6DMoOaT8==Y%onsobX#xiYPx#50Rh>LJP$>nIUyzXT#e~ zt3(tyAxu0Fv*6_Jijk|wO4RR&+>fq7m-s@-lOyBlxrs&WnPJi~47;G5+x&wZ8l$N89;;NvS8gq#WF(}qqY+7~;$eF0CQ<#`#|Ut*l&Tw>4CK}d-%fCw)H zJs!sPo5XwtAOM&|Z$pobQ1sqcsoc-vhwLodkTW;v8xSLlK(SMwUHde76?uf%n-~X#AYwJBtUflTJl=Hd@4<5 zXCvSTZ4g`dH&`Y17{SW~lw@q~@9ha_xT>(&+JtT-pxc2pS)6?MtsL>j2_^_=08l}Q zQ#;}yPGMK!EUKZ4fFrs8B*U=;%?0Ok=&G{ssp0VR$u0IY6}pB!rR8fFsy}fmj37>f zi1uMOHr9e&fopOe3GhzBT9*r6C9N!D@|?H)%!_D`tvv{^j_4pMjam&p!Ak`6Bb5=* z92K#}&BO%d98>upEN+?$4H^pMz zL`&>-`+J?8sEMtKg3X}U+~|Y?Xf2W@u;j1BqC=Mek~=B?DHa4>nPiRBnl(v%f?$#0 yb%IfA{?pyqTImOTzb9URKJsYw!@m&jq!?p==msYD%Rd1<1irHrvb)w%uQh zotBLR?dst;%dtEwz-zNnHuh;YEHaZjY+01;BALrF2pD z3p2+iz#U`~J>u;*)se%~a0j!|Xmwk0qp@S3J-hmH^^v(LzbU**eOa~-xyvBDPOejN z5=ew|z*Jwc{qZ(BFsJv)Ph+m|<%jn1Cys+HUckshDcV@;PC*@Ug^Q@w>-OcZwhO5m zqw^YFl0k8i&dT*-)wfVzML2;_MwkIejCSuT7phLNBySYU&yAovf3e2~4Z*iyQ}H6I zO@z}3m`h@8_xoG~w#q*-H$^MvqSX{HNzHkDc?@;q2t@>Jvv>?3(Ju2rq6r>}b6`d` z05tyJ2YRsKRA{{auCqmt`Vc-3Jvt2EKv(r9S`>>UD|oyux`}?i*X1zvH$WgRq^fmU zF3n|?$CM{a^<$^NpO}5ogM{NjS_8IDyoT^R0v@EMFK{jjHsp=cT2=*?WybxWj!WPu zCBT)y*HMiA|BgT178Re%+wS}|Rm8*WRgP)4@T-xaAeL~8X@vTaJw0U444G J+|{wE{{Wo31Ofm6 diff --git a/src/impakt/protocol/__pycache__/us_ncap.cpython-312.pyc b/src/impakt/protocol/__pycache__/us_ncap.cpython-312.pyc index 3909fd9a791954876d48ae6563393df6ec31d5ca..b4b0b4436a8d5370636607211b7f88238c8eb084 100644 GIT binary patch delta 1111 zcmZ8g&1(};5Z{;GY&IXeNwcZ(+iIw7{D@*|6)6=J?M17oMajWsBv0`)&%BD)(%kei+#121yuh=_ zFk{M?!gI3sY{i0 z$1$V_NrVT=*Uh`;v?nGM#239{??&HS2Ky+^5c(i5dJ}`W6Xb{^JW#%8HzWB%;`rGC zT*Dwig~DrmTJJ}=XRRH2$AiFcw?>JRL7)`&)0syz%f@ynEG#8{tLZJYYa781v6w!% z!$LFCx>$XrgB$v-sVKp{(DfSuBt{l zv;CrK4ja^Qxd=sZ+br57E65{sx6|=j91`NCx%V4+qm_i=IH@J)NOJ$7xiN%?=VkxZjQ%Q>t J_M}Ey`~@Dv_sakP delta 1139 zcmY*YOKTff6rL+-Mk9?!504ZpNpVFLY)>AtNw5?0tQ%-cA*RG}Xcg!%@*RgEdgaV0 zAZ=`lp#~Fza|6jD1d?587X>Z43Z;v#3hg$TQ1a>zNCH_Fdd^jx;2HRI?svX>=bZ0c zy`8)>W&UZJX$7A`^|8OXW$tG8wkBs+nZg;@8hSHTOEuHAbknFA$Q5{UJy9FsI#2O5 zH~0uQ@2WMEXS@v0p2Smlk>^m6jVWI!Jio5+(Vt>;x}wx_DTVJ!Dcdg}<^|EaC?5xk3V7+3&Uv42|^bFX9e0# zrvvS}TlX7&*n@qTofKVljupkv>I-Ix8`^l`7_vbMVNH2d`gkL8-Grj}U8`E3AonAK z1C(b7Ww1mnIh8v>go?1HJj<3)d^~wMQWgW&4a*ix6X+B z$s#KbeoelP8+%Cg5y7|^7>8I^JU8~TwD`~Xe4M;r0II}}MX~A(R4@$4voe+AjvQuJ z#jTM|c2JbfIh}g3$Dky>Gsi6Q1+oZ{=5{(>i-RsU%!%_FQAWMZdk%P)u^N~orAc%G z{o>LnND?_iu%BR-K%SqKmPGn8_@M{B3!mN0R9178&m%+{ctN)jM)50NFN)(r77a%! zmtJl+>we4QIG9g~h(QogjpdyW$VuwnHMh}qL$9BG=ifGQ7Pkbyq04K^tF$t6cw_J& z^A|%GuI6@|-(nKJL9i2WPTbCwHOVB!!`zXhhmenSY_sD5-U-%+0AD~+}rf^@D< zTCxvhBnzepTcDADV57L-3gIH9wBaW&QC_w{J;Sh9%8!DBKT+CoR=QyqJjVg?bX2wF zq_wcu@mrT6L6U;s?6_CL>h?&hbvN)py=mu1)U;!sY+tnDt@((*3u!_<4)@$ s{baMvyAAI&_^6}*L(oTf6=#gSiK#5{Hlwi0OU3v{*(Elul>|BSKSPQ5DF6Tf 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 f7e6d878bb708165c9ecbe390fe60d2b25f17132..f399c8a63379e5d327d2514c14322f3cba139715 100644 GIT binary patch literal 412 zcmZvWJxc^J5QdZeJe5^!#KPhbn{9R*u@XV9@j#A)JDY7n*u*oKk0n{bv-3X)_ICaj zODh9b7GftJHdZF*MDu(_&PBq)<(5vP0Qq@yFcuAu_WEzVZV6s=sdDn z?*vOp`GG5NYSR%lhAC+~V=5YsTWXib8cTDmv>0l2KFy(x1ZFaib?6W}@)CPVyrfPB z`GQRmBC1Bnt}@Z!)3z}4Zxpy;|IPhvt2vvaJO^`*&@GrbGWggjj||nOl!_Tz_7tJz z*MidM`q$2Pl?XA(T7-&bYCF3^kuy&B^oqkkcg(tz*APPZ05+b%;2sPf!RA|(ECrXT H&Rp>mv-59e delta 102 zcmbQkyob@`G%qg~0}w3JzL_Ztq#uJgFu(|9eC7Z$rZc24L@`t{YBEhskl^yuWW2>5 iAD@z+JXwv=M~V%o0HnGYr1AqZBO~K22ALujAO`@4nh@;( diff --git a/src/impakt/script/__pycache__/api.cpython-312.pyc b/src/impakt/script/__pycache__/api.cpython-312.pyc index 4abac9faca885049d14e0fb3c818c3f462e85961..b56ab85d60d403015887933cb7a209b8aa62293f 100644 GIT binary patch delta 4742 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 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 b48b706b291c37e917a2ec87b18bd9a975293f99..8cb720255d41042bda9c6dd2a532ce5b6da7e2b7 100644 GIT binary patch literal 493 zcmZXRJx{|h5QfkBETt_hh=C;oLdudzurMGN7z;uwFnF;-W2y){iR>_xjUR#7+4wC? zj95Yph=C2!jR`v+3wU^S_T4@2o%}Q$_CcS!O_iM^fOm80j@1T>JssRb3K&w9S`2Xu zTgb#Vv$=yE?qZjF*sI?h=JOuzA(%nxJ|Mg__d@?BIHkN~NkxMsPXkG%%!)kVNuJCo zr+F298nY0om9B7{U5G@?LtBkbSWz9)%Ph|--K7STrhQUro*Hxwlafv~VAN)hYPJYn z;b^k(jkrc^Bc7Jyu&;bV(qc-8TF-cyTvbuk&Ka@Bzb;p6f3v(uDQn0z)bRc@+C6J? zW0f#Q$RjLtG5qYtXOapT=S3!Ie4v4G;}T0T?ZR%Hm=Lo*AyGM3y*+Jz&FH>Z)gb+C hAUCvp*$AN**!qB-HyA&|_!YLl9dFsX^~Ff%e*qJpjtKw& delta 102 zcmaFMe1g&BG%qg~0}w3HzL}{Gq#uJgFu(|9eC7Z$rZc24L@`t{YBEjCP~!5_WW2>5 iAD@z+Jh_O`Pl^qw0HnGYr1AqZBO~K22DKs BA9Mf! delta 91 zcmbQH+^fucnwOW00SG4P-OTiy$lJuoI&s1dL6(5Tk_=78TO6JxsYQt;`9+%}8Qu99 sSvD`^7h@DqTM-071rHlU!4J^cv{4+Gncw^WXWn}=Kc|+b^cT9W z5-mAe{un|C6DUSiEBYlX-esrlwPF2Wcb zz^km^`vlu5#Ju(FngmJwIr8%=gPDp<33}ymmAwg7V_vDc~J3aG=ZbhFw-chV~0OtCVGg= zK~s;Tr$GxyPui*8Kh`yGI(82rjMU8Gh&jZ!gE|CVv;O~K3#zaW{A`mg=(}2p}!{jdk7QF}QvlQ-n)fx+*SmePRF^NG;FlinvYEW>nkQWSrdoeFsk|9|x!_~}7dD)T;xrtqQ zx8*TBO)TdXOEuK?Tg}iiQtk4#F_w`iIZRCDDlyeMb+ieow;VCNZC2wf`;YaNh1#oS zIEHs&)Lwz36uN9(oRwj({ZKs(a2*+&2_y)qidj;XxVgB=c*>_og)HgxJ48-i;x+D) z)RrX`H-2C{!|pn?GC*z*Z($SOUErP#^kCLI(gF+KKJYl5@ts$hQYuPuM=7}3Pw8|r zJ?FO*QtT`B{N+p~%!5wF7DoM-IZLY^9QWngI!nGGni6rhKZHMh8mRcoud_gIbFHd| zX}^VofnI#%FM!6!WnnYY*}CE+?5V�EQb~30e%I9!yA#&l1D2;68bhqv_v((bxdq z364mwLy?L~xY6t+ZPDK#EXawS`M17CtR9Y_q#2du9xja=Hw3)boPgDJW0L%iS AEdT%j 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 2f4e91f2bd17393f8791d2e0cecfba63ce59e885..941d8369e0360a5742af265afcb2a671c67b4e3b 100644 GIT binary patch delta 29 jcmaE9_0o#>G%qg~0}wn?zLhDpk@p@iqx|MKytVuQlF|vL delta 29 jcmaE9_0o#>G%qg~0}#B_xtS@wk@p@iqvGZ_ytVuQlhX;f diff --git a/src/impakt/transform/__pycache__/base.cpython-312.pyc b/src/impakt/transform/__pycache__/base.cpython-312.pyc index a502cadb1a69856f92c824241c8aaa634dcc7c02..c503ab5d203fc69a734ad6b8736a7e0d90c63fd4 100644 GIT binary patch delta 1522 zcmZux&5smC6z}Tq`TlTrW(OH})>(xHMHXB&WOpH}fepr4S0S=P2(;4$jCA)*s(Tag z;>CkTVi@ZmFwqz{dx7jjcEBBKTVM=5h>4&-LCIO(bm7@ zCMjis&2j({0cf%UzpXuMme5-MLVv%3en(|_ScKKR=!PNnBTUgm?nnBb$uhjOH&kIV z8n3fZXi?1IM4f-9SBq6}G8>^Tcl>vKVWx=N3PKG*mVi-M0RfxD(tO!iFr_ig|18(H zzBU?!Wcl~zje|2tz>|(H>tdJ$(?{!mEc7U*gGdKc6P_ewjeldkNy_}bUFR8_RWQPa z%383n?R}mP?J6noZ|!AC{G{ffcMrl|fJfhaEp-eSw^KhKMuuYu-S5FCV;rFk`U4qO z81eeRr;e6%&gg|6y(~Eq4p3Ic@1@ru(o*JD3lFnVTekbnc7b)2ck4j*I$3!YvnWhv zHbt=)4BSiczL>dTbiU65b_fF8os+WINygV(NqJPlu zg%34kgl<3|W6hVkhopkoPK>5AtR~TbR1D4ttS`eNv|bn|I+9k@*e|7>^M~=oD#D`^ z;6uaP2$KL#fi-ad48jyb69F1fo;WU5DEssh8s`vR0}vT`Q34uY?0akp%|{Vd5S~!2 z)KZIIE3YN0ZGmdV6n1EXi1gX>Za40;9*xHD+P$adp_k89j?5sFy$7%@nM4hXV0cV0 zpH^N6`mIX;70eDTJq3%laa!PEpRrmDrZ;czfHWg8*@UsS@g+B z`Xzd0yh3!f`8(Ak$K{Y9CH7V5?S4odGbwThk}w@Zbta8J2fH!9yzWm+KmWBlc>=?H zn8KTL8ZOTnC-uMcS;GUT5f%`p5!wh(bpIsUPV(j2TweY&@VyN)Z}QJ-&sT&QUm5hm z3(P>8iD2;`YCBftk4gS-=Ff9Dm_ Ox~oj{xyg5Rtl&RRA1NsS delta 1547 zcmZuxO>7%Q6yDiiuN~WQoWxF=5T|iTHVst@H2gJbP-%leqy(yn7%FS=j?)G2ZZx}< znjRn$RH_6QYc5C#ap@(>g(?T2C+G=;iqvCQRK2xV5UPX_NW3?jI(5Y!zMXyVn|a^9 zpZRC@^P|~cve~o*&kr-V-BLR{M6R|c$4+}%O(T*+9JQr;M$Pb2wUlSpOkvYnX)jaD z5Q&=8f(;Qa)}jj=Eov!A&NSC|=taQ^v4j#-I2>D#*=7jj6PD1uF&8C3a z3BNnp^9@Nok5MEx&F^N*t1rO^dj>6fgHb!8>(ul;~#N}h%D>|;qFZZn_-CN~b+7SP@@5eDr+ct^Q^`_m3 z0_M`Nd+_cQk3)io`xmRoWbXj%HIYa)I{}7ATIi$xlR&@GAI$BI=o2NDq?_I9&~=6{4KBVWss?4nM91oK;8ROa z1Rg*Vs$|%!NhNGpb@|2Ej|e}%HCR52$6PXTj_}?%XPN(7uo`Z74`Bh}Ai^1h$FjeQ zwpBh~I+jD748NDHUN+9RN>2_k4L)NXM!@ygnT`bPf0p9cOT#PTmJ*kbsG&GYj8Aur rICb0$oC__wz?!fbug%Z__+24{{2^;ZeUOvL#C>UsA1c2su8scy;Po+P diff --git a/src/impakt/transform/__pycache__/cfc.cpython-312.pyc b/src/impakt/transform/__pycache__/cfc.cpython-312.pyc index 3a1f0d681a54f487edf62db30d931bba2d29b6a8..b2a387c323812994bf795a3114b8bc67b948b391 100644 GIT binary patch delta 256 zcmaE>a8-ftG%qg~0}wn?zLoixYa*WnIb6BiQQWya zQ9NKidk$|dUlboB0~13kKM-?gDNLNCY%U07vZQdV;atrKGLZo&?aq+G)xwa%y_yLk zvKm4$L|_H0TL7W}Iz|8h delta 280 zcmcbr@K%BEG%qg~0}x!$xtS@*J&{j>@z6wdV~Z$`6vh;`9L`*>D6U-YC~iguAfG*l zCzm&hHVYJ+=&CSBZD6`pscMT(B^yIsI>XTXd6&b}QtMi+2vF4TL7F6D1$}5;0 z!{28d2($&{gyJ|L@qw9xrwodB8N?nl2;FDUe9qv0mqFtr8-u9a IWC;OV027=*y8r+H diff --git a/src/impakt/transform/__pycache__/math_expr.cpython-312.pyc b/src/impakt/transform/__pycache__/math_expr.cpython-312.pyc index 9df83959559250cdbfa670a1edb97c4b891b4a7a..8ccc3d64d84518f264456aa2232a3b38c64e01e2 100644 GIT binary patch delta 731 zcmZvaJ!lj`6vyYy?C#C&-5z?m&E_s4CM5Wgk4pk7M^M2JB#4Bd5X=^yhqEDBb}w)r z$W=CJ(gczTh@EZFZrk)CHWpR~XQ8Cg=2BSL`F2tr*@1!I`~7Ez$ISez{H|DEEz2Z0 zeol3_j}NTk`X@%nJ0gfn1PB^HNL>msvw($~t0~q3Jv3ZHu^yycj=2$}L(?^tO9h!Q z>t=y$6Tv<1m*fQ4dKzg=k%=^}D1$T+6U%!1;G4rNkIPrN0+%3im|exp0WQbE(Jx^= zqjp_Jnp2cVDk~a7T8xPm@Q4q%-bQm%Wy46fWgi^I{faUpXqJ%*WrREN4Ga#n_XbT| z;lXRve#`v_NtypB(LPSJD~a|=qJ27O$KnzjTpiTs_K?{Rx&Q29M2R=!aZ&t8&$9+z zxW9gJT|Iu17qZTZ*1VV$qq~}0#=V+6V%K0np0F!yv7^f$?7Mvfx@l2s)m{g+W)ll` zzusAmK5M|>R`exxL_x@x<_T;?qnWz^JJCkAWI#J=<+mt2k9vjo436cSkwa&x(ZNo- zX0^K6ZnYa;wc5acNTTWDGR*HG0nVm@t@jxKoKYRLzc~U^r)086hJTU#DVgn&>wP2I HFa3f40c3va delta 811 zcmZvaPiPZC6o=oN$t0Ogw$_;ct@T)$0j<#! zKbAIpyYyg#1H1$c3N&iwqQ?|N!`$VH(;jnUo={@P2{-N~RN{X`(jraykO7O zFGt!dQ8swXavj;Y7FBCpkF+-;?Lwrz8EJ11+m4Ae_MKsE5(8)2k-F4nyS1kI^!NAn zEvLwqbJfX+mAME08FN3R~gEu^1sY;Nbr_6i!(G@+hCe*1m z55DuSxrL+Hm%6@Ef9Td*Eeib7ay$GKLyl*|&xscd7lV7&M{I-#Qs)t$gtyY=I6e*C z%p-<(!*AJF9J|5e;`-QZvrR+vty*nyb!D~b)M`!op`p*3PFO6>WA!;DGiOWE?YB50 hZZm 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__)