From 017c3b19f0125a4bf7be1c951213e6c06a432060 Mon Sep 17 00:00:00 2001 From: david rice Date: Wed, 8 Apr 2026 15:42:51 +0100 Subject: [PATCH] updates --- __pycache__/analyze_captures.cpython-312.pyc | Bin 11444 -> 13447 bytes analyze_captures.py | 121 +++++++++++++------ mipi_test.py | 10 +- 3 files changed, 95 insertions(+), 36 deletions(-) diff --git a/__pycache__/analyze_captures.cpython-312.pyc b/__pycache__/analyze_captures.cpython-312.pyc index d915d7f491bba7bf905fbe3a139eb70cb07e13ad..ce869eed21359a026c1793505de1163befb7caf6 100644 GIT binary patch delta 5327 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 D67HQ- delta 3539 zcmai0eN0r@6~FJz8|KY0pL`7<;1fg`V1PkDL0m=g14VIRwKD5Ej&mP4FkjyHScDD^ zn{3lHYa-@tx-`awr2Ru9e_+@q_CL4TY_#3fp^L?Rb(40Rw6#f7TsN_0|7h=d!w9HN z@5}q$^Ugi@-1F`|_jm7)gP-(Se`B$j5G|&j-v@`wu3GaWyBUN&Mi_Y!W-uFKyo^?} zUY6FnkS?tA>KJI_LR?ty)oU^v%J61rb_TD3*7^`1HhPU=lh+i^^k#<5UUS&uwJ?Z9 zqF%J#qGLcAEK%NbWIHp)G7<9HU)Po}Xl)M6d$X|7n~hE093#R`oGIpFvzUi1qEpNj z_5ExC^@puuKDLPkR8uILL6g$=o3Q|n4P<^guX%ln%Ih^%A=PkndIGcL1VU}ug zs9iBGz`3-gMBG?}^M)!BB0LSQs zMF(o-3-Xw7PL^Y>b#1P_4W02I4H$J{?WO==sNTn8Pp%qEA_?- zJDmhX%m5Yut1@7W>no^OJ@+@GiSr0lbg_hngH-Np6pu{@F%k{O zWKvC4T}qj?t(Xh>B{{G%;Z%!|mA*RqKC|lQJ})ZNDNieNls{T|cF+yW(Ug~7o^ejD zgAP#S`czFP*@^RK3T}Z1TAI+fHEtc;3Ox^JVaGLuv*H}Z!Z?d9+Ej1Qu;?0xi>a>U zCEZG&>zV0M8Kn7}&&amDtfONjHpb1=yKr5%YL8iXZozEO!XR0nl4z`(R^z^guCuFs zj7V-uTMag+rPW&Ao|bWGJTu7PvYE~B&k0bk8=`#G=_p%y$$-_jHgeQZR`NECmVn{j z5qiW}l*ocV784^_#DW|ZR8AHz$WLGiZn7#jC`BWxK@Nt+=&(Fis&!u1R}v#K^jp~# zwPV5>n~v005vUOs`u)L>h+73$2{Cx7dzJ5Kf2-Zs(b+>v;4?#_v4~jZhQv`x)rG_e zf!#wk0KkS&&8hkF9qI1a*QFZ$5&0a6#)1LW*q#*~dyjRs_xX`ELk)P^F|@Lsqsr= z)5M;~M$6^U)ZQBv3&w^eq|;Z-`XBMeD~3yk$#-xIiB2iB6;Fuvd@=1c_w-0Y|rw`%Z+^_%uUIZwJ{r1_H=CcV zY`xq5kl+89w^5TpeX#W-Yx*sXT>@))gQ&DD6fuEw*Tm?&#@rz0JNM1HBrJ!u7Qj9V#dHqA7zWzE$gwyg#}iUO1` z1MIkd1iiwahhOmLTWQxCe`c+j-tFa|5;O*0r8S7#pd40)G0^Y8*1x8;Q?p>fFp#!xkG^6 z$qs-vfSmxlm8))N;tddY0ki|`0f6iw2LKLI7|SuehDZry2Z6*Ohr!6XV)F`N=DwaK zt5x1l24cz=?uuenC&@%LQF@U?AJt040Dg}GlqmLEX9AX0qbAj`aujH+McxFxMx$_T zSi_|TOh$c_h6pS<=>>olA}s*N0Zss0AK(SpdVlW;2b~@VCNv< z5I~56lucnB@1{Od?8}Ay!3g<)n*0=OXLWORb=O$uV@JWqMOTZIl0`@5yrVMdYWvc$ zb7GJ3YtN2^$@;$;wr}$A6qzoa&YAAHVVwTqY-!Tow7@s7W6u9aoSS}LGhLE2)h}=! zAkONSh%>Qs=?>O(SXlKMyY}%lyHNyU&|-R0o5Lm-lex%kzQ;AqC}5)0#^&7 z-J7)}=Hb%_ro7MBPp=3*pOz=|UXe<0Evd9D(o$7R%W>!wSN_pZoY0aRt_RvVsx2#c ziul9S(@jKbpb4(n%Y0iniibnu8{{YSlcBHloB};v80IN!Vz_7dh_U|xnZ8EFU!(j# zBP!eeio}OV{F5zv^5BOT7j2v8ZJVdxowvCs_^&iw)`~J&(K2sqnJ_=Hd6ZAKUTAz; u)xfLtLR2=6?YJ0aO(L 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)