inserita nuova logica per errori B8, modificata documentazione, prearazione readme per presentare modifiche.

This commit is contained in:
VALLONGOL 2026-02-05 11:01:17 +01:00
parent 0b0f7b9f10
commit 50a5a97820
9 changed files with 279 additions and 58 deletions

87
GRIFO_M_PBIT_README.md Normal file
View File

@ -0,0 +1,87 @@
# GRIFO_M_PBIT — Release Notes & Feature Summary
Questo documento sintetizza le funzionalità implementate nella versione corrente dello
script `GRIFO_M_PBIT.py` rispetto a una versione iniziale minimale (senza GUI, senza
handling avanzato di "known failures", senza propagation della versione dello script, senza aggregazione dei risultati dei test, senza gestione del test del target).
Serve per informare altri sviluppatori sulle modifiche principali e sulle modalità d'uso.
## Scopo
Fornire un elenco chiaro e conciso delle funzionalità aggiunte, dei comportamenti modificati
(e dove cercare il codice/documentazione per approfondire).
## Principali funzionalità implementate
- `TEST_VERSION`:
- Aggiunta di una costante `TEST_VERSION` nello script principale.
- La stringa di versione viene inserita in tutti gli output generati: log, CSV, JSON e PDF.
- Gestione estesa di `KNOWN_FAILURES` (ora applicata anche a B8):
- `KNOWN_FAILURES` può ora essere usata per classificare come "Known" anche le failure
individuate nel drill-down B8.
- Aggiunto helper `is_known_failure(field_name)` (matching case-insensitive, substring).
- Comportamento: le failure classificate come "known" sono registrate ma NON rendono
la run fallita; le failure non note su B8 causano la FAIL della run.
- B8 drill-down migliorato:
- Il drill-down B8 classifica ogni field in PASS / REAL FAIL / KNOWN FAIL.
- Conta `b8_pass`, `b8_fail`, `b8_known_fail` e mantiene dettagli separati per real e known.
- Drill-down eseguito solo se necessario (presenza di real failures) oppure se forzato
tramite `FORCE_B8_DRILL_DOWN`.
- GUI real-time (Tkinter) — `GRIFO_M_PBIT_gui.py`:
- Aggiunta di un monitor in tempo reale opzionale (avviabile di default, disabilitabile con `--no-gui`).
- Tabella `Runs Overview` aggiornata per mostrare la colonna B8 nel formato `Pass/Fail/Known`.
- Comunicazione thread-safe tra test thread e GUI tramite `queue.Queue()`.
- Reportistica aggiornata (CSV / JSON / PDF):
- I file CSV/JSON includono ora `script_version` e i conteggi `b8_known_fail` per run.
- Il PDF include la `Script Version` nella sezione metadati e mostra, nella tabella Run Details,
la colonna B8 come `Pass/Fail/Known`.
- Simulation & Mocks migliorati:
- `GRIFO_M_PBIT_mock.py` include scenari aggiuntivi (`pedestal_fail`, `mixed_b8_failures`, `agc_tests_fail`, ecc.)
- Launcher `run_simulate.bat` e `run_simulate_simple.bat` sono mantenuti per eseguire rapidamente
test in modalità simulata.
## Differenze rispetto alla versione iniziale (baseline)
Baseline: script senza GUI, senza gestione avanzata di known-failures, senza propagation della versione, ecc.
Cosa è stato aggiunto/alterato:
- Interfaccia grafica opzionale per monitoraggio realtime.
- Meccanismo `KNOWN_FAILURES` esteso a B8 con helper dedicato.
- Maggiori dettagli nei report (terzo campo B8: known count; script version metadata).
- Drill-down B8 condizionato e classificazione Known vs Real.
- Mocks e scenari di simulazione arricchiti per testare casi complessi.
- Test automatici per prevenire regressioni nelle regole di notazione delle failure.
- Correzioni logiche che garantiscono che real B8 failures provochino correttamente la FAIL della run.
- Gestione dei test con target generator
## Dove trovare il codice e la documentazione
- Script principale: `TestEnvironment/scripts/GRIFO_M_PBIT.py`
- GUI: `TestEnvironment/scripts/GRIFO_M_PBIT_gui.py`
- Mock / simulazione: `TestEnvironment/scripts/GRIFO_M_PBIT_mock.py`
- Report / PDF generator: `TestEnvironment/env/leo_grifo_pdf2report.py`
## Esempi di esecuzione
- Modalità simulazione (rapida):
```powershell
.\run_simulate.bat
```
- Modalità hardware:
```powershell
.\run_target.bat
```
Output generati in (cartelle):
- Log: `TestEnvironment/LOG/GRIFO_M_PBIT.log`
- PDF/CSV/JSON: `TestEnvironment/pdf_reports/` (file con prefisso `GRIFO_M_PBIT_YYYYMMDD_HHMMSS`)
## Note per sviluppatori
- Matching `KNOWN_FAILURES`: la logica attuale usa substring case-insensitive. Per renderla più restrittiva
usare regex o nomi completi: modificare `is_known_failure()` in `GRIFO_M_PBIT.py`.
- Per aggiungere un known failure permanente, aggiornare la lista `KNOWN_FAILURES` in `GRIFO_M_PBIT.py`.
- Se si modifica la struttura dei report JSON, aggiornare anche `TestEnvironment/env/leo_grifo_test_report.py`.

View File

@ -373,7 +373,7 @@ class PDFClass:
# Table header row (added Target column) # Table header row (added Target column)
headers = ('Run', 'Status', 'PBIT\nTime', 'B6 Status\n(P/F/K)', headers = ('Run', 'Status', 'PBIT\nTime', 'B6 Status\n(P/F/K)',
'B8 Checks\n(Pass/Fail)', 'Serial Messages\n(E/F/R)', 'Target', 'Target Test') 'B8 Checks\n(P/F/K)', 'Serial Messages\n(E/F/R)', 'Target', 'Target Test')
with doc.table( with doc.table(
borders_layout="ALL", borders_layout="ALL",
@ -401,7 +401,7 @@ class PDFClass:
# Format data compactly # Format data compactly
pbit_time = f"{run['pbit_time']:.1f}s" if run.get('bit_available', True) else "N/A" pbit_time = f"{run['pbit_time']:.1f}s" if run.get('bit_available', True) else "N/A"
b6_status = f"{run['b6_pass']}/{run['b6_fail']}/{run['b6_known_fail']}" b6_status = f"{run['b6_pass']}/{run['b6_fail']}/{run['b6_known_fail']}"
b8_checks = f"{run['b8_pass']}/{run['b8_fail']}" if run['b8_checked'] > 0 else "-" b8_checks = f"{run['b8_pass']}/{run['b8_fail']}/{run.get('b8_known_fail',0)}" if run['b8_checked'] > 0 else "-"
serial_stats = f"{run.get('serial_errors', 0)}/{run.get('serial_fatal', 0)}/{run.get('serial_recycles', 0)}" serial_stats = f"{run.get('serial_errors', 0)}/{run.get('serial_fatal', 0)}/{run.get('serial_recycles', 0)}"
result_text = f"{result_symbol} {status_text}" result_text = f"{result_symbol} {status_text}"
@ -438,8 +438,8 @@ class PDFClass:
doc.set_font(size=8, style='I') doc.set_font(size=8, style='I')
doc.multi_cell(0, 4, doc.multi_cell(0, 4,
"Legend: B6 Status = Pass/Fail/Known (known failures ignored) | " "Legend: B6 Status = Pass/Fail/Known (known failures ignored) | "
"B8 Checks = Pass/Fail (diagnostic drill-down) | " "B8 Checks = Pass/Fail/Known (known failures ignored) | "
"Serial = Errors(%%E)/Fatal(%%F)/Recycles | " "Serial = Errors(%E)/Fatal(%F)/Recycles | "
"Target Test = Target detection result (PASS/FAIL)", "Target Test = Target detection result (PASS/FAIL)",
align='L') align='L')
doc.reset_font() doc.reset_font()
@ -531,11 +531,15 @@ class PDFClass:
b8_pass_rate = (aggregate['total_b8_pass'] / total_b8 * 100) b8_pass_rate = (aggregate['total_b8_pass'] / total_b8 * 100)
b8_fail_rate = (aggregate['total_b8_fail'] / total_b8 * 100) b8_fail_rate = (aggregate['total_b8_fail'] / total_b8 * 100)
b8_known = aggregate.get('total_b8_known', 0)
b8_known_rate = (b8_known / total_b8 * 100) if total_b8 > 0 else 0
rows_b8 = ( rows_b8 = (
('Metric', 'Count', 'Percentage'), ('Metric', 'Count', 'Percentage'),
('B8 Diagnostic - Total Checks', str(total_b8), '100%'), ('B8 Diagnostic - Total Checks', str(total_b8), '100%'),
('B8 - Pass', str(aggregate['total_b8_pass']), f"{b8_pass_rate:.1f}%"), ('B8 - Pass', str(aggregate['total_b8_pass']), f"{b8_pass_rate:.1f}%"),
('B8 - Fail', str(aggregate['total_b8_fail']), f"{b8_fail_rate:.1f}%") ('B8 - Fail', str(aggregate['total_b8_fail']), f"{b8_fail_rate:.1f}%"),
('B8 - Known Failures (Ignored)', str(b8_known), f"{b8_known_rate:.1f}%")
) )
with doc.table( with doc.table(

View File

@ -138,8 +138,40 @@ FORCE_B8_DRILL_DOWN = True
KNOWN_FAILURES = [ KNOWN_FAILURES = [
"radar_health_status_and_bit_report_valid_RdrHealthStatusAndBitReport_pedestal_status", "radar_health_status_and_bit_report_valid_RdrHealthStatusAndBitReport_pedestal_status",
# Add more known HW setup limitations here as needed # Add more known HW setup limitations here as needed
# Treat any pedestal-related B8 fields as known failures by default
"pedestal",
] ]
def is_known_failure(field_name: str) -> bool:
"""
Decide whether a given diagnostic `field_name` should be considered a known
(ignored) failure based on the `KNOWN_FAILURES` list.
Matching rules:
- Substring match (case-insensitive): if any entry in `KNOWN_FAILURES`
is found inside `field_name`, it's considered known.
Args:
field_name: Full diagnostic field identifier (e.g. '..._pedestal_status')
Returns:
True if the field is configured as a known failure, False otherwise.
"""
try:
if not field_name:
return False
fname = field_name.lower()
for k in KNOWN_FAILURES:
try:
if k.lower() in fname:
return True
except Exception:
continue
except Exception:
return False
return False
interruptRequest = False # Global flag for graceful Ctrl-C handling interruptRequest = False # Global flag for graceful Ctrl-C handling
# ==================== # ====================
@ -292,6 +324,7 @@ def generate_final_statistics_report(report, stats):
total_b8_checks = sum(r['b8_checked'] for r in stats['repetitions']) total_b8_checks = sum(r['b8_checked'] for r in stats['repetitions'])
total_b8_pass = sum(r['b8_pass'] for r in stats['repetitions']) total_b8_pass = sum(r['b8_pass'] for r in stats['repetitions'])
total_b8_fail = sum(r['b8_fail'] for r in stats['repetitions']) total_b8_fail = sum(r['b8_fail'] for r in stats['repetitions'])
total_b8_known = sum(r.get('b8_known_fail', 0) for r in stats['repetitions'])
total_serial_msgs = sum(r.get('serial_total', 0) for r in stats['repetitions']) total_serial_msgs = sum(r.get('serial_total', 0) for r in stats['repetitions'])
total_serial_errors = sum(r.get('serial_errors', 0) for r in stats['repetitions']) total_serial_errors = sum(r.get('serial_errors', 0) for r in stats['repetitions'])
@ -343,6 +376,7 @@ def generate_final_statistics_report(report, stats):
'total_b8_checks': total_b8_checks, 'total_b8_checks': total_b8_checks,
'total_b8_pass': total_b8_pass, 'total_b8_pass': total_b8_pass,
'total_b8_fail': total_b8_fail, 'total_b8_fail': total_b8_fail,
'total_b8_known': total_b8_known,
# Serial communication statistics # Serial communication statistics
'total_serial_msgs': total_serial_msgs, 'total_serial_msgs': total_serial_msgs,
@ -380,7 +414,7 @@ def generate_final_statistics_report(report, stats):
logging.info(f"Successful: {stats['successful_runs']} ({stats['successful_runs']/stats['total_runs']*100:.1f}%)") logging.info(f"Successful: {stats['successful_runs']} ({stats['successful_runs']/stats['total_runs']*100:.1f}%)")
logging.info(f"Failed: {stats['failed_runs']} ({stats['failed_runs']/stats['total_runs']*100:.1f}%)") logging.info(f"Failed: {stats['failed_runs']} ({stats['failed_runs']/stats['total_runs']*100:.1f}%)")
logging.info(f"B6 Checks: {total_b6_checks} (Pass: {total_b6_pass}, Fail: {total_b6_fail}, Known: {total_b6_known})") logging.info(f"B6 Checks: {total_b6_checks} (Pass: {total_b6_pass}, Fail: {total_b6_fail}, Known: {total_b6_known})")
logging.info(f"B8 Checks: {total_b8_checks} (Pass: {total_b8_pass}, Fail: {total_b8_fail})") logging.info(f"B8 Checks: {total_b8_checks} (Pass: {total_b8_pass}, Fail: {total_b8_fail}, Known: {total_b8_known})")
logging.info(f"Serial: {total_serial_msgs} messages ({total_serial_errors} errors, {total_serial_fatal} fatal, {total_serial_recycles} recycles)") logging.info(f"Serial: {total_serial_msgs} messages ({total_serial_errors} errors, {total_serial_fatal} fatal, {total_serial_recycles} recycles)")
logging.info(f"Target: {target_detected_count} detected, {target_not_detected_count} not detected ({target_pass_rate:.1f}% pass rate)") logging.info(f"Target: {target_detected_count} detected, {target_not_detected_count} not detected ({target_pass_rate:.1f}% pass rate)")
if avg_pbit is not None: if avg_pbit is not None:
@ -441,6 +475,7 @@ def export_statistics_to_csv(custom_statistics, test_name, output_folder):
'Scenario', 'Scenario',
'B6 Total', 'B6 Pass', 'B6 Fail', 'B6 Known', 'B6 Total', 'B6 Pass', 'B6 Fail', 'B6 Known',
'B8 Checked', 'B8 Pass', 'B8 Fail', 'B8 Checked', 'B8 Pass', 'B8 Fail',
'B8 Known',
'Serial Msgs', 'Serial Errors', 'Serial Fatal', 'Serial Recycles', 'Serial Msgs', 'Serial Errors', 'Serial Fatal', 'Serial Recycles',
'Real Failures', 'Known Failures', 'Target Simulated', 'Real Failures', 'Known Failures', 'Target Simulated',
'Failures Detail (JSON)', 'Known Failures Detail (JSON)', 'Serial Details (JSON)' 'Failures Detail (JSON)', 'Known Failures Detail (JSON)', 'Serial Details (JSON)'
@ -489,6 +524,7 @@ def export_statistics_to_csv(custom_statistics, test_name, output_folder):
run.get('b8_checked', 0), run.get('b8_checked', 0),
run.get('b8_pass', 0), run.get('b8_pass', 0),
run.get('b8_fail', 0), run.get('b8_fail', 0),
run.get('b8_known_fail', 0),
run.get('serial_total', 0), run.get('serial_total', 0),
run.get('serial_errors', 0), run.get('serial_errors', 0),
run.get('serial_fatal', 0), run.get('serial_fatal', 0),
@ -530,6 +566,7 @@ def export_statistics_to_csv(custom_statistics, test_name, output_folder):
writer.writerow(['B8 Total Checks', agg['total_b8_checks']]) writer.writerow(['B8 Total Checks', agg['total_b8_checks']])
writer.writerow(['B8 Pass', agg['total_b8_pass']]) writer.writerow(['B8 Pass', agg['total_b8_pass']])
writer.writerow(['B8 Fail', agg['total_b8_fail']]) writer.writerow(['B8 Fail', agg['total_b8_fail']])
writer.writerow(['B8 Known Fail', agg.get('total_b8_known', 0)])
writer.writerow([]) # Blank line writer.writerow([]) # Blank line
# Serial Communication # Serial Communication
@ -1364,6 +1401,7 @@ def execute_pbit_test(interface, report, run_stats, bit_fields, bit_fields_categ
b8_fields = bit_fields[12:] b8_fields = bit_fields[12:]
b8_failures = [] b8_failures = []
b8_known_failures = []
for category, fields in list(bit_fields_categories.items())[1:]: for category, fields in list(bit_fields_categories.items())[1:]:
category_fail = 0 category_fail = 0
@ -1384,29 +1422,46 @@ def execute_pbit_test(interface, report, run_stats, bit_fields, bit_fields_categ
category_pass += 1 category_pass += 1
run_stats['b8_pass'] += 1 run_stats['b8_pass'] += 1
else: else:
category_fail += 1 # Classify known vs real B8 failures
run_stats['b8_fail'] += 1 if is_known_failure(f):
b8_failures.append((category, f, err)) run_stats['b8_known_fail'] = run_stats.get('b8_known_fail', 0) + 1
test_passed = False b8_known_failures.append((category, f, err))
run_stats['known_failures'].append((f, err))
else:
category_fail += 1
run_stats['b8_fail'] += 1
b8_failures.append((category, f, err))
run_stats['failures'].append((f, err))
test_passed = False
if category_fail > 0: if category_fail > 0:
logging.warning(f"{category}: {category_fail}/{len(fields)} failures") logging.warning(f"{category}: {category_fail}/{len(fields)} failures")
logging.info(f"B8 Diagnostics: {run_stats['b8_checked']} checked, " logging.info(f"B8 Diagnostics: {run_stats['b8_checked']} checked, "
f"{run_stats['b8_pass']} pass, {run_stats['b8_fail']} fail") f"{run_stats['b8_pass']} pass, {run_stats['b8_fail']} fail, {run_stats.get('b8_known_fail',0)} known")
if b8_failures: if b8_failures or b8_known_failures:
for cat, field, err in b8_failures: # Log real failures by category
run_stats['failures'].append((field, err)) if b8_failures:
fail_by_cat = {}
fail_by_cat = {} for cat, field, err in b8_failures:
for cat, field, err in b8_failures: if cat not in fail_by_cat:
if cat not in fail_by_cat: fail_by_cat[cat] = []
fail_by_cat[cat] = [] fail_by_cat[cat].append(field.split('_')[-1])
fail_by_cat[cat].append(field.split('_')[-1])
for cat, fails in fail_by_cat.items():
for cat, fails in fail_by_cat.items(): logging.warning(f" {cat}: {len(fails)} failures - {', '.join(fails[:3])}{'...' if len(fails) > 3 else ''}")
logging.warning(f" {cat}: {len(fails)} failures - {', '.join(fails[:3])}{'...' if len(fails) > 3 else ''}")
# Log known (ignored) failures summary
if b8_known_failures:
known_by_cat = {}
for cat, field, err in b8_known_failures:
if cat not in known_by_cat:
known_by_cat[cat] = []
known_by_cat[cat].append(field.split('_')[-1])
for cat, fails in known_by_cat.items():
logging.warning(f" {cat}: {len(fails)} KNOWN failures (ignored) - {', '.join(fails[:3])}{'...' if len(fails) > 3 else ''}")
else: else:
logging.info(f"All B6 LRU Status PASS (no B8 drill-down needed)") logging.info(f"All B6 LRU Status PASS (no B8 drill-down needed)")
@ -1418,6 +1473,12 @@ def execute_pbit_test(interface, report, run_stats, bit_fields, bit_fields_categ
remaining_time = 0 remaining_time = 0
logging.info(f'{remaining_time:.1f}s remaining ...') logging.info(f'{remaining_time:.1f}s remaining ...')
# Ensure run_stats records final success flag (reflect B6 and B8 real failures)
try:
run_stats['success'] = bool(test_passed)
except Exception:
pass
return test_passed return test_passed
@ -1929,6 +1990,7 @@ def test_proc():
'b8_checked': 0, 'b8_checked': 0,
'b8_pass': 0, 'b8_pass': 0,
'b8_fail': 0, 'b8_fail': 0,
'b8_known_fail': 0,
'failures': [], 'failures': [],
'known_failures': [], 'known_failures': [],
'success': True, 'success': True,

View File

@ -285,7 +285,7 @@ class TestMonitorGUI:
('Status', None, 80), ('Status', None, 80),
('PBIT Time', None, 80), ('PBIT Time', None, 80),
('B6 Status (P/F/K)', 'P = Pass\nF = Fail\nK = Known Failures', 120), ('B6 Status (P/F/K)', 'P = Pass\nF = Fail\nK = Known Failures', 120),
('B8 Checks (Pass/Fail)', 'Number of B8 checks\nPass/Fail format', 120), ('B8 Checks (P/F/K)', 'P = Pass\nF = Fail\nK = Known Failures', 120),
('Serial Messages (E/F/R)', 'E = Errors\nF = Fatal\nR = Recycles', 120), ('Serial Messages (E/F/R)', 'E = Errors\nF = Fatal\nR = Recycles', 120),
('Target', 'Target distance and cycle\nFormat: XXXXm @ cycle Y', 140), ('Target', 'Target distance and cycle\nFormat: XXXXm @ cycle Y', 140),
('Target Test', None, 80) ('Target Test', None, 80)
@ -707,13 +707,14 @@ class TestMonitorGUI:
b6_fail = data.get('b6_fail', 0) b6_fail = data.get('b6_fail', 0)
b6_known = data.get('b6_known_fail', 0) b6_known = data.get('b6_known_fail', 0)
new[3] = f"{b6_pass}/{b6_fail}/{b6_known}" new[3] = f"{b6_pass}/{b6_fail}/{b6_known}"
elif k in ('b8_pass', 'b8_fail', 'b8_checked'): elif k in ('b8_pass', 'b8_fail', 'b8_checked', 'b8_known_fail'):
# Column 4: B8 Checks (Pass/Fail) - format like PDF # Column 4: B8 Checks (Pass/Fail/Known) - format like PDF
b8_pass = data.get('b8_pass', 0) b8_pass = data.get('b8_pass', 0)
b8_fail = data.get('b8_fail', 0) b8_fail = data.get('b8_fail', 0)
b8_known = data.get('b8_known_fail', 0)
b8_checked = data.get('b8_checked', 0) b8_checked = data.get('b8_checked', 0)
if b8_checked > 0: if b8_checked > 0:
new[4] = f"{b8_pass}/{b8_fail}" new[4] = f"{b8_pass}/{b8_fail}/{b8_known}"
else: else:
new[4] = "-" new[4] = "-"
elif k in ('serial_errors', 'serial_fatal', 'serial_recycles'): elif k in ('serial_errors', 'serial_fatal', 'serial_recycles'):

View File

@ -12,6 +12,48 @@
2. **Orchestrazione scenari**: Configurazione failure simulati 2. **Orchestrazione scenari**: Configurazione failure simulati
3. **Monitoring multi-canale**: 1553, seriale, power control 3. **Monitoring multi-canale**: 1553, seriale, power control
4. **Aggregazione statistiche**: B6/B8/Serial/Target metrics 4. **Aggregazione statistiche**: B6/B8/Serial/Target metrics
## 3.2.2 Versione Script e gestione KNOWN_FAILURES
Lo script espone ora una variabile di versione `TEST_VERSION` che viene
inserita in tutti gli output (log, CSV, JSON e PDF). Questa stringa è utile
per tracciare quale versione del codice ha generato un report.
Esempio in codice:
```python
# Script version propagated to outputs
TEST_VERSION = "0.0.1"
```
KNOWN_FAILURES: il comportamento della lista `KNOWN_FAILURES` è stato
esteso rispetto alle versioni precedenti. Ora gli elementi configurati in
questa lista sono usati per classificare come "Known" anche le failure
emerse durante il drill-down di B8. In pratica:
- Se un campo B6 è in fallimento ma il relativo test B8 corrispondente è
segnato come "known" nella configurazione, quello specifico fallimento
NON rende la run fallita (viene registrato come `known_fail`).
- Se un campo B8 fallisce e non è classificato come known, viene marcato
come real failure e la run viene considerata FAILED.
Implementazione helper (semplificata):
```python
def is_known_failure(field_name: str) -> bool:
"""Restituisce True se `field_name` corrisponde a una voce di KNOWN_FAILURES.
Matching: case-insensitive substring match (config semplice, ma efficace).
"""
for k in KNOWN_FAILURES:
if k.lower() in field_name.lower():
return True
return False
```
Questa estensione permette di mantenere la stessa logica operativa (known
failures ignorate per il giudizio di PASS/FAIL) anche quando il dettaglio
diagnostico è ottenuto con il drill-down B8.
5. **Generazione report**: PDF, CSV, JSON output 5. **Generazione report**: PDF, CSV, JSON output
### 3.1.1 Caratteristiche Chiave ### 3.1.1 Caratteristiche Chiave

View File

@ -392,16 +392,27 @@ Run Result: FAIL (due to 2 real failures)
### 4.4.1 Quando Eseguire B8 Drill-Down ### 4.4.1 Quando Eseguire B8 Drill-Down
**Trigger Condition**: Il comportamento è stato aggiornato per tenere conto delle `KNOWN_FAILURES`.
Il drill-down B8 viene eseguito quando sono presenti real failures (non-known)
o quando è forzato (opzione `FORCE_B8_DRILL_DOWN`). Se tutti i fallimenti
osservati sono conosciuti, il drill-down può essere saltato per risparmiare tempo.
**Trigger Condition (semplificata)**:
```python ```python
if b6_fail > 0: # Almeno un real failure in B6 # Esegui drill-down se ci sono real failures oppure se forzato
if b6_fail > 0 and b6_real_fail > 0:
drill_down_b8()
elif FORCE_B8_DRILL_DOWN:
drill_down_b8() drill_down_b8()
else: else:
# Skip B8 (solo known failures o tutto OK) # Tutti i fallimenti (se presenti) sono classificati come KNOWN -> skip
logging.info("B8 drill-down skipped (no real failures)") logging.info("B8 drill-down skipped (no real failures)")
``` ```
**Rationale**: B8 contiene 185 campi diagnostici dettagliati. Eseguire sempre sarebbe dispendioso (5-10s). Si esegue solo quando necessario per analisi failure. Rationale: B8 contiene 185 campi diagnostici. Eseguire sempre sarebbe dispendioso
(5-10s). Il criterio adottato mantiene il drill-down per le situazioni che
richiedono indagine (real failures) e lo salta quando si tratta solo di
known failures attese.
### 4.4.2 Struttura Messaggio B8 ### 4.4.2 Struttura Messaggio B8
@ -446,8 +457,10 @@ def drill_down_b8() -> Dict:
b8_pass = 0 b8_pass = 0
b8_fail = 0 b8_fail = 0
b8_known_fail = 0
b8_checked = len(B8_FIELDS) b8_checked = len(B8_FIELDS)
b8_failures = [] b8_failures = []
b8_known_failures = []
logging.info(f"Starting B8 drill-down: {b8_checked} fields to check") logging.info(f"Starting B8 drill-down: {b8_checked} fields to check")
start_time = time.time() start_time = time.time()
@ -460,10 +473,17 @@ def drill_down_b8() -> Dict:
if ret: if ret:
b8_pass += 1 b8_pass += 1
else: else:
b8_fail += 1 # Leggi valore per logging
value = getValue(theGrifo1553, "B8_MsgBitReport", field) value = getValue(theGrifo1553, "B8_MsgBitReport", field)
b8_failures.append((field, value)) # Classifica known vs real failure usando is_known_failure()
logging.warning(f"B8 FAIL: {field} = {value}") if is_known_failure(field):
b8_known_fail += 1
b8_known_failures.append((field, value))
logging.info(f"Known failure (B8 ignored): {field} = {value}")
else:
b8_fail += 1
b8_failures.append((field, value))
logging.warning(f"B8 REAL FAIL: {field} = {value}")
except Exception as e: except Exception as e:
logging.error(f"B8 check failed for {field}: {e}") logging.error(f"B8 check failed for {field}: {e}")
@ -471,13 +491,15 @@ def drill_down_b8() -> Dict:
elapsed = time.time() - start_time elapsed = time.time() - start_time
logging.info(f"B8 drill-down completed in {elapsed:.2f}s") logging.info(f"B8 drill-down completed in {elapsed:.2f}s")
logging.info(f"B8 Results: {b8_pass} PASS, {b8_fail} FAIL") logging.info(f"B8 Results: {b8_pass} PASS, {b8_fail} FAIL, {b8_known_fail} KNOWN")
return { return {
'b8_pass': b8_pass, 'b8_pass': b8_pass,
'b8_fail': b8_fail, 'b8_fail': b8_fail,
'b8_known_fail': b8_known_fail,
'b8_checked': b8_checked, 'b8_checked': b8_checked,
'b8_failures': b8_failures, 'b8_failures': b8_failures,
'b8_known_failures': b8_known_failures,
'b8_time': elapsed 'b8_time': elapsed
} }
``` ```

View File

@ -47,16 +47,16 @@ L'interfaccia grafica fornisce **monitoraggio real-time** dell'esecuzione test,
│ Runs Overview │ │ Runs Overview │
│ ┌──────────────────────────────────────────────────────────┐ │ │ ┌──────────────────────────────────────────────────────────┐ │
│ │ Run Status PBIT B6 Status B8 Checks Serial Targ..│ │ │ │ Run Status PBIT B6 Status B8 Checks Serial Targ..│ │
│ │ Time (P/F/K) (Pass/Fail) Msgs et │ │ │ │ Time (P/F/K) (Pass/Fail/Known) Msgs et │ │
│ │ (E/F/R) │ │ │ │ (E/F/R) │ │
│ ├──────────────────────────────────────────────────────────┤ │ │ ├──────────────────────────────────────────────────────────┤ │
│ │ 1/10 PASS 2.2s 11/0/1 173/0 0/0/0 2000m │ │ │ │ 1/10 PASS 2.2s 11/0/1 173/0/0 0/0/0 2000m │ │
│ │ 2/10 PASS 0.7s 11/0/1 173/0 0/0/0 - │ │ │ │ 2/10 PASS 0.7s 11/0/1 173/0/0 0/0/0 - │ │
│ │ 3/10 FAIL 0.1s 9/2/1 - 0/3/3 - │ │ │ │ 3/10 FAIL 0.1s 9/2/1 - 0/3/3 - │ │
│ │ 4/10 FAIL 0.1s 9/2/1 173/0 0/3/0 - │ │ │ │ 4/10 FAIL 0.1s 9/2/1 173/0/0 0/3/0 - │ │
│ │ 5/10 PASS 2.0s 9/2/1 173/0 0/0/0 - │ │ │ │ 5/10 PASS 2.0s 9/2/1 173/0/0 0/0/0 - │ │
│ │ 6/10 PASS 0.3s 11/0/1 173/0 0/2/2 - │ │ │ │ 6/10 PASS 0.3s 11/0/1 173/0/0 0/2/2 - │ │
│ │ 7/10 PASS 0.0s 11/0/1 173/0 0/2/2 2000m │ │ │ │ 7/10 PASS 0.0s 11/0/1 173/0/0 0/2/2 2000m │ │
│ │ 8/10 PASS .... ...... ...... ...... .... │ │◄ Running │ │ 8/10 PASS .... ...... ...... ...... .... │ │◄ Running
│ │ 9/10 │ │ │ │ 9/10 │ │
│ │10/10 │ │ │ │10/10 │ │
@ -392,7 +392,7 @@ def _create_runs_table(self, parent, row):
('Status', None, 80), ('Status', None, 80),
('PBIT Time', None, 80), ('PBIT Time', None, 80),
('B6 Status (P/F/K)', 'P = Pass\nF = Fail\nK = Known Failures', 120), ('B6 Status (P/F/K)', 'P = Pass\nF = Fail\nK = Known Failures', 120),
('B8 Checks (Pass/Fail)', 'Number of B8 checks\nPass/Fail format', 120), ('B8 Checks (Pass/Fail/Known)', 'Number of B8 checks\nFormat: Pass/Fail/Known', 140),
('Serial Messages (E/F/R)', 'E = Errors\nF = Fatal\nR = Recycles', 120), ('Serial Messages (E/F/R)', 'E = Errors\nF = Fatal\nR = Recycles', 120),
('Target', 'Target distance and cycle\nFormat: XXXXm @ cycle Y', 140), ('Target', 'Target distance and cycle\nFormat: XXXXm @ cycle Y', 140),
('Target Test', None, 80) ('Target Test', None, 80)

View File

@ -217,11 +217,13 @@ def generate_pdf_report(output_folder: str, json_data: Dict) -> str:
story.append(Spacer(1, 0.2*inch)) story.append(Spacer(1, 0.2*inch))
# Metadata # Metadata
script_version = json_data.get('test_metadata', {}).get('script_version', 'unknown')
meta_data = [ meta_data = [
['Date:', timestamp.split('_')[0]], ['Date:', timestamp.split('_')[0]],
['Time:', timestamp.split('_')[1].replace('-', ':') if '_' in timestamp else ''], ['Time:', timestamp.split('_')[1].replace('-', ':') if '_' in timestamp else ''],
['Total Runs:', str(total_runs)], ['Script Version:', script_version],
['Duration:', _format_duration(duration)] ['Total Runs:', str(total_runs)],
['Duration:', _format_duration(duration)]
] ]
meta_table = Table(meta_data, colWidths=[1.5*inch, 3*inch]) meta_table = Table(meta_data, colWidths=[1.5*inch, 3*inch])
meta_table.setStyle(TableStyle([ meta_table.setStyle(TableStyle([
@ -272,8 +274,8 @@ def generate_pdf_report(output_folder: str, json_data: Dict) -> str:
# Build table data # Build table data
table_data = [ table_data = [
['Run', 'Status', 'PBIT\nTime', 'B6 Status\n(P/F/K)', ['Run', 'Status', 'PBIT\nTime', 'B6 Status\n(P/F/K)',
'B8 Checks\n(Pass/Fail)', 'Serial\n(E/F/R)', 'Target'] 'B8 Checks\n(Pass/Fail/Known)', 'Serial\n(E/F/R)', 'Target']
] ]
runs = json_data.get('runs', []) runs = json_data.get('runs', [])
@ -291,15 +293,16 @@ def generate_pdf_report(output_folder: str, json_data: Dict) -> str:
serial_r = r.get('serial_recycles', 0) serial_r = r.get('serial_recycles', 0)
target = r.get('target_info', '-') target = r.get('target_info', '-')
table_data.append([ # Per-run B8 now includes known failures count
f"{run_id}/{total_runs}", table_data.append([
status, f"{run_id}/{total_runs}",
f"{pbit_time:.1f}s" if pbit_time else '-', status,
f"{b6_pass}/{b6_fail}/{b6_known}", f"{pbit_time:.1f}s" if pbit_time else '-',
f"{b8_pass}/{b8_fail}" if b8_pass or b8_fail else '-', f"{b6_pass}/{b6_fail}/{b6_known}",
f"{serial_e}/{serial_f}/{serial_r}", f"{b8_pass}/{b8_fail}/{r.get('b8_known_fail', 0)}" if (b8_pass or b8_fail or r.get('b8_known_fail', 0)) else '-',
target f"{serial_e}/{serial_f}/{serial_r}",
]) target
])
runs_table = Table(table_data, colWidths=[ runs_table = Table(table_data, colWidths=[
0.6*inch, 0.7*inch, 0.7*inch, 0.9*inch, 0.6*inch, 0.7*inch, 0.7*inch, 0.9*inch,