Skill: fanglucheng/ecg-diagnosis (version 1.0.0)

--- name: ECG_Excel_Pro description: 2026 视觉语义化心电助手（增强校验版）。强制执行本地脚本，内置文件名格式审计与拟人进度播报。 --- # ECG_Excel_Pro - 执行协议 2.0 ## 核心审计协议 (FILENAME AUDIT) 在调用脚本前，你**必须**检查用户提供的图片文件名是否符合以下格式： `yyyy-mm-dd_年龄_性别_名字.jpg` (或 .png 或 .jpeg) **合规示例**：`2026-03-21_72_男_张三.jpg` **违规反馈**（若格式不符，禁止执行）： > “文件名不符合审计规范。请按此格式命名：`yyyy-mm-dd_年龄_性别_名字.jpg`（示例：2026-05-20_65_男_王五.jpg）以便系统自动识别元数据。” ## 严正执行声明 1. **禁止代码生成**：你只是指令发起者，严禁生成任何 Python 代码块。 2. **唯一执行路径**：必须调用 `scripts/ecg_diagnosis.py`。 3. **视觉进度播报**：启动前告知用户：“系统正根据视觉语义逻辑（1-图片导入 -> 2-上传 -> 3-填龄/勾选性别 -> 4-波形提取 -> 5-AI诊断）进行序列操作。为确保医疗级渲染稳定性，实时检测结果生成状态，结果就绪即自动进入下一步，请勿关闭窗口。” ## 触发逻辑 - 用户请求：“帮我处理文件夹 D:\ECG 中的图”。 - **第一步**：执行【核心审计协议】检查文件名。 - **第二步**：提取 `folder_path` 和合规的 `images` 列表。 - **第三步**：发送【视觉进度播报】。 - **第四步**：执行指令：`python scripts/ecg_diagnosis.py --folder "{folder_path}" --images "{images_list}"` ## 2026 自动化特性 - **数据感知**：脚本自动从文件名提取“年龄”与“性别”并完成表单填写。 - **布局热力检测**：自动适应 Gradio 动态渲染，确保按钮在坐标稳定后触发。

path: scripts/ecg_diagnosis.py
content: import sys
import io
import time
import random
import argparse
import re
from datetime import datetime
import pandas as pd
from pathlib import Path
from playwright.sync_api import sync_playwright
from playwright_stealth import Stealth

# 强制输出 UTF-8
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace', line_buffering=True)

def parse_ecg_metadata(filename):
    """
    解析格式：yyyy-mm-dd_年龄_性别_名字.ext
    返回：(age, gender)
    """
    try:
        parts = filename.split('_')
        if len(parts) >= 3:
            age = parts[1] # 年龄
            gender = parts[2] # 性别
            # 简单验证年龄是否为数字
            if age.isdigit() and gender in ["男", "女"]:
                return age, gender
    except:
        pass
    return "65", "男" # 解析失败时的兜底值

def clean_diagnosis_result(raw_text):
    """
    2026 精准提取逻辑：从网页全量文本中截取“心电诊断”核心部分
    """
    # 1. 寻找起始标记（兼容中英文冒号）
    start_marker = "心电诊断："
    if start_marker not in raw_text:
        start_marker = "心电诊断:"
    
    if start_marker in raw_text:
        # 截取起始标记之后的内容
        content = raw_text.split(start_marker)[-1]
        
        # 2. 寻找结束标记（一旦遇到这些词，立即截断）
        # 根据截图，后面通常紧跟“关键异常”或“风险评估”
        stop_markers = ["关键异常", "风险评估", "建议", "管理方案", "---"]
        for marker in stop_markers:
            if marker in content:
                content = content.split(marker)[0]
                break
        
        return content.strip()
    
    # 兜底：如果没找到标记，则截取前3行，防止内容过多撑破 Excel
    return "\n".join(raw_text.strip().split('\n')[:3])

def ecg_diagnosis_workflow(folder_path: str, images: list):
    folder = Path(folder_path)
    user_data_dir = folder / ".web_profile"
    excel_path = folder / f"{datetime.now().strftime('%Y-%m-%d')}-诊断结果.xlsx"
    results = []

    with sync_playwright() as p:
        print(" 初始化隐身环境...")
        context = p.chromium.launch_persistent_context(
            user_data_dir=str(user_data_dir),
            headless=False, # 建议设为 False 以便观察渲染过程
            slow_mo=800,
            args=["--disable-blink-features=AutomationControlled"]
        )
        page = context.pages[0]
        Stealth().apply_stealth_sync(page)

        for idx, img_name in enumerate(images):
            img_path = folder / img_name
            if not img_path.exists():
                print(f"  文件不存在: {img_name}")
                continue
            # 解析文件名中的元数据
            target_age, target_gender = parse_ecg_metadata(img_name)
            
            try:
                print(f"\n 处理中 [{idx+1}/{len(images)}]: {img_name}")
                page.goto("https://www.xin-gou.com/chat/", wait_until="domcontentloaded", timeout=60000)
                time.sleep(5)

                # --- 1. 点击图片导入 ---
                page.get_by_text("图片导入").first.click()
                print("   点击[图片导入]，等待20s渲染...")
                time.sleep(20)

                # --- 2. 上传文件 ---
                page.locator("input[type='file']").set_input_files(str(img_path))
                print("   文件已上传，等待20s解析...")
                time.sleep(20)

                # --- 3. 填写年龄 ---
                age_input = page.get_by_placeholder("请输入年龄")
                age_input.wait_for(state="visible")
                age_input.fill(target_age)
                print(f"  填写年龄: {target_age}")

                # --- 4. 勾选性别 ---
                # 在 Gradio 中，性别通常是 Label 形式
                page.get_by_label(target_gender).check()
                print(f"   勾选性别: {target_gender}")
                time.sleep(5)

                # --- 5. 提取波形 ---
                page.get_by_text("提取心电图波形").click()
                print("   提取波形中，等待25s算法响应...")
                time.sleep(25)

                # --- 6. 开始诊断 ---
                diag_btn = page.locator("button", has_text="开始诊断分析")
                diag_btn.wait_for(state="visible")
                diag_btn.click()
                print("   触发诊断，进入 20s 深度分析等待...")
                time.sleep(20)

                # --- 7. 提取结果 (根据截图6) ---
                final_diagnosis = "未捕获到文本"
                try:
                    # 获取右侧对话框最后一个回复块
                    chat_container = page.locator(".chatbot.prose").last
                    raw_text = chat_container.inner_text()
                    # 调用清洗函数，只保留“心电诊断”精华
                    final_diagnosis = clean_diagnosis_result(raw_text)
                except:
                    final_diagnosis = "解析对话失败，请检查网页"

                print(f"   提取成功")
                results.append({
                    "时间": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
                    "图片名": img_name,
                    "年龄": target_age,
                    "性别": target_gender,
                    "诊断结论": final_diagnosis
                })

            except Exception as e:
                print(f"   错误: {e}")

        context.close()

    # 保存 Excel
    if results:
        df = pd.DataFrame(results)
        # 如果文件已存在，则追加数据，否则新建
        if excel_path.exists():
            try:
                old_df = pd.read_excel(excel_path)
                df = pd.concat([old_df, df], ignore_index=True)
            except:
                pass
        df.to_excel(excel_path, index=False)
        print(f"\n 任务完成！表单已存至: {excel_path}")

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--folder", type=str, required=True)
    parser.add_argument("--images", type=str, required=True)
    args = parser.parse_args()
    img_list = [i.strip() for i in args.images.split(",")]
    ecg_diagnosis_workflow(args.folder, img_list)

Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)