diff --git a/__pycache__/analyze_captures.cpython-312.pyc b/__pycache__/analyze_captures.cpython-312.pyc index d915d7f..ce869ee 100644 Binary files a/__pycache__/analyze_captures.cpython-312.pyc and b/__pycache__/analyze_captures.cpython-312.pyc differ diff --git a/analyze_captures.py b/analyze_captures.py index 47c7dd1..12c253a 100644 --- a/analyze_captures.py +++ b/analyze_captures.py @@ -11,18 +11,20 @@ Usage: """ import argparse +import html import sys from datetime import datetime from pathlib import Path import anthropic -import requests +from dotenv import load_dotenv + +load_dotenv(Path(__file__).parent / ".env") from csv_preprocessor import analyze_file, analyze_lp_file, group_captures, ChannelMetrics, LPMetrics -DATA_DIR = Path(__file__).parent / "data" -ANALYSIS_LOG = Path(__file__).parent / "analysis_log.txt" -DISPLAY_URL = "http://192.168.45.8:5000/display" +DATA_DIR = Path(__file__).parent / "data" +REPORTS_DIR = Path(__file__).parent / "reports" CLAUDE_MODEL = "claude-opus-4-6" SYSTEM_PROMPT = ( @@ -86,10 +88,83 @@ def build_prompt(all_summaries: list[str]) -> str: "1. Identify any consistent spec concerns (HS voltage, LP-11 voltage, LP-low timing).\n" "2. Highlight any trends over captures (amplitude drift, jitter, LP-11 voltage, etc.).\n" "3. Flag anomalies — missing LP transitions, short LP-low, unexpected burst counts.\n" - "4. Summarise overall signal health in 2–3 sentences." + "4. For any ERROR or WARNING lines in the summaries, explain the most likely cause " + " (e.g. missing file, bad trigger, signal absent, probe issue) and what to check.\n" + "5. Provide specific, actionable recommendations to address all identified issues and anomalies.\n" + "6. Summarise overall signal health in 2–3 sentences." ) +def save_html_report(analysis: str, token_line: str, keys: list) -> Path: + """Write a timestamped HTML report to the reports/ directory.""" + REPORTS_DIR.mkdir(exist_ok=True) + now = datetime.now() + filename = now.strftime("%Y%m%d_%H%M%S_analysis.html") + path = REPORTS_DIR / filename + + cap_range = ( + f"Capture {keys[0][1]:04d}" + if len(keys) == 1 + else f"Captures {keys[0][1]:04d}–{keys[-1][1]:04d}" + ) + date_str = now.strftime("%Y-%m-%d %H:%M:%S") + + # Convert plain text analysis to basic HTML (preserve line breaks, bold **) + def text_to_html(text: str) -> str: + escaped = html.escape(text) + # **bold** + import re + escaped = re.sub(r'\*\*(.+?)\*\*', r'\1', escaped) + # Blank lines → paragraph breaks + paragraphs = re.split(r'\n{2,}', escaped) + parts = [] + for para in paragraphs: + lines = para.strip().splitlines() + if not lines: + continue + # Numbered or bullet list + if lines[0].lstrip().startswith(('1.', '2.', '3.', '-', '*')): + items = ''.join(f'
  • {l.lstrip("0123456789.-* ")}
  • ' for l in lines if l.strip()) + tag = 'ol' if lines[0].lstrip()[0].isdigit() else 'ul' + parts.append(f'<{tag}>{items}') + else: + parts.append('

    ' + '
    '.join(lines) + '

    ') + return '\n'.join(parts) + + body_html = text_to_html(analysis) + + html_content = f""" + + + +MIPI Analysis — {cap_range} + + + +

    MIPI D-PHY Analysis Report

    +

    + Generated: {date_str}  |  + Scope: {cap_range}  |  + Model: {CLAUDE_MODEL} +

    +{body_html} +

    {html.escape(token_line)}

    + + +""" + path.write_text(html_content, encoding="utf-8") + return path + + # --------------------------------------------------------------------------- # Main # --------------------------------------------------------------------------- @@ -122,7 +197,7 @@ def run_analysis(last: int = 10) -> None: system = SYSTEM_PROMPT, messages = [{"role": "user", "content": prompt}], ) - analysis = message.content[0].text + analysis = message.content[0].text token_line = f"Tokens: {message.usage.input_tokens} in / {message.usage.output_tokens} out" # ── Console ─────────────────────────────────────────────────────────── @@ -134,20 +209,9 @@ def run_analysis(last: int = 10) -> None: print(f"({token_line})") print(separator + "\n") - # ── Append to log file ──────────────────────────────────────────────── - ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - with open(ANALYSIS_LOG, "a", encoding="utf-8") as f: - f.write(f"\n{'='*60}\n{ts} — captures {keys[0][1]:04d}–{keys[-1][1]:04d}\n{'='*60}\n") - f.write(analysis) - f.write(f"\n({token_line})\n") - print(f"[ANALYSIS] Report appended to {ANALYSIS_LOG}") - - # ── Send to display ─────────────────────────────────────────────────── - try: - requests.post(DISPLAY_URL, json={"text": analysis}, timeout=5) - print("[ANALYSIS] Report sent to display.") - except Exception as e: - print(f"[ANALYSIS] Display send failed: {e}") + # ── HTML report ─────────────────────────────────────────────────────── + report_path = save_html_report(analysis, token_line, keys) + print(f"[ANALYSIS] Report saved to {report_path}") def main() -> None: @@ -210,7 +274,6 @@ def main() -> None: analysis = message.content[0].text token_line = f"Tokens: {message.usage.input_tokens} in / {message.usage.output_tokens} out" separator = "=" * 60 - ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S") # Console print(f"\n{separator}\nCLAUDE ANALYSIS\n{separator}") @@ -218,19 +281,9 @@ def main() -> None: print(f"({token_line})") print(separator) - # Log file - with open(ANALYSIS_LOG, "a", encoding="utf-8") as f: - f.write(f"\n{separator}\n{ts}\n{separator}\n") - f.write(analysis) - f.write(f"\n({token_line})\n") - print(f"\nReport appended to {ANALYSIS_LOG}") - - # Display - try: - requests.post(DISPLAY_URL, json={"text": analysis}, timeout=5) - print("Report sent to display.") - except Exception as e: - print(f"Display send failed: {e}") + # HTML report + report_path = save_html_report(analysis, token_line, keys) + print(f"\nReport saved to {report_path}") if __name__ == "__main__": diff --git a/mipi_test.py b/mipi_test.py index 4275ffb..0ac84e3 100644 --- a/mipi_test.py +++ b/mipi_test.py @@ -364,11 +364,17 @@ def test_worker(): resume_event.wait() # block here while mgmt_worker is running if not test_running: break - requests.put(URL, json={"state": "on"}, timeout=2) + try: + requests.put(URL, json={"state": "on"}, timeout=2) + except requests.exceptions.RequestException as e: + print(f" WARNING: display ON failed: {e}") time.sleep(DISPLAY_SETTLE_S) dual_capture(count) count += 1 - requests.put(URL, json={"state": "off"}, timeout=2) + try: + requests.put(URL, json={"state": "off"}, timeout=2) + except requests.exceptions.RequestException as e: + print(f" WARNING: display OFF failed: {e}") time.sleep(1.0)