Install
openclaw skills install @yujiecharles/ecg-ai-diagnosisAnalyze ECG signals via heartvoice (心之声) API — single-lead and 12-lead. Automatically selects endpoint based on user intent and responds in the user's language. Use when user mentions ECG, 心电图, 心电分析, 单导联, 十二导联, 1-lead, 12-lead, heart rhythm, arrhythmia, QRS, QT interval, signal quality, or asks to analyze an ECG JSON file.
openclaw skills install @yujiecharles/ecg-ai-diagnosisAnalyze ECG data via the heartvoice (心之声) cloud API. The agent is responsible for detecting user intent (1-lead vs 12-lead) and language (Chinese vs English), then passing the appropriate explicit flags to the CLI script. The script itself requires explicit --mode and --lang parameters — all "automatic" behavior lives in the agent layer, not the script.
# Required — get your key at https://www.heartvoice.com.cn/aiCloud
export HEARTVOICE_API_KEY="your_api_key"
# Install dependencies
pip install requests
Never hardcode API keys. Always use environment variables or .env files.
The agent (not the CLI script) is responsible for two kinds of detection:
Scan the user message for keywords and map to the explicit CLI mode argument:
| User Intent | CN Keywords | EN Keywords | CLI Mode |
|---|---|---|---|
| Single-lead signal | 单导联、单导、1导联 | 1-lead, single lead, one lead | 1-lead |
| 12-lead signal | 十二导联、12导联、多导联 | 12-lead, twelve lead | 12-lead |
If the user does not specify a type, ask before running the script:
--lang flagDetect the language the user writes in and pass it as the --lang CLI flag:
| User Language | --lang Value |
|---|---|
| Chinese (中文) | zh (default) |
| English | en |
Then respond entirely in that language throughout the session.
python3 scripts/call_api.py <mode> --json_path <path> --lang <lang>.# 1-lead signal analysis (Chinese output)
python3 scripts/call_api.py 1-lead --json_path <path/to/file.json>
# 1-lead signal analysis (English output)
python3 scripts/call_api.py 1-lead --json_path <path/to/file.json> --lang en
# 12-lead signal analysis (Chinese output)
python3 scripts/call_api.py 12-lead --json_path <path/to/file.json>
# 12-lead signal analysis (English output)
python3 scripts/call_api.py 12-lead --json_path <path/to/file.json> --lang en
Set --lang zh (default) or --lang en based on the language the user writes in.
The data arrays contain raw sample values from your recording device (integers or floats, depending on the device). The API internally converts them to millivolts (mV) using:
voltage_mV = (sampleValue - adcZero) / adcGain
adcZero — the sample value that corresponds to 0 mV (baseline offset)adcGain — the number of sample units per 1 mVExample 1 (integer samples): adcGain = 1000, adcZero = 0, sample = 512 → voltage = (512 - 0) / 1000 = 0.512 mV.
Example 2 (float samples, already in mV): if your device outputs values already in millivolts (e.g. 0.512), set adcGain = 1.0 and adcZero = 0.0 so the formula becomes a no-op: voltage = (0.512 - 0) / 1 = 0.512 mV.
These two parameters must match your recording device's output configuration; incorrect values will produce wrong voltage readings and unreliable diagnoses.
{
"ecgData": [0.512, 0.515, 0.520, 0.518, 0.525, 0.530, 0.528, 0.535],
"ecgSampleRate": 500,
"adcGain": 1.0,
"adcZero": 0.0
}
In this example, values are already in mV, so adcGain = 1.0 and adcZero = 0.0 (identity conversion). If your device outputs raw integer ADC values instead, adjust adcGain and adcZero accordingly.
| Field | Type | Required | Description |
|---|---|---|---|
ecgData | number[] | Yes | Sample value array — integers or floats, converted to mV via the formula above |
ecgSampleRate | number | Yes | Sampling rate in Hz (e.g. 500) |
adcGain | number | Yes | Sample units per 1 mV |
adcZero | number | Yes | Sample value corresponding to 0 mV baseline |
{
"dataI": [...], "dataII": [...], "dataIII": [...],
"dataAVR": [...], "dataAVL": [...], "dataAVF": [...],
"dataV1": [...], "dataV2": [...], "dataV3": [...],
"dataV4": [...], "dataV5": [...], "dataV6": [...],
"ecgSampleRate": 500,
"adcGain": 1000.0,
"adcZero": 0.0
}
In this example, values are raw integer ADC samples with adcGain = 1000 (1000 units = 1 mV).
| Field | Type | Required | Description |
|---|---|---|---|
dataI … dataV6 | number[] | Yes | 12 lead sample arrays (I, II, III, aVR, aVL, aVF, V1–V6) — integers or floats, converted to mV via the formula above |
ecgSampleRate | number | Yes | Sampling rate in Hz |
adcGain | number | Yes | Sample units per 1 mV — shared across all 12 leads |
adcZero | number | Yes | Sample value corresponding to 0 mV baseline |
The fields below are the script's output (printed to stdout as JSON). The script normalizes the API's camelCase field names (e.g. avgHr, avgQrs, isAbnormal) to snake_case (e.g. avg_hr, avg_qrs, is_abnormal) for consistency. When reading the script output, always use the snake_case names listed here.
| Field | Type | Description |
|---|---|---|
status | string | "success" or "error" |
summary | string | Natural-language summary in requested language |
diagnosis | string[] | Label list, e.g. ["SN"] |
possible_diagnosis | string[] | Possible labels list |
is_abnormal | boolean | Whether abnormal rhythm detected |
is_reverse | boolean | Whether lead reversal detected |
sq_grade | string | Signal quality score, e.g. "0.95" |
avg_hr | number | Average heart rate (bpm) |
avg_qrs | number | QRS duration (ms) |
pr_interval | number | PR interval (ms) |
avg_qt | number | QT interval (ms) |
avg_p | number | P wave duration (ms) |
avg_qtc | number | Corrected QT interval (ms) |
pac_count | number | Atrial premature beat count |
pvc_count | number | Ventricular premature beat count |
| Field | Type | Description |
|---|---|---|
status | string | "success" or "error" |
analysis_state | boolean | Whether analysis succeeded |
summary | string | Natural-language summary in requested language |
diagnoses | object[] | Array of {"label", "result", "description"} |
diagnosis_results | string[] | Flat list of result strings |
risk_level | number | Risk level — 0 = normal |
HR | number | Heart rate (bpm) |
atrial_rate | number | Atrial rate (bpm) |
ventricular_rate | number | Ventricular rate (bpm) |
P / PR / QRS / QT / QTc / T | number | Intervals (ms) |
QRS_axis / P_axis / T_axis | number | Electrical axes (degrees) |
SV1 / SV2 / SV5 / RV1 / RV2 / RV5 / RV6 | number | Wave amplitudes (mV) |
用户:请帮我分析这个单导联信号,数据放在 data/ecg_data.json 里
Agent runs:
python3 scripts/call_api.py 1-lead --json_path data/ecg_data.json --lang zh
Output:
{
"status": "success",
"summary": "检测到:窦性心律。平均心率 72 次/分钟,QRS 宽度 86 毫秒,PR 间期 156 毫秒,QT 间期 380 毫秒,QTc 412 毫秒,P 波宽度 102 毫秒。信号质量 0.95。房性早搏 0 次,室性早搏 0 次。",
"diagnosis": ["SN"],
"possible_diagnosis": [],
"is_abnormal": false,
"avg_hr": 72,
"sq_grade": "0.95"
}
Agent responds:
分析完成。该心电信号显示窦性心律,平均心率 72 次/分钟,各项参数均在正常范围内。信号质量评分 0.95(优秀),未检测到房性或室性早搏。
User: Analyze my 12-lead ECG data from records/12lead.json
Agent runs:
python3 scripts/call_api.py 12-lead --json_path records/12lead.json --lang en
Output:
{
"status": "success",
"summary": "Detected: Sinus Rhythm. Heart rate 75 bpm, QRS duration 88 ms, PR interval 160 ms, QT interval 376 ms, QTc 420 ms, P wave 100 ms, T wave 180 ms. Risk assessment: normal.",
"diagnoses": [{"label": "SN", "result": "Sinus Rhythm", "description": "Normal sinus rhythm"}],
"risk_level": 0,
"HR": 75
}
Agent responds:
Analysis complete. The 12-lead ECG shows Sinus Rhythm with a heart rate of 75 bpm. All intervals are within normal range. Risk level: normal (0).
User: Can you analyze this ECG for me? The file is at data/signal.json
Agent asks:
Would you like to analyze a single-lead (1-lead) or 12-lead signal?
| Situation | Action |
|---|---|
| File not found | Report the exact path; ask user for the correct path |
| Missing JSON fields | List the missing required fields by name |
HEARTVOICE_API_KEY not set | Tell the user to visit https://www.heartvoice.com.cn/aiCloud to obtain an API Key and set it as the HEARTVOICE_API_KEY environment variable |
| HTTP error from API | Show status code and error message from the response |
API business error (errorCode != 0) | Show the error message returned by the API |
| File too large (> 5 MB) | Tell the user the file exceeds the 5 MB limit |
| Ambiguous intent | Ask whether the data is 1-lead or 12-lead |
Data flow: This skill reads a user-specified local JSON file and sends its contents (ECG signal data, sample rate, ADC parameters) over HTTPS to the heartvoice cloud API (api.heartvoice.com.cn) for analysis. The user should be aware that their ECG data leaves the local machine. No other files or environment variables beyond HEARTVOICE_API_KEY are accessed.
.env to .gitignore if using dotenv filessq_grade) below 0.6 usually indicates noisy data — suggest the user re-record or check electrode contactadcGain and adcZero values must match your recording device's ADC configuration — see the ADC Conversion Formula section for how they translate raw samples to millivoltsecgSampleRate values: 250, 500, 1000 Hz — check your device specsdata/ to verify your setup before analyzing real signalsYou can also import the script functions directly in Python:
import os
from scripts.call_api import load_json, build_1lead_payload, call_api, format_1lead_result
api_key = os.environ["HEARTVOICE_API_KEY"]
data = load_json("data/example_1lead.json")
payload = build_1lead_payload(data)
raw_result = call_api(
"https://api.heartvoice.com.cn/api/v1/basic/ecg/1-lead/analyze",
payload, api_key
)
output = format_1lead_result(raw_result)
print(output["summary"])
print(f"Heart rate: {output['avg_hr']} bpm")