This commit is contained in:
david rice
2026-04-08 15:42:51 +01:00
parent 15dc295ae1
commit 017c3b19f0
3 changed files with 95 additions and 36 deletions

View File

@@ -11,18 +11,20 @@ Usage:
""" """
import argparse import argparse
import html
import sys import sys
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
import anthropic 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 from csv_preprocessor import analyze_file, analyze_lp_file, group_captures, ChannelMetrics, LPMetrics
DATA_DIR = Path(__file__).parent / "data" DATA_DIR = Path(__file__).parent / "data"
ANALYSIS_LOG = Path(__file__).parent / "analysis_log.txt" REPORTS_DIR = Path(__file__).parent / "reports"
DISPLAY_URL = "http://192.168.45.8:5000/display"
CLAUDE_MODEL = "claude-opus-4-6" CLAUDE_MODEL = "claude-opus-4-6"
SYSTEM_PROMPT = ( 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" "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" "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" "3. Flag anomalies — missing LP transitions, short LP-low, unexpected burst counts.\n"
"4. Summarise overall signal health in 23 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 23 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'<strong>\1</strong>', 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'<li>{l.lstrip("0123456789.-* ")}</li>' for l in lines if l.strip())
tag = 'ol' if lines[0].lstrip()[0].isdigit() else 'ul'
parts.append(f'<{tag}>{items}</{tag}>')
else:
parts.append('<p>' + '<br>'.join(lines) + '</p>')
return '\n'.join(parts)
body_html = text_to_html(analysis)
html_content = f"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>MIPI Analysis — {cap_range}</title>
<style>
body {{ font-family: Arial, sans-serif; max-width: 900px; margin: 40px auto; padding: 0 20px; color: #222; }}
h1 {{ color: #1a3a5c; border-bottom: 2px solid #1a3a5c; padding-bottom: 8px; }}
.meta {{ color: #555; font-size: 0.95em; margin-top: -8px; margin-bottom: 24px; }}
p {{ line-height: 1.6; }}
ol, ul {{ line-height: 1.8; padding-left: 24px; }}
li {{ margin: 4px 0; }}
.tokens {{ color: #888; font-size: 0.8em; margin-top: 32px; border-top: 1px solid #ddd; padding-top: 8px; }}
@media print {{ body {{ margin: 20px; }} }}
</style>
</head>
<body>
<h1>MIPI D-PHY Analysis Report</h1>
<p class="meta">
<strong>Generated:</strong> {date_str} &nbsp;|&nbsp;
<strong>Scope:</strong> {cap_range} &nbsp;|&nbsp;
<strong>Model:</strong> {CLAUDE_MODEL}
</p>
{body_html}
<p class="tokens">{html.escape(token_line)}</p>
</body>
</html>
"""
path.write_text(html_content, encoding="utf-8")
return path
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Main # Main
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -134,20 +209,9 @@ def run_analysis(last: int = 10) -> None:
print(f"({token_line})") print(f"({token_line})")
print(separator + "\n") print(separator + "\n")
# ── Append to log file ──────────────────────────────────────────────── # ── HTML report ───────────────────────────────────────────────────────
ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S") report_path = save_html_report(analysis, token_line, keys)
with open(ANALYSIS_LOG, "a", encoding="utf-8") as f: print(f"[ANALYSIS] Report saved to {report_path}")
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}")
def main() -> None: def main() -> None:
@@ -210,7 +274,6 @@ def main() -> None:
analysis = message.content[0].text analysis = message.content[0].text
token_line = f"Tokens: {message.usage.input_tokens} in / {message.usage.output_tokens} out" token_line = f"Tokens: {message.usage.input_tokens} in / {message.usage.output_tokens} out"
separator = "=" * 60 separator = "=" * 60
ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# Console # Console
print(f"\n{separator}\nCLAUDE ANALYSIS\n{separator}") print(f"\n{separator}\nCLAUDE ANALYSIS\n{separator}")
@@ -218,19 +281,9 @@ def main() -> None:
print(f"({token_line})") print(f"({token_line})")
print(separator) print(separator)
# Log file # HTML report
with open(ANALYSIS_LOG, "a", encoding="utf-8") as f: report_path = save_html_report(analysis, token_line, keys)
f.write(f"\n{separator}\n{ts}\n{separator}\n") print(f"\nReport saved to {report_path}")
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}")
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -364,11 +364,17 @@ def test_worker():
resume_event.wait() # block here while mgmt_worker is running resume_event.wait() # block here while mgmt_worker is running
if not test_running: if not test_running:
break break
try:
requests.put(URL, json={"state": "on"}, timeout=2) 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) time.sleep(DISPLAY_SETTLE_S)
dual_capture(count) dual_capture(count)
count += 1 count += 1
try:
requests.put(URL, json={"state": "off"}, timeout=2) 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) time.sleep(1.0)