Skill: aaiccee/med-record-struct (version 1.0.0)

--- name: med-followup-record-struct description: 将中文门诊复诊病历文本结构化为细粒度字段，输出 JSON（如现病史/既往史/诊断/处理意见等）。 metadata: { "openclaw": { "emoji": "🧩" } } --- # 门诊复诊病历结构化 概述 ---- 给定一份中文门诊 **复诊病历文本**（医生已书写），本技能抽取并规范化为细粒度字段 JSON，例如： - 现病史.病情概述 - 现病史.药物 - 现病史.其他治疗措施 - 现病史.病情转归 - 现病史.一般情况 - 既往史.疾病 - 既往史.其他信息 - 既往史.手术史 - 既往史.过敏史 - 既往史.输血史 - 婚育史 - 月经史 - 个人史 - 家族史 - 查体 - 辅助检查 - 诊断 - 处理意见.药物 - 处理意见.其他建议 数据安全、隐私与伦理声明 ------------------------ - **最小必要原则**：仅处理结构化抽取所必需的文本内容；不要求也不鼓励包含姓名、证件号、手机号、详细地址等身份信息。 - **严格脱敏**：在发送至任何模型/接口前，会对可识别个人身份的信息进行脱敏/去标识化处理（如姓名、证件号、手机号、详细地址、人脸/影像等）。仅传递脱敏后的必要信息用于本次 skill 调用。 - **不做本地持久化**：不将用户输入与中间结果写入本地持久化存储（包含磁盘文件、数据库、日志）。仅在内存中短暂处理；**本次调用结束即销毁**。 - **第三方 API 风险提示**：在功能需要时，可能会调用第三方模型/服务接口；此时仅会发送**脱敏后的必要信息**，并使用加密传输。除完成本次请求外，不用于任何其他用途（如训练、画像、营销）。 - **医疗边界**：本技能输出为文本抽取与结构化结果，不构成医疗诊断或治疗建议；如涉及临床判断请以执业医生意见为准。 输入格式 -------- 纯文本病历（UTF-8），可包含如下分段： 主诉：…… 现病史：…… 既往史：…… 婚育史：…… 月经史：…… 个人史：…… 家族史：…… 查体：…… 辅助检查：…… 诊断：…… 处理：…… 快速开始 -------- ```bash # 从 skills 目录运行 python3 scripts/struct_followup_record.py \ --input data/med-followup-record-struct/followup_record.txt ``` 参数说明 -------- * `--input PATH` - 输入复诊病历文本路径（UTF-8）。 * `--output PATH` - 输出结构化 JSON 路径（默认：`../runs/med-followup-record-struct/structured.json`）。 * `--timeout SECONDS` - 超时秒数；`0` 表示一直等待（默认：0）。 * `--diag-id STRING` - 对话/就诊 ID（默认：`skill-diag`）。 * `--department STRING` - 科室（可选）。 输出约定 -------- - 输出为 UTF-8 JSON，key 形如 `现病史.病情概述`、`现病史.药物`、`既往史.疾病`、`诊断`、`处理意见.药物` 等。 - 未提及字段在服务支持的情况下可能填充为“未提及”。 测试命令 -------- 从 `skills` 目录执行（网络自测）： ```bash python3 self_tests/med-followup-record-struct/self_test_followup_record_struct.py --run-network ```

path: _meta.json
content: ownerId: kn76wejkeqxfc03j0rfxp2jaj982m7aa
slug: med-followup-record-struct
version: 1.0.0
publishedAt: 1773287610243

path: scripts/struct_followup_record.py
content: #!/usr/bin/env python3
# -*- coding: utf-8 -*-

import os
import sys
import json
import argparse
from typing import Any, Dict

import urllib.error
import urllib.request


API_URL = "https://shangbao.yunzhisheng.cn/skills/record-struct/gen_abstract_by_his"


def _read_http_body(resp: Any) -> str:
    return resp.read().decode("utf-8", errors="replace")


def _post_json(url: str, payload: Dict[str, Any], *, timeout: int = 0) -> str:
    # 兼容部分服务端对非 ASCII JSON 处理不一致：这里使用 ASCII JSON（\uXXXX）编码
    data_bytes = json.dumps(payload, ensure_ascii=True).encode("utf-8")
    req = urllib.request.Request(
        url=url,
        data=data_bytes,
        method="POST",
        headers={
            "Content-Type": "application/json; charset=utf-8",
            "Accept": "application/json",
        },
    )
    try:
        if timeout and timeout > 0:
            ctx = urllib.request.urlopen(req, timeout=timeout)
        else:
            ctx = urllib.request.urlopen(req)
        with ctx as resp:
            return _read_http_body(resp)
    except urllib.error.HTTPError as e:
        detail = e.read().decode("utf-8", errors="replace")
        raise RuntimeError(f"HTTP {e.code}: {detail}") from e
    except urllib.error.URLError as e:
        raise RuntimeError(f"Network error: {e}") from e


def call_followup_struct_api(
    his_record: str,
    *,
    diag_id: str = "skill-diag",
    department: str = "",
    timeout: int = 0,
) -> Dict[str, Any]:
    """
    调用公司接口，对复诊病历进行结构化。
    :param his_record: 门诊复诊病历文本（his_record）
    :param timeout: 超时时间（秒）
    :return: 结构化结果（字典）
    """
    if not isinstance(his_record, str) or not his_record.strip():
        raise ValueError("his_record is required and must be non-empty.")

    payload: Dict[str, Any] = {
        "his_record": his_record,
        "diag_id": diag_id or "skill-diag",
    }
    if department:
        payload["department"] = department

    body = _post_json(API_URL, payload, timeout=timeout)

    try:
        data = json.loads(body)
    except json.JSONDecodeError:
        raise ValueError(f"Unexpected non-JSON response: {body[:500]}") from None

    # 假定接口返回字段 'structured' / 'result' / 'data' 中包含结构化结果
    structured = data.get("structured") or data.get("result") or data.get("data")
    if structured is None:
        raise ValueError(f"Unexpected API response: {data}")

    return structured


def struct_followup_record(
    input_path: str,
    output_path: str,
    timeout: int = 0,
    diag_id: str = "skill-diag",
    department: str = "",
) -> str:
    """
    将复诊病历结构化并保存为 JSON。
    :param input_path: 输入病历文件路径
    :param output_path: 输出 JSON 文件路径
    :param timeout: 超时时间（秒）
    :return: 输出文件路径
    """
    if not os.path.exists(input_path):
        base_name = os.path.basename(input_path)
        fallback = os.path.join(os.path.dirname(__file__), "..", "..", "data", "med-followup-record-struct", base_name)
        if os.path.exists(fallback):
            input_path = fallback
        else:
            raise FileNotFoundError(f"Input file not found: {input_path}")

    with open(input_path, "r", encoding="utf-8") as f:
        record_text = f.read()

    print(f"Structuring follow-up outpatient record: {input_path}")
    structured = call_followup_struct_api(record_text, diag_id=diag_id, department=department, timeout=timeout)

    out_dir = os.path.dirname(output_path) or "."
    os.makedirs(out_dir, exist_ok=True)

    with open(output_path, "w", encoding="utf-8") as f:
        json.dump(structured, f, ensure_ascii=False, indent=2)

    print(f"✓ Structured record saved to: {output_path}")
    return output_path


def main():
    parser = argparse.ArgumentParser(
        description="Structure outpatient follow-up medical record into fine-grained fields."
    )

    parser.add_argument(
        "--input",
        required=True,
        help="Path to outpatient follow-up record text file (UTF-8)."
    )

    parser.add_argument(
        "--output",
        default="",
        help="Output path for structured JSON (default: ../runs/med-followup-record-struct/structured.json)."
    )

    parser.add_argument(
        "--timeout",
        type=int,
        default=0,
        help="HTTP request timeout seconds. 0 means wait forever (default: 0)."
    )
    parser.add_argument("--diag-id", default="skill-diag", help="diag_id (default: skill-diag)")
    parser.add_argument("--department", default="", help="department (optional)")

    args = parser.parse_args()

    try:
        default_out = os.path.join("..", "runs", "med-followup-record-struct", "structured.json")
        out_path = args.output or default_out
        struct_followup_record(
            input_path=args.input,
            output_path=out_path,
            timeout=args.timeout,
            diag_id=args.diag_id,
            department=args.department,
        )
        print("\n✓ Follow-up record structured successfully!")
        return 0
    except FileNotFoundError as e:
        print(f"✗ Error: {e}", file=sys.stderr)
        return 1
    except Exception as e:
        print(f"✗ Unexpected Error: {e}", file=sys.stderr)
        return 1


if __name__ == "__main__":
    sys.exit(main())



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