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 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"
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 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
# ---------------------------------------------------------------------------
@@ -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__":

View File

@@ -364,11 +364,17 @@ def test_worker():
resume_event.wait() # block here while mgmt_worker is running
if not test_running:
break
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
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)