33import argparse
44import json
55import os
6- from typing import Dict , Iterable , List , Mapping , Tuple
6+ from collections .abc import Iterable , Mapping
7+ from typing import Any
78
89from rich .console import Console
910from rich .panel import Panel
1011from rich .table import Table
1112
12- from .report import Summary , SEVERITIES , to_markdown
13+ from .report import SEVERITIES , Summary , to_markdown
1314
14-
15- SEV_ORDER : Tuple [str , ...] = tuple (SEVERITIES )
16- SEV_TO_COLOR : Dict [str , str ] = {
15+ SEV_ORDER : tuple [str , ...] = tuple (SEVERITIES )
16+ SEV_TO_COLOR : dict [str , str ] = {
1717 "CRITICAL" : "bold bright_red" ,
1818 "HIGH" : "red3" ,
1919 "MEDIUM" : "yellow3" ,
@@ -26,8 +26,10 @@ def _normalize(sev: str) -> str:
2626 return sev .strip ().upper ()
2727
2828
29- def _count_by_severity_from_list (items : Iterable [Mapping [str , object ]]) -> Dict [str , int ]:
30- counts : Dict [str , int ] = {k : 0 for k in SEVERITIES }
29+ def _count_by_severity_from_list (
30+ items : Iterable [Mapping [str , Any ]],
31+ ) -> dict [str , int ]:
32+ counts : dict [str , int ] = {k : 0 for k in SEVERITIES }
3133 for it in items :
3234 raw = it .get ("severity" ) or it .get ("level" ) or ""
3335 sev = _normalize (str (raw ))
@@ -37,10 +39,10 @@ def _count_by_severity_from_list(items: Iterable[Mapping[str, object]]) -> Dict[
3739
3840
3941def _build_summary (apv_obj : object ) -> Summary :
40- # Soportar dos formas :
42+ # Soporta :
4143 # 1) {"by_severity": {"HIGH": 1, ...}}
42- # 2) [ {"severity": "HIGH"}, ... ]
43- counts : Dict [str , int ] = {k : 0 for k in SEVERITIES }
44+ # 2) [{"severity": "HIGH"}, ...] o en "findings"/"items"
45+ counts : dict [str , int ] = {k : 0 for k in SEVERITIES }
4446
4547 if isinstance (apv_obj , Mapping ):
4648 bs = apv_obj .get ("by_severity" )
@@ -52,36 +54,34 @@ def _build_summary(apv_obj: object) -> Summary:
5254 counts [kk ] += int (v ) # type: ignore[arg-type]
5355 except Exception :
5456 pass
55- # fallback: lista en "findings" / "items"
5657 items = apv_obj .get ("findings" ) or apv_obj .get ("items" )
5758 if isinstance (items , list ) and not any (counts .values ()):
5859 counts = _count_by_severity_from_list (items ) # type: ignore[arg-type]
5960 elif isinstance (apv_obj , list ):
6061 counts = _count_by_severity_from_list (apv_obj ) # type: ignore[arg-type]
6162
6263 total = sum (counts .values ())
63- # peor severidad
64+
6465 worst = "INFO"
6566 for sev in SEV_ORDER :
6667 if counts .get (sev , 0 ) > 0 :
6768 worst = sev
6869 break
69- # nivel de riesgo
70- risk = "green"
70+
7171 if worst in ("CRITICAL" , "HIGH" ):
7272 risk = "red"
7373 elif worst in ("MEDIUM" , "LOW" ):
7474 risk = "yellow"
75+ else :
76+ risk = "green"
7577
7678 return Summary (total = total , by_severity = counts , worst = worst , risk_level = risk )
7779
7880
7981def _load_input (s : str ) -> object :
80- # Si existe ruta -> JSON desde archivo
8182 if os .path .isfile (s ):
82- with open (s , "r" , encoding = "utf-8" ) as fh :
83+ with open (s , encoding = "utf-8" ) as fh :
8384 return json .load (fh )
84- # Si parece JSON inline ({ o [) -> parsear
8585 if s .lstrip ().startswith ("{" ) or s .lstrip ().startswith ("[" ):
8686 return json .loads (s )
8787 raise SystemExit (f"✗ Not found and not JSON: { s } " )
@@ -90,13 +90,21 @@ def _load_input(s: str) -> object:
9090def _render_table (summary : Summary , console : Console ) -> None :
9191 if summary .total == 0 :
9292 console .print (
93- Panel .fit ("✅ No findings" , border_style = "green" , title = "Diff Risk Dashboard" )
93+ Panel .fit (
94+ "✅ No findings" ,
95+ border_style = "green" ,
96+ title = "Diff Risk Dashboard" ,
97+ )
9498 )
9599 return
96100
97- title_dot = (
98- "🔴" if summary .risk_level == "red" else "🟡" if summary .risk_level == "yellow" else "🟢"
99- )
101+ if summary .risk_level == "red" :
102+ title_dot = "🔴"
103+ elif summary .risk_level == "yellow" :
104+ title_dot = "🟡"
105+ else :
106+ title_dot = "🟢"
107+
100108 table = Table (
101109 title = f"Diff Risk Dashboard { title_dot } — Worst: { summary .worst } " ,
102110 title_justify = "center" ,
@@ -110,7 +118,6 @@ def _render_table(summary: Summary, console: Console) -> None:
110118 table .add_column ("Bar" )
111119
112120 total = max (1 , summary .total )
113- # ancho aproximado para barra proporcional
114121 bar_width = max (10 , min (48 , console .size .width - 60 ))
115122
116123 for sev in SEVERITIES :
@@ -122,7 +129,10 @@ def _render_table(summary: Summary, console: Console) -> None:
122129
123130 table .add_row ("TOTAL" , str (summary .total ), "100%" if summary .total else "0%" , "" )
124131 console .print (table )
125- console .print ("Tip: usa -f md para reporte Markdown o -f json para máquinas." , style = "dim" )
132+ console .print (
133+ "Tip: usa -f md para reporte Markdown o -f json para máquinas." ,
134+ style = "dim" ,
135+ )
126136
127137
128138def _render_bars (summary : Summary ) -> None :
@@ -138,10 +148,9 @@ def _render_bars(summary: Summary) -> None:
138148
139149
140150def main () -> None :
141- p = argparse .ArgumentParser (
142- prog = "diff_risk_dashboard" , description = "Diff Risk Dashboard (APV JSON -> summary)"
143- )
144- p .add_argument ("input" , help = "File path or inline JSON from ai-patch-verifier" )
151+ desc = "Diff Risk Dashboard (APV JSON -> summary)"
152+ p = argparse .ArgumentParser (prog = "diff_risk_dashboard" , description = desc )
153+ p .add_argument ("input" , help = "File path or inline JSON" )
145154 p .add_argument (
146155 "-f" ,
147156 "--format" ,
@@ -151,7 +160,9 @@ def main() -> None:
151160 )
152161 p .add_argument ("-o" , "--output" , default = "-" , help = "Output file; '-' = stdout" )
153162 p .add_argument (
154- "--no-exit-by-risk" , action = "store_true" , help = "Do not set exit code by risk level"
163+ "--no-exit-by-risk" ,
164+ action = "store_true" ,
165+ help = "Do not set exit code by risk level" ,
155166 )
156167 args = p .parse_args ()
157168
@@ -183,11 +194,10 @@ def main() -> None:
183194 elif args .format == "bar" :
184195 _render_bars (summary )
185196
186- else : # table (TTY pretty)
197+ else : # table
187198 console = Console (force_jupyter = False , force_terminal = None , soft_wrap = False )
188199 _render_table (summary , console )
189200
190- # Exit code unless overridden
191201 if not args .no_exit_by_risk :
192202 if summary .risk_level == "red" :
193203 raise SystemExit (2 )
0 commit comments