Skill: 18072937735/smyx-infant-cry-cause-classification-analysis (version 1.0.0)

--- name: [REDACTED_SECRET] description: "Using the built-in microphone of a baby monitor or smart camera to capture infant cry audio, AI acoustic analysis extracts cry features such as frequency, pitch, rhythm, and duration, and classifies the possible causes behind the cry (hunger, sleepiness, pain/discomfort, boredom/need for comfort, fear, etc.), outputting the most likely cause and its confidence. The skill helps new parents quickly understand what their baby needs, reducing anxiety and improving parenting efficiency. Application scenarios: nurseries, neonatal monitoring rooms, daycare institutions, smart baby monitors. The system monitors crying in real time and, when a cry is detected, automatically analyzes it and pushes the result to the parents' mobile app (e.g., 'baby may be hungry, try feeding'). Skill features: new parents often feel anxious because they cannot distinguish the cause of crying. AI-assisted analysis provides objective reference, helps parents meet the baby's needs in time, reduces crying duration, and improves the parenting experience. A core selling point of smart maternal-and-child products. | 通过婴儿监护器或智能摄像头的内置麦克风采集婴儿哭声音频，利用AI声学分析技术提取哭声的频率、音调、节奏、持续时间等特征，分类识别婴儿哭声背后的可能原因（饥饿、困倦、疼痛/不适、无聊/需要安抚、恐惧等），输出最可能的原因类别及置信度。该技能有助于新手父母快速理解婴儿需求，减少焦虑，提高育儿效率。应用场景：婴儿房、新生儿监护室、托育机构、智能婴儿监护器。系统实时监测哭声，当检测到哭声时自动分析并在父母手机APP上推送结果（如'宝宝可能是饿了，建议喂奶'）。技能特点：新手父母常因无法分辨婴儿哭声原因而焦虑。通过AI辅助分析，可提供客观参考，帮助父母及时满足婴儿需求，减少哭闹时长，提升育儿幸福感。该技能是智能母婴产品的核心卖点之一。" version: "1.0.0" --- # Infant Cry Cause Classification | 婴幼儿哭声原因分类 Using the built-in microphone of a baby monitor or smart camera to capture infant cry audio, AI acoustic analysis extracts cry features such as frequency, pitch, rhythm, and duration, and classifies the possible causes behind the cry (hunger, sleepiness, pain/discomfort, boredom/need for comfort, fear, etc.), outputting the most likely cause and its confidence. The skill helps new parents quickly understand what their baby needs, reducing anxiety and improving parenting efficiency. Application scenarios: nurseries, neonatal monitoring rooms, daycare institutions, smart baby monitors. The system monitors crying in real time and, when a cry is detected, automatically analyzes it and pushes the result to the parents' mobile app (e.g., 'baby may be hungry, try feeding'). Skill features: new parents often feel anxious because they cannot distinguish the cause of crying. AI-assisted analysis provides objective reference, helps parents meet the baby's needs in time, reduces crying duration, and improves the parenting experience. A core selling point of smart maternal-and-child products. 通过婴儿监护器或智能摄像头的内置麦克风采集婴儿哭声音频，利用AI声学分析技术提取哭声的频率、音调、节奏、持续时间等特征，分类识别婴儿哭声背后的可能原因（饥饿、困倦、疼痛/不适、无聊/需要安抚、恐惧等），输出最可能的原因类别及置信度。该技能有助于新手父母快速理解婴儿需求，减少焦虑，提高育儿效率。应用场景：婴儿房、新生儿监护室、托育机构、智能婴儿监护器。系统实时监测哭声，当检测到哭声时自动分析并在父母手机APP上推送结果（如'宝宝可能是饿了，建议喂奶'）。技能特点：新手父母常因无法分辨婴儿哭声原因而焦虑。通过AI辅助分析，可提供客观参考，帮助父母及时满足婴儿需求，减少哭闹时长，提升育儿幸福感。该技能是智能母婴产品的核心卖点之一。 ## 🎯 AI 角色 **假设你是一个专业的婴儿哭声分析 AI。你的任务是分析婴儿哭声音频片段，提取声学特征（基频、共振峰、能量包络、持续时间、间隔模式等），并分类输出最可能的原因。不要提供医疗诊断或临床建议，仅输出基于声学的分类结果与方向性安抚提示。** ## 任务目标 - 本 Skill 用于：基于婴儿哭声音频片段（或带音频的视频），多分类识别哭声背后的可能原因，给出主原因置信度与方向性安抚提示 - 能力包含：哭声检测（区分哭声与背景噪音/语音）、声学特征提取（F0 基频 / 共振峰 / 能量包络 / 节奏间隔 / MFCC）、哭声持续时间统计、原因多分类（hunger / sleepy / pain_discomfort / boredom_need_attention / fear / colic / unknown）、置信度输出、次要原因及概率、行动建议提示 - 触发条件: 1. **默认触发**：当用户提供婴儿哭声音频或带音频的视频 URL/文件需要分析时，默认触发本技能进行婴儿哭声原因分类 2. 当用户明确提及婴儿哭声、宝宝哭、饥饿、困倦、肠绞痛、哭闹原因、安抚、婴儿监护器、母婴 APP、新生儿哭声等关键词，并且上传了音频/音视频文件 3. 当用户提及以下关键词时，**自动触发历史报告查询功能** ：查看婴儿哭声历史报告、哭声原因分类报告清单、婴儿哭声分析清单、查询历史婴儿哭声记录、显示所有哭声分析报告、显示婴儿哭声诊断报告，查询哭声安抚提示清单 - 自动行为： 1. 如果用户上传了附件或者音频/音视频文件，则自动保存为本地文件 2. **⚠️ 强制数据获取规则（次高优先级）**：如果用户触发任何历史报告查询关键词（如"查看所有婴儿哭声报告"、" 显示所有哭声原因分类报告"、" 查看历史报告"等），**必须**： - 直接使用 `python -m scripts.smyx_infant_cry_cause_classification_analysis --list --open-id` 参数调用 API 查询云端的历史报告数据 - **严格禁止**：从本地 memory 目录读取历史会话信息、严格禁止手动汇总本地记录中的报告、严格禁止从长期记忆中提取报告 - **必须统一**从云端接口获取最新完整数据，然后以 Markdown 表格格式输出结果 ## 前置准备 - 依赖说明:scripts 脚本所需的依赖包及版本 ``` requests>=2.28.0 ``` ## 操作步骤 ### 🔒 open-id 获取流程控制（强制执行，防止遗漏） **在执行婴幼儿哭声原因分类前，必须按以下优先级顺序获取 open-id：** ``` 第 1 步：【最高优先级】检查技能所在目录的配置文件（优先） 路径：skills/smyx_common/scripts/config.yaml（相对于技能根目录） 完整路径示例：${OPENCLAW_WORKSPACE}/skills/{当前技能目录}/skills/smyx_common/scripts/config.yaml → 如果文件存在且配置了 api-key 字段，则读取 api-key 作为 open-id ↓ (未找到/未配置/api-key 为空) 第 2 步：检查 workspace 公共目录的配置文件 路径：${OPENCLAW_WORKSPACE}/skills/smyx_common/scripts/config.yaml → 如果文件存在且配置了 api-key 字段，则读取 api-key 作为 open-id ↓ (未找到/未配置) 第 3 步：检查用户是否在消息中明确提供了 open-id ↓ (未提供) 第 4 步：❗ 必须暂停执行，明确提示用户提供用户名或手机号作为 open-id ``` **⚠️ 关键约束：** - **禁止**自行假设,自行推导,自行生成 open-id 值（如 openclaw-control-ui、default、userC113、user123 等） - **禁止**跳过 open-id 验证直接调用 API - **必须**在获取到有效 open-id 后才能继续执行分析 - 如果用户拒绝提供 open-id，说明用途（用于保存和查询历史报告记录），并询问是否继续 --- - 标准流程: 1. **准备婴儿哭声音频输入** - 提供本地哭声音频/带音频的视频路径或网络 URL - 推荐采样率 ≥ 16 kHz、时长 3-30 秒；背景噪音尽量小 - 支持 wav / mp3 / m4a / aac / opus 等常见音频格式，也支持带音频的 mp4/mov - 可选附带：婴儿月龄、上一次喂奶/睡觉时间、近期身体状况 2. **获取 open-id（强制执行）** - 按上述流程控制获取 open-id - 如无法获取，必须提示用户提供用户名或手机号 3. **执行婴儿哭声原因分类** - 调用 `-m scripts.smyx_infant_cry_cause_classification_analysis` 处理输入（**必须在技能根目录下运行脚本**） - 参数说明: - `--input`: 本地婴儿哭声音频/音视频文件路径 - `--url`: 网络婴儿哭声音频/音视频 URL 地址（API 服务自动下载） - `--pet-type`: 类别标识，婴儿哭声分析场景默认 `other` - `--open-id`: 当前用户的 open-id（必填，按上述流程获取） - `--list`: 显示婴儿哭声历史分类报告列表清单（可以输入起始日期参数过滤数据范围） - `--api-key`: API 访问密钥（可选） - `--api-url`: API 服务地址（可选，使用默认值） - `--detail`: 输出详细程度（basic/standard/json，默认 json） - `--output`: 结果输出文件路径（可选） 4. **查看分析结果** - 接收结构化的婴儿哭声原因分类报告 - 包含：是否检测到哭声（cry_detected）、最可能的原因（dominant_cause：hunger / sleepy / pain_discomfort / boredom_need_attention / fear / colic / unknown）、置信度（confidence）、次要原因及概率（secondary_causes）、哭声持续秒数（cry_duration_sec）、关键声学特征摘要（audio_features_summary）、行动建议提示（如"宝宝可能是饿了，建议喂奶"） - **重要提示**：仅输出基于声学特征的分类结果与方向性安抚提示，不提供医疗诊断；若婴儿持续哭闹超过常态或伴随发热、呕吐等症状，请及时就医 ## 资源索引 - 必要脚本：见 [scripts/smyx_infant_cry_cause_classification_analysis.py](scripts/smyx_infant_cry_cause_classification_analysis.py)( 用途：调用 API 进行婴幼儿哭声原因分类，本地文件上传，网络 URL 由 API 服务自动下载) - 配置文件：见 [scripts/config.py](scripts/config.py)(用途：配置 API 地址、默认参数和场景码) - 领域参考：见 [references/api_doc.md](references/api_doc.md)(何时读取：需要了解 API 接口详细规范、原因类别枚举和错误码时) ## 注意事项 - 仅在需要时读取参考文档，保持上下文简洁 - 输入要求：支持 wav/mp3/m4a/aac/opus 音频或 mp4/avi/mov 视频，最大 10MB；建议背景安静 - API 密钥可选，如果通过参数传入则必须确保调用鉴权成功，否则忽略鉴权 - 分类结果仅作为育儿辅助参考，不替代专业儿科诊断；婴儿持续异常哭闹请就医 - 隐私合规：婴儿音频涉及未成年人隐私，使用前需取得监护人知情同意，并妥善保管/加密相关录音 - 禁止临时生成脚本，只能用技能本身的脚本 - 传入的网络地址参数，不需要下载本地，默认地址都是公网地址，api 服务会自动下载 - 当显示历史分析报告清单的时候，从接口返回 json 数据中提取字段 reportImageUrl 作为超链接地址，且自动转化为如下 Markdown 表格格式输出，包含" 报告名称"、"主因类别"、"分析时间"、"点击查看"四列，其中"报告名称"列使用`婴儿哭声原因分类报告-{记录id}`形式拼接, "点击查看" 列使用 `[🔗 查看报告](reportImageUrl)` 格式的超链接，用户点击即可直接跳转到对应的完整报告页面。 - 表格输出示例： | 报告名称 | 主因类别 | 分析时间 | 点击查看 | |----------|----------|----------|----------| | 婴儿哭声原因分类报告-20260312172200001 | 饥饿（hunger） | 2026-03-12 17:22:00 | [🔗 查看报告](https://example.com/report?id=xxx) | ## 使用示例 ```bash # 分析本地婴儿哭声音频（以下只是示例，禁止直接使用openclaw-control-ui 作为 open-id） python -m scripts.smyx_infant_cry_cause_classification_analysis --input /path/to/baby_cry.wav --open-id your-open-id # 分析网络婴儿哭声音频/音视频（以下只是示例，禁止直接使用openclaw-control-ui 作为 open-id） python -m scripts.smyx_infant_cry_cause_classification_analysis --url https://example.com/baby_cry.mp4 --open-id your-open-id # 显示历史婴儿哭声原因分类报告（自动触发关键词：查看婴儿哭声历史报告、哭声原因分类报告清单等） python -m scripts.smyx_infant_cry_cause_classification_analysis --list --open-id your-open-id # 输出精简报告 python -m scripts.smyx_infant_cry_cause_classification_analysis --input cry.wav --open-id your-open-id --detail basic # 保存结果到文件 python -m scripts.smyx_infant_cry_cause_classification_analysis --input cry.wav --open-id your-open-id --output result.json ```

path: references/api_doc.md
content: # API 接口文档

此处用于存放婴幼儿哭声原因分类 API 的接口文档，待后续补充。

## 接口规范

- 基础地址：由 smyx_common 配置统一管理
- 认证方式：API Key 鉴权
- 响应格式：JSON

## 主要接口

1. `/web/health-analysis/v2/start-health-analysis` - 启动婴儿哭声原因分类任务
2. `/web/health-analysis/v2/get-health-analysis-result` - 获取分析结果与最可能原因
3. `/web/health-analysis/page-health-analysis-result` - 分页查询历史哭声分析记录
4. `/health/order/api/getReportDetailExport?id={id}` - 导出完整报告

## 场景代码

- `SMYX_INFANT_CRY_CAUSE_CLASSIFICATION_ANALYSIS` - 婴幼儿哭声原因分类

## 输入约束

- 推荐采样率 ≥ 16 kHz、单声道 PCM/AAC，时长建议 3-30 秒
- 支持 wav / mp3 / m4a / aac / opus 等常见音频格式，也支持带音频的 mp4/mov
- 录音环境建议尽量减少背景噪音（电视、对话等）

## 提取的声学特征

- 基频（F0 / pitch）
- 共振峰（formant F1-F3）
- 能量包络（energy envelope）
- 哭声/停顿持续时间
- 节奏与间隔模式（rhythm / interval pattern）
- 谱质心 / MFCC 等

## 哭声原因类别

- `hunger` - 饥饿
- `sleepy` - 困倦
- `pain_discomfort` - 疼痛 / 不适（如尿布湿、肚子胀）
- `boredom_need_attention` - 无聊 / 需要安抚
- `fear` - 恐惧
- `colic` - 肠绞痛（特殊高频长哭）
- `unknown` - 不明（置信度偏低时）

## 输出字段（参考）

- `cry_detected` - 是否检测到婴儿哭声（与非哭声噪音区分）
- `dominant_cause` - 最可能的哭声原因类别
- `confidence` - 主原因置信度
- `secondary_causes` - 次要原因及概率列表
- `cry_duration_sec` - 哭声持续秒数
- `audio_features_summary` - 关键声学特征摘要
- `suggestion_hint` - 行动建议提示（如"宝宝可能是饿了，建议喂奶"）

> 仅输出基于声学特征的分类结果与方向性安抚提示，不提供医疗诊断或临床建议。若婴儿持续哭闹超过常态或伴随发热、呕吐等症状，请及时就医。


path: scripts/__init__.py
content: # Infant Cry Cause Classification Analysis scripts package


path: scripts/api_service.py
content: #!/usr/bin/env python3

import os
import sys

from .config import ApiEnum, ConstantEnum

from skills.smyx_common.scripts.api_service import ApiService as ApiServiceBase
from skills.smyx_common.scripts.util import RequestUtil


class ApiService(ApiServiceBase):

    def __init__(self):
        super().__init__()
        self.analysis_url = ApiEnum.ANALYSIS_URL

    def analysis_result(self, *args, **argss):
        return self.http_post(ApiEnum.ANALYSIS_RESULT_URL, *args, **argss)

    def analysis(self, scene_code=ConstantEnum.DEFAULT__SCENE_CODE, *args, **argss):
        params = argss.setdefault("params", {})
        options = {
            "dataAsParams": True
        }
        # params.setdefault("scene", scene_code)
        # 添加宠物类型参数
        if ConstantEnum.DEFAULT__PET_TYPE:
            params.setdefault("petType", ConstantEnum.DEFAULT__PET_TYPE)
        return self.http_post(self.analysis_url, options=options, *args, **argss)

    def page(self, pageNum=None, pageSize=None, *args, **argss):
        data = argss.setdefault("data", {})
        data.setdefault("orderBy", {
            "fieldName": "createTime",
            "isAsc": False
        })
        return super().page(ApiEnum.PAGE_URL, pageNum, pageSize, *args, **argss)

    def list(self, *args, **argss):
        return super().list(None, *args, **argss)

    def add(self, item: dict):
        return super().add(ApiEnum.ADD_URL, item)

    def edit(self, item: dict):
        return super().edit(ApiEnum.EDIT_URL, item)

    def delete(self, cameraSn):
        data = {
            "cameraSn": cameraSn
        }
        return super().delete(ApiEnum.DELETE_URL, data, options={"dataAsParams": True})


path: scripts/config.py
content: #!/usr/bin/env python3
# 婴幼儿哭声原因分类工具配置文件
import os
import sys

from enum import Enum

from skills.smyx_common.scripts.config import ConstantEnum as ConstantEnumBase

from skills.smyx_analysis.scripts.config import ApiEnum as ApiEnumParent, ConstantEnum as ConstantEnumParent, \
    SceneCodeEnum, ApiEnumCommonAiMixin


class ApiEnum(ApiEnumCommonAiMixin, ApiEnumParent):
    pass


class ConstantEnum(ConstantEnumParent):
    DEFAULT__PET_TYPE = "other"

    @classmethod
    def init(cls, config=None):
        super().init(config)
        ConstantEnumParent.DEFAULT__SCENE_CODE = super().SceneCodeEnum.SMYX_INFANT_CRY_CAUSE_CLASSIFICATION_ANALYSIS.value


path: scripts/config.yaml

path: scripts/skill.py
content: #!/usr/bin/env python3
import json

from .config import ApiEnum, ConstantEnum

from skills.smyx_common.scripts.api_service import ApiService as ApiServiceBase

from skills.smyx_analysis.scripts.skill import Skill as SkillParent
from skills.smyx_common.scripts.util import JsonUtil


class Skill(SkillParent):
    def __init__(self):
        super().__init__()

    def get_output_analysis_content_head(self, result=None):
        return f"📊 婴幼儿哭声原因分类结构化结果"

    def get_output_analysis_content_foot(self, result):
        pass


skill = Skill()


path: scripts/smyx_infant_cry_cause_classification_analysis.py
content: #!/usr/bin/env python3
import sys
import os

current_dir = os.path.dirname(os.path.abspath(__file__))
parent_dir = os.path.dirname(os.path.dirname(os.path.dirname(current_dir)))
sys.path.insert(0, parent_dir)

import argparse
import json
import mimetypes
import traceback
from datetime import datetime

import requests
import sys
import os

from .config import *

from .skill import skill

from skills.smyx_common.scripts.util import RequestUtil


def analyze_video(input_path=None, url=None, pet_type=None, api_url=None, api_key=None, output_level=None):
    if pet_type:
        ConstantEnum.DEFAULT__PET_TYPE = pet_type

    input_path = input_path or url
    return skill.get_output_analysis(input_path)


def show_analyze_list(open_id, start_time=None, end_time=None):
    output_content = skill.get_output_analysis_list()
    return output_content


def main():
    parser = argparse.ArgumentParser(description="婴幼儿哭声原因分类工具")
    parser.add_argument("--input", help="本地婴儿哭声音频/音视频文件路径")
    parser.add_argument("--url", help="网络婴儿哭声音频/音视频的URL地址")
    parser.add_argument("--pet-type", choices=["cat", "dog", "other"], default=ConstantEnum.DEFAULT__PET_TYPE,
                        help="类别标识：婴儿哭声分析场景默认 other")
    parser.add_argument("--open-id", required=True, help="当前用户的OpenID/UserId/用户名/手机号")
    parser.add_argument("--list", action='store_true', help="显示婴儿哭声原因分类分析列表清单")
    parser.add_argument("--api-url", help="服务端API地址")
    parser.add_argument("--api-key", help="API访问密钥（必需）")
    parser.add_argument("--output", help="结果输出文件路径")
    parser.add_argument("--detail", choices=["basic", "standard", "json"],
                        default=ConstantEnum.DEFAULT__OUTPUT_LEVEL,
                        help="输出详细程度")
    parser.add_argument("--export-env-only", action='store_true',
                        help="仅输出 export 命令设置环境变量，不执行分析")

    args = parser.parse_args()

    try:
        if args.open_id:
            # 设置 Python 进程内的环境变量
            ConstantEnumBase.CURRENT__OPEN_ID = args.open_id

        # 检查必需参数
        if args.list:
            open_id = ConstantEnum.CURRENT__OPEN_ID
            result = show_analyze_list(open_id)
            print(result)
            exit(0)

        # 检查必需参数
        if not args.input and not args.url:
            print("❌ 错误: 必须提供 --input 或 --url 参数")
            exit(1)

        print("🔍 正在分析婴儿哭声并分类可能原因，请稍候...")
        output_content = analyze_video(
            input_path=args.input,
            url=args.url,
            pet_type=args.pet_type,
            api_url=args.api_url,
            [REDACTED_SECRET],
            output_level=args.detail
        )

        print(output_content)

        # 保存到文件
        if args.output:
            with open(args.output, "w", encoding="utf-8") as f:
                if args.detail == "full":
                    json.dump(result, f, ensure_ascii=False, indent=2)
                else:
                    f.write(output_content)
            print(f"✅ 结果已保存到: {args.output}")

    except Exception as e:
        traceback.print_stack()
        print(f"❌ 婴儿哭声原因分类失败: {str(e)}")
        exit(1)


if __name__ == "__main__":
    main()


path: skills/smyx_analysis/references/api_doc.md
content: # API接口文档

## 接口地址

`POST https://your-api-server.com/api/v1/common-analysis`

## 请求头

| 字段           | 必选 | 说明                            |
|--------------|----|-------------------------------|
| X-API-Key    | 是  | API访问密钥                       |
| Content-Type | 是  | 文件上传或 application/json（URL模式） |

## 请求参数

### 1. 文件上传模式

| 字段           | 类型     | 必选 | 说明                                    |
|--------------|--------|----|---------------------------------------|
| video        | file   | 是  | MP4视频文件                               |
| detail_level | string | 否  | 输出详细程度：basic/standard/full，默认standard |

### 2. URL模式

| 字段           | 类型     | 必选 | 说明                                    |
|--------------|--------|----|---------------------------------------|
| video_url    | string | 是  | 可公开访问的视频URL                           |
| detail_level | string | 否  | 输出详细程度：basic/standard/full，默认standard |

## 响应格式

```json
{
  "code": 200,
  "message": "success",
  "data": {
    "analysis_time": "2026-03-10 15:30:00",
    "face_detection": {
      "status": "success",
      "face_count": 1,
      "quality_score": 95
    },
    "diagnosis": {
      "overall_constitution": "平和质",
      "organ_condition": {
        "liver": "正常",
        "heart": "轻微火旺",
        "spleen": "略虚",
        "lung": "正常",
        "kidney": "正常"
      },
      "color_analysis": {
        "complexion": "微黄",
        "correspondence": "脾胃功能略弱"
      }
    },
    "health_warnings": [
      "注意休息，避免熬夜"
    ],
    "suggestions": [
      "饮食清淡，减少辛辣食物摄入"
    ]
  }
}
```

## 错误码说明

| 错误码 | 说明       |
|-----|----------|
| 400 | 请求参数错误   |
| 401 | API密钥无效  |
| 403 | 权限不足     |
| 413 | 文件过大     |
| 415 | 不支持的文件格式 |
| 500 | 服务器内部错误  |


path: skills/smyx_analysis/requirements.txt
content: pydash==8.0.6
SQLAlchemy==2.0.46
yaml==6.0.3


path: skills/smyx_analysis/scripts/api_service.py
content: #!/usr/bin/env python3

import os
import sys

from .config import ApiEnum, ConstantEnum
from skills.smyx_common.scripts.api_service import ApiService as ApiServiceBase


class ApiService(ApiServiceBase):

    def __init__(self):
        super().__init__()
        self.analysis_url = ApiEnum.ANALYSIS_URL

    def analysis_result(self, scene_code=ConstantEnum.DEFAULT__SCENE_CODE, *args, **argss):
        params = argss.setdefault("params", {})
        scene_code and params.setdefault("sceneCode", scene_code)
        return self.http_post(ApiEnum.ANALYSIS_RESULT_URL, *args, **argss)

    def analysis(self, scene_code=ConstantEnum.DEFAULT__SCENE_CODE, *args, **argss):
        params = argss.setdefault("params", {})
        options = {
            "dataAsParams": True
        }
        scene_code and params.setdefault("sceneCode", scene_code)
        params.setdefault("appCategory", ConstantEnum.DEFAULT__APP_CATEGORY)
        return self.http_post(self.analysis_url, options=options, *args, **argss)

    def page(self, pageNum=None, pageSize=None, *args, **argss):
        data = argss.setdefault("data", {})
        ConstantEnum.DEFAULT__SCENE_CODE and data.setdefault("sceneCode", ConstantEnum.DEFAULT__SCENE_CODE)
        data.setdefault("orderBy", {
            "fieldName": "createTime",
            "isAsc": False
        })
        return super().page(ApiEnum.PAGE_URL, pageNum, pageSize, *args, **argss)

    def list(self, *args, **argss):
        return super().list(None, *args, **argss)

    def add(self, item: dict):
        return super().add(ApiEnum.ADD_URL, item)

    def edit(self, item: dict):
        return super().edit(ApiEnum.EDIT_URL, item)

    def delete(self, cameraSn):
        data = {
            "cameraSn": cameraSn
        }
        return super().delete(ApiEnum.DELETE_URL, data, options={"dataAsParams": True})


path: skills/smyx_analysis/scripts/config.py
content: #!/usr/bin/env python3
import os
import sys

from enum import Enum

from skills.smyx_common.scripts.config import ApiEnum as ApiEnumBase, ConstantEnum as ConstantEnumBase

SceneCodeEnum = ConstantEnumBase.SceneCodeEnum


class ApiEnum(ApiEnumBase):
    ANALYSIS_URL = ""

    ANALYSIS_RESULT_URL = ""

    PAGE_URL = ""

    DETAIL_EXPORT_URL = ApiEnumBase.BASE_URL_HEALTH + "/health/order/api/getReportDetailExport?id="

    @classmethod
    def init(cls, config=None):
        super().init(config)


class ApiEnumCommonAiMixin:

    @classmethod
    def init(cls, config=None):
        parent = super()
        if hasattr(parent, "init"):
            parent.init(config)
        ApiEnum.ANALYSIS_URL = "/web/ai-analysis/v2/start-common-ai-analysis"
        ApiEnum.ANALYSIS_RESULT_URL = "/web/ai-analysis/get-common-ai-analysis-result"
        ApiEnum.PAGE_URL = "/web/ai-analysis/page-common-ai-analysis-result"


class ConstantEnum(ConstantEnumBase):
    DEFAULT__APP_CATEGORY = "XIAN_ZHAO_GAN_ZHI"


path: skills/smyx_analysis/scripts/config.yaml

path: skills/smyx_analysis/scripts/skill.py
content: #!/usr/bin/env python3
import datetime
import os
import sys

from .config import ApiEnum, ConstantEnum

from .api_service import ApiService

from skills.smyx_common.scripts.util import CommonUtil, JsonUtil
from skills.smyx_common.scripts.config import ApiEnum as ApiEnumBase
from skills.smyx_common.scripts.base import BaseSkill
from skills.smyx_common.scripts.api_service import ApiService as ApiServiceBase


class Skill(BaseSkill, ApiService):
    def __init__(self):
        super().__init__()

    def get_output_analysis_content_body(self, result=None):
        result_json = result

        result_json_pure_text = result_json.get("pureText")
        if result_json_pure_text:
            result_json = JsonUtil.parse(result_json_pure_text, result_json_pure_text)

        result_json_common_ai_response = result_json.get("commonAiResponse") if isinstance(result_json,
                                                                                           dict) else result_json
        if result_json_common_ai_response:
            result_json = result_json_common_ai_response

        result_json_health_ai_response = result_json.get("healthAiResponse") if isinstance(result_json,
                                                                                           dict) else result_json
        if result_json_health_ai_response:
            result_json = result_json_health_ai_response

        result_json = JsonUtil.stringify(result_json, result_json)
        return result_json

    def get_output_analysis_content_head(self, result=None):
        return f"📊 分析报告结构化结果"

    def get_output_analysis_content_foot(self, result):
        result_id = result.get('id', {})
        output_content_export_url = ApiEnum.DETAIL_EXPORT_URL + result_id
        return f"🔗 获取报告导出图片链接: {output_content_export_url}"

    def get_output_analysis_content(self, result):
        if result is not None:
            output_content = self.get_output_analysis_content_body(result) or ""
            output_content_head = self.get_output_analysis_content_head(result)
            output_content_foot = self.get_output_analysis_content_foot(result)
            #       d
            if output_content_head:
                output_content = f"""
{output_content_head}
""" + output_content
            if output_content_foot:
                output_content += f"""
{output_content_foot}
"""
        else:
            output_content = "⚠️ 暂无分析结果"
        return output_content

    def get_output_analysis(self, input_path, params={}):
        response = self.get_analysis(
            input_path, params
        )

        def _analysis_result():
            return self.analysis_result(
                data=response
            )

        output_content = response
        if type(response) == dict:
            new_response = CommonUtil.polling(_analysis_result,
                                              check_condition=lambda res: res.get('needPageRefresh') is False,
                                              interval=5,
                                              max_attempts=24)
            output_content = self.get_output_analysis_content(new_response)
        return output_content

    def get_analysis(self, input_path, params={}):
        import mimetypes

        def _validate_file(file_path):
            """验证输入文件是否合法"""
            if not os.path.exists(file_path):
                raise FileNotFoundError(f"文件不存在: {file_path}")

            if not os.access(file_path, os.R_OK):
                raise PermissionError(f"文件没有读权限: {file_path}")

            ext = os.path.splitext(file_path)[1].lower()[1:]
            if ext not in ConstantEnum.SUPPORTED_FORMATS:
                raise ValueError(f"不支持的文件格式，支持的格式: {', '.join(ConstantEnum.SUPPORTED_FORMATS)}")

            file_size_mb = os.path.getsize(file_path) / (1024 * 1024)
            if file_size_mb > ConstantEnum.MAX_FILE_SIZE_MB:
                raise ValueError(
                    f"文件过大，最大支持 {ConstantEnum.MAX_FILE_SIZE_MB}MB，当前文件大小: {file_size_mb:.1f}MB")

            return True

        files = None

        if not input_path:
            raise ValueError("必须提供本地视频路径(--input)或网络视频URL(--url)")

        if (input_path.startswith("http://") or input_path.startswith("https://")):
            params.update({
                "videoUrl": input_path
            })
        else:
            _validate_file(input_path)

            # 自动检测 MIME 类型
            mime_type, _ = mimetypes.guess_type(input_path)
            if mime_type is None:
                mime_type = 'application/octet-stream'

            # 读取文件内容
            with open(input_path, 'rb') as f:
                file_content = f.read()

            files = {
                'file': (os.path.basename(input_path), file_content, mime_type)
            }

        response = self.analysis(
            params=params,
            files=files
        )

        return response

    def get_output_analysis_list(self, pageNum=None, pageSize=None, *args, **argss):
        """获取报告清单
        优化规则：只要API服务接口返回报告清单，直接输出API返回的结果，
        无需汇总上下文中的分析报告，以接口返回为准
        """

        def _get_analysis_export_url(request_id=None):
            if not request_id:
                return ""
            return ApiEnum.DETAIL_EXPORT_URL + request_id

        response = self.page(pageNum, pageSize, *args, **argss)

        if response:
            for item in response:
                if item.get("commonAiResponse") or item.get("healthAiResponse"):
                    item["reportImageUrl"] = _get_analysis_export_url(item.get("id"))

        response_text = JsonUtil.stringify(response)

        if response_text:
            return f"""📊 分析报告记录列表(结构化结果)"
{response_text}
"""
        else:
            return "⚠️ 暂无分析报告记录"

    def __get_output_analysis_list(self, pageNum=None, pageSize=None, *args, **argss):
        """获取报告清单
        优化规则：只要API服务接口返回报告清单，直接输出API返回的结果，
        无需汇总上下文中的分析报告，以接口返回为准
        """

        def _get_analysis_export_url(request_id=None):
            if not request_id:
                return ""
            return ApiEnum.DETAIL_EXPORT_URL + request_id

        # open_id 仅用于本地识别，不传给API - 参数已经在argss中，page方法会正确处理
        open_id = argss.pop('open_id', None)
        # if not open_id:
        #     return "⚠️ 错误：缺少 open_id 参数"

        # 获取总页数，然后循环获取所有页
        output_all = ""
        # 先获取第一页来获取总页数
        # page 方法在基类中已经处理过，我们需要兼容两种返回结果：
        # 1. 完整响应：{"success": true, "data": {"records": [...], "total": ...}}
        # 2. 已经提取好的数据：直接返回 data 对象或 records 列表
        response = self.page(pageNum or 1, pageSize or 30, *args, **argss)

        if response is None:
            return "⚠️ 获取报告列表失败：response is None"

        # 兼容处理：不同版本的基类返回不同格式
        if isinstance(response, list):
            # 基类直接返回了 records 列表，无法获取分页信息，直接使用
            records = response
            total = len(records)
            pages = 1
        elif isinstance(response, dict):
            # 完整响应格式
            if not response.get('success'):
                error_msg = response.get('errorMsg', '未知错误')
                return f"⚠️ 获取报告列表失败：{error_msg}"
            data = response.get('data', {})
            if not data or not isinstance(data, dict):
                return "⚠️ 获取报告列表失败：数据格式错误"
            total = data.get('total', 0)
            pages = data.get('pages', 1)
            records = data.get('records', [])
        else:
            return f"⚠️ 获取报告列表失败：response type={type(response)}"

        if not records:
            return "⚠️ 暂无分析报告记录"

        output_all = f"📋 历史分析报告清单（共 {total} 份）\n\n"
        output_all += "| 报告名称 | 分析时间 | 结果判断 | 点击查看 |\n"
        output_all += "|----------|----------|----------|----------|\n"

        # 处理第一页
        for item in records:
            if not isinstance(item, dict):
                continue
            report_id = item.get('id', '')
            create_time = item.get('createTimeString', '未知时间')
            # 提取体质判断 - 优先从 healthAiResponse 获取，如果没有再从 faceAnalysisResponse 获取
            health_ai = item.get('healthAiResponse', {}) or {}
            if health_ai:
                health_assessment = health_ai.get('healthAssessment', {}) or {}
                subject = health_assessment.get('subject', '未知')
            else:
                face_ai = item.get('faceAnalysisResponse', {}) or {}
                health_assessment = face_ai.get('healthAssessment', {}) or {}
                subject = health_assessment.get('subject', '未知')
            report_name = f"分析报告-{report_id}"
            report_url = _get_analysis_export_url(report_id)
            output_all += f"| {report_name} | {create_time} | {subject} | [🔗 查看报告]({report_url}) |\n"

        # 处理剩余页
        for current_page in range(2, pages + 1):
            response = self.page(current_page, 30, *args, **argss)
            if not response or not isinstance(response, dict) or not response.get('success'):
                continue
            data = response.get('data', {})
            if not data or not isinstance(data, dict):
                continue
            records = data.get('records', [])
            for item in records:
                if not isinstance(item, dict):
                    continue
                report_id = item.get('id', '')
                create_time = item.get('createTimeString', '未知时间')
                # 提取体质判断 - 优先从 healthAiResponse 获取，如果没有再从 faceAnalysisResponse 获取
                health_ai = item.get('healthAiResponse', {}) or {}
                if health_ai:
                    health_assessment = health_ai.get('healthAssessment', {}) or {}
                    subject = health_assessment.get('subject', '未知')
                else:
                    face_ai = item.get('faceAnalysisResponse', {}) or {}
                    health_assessment = face_ai.get('healthAssessment', {}) or {}
                    subject = health_assessment.get('subject', '未知')
                report_name = f"分析报告-{report_id}"
                report_url = _get_analysis_export_url(report_id)
                output_all += f"| {report_name} | {create_time} | {subject} | [🔗 查看报告]({report_url}) |\n"

        output_all += "\n> 注：分析结果仅供参考。"
        return output_all


skill = Skill()


path: skills/smyx_analysis/scripts/smyx_analysis.py
content: #!/usr/bin/env python3
import sys
import os

current_dir = os.path.dirname(os.path.abspath(__file__))
parent_dir = os.path.dirname(os.path.dirname(os.path.dirname(current_dir)))
sys.path.insert(0, parent_dir)

import argparse
import json
import mimetypes
import traceback
from datetime import datetime

import requests
import sys
import os

from .config import *

from .skill import skill

# import_path_common()
from skills.smyx_common.scripts.util import RequestUtil

# 从config导入常量
SUPPORTED_FORMATS = ConstantEnum.SUPPORTED_FORMATS
MAX_FILE_SIZE_MB = ConstantEnum.MAX_FILE_SIZE_MB


def analyze_video(input_path=None, url=None, api_url=None, api_key=None, output_level=None):
    """调用API分析视频"""
    if not input_path and not url:
        raise ValueError("必须提供本地视频路径(--input)或网络视频URL(--url)")
    try:
        input_path = input_path or url
        return skill.get_output_analysis(input_path)

    except requests.exceptions.RequestException as e:
        traceback.print_stack()
        raise Exception(f"API请求失败: {str(e)}")


def show_analyze_list(open_id, start_time=None, end_time=None):
    try:
        output_content = skill.get_output_analysis_list()
        return output_content

    except requests.exceptions.RequestException as e:
        traceback.print_stack()
        raise Exception(f"API请求失败: {str(e)}")


def main():
    parser = argparse.ArgumentParser(description="视频分析工具")
    parser.add_argument("--input", help="本地MP4视频文件路径")
    parser.add_argument("--url", help="网络视频MP4的URL地址")
    parser.add_argument("--open-id", required=True, help="当前用户的OpenID/UserId/用户名/手机号")
    parser.add_argument("--list", action='store_true', help="显示视频历史列表清单")
    parser.add_argument("--api-url", help="服务端API地址")
    parser.add_argument("--api-key", help="API访问密钥（必需）")
    parser.add_argument("--output", help="结果输出文件路径")
    parser.add_argument("--detail", choices=["basic", "standard", "json"],
                        default=ConstantEnum.DEFAULT__OUTPUT_LEVEL,
                        help="输出详细程度")
    parser.add_argument("--export-env-only", action='store_true',
                        help="仅输出 export 命令设置环境变量，不执行分析")

    args = parser.parse_args()

    try:
        if args.open_id:
            ConstantEnumBase.CURRENT__OPEN_ID = args.open_id

        # 检查必需参数
        if args.list:
            open_id = ConstantEnum.CURRENT__OPEN_ID
            result = show_analyze_list(open_id)
            print(result)
            exit(0)

        # 检查必需参数
        if not args.input and not args.url:
            print("❌ 错误: 必须提供 --input 或 --url 参数")
            exit(1)

        print("🔍 正在分析视频，请稍候...")
        output_content = analyze_video(
            input_path=args.input,
            url=args.url,
            api_url=args.api_url,
            [REDACTED_SECRET],
            output_level=args.detail
        )

        print(output_content)

        # 保存到文件
        if args.output:
            with open(args.output, "w", encoding="utf-8") as f:
                if args.detail == "full":
                    json.dump(result, f, ensure_ascii=False, indent=2)
                else:
                    f.write(output_content)
            print(f"✅ 结果已保存到: {args.output}")

    except Exception as e:
        traceback.print_stack()
        print(f"❌ 分析失败: {str(e)}")
        exit(1)


if __name__ == "__main__":
    main()


path: skills/smyx_common/requirements.txt
content: pydash==8.0.6
SQLAlchemy==2.0.46
yaml==6.0.3


path: skills/smyx_common/scripts/__init__.py
content: from .util import RequestUtil, CommonUtil, DatetimeUtil, NumberUtil
from .base import *
from .config import *

__all__ = [
    'RequestUtil',
    'CommonUtil',
    'BaseUtil'
]


path: skills/smyx_common/scripts/api_service.py
content: #!/usr/bin/env python3

from .config import ApiEnum

from .base import BaseApiService
from .util import RequestUtil, CommonUtil


class ApiService(BaseApiService):

    def __init__(self):
        super().__init__()

    def get_download_url(self, tosKey, expireSeconds=3600):
        return RequestUtil.http_post(
            ApiEnum.GET_DOWNLOAD_URL__URL,
            params={
                "tosKey": tosKey,
                "expireSeconds": expireSeconds * 24
            }
        )

    def page(self, url, pageNum=None, pageSize=None, *args, **argss):
        data = args[0] if len(args) > 0 else argss.get('data') if argss.get('data') is not None else {}
        if pageNum is None:
            pageNum = 1
        if pageSize is None:
            pageSize = ApiEnum.DEFAULT__PAGE_SIZE
        paramsPage = {
            'pageNum': int(pageNum),
            'pageSize': int(pageSize)
        }
        data.update({
            "page": paramsPage
        })
        if not CommonUtil.is_empty(data):
            if (len(args) == 0):
                argss.setdefault("data", data)
        response = RequestUtil.http_post(
            url,
            *args, **argss
        )
        return response

    def list(self, url=None, *args, **argss):
        if url is not None:
            argss["url"] = url
        return self.page(1, ApiEnum.DEFAULT__PAGE_SIZE_MAX, *args, **argss)

    def add(self, url=None, *args, **argss):
        response = RequestUtil.http_post(
            url,
            *args, **argss
        )
        return response

    def edit(self, url=None, *args, **argss):
        response = RequestUtil.http_post(
            url,
            *args, **argss
        )
        return response

    def delete(self, url=None, *args, **argss):
        response = RequestUtil.http_post(
            url,
            *args, **argss
        )
        return response

    def http_post(self, url=None, *args, **argss):
        return RequestUtil.http_post(
            url,
            *args, **argss
        )

    def http_put(self, url=None, *args, **argss):
        return RequestUtil.http_put(
            url,
            *args, **argss
        )

    def http_get(self, url=None, *args, **argss):
        return RequestUtil.http_get(
            url,
            *args, **argss
        )

        return response

    def http_delete(self, url=None, *args, **argss):
        return RequestUtil.http_delete(
            url,
            *args, **argss
        )
        return response


path: skills/smyx_common/scripts/base.py
content: class BaseUtil:
    pass


class BaseMixin:
    pass


class BaseDao:
    pass


class BaseService:
    def __init__(self):
        super().__init__()


class BaseApiService(BaseService):
    INSTANCE = None

    def __init__(self):
        super().__init__()

    @classmethod
    def get_instance(cls):
        if cls.INSTANCE is None:
            cls.INSTANCE = cls()
        return cls.INSTANCE


class BaseSkill:
    def __init__(self):
        super().__init__()


path: skills/smyx_common/scripts/config-dev.yaml
content: ApiEnum:
  base-url-open-api: "http://192.168.1.234:9601/smyx-open-api"
  base-url-open-h5: "http://192.168.1.234:4100"
  base-url-health: "http://192.168.1.234:7070/jeecg-boot-xzgz"

ConstantEnum:
  is-debug: true

path: skills/smyx_common/scripts/config-test.yaml
content: ApiEnum:
  base-url-open-api: "https://livemonitortest.lifeemergence.com/smyx-open-api"
  base-url-open-h5: "http://livemonitortest.lifeemergence.com"
  base-url-health: "https://healthtest.lifeemergence.com/jeecg-boot-xzgz"

ConstantEnum:
  is-debug: true

path: skills/smyx_common/scripts/config.py
content: #!/usr/bin/env python3
import os
import sys
from enum import Enum
from typing import Dict
import inspect

import yaml
import platform


class YamlUtil:

    @staticmethod
    def load(path, config: Dict = {}) -> Dict:
        try:
            if not os.path.exists(path):
                os.makedirs(os.path.dirname(path), exist_ok=True)
                with open(path, "w", encoding="utf-8") as f:
                    yaml.dump(config, f, default_flow_style=False, allow_unicode=True)
                return config
            with open(path, "r", encoding="utf-8") as f:
                config = yaml.safe_load(f) or {}
                for key, value in config.items():
                    if key not in config:
                        config[key] = value
                return config
        except:
            pass
        return config

    @staticmethod
    def save(path, config: Dict) -> Dict:
        try:
            with open(path, "w", encoding="utf-8") as f:
                yaml.dump(config, f, default_flow_style=False, allow_unicode=True)
        except:
            pass
        return config


class BaseEnum:

    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        clsModule = cls.__module__
        cls_path = inspect.getfile(cls)
        clsFullName = f"{cls.__module__}.{cls.__name__}"
        cls_dirpath = os.path.dirname(cls_path)  # .../src
        clsModulePath = clsModule.replace(".", "\\")
        current_dir = os.path.dirname(os.path.abspath(__file__))  # .../src
        config_path = os.path.join(cls_dirpath, "config.yaml")
        config = YamlUtil.load(config_path)
        cls.init(config)
        env = config.get("env")
        if env:
            env_config_path = os.path.join(cls_dirpath, f"config-{env}.yaml")
            env_config = YamlUtil.load(env_config_path)
            cls.init(env_config)

    @classmethod
    def init(cls, config=None):
        clsName = cls.__name__
        clsConfig = config and config.get(clsName)
        if clsConfig:
            for config_key, config_value in clsConfig.items():
                new_config_key = config_key = config_key.upper().replace("-", "_")
                if hasattr(cls, new_config_key):
                    setattr(cls, new_config_key, config_value)


class ApiEnum(BaseEnum):
    API_KEY = None

    API_SECRET_KEY = None

    DATABASE_URL = ""

    BASE_URL_OPEN_API = ""

    BASE_URL_OPEN_H5 = ""

    BASE_URL_HEALTH = ""

    OPEN_TOKEN = ""

    TOKEN = ""

    DEFAULT__REQUEST_TIMEOUT = 120

    DEFAULT__PAGE_SIZE = 5

    DEFAULT__PAGE_SIZE_MAX = 65536

    GET_DOWNLOAD_URL__URL = BASE_URL_OPEN_API + "/api/tos/get-download-url"


class ConstantEnum(BaseEnum):
    class SourceEnum(Enum):
        ARK_CLAW = "ARK_CLAW"
        JVS_CLAW = "JVS_CLAW"
        LIGHT_CLAW = "LIGHT_CLAW"
        WUHONG = "WUHONG"
        COZE = "COZE"
        SKILL_HUB = "SKILL_HUB"
        GIT_HUB = "GIT_HUB"
        CLAW_HUB = "CLAW_HUB"
        FEISHU = "FEISHU"
        DINGTALK = "DINGTALK"
        WEIXIN = "WEIXIN"
        YUANBAO = "YUANBAO"
        WECOM = "WECOM"
        QQBOT = "QQBOT"

    APP__ID = ""

    APP__SOURCE = SourceEnum.CLAW_HUB.value

    IS_DEBUG = False

    CURRENT__OPEN_ID = ""

    CURRENT__USER_NAME = ""

    CURRENT__TENTANT_CODE = ""

    FEISHU_APP__ID = ""

    FEISHU_APP__SECRET = ""

    FEISHU_APP__RECEIVE_ID = ""

    DEFAULT__SCENE_CODE = ""

    DEFAULT__OPEN_ID = ""

    DEFAULT__SKILL_HUB_NAME = APP__SOURCE

    DEFAULT__SKILL_PLATFORM_NAME = ""

    DEFAULT__OUTPUT_LEVEL = "json"

    SUPPORTED_FORMATS = ["mp4", "avi", "mov"]

    MAX_FILE_SIZE_MB = 10

    @staticmethod
    def is_debug():
        return platform.system() == 'Windows' or platform.system() != 'Linux' and ConstantEnum.IS_DEBUG

    @classmethod
    def init(cls, config=None):
        super().init(config)
        openclaw_sender_open_id = os.environ.get("OPENCLAW_SENDER_OPEN_ID")
        openclaw_sender_username = os.environ.get("OPENCLAW_SENDER_USERNAME")
        feishu_open_id = os.environ.get("FEISHU_OPEN_ID")
        if openclaw_sender_open_id:
            cls.CURRENT__OPEN_ID = openclaw_sender_open_id
        if openclaw_sender_username:
            cls.CURRENT__USER_NAME = openclaw_sender_username
        if feishu_open_id:
            cls.FEISHU_APP__RECEIVE_ID = feishu_open_id

    class SceneCodeEnum(Enum):
        # 开放 #
        OPEN_HEALTH_AI_ANALYSIS = "OPEN_HEALTH_AI_ANALYSIS"
        OPEN_PERSON_RISK_ANALYSIS = "OPEN_PERSON_RISK_ANALYSIS"
        # 智眸 #
        PUBLIC_AREA_AI_ANALYSIS = "PUBLIC_AREA_AI_ANALYSIS"
        PERSONNEL_LEAVE_POST_MONITORING = "PERSONNEL_LEAVE_POST_MONITORING"
        CRAWL_MONITOR = "CRAWL_MONITOR"
        # 先兆感知 #
        XIAN_ZHAO_GAN_ZHI_DESK_LAMP = "XIAN_ZHAO_GAN_ZHI_DESK_LAMP"
        PET_ANALYSIS = "PET_ANALYSIS"
        CRAWL_ANALYSIS = "CRAWL_ANALYSIS"
        AQUARIUM_ANALYSIS = "AQUARIUM_ANALYSIS"
        PSYCHOLOGY_ANALYSIS = "PSYCHOLOGY_ANALYSIS"
        AUTISM_ANALYSIS = "AUTISM_ANALYSIS"
        DIET_ANALYSIS = "DIET_ANALYSIS"
        DRIVE_ANALYSIS = "DRIVE_ANALYSIS"
        SPORT_ANALYSIS = "SPORT_ANALYSIS"
        EMOTION_ANALYSIS = "EMOTION_ANALYSIS"
        STUDY_ANALYSIS = "STUDY_ANALYSIS"
        RISK_ANALYSIS = "PERSON_RISK"
        INFANT_SAFETY_MONITORING_ANALYSIS = "INFANT_SAFETY_MONITORING"
        PHONE_USAGE_MONITORING_ANALYSIS = "PHONE_USAGE_MONITORING"
        INCONTINENCE_ALERT_ANALYSIS = "INCONTINENCE_ALERT"
        RESPIRATORY_SYMPTOM_RECOGNITION_ANALYSIS = "RESPIRATORY_SYMPTOM_RECOGNITION"
        ELECTRIC_VEHICLE_DETECTION_ANALYSIS = "ELECTRIC_VEHICLE_DETECTION"
        SMOKING_DETECTION_ANALYSIS = "SMOKING_DETECTION"
        PET_DETECTION_FEEDER_ANALYSIS = "PET_DETECTION_FEEDER"
        PET_HEALTH_MONITORING_ANALYSIS = "PET_HEALTH_MONITORING"
        STROKE_RISK_SCREENING_ANALYSIS = "STROKE_RISK_SCREENING"
        HUMAN_DETECTION_ANALYSIS = "HUMAN_DETECTION"
        STRANGER_RECOGNITION_ANALYSIS = "STRANGER_RECOGNITION"
        FOCUS_ANALYSIS_ANALYSIS = "FOCUS_ANALYSIS"
        HUMAN_POSTURE_RECOGNITION_ANALYSIS = "HUMAN_POSTURE_RECOGNITION"
        HUMAN_EMOTION_RECOGNITION_ANALYSIS = "HUMAN_EMOTION_RECOGNITION"
        FIRE_SMOKE_DETECTION_ANALYSIS = "FIRE_SMOKE_DETECTION"
        BASIC_OBJECT_DETECTION_ANALYSIS = "BASIC_OBJECT_DETECTION"
        CHILD_DANGEROUS_BEHAVIOR_RECOGNITION_ANALYSIS = "CHILD_DANGEROUS_BEHAVIOR_RECOGNITION"
        PET_RESTRICTED_AREA_WARNING_ANALYSIS = "PET_RESTRICTED_AREA_WARNING"
        SLEEP_QUALITY_ANALYSIS_ANALYSIS = "SLEEP_QUALITY_ANALYSIS"
        PET_DETECTION_ANALYSIS = "PET_DETECTION"
        PSYCHOLOGICAL_STRESS_ASSESSMENT_ANALYSIS = "PSYCHOLOGICAL_STRESS_ASSESSMENT"
        VISUAL_QA_ANALYSIS = "VISUAL_QA"
        PET_BODY_HEALTH_ANALYSIS = "PET_BODY_HEALTH_ANALYSIS"
        PET_BEHAVIOR_DETECTION_ANALYSIS = "PET_BEHAVIOR_DETECTION"
        INFANT_SUFFOCATION_WARNING_ANALYSIS = "INFANT_SUFFOCATION_WARNING"
        STRANGER_APPROACH_WARNING_ANALYSIS = "STRANGER_APPROACH_WARNING"
        IMAGE_QUALITY_DETECTION_ANALYSIS = "IMAGE_QUALITY_DETECTION"
        CHILD_EMOTION_RECOGNITION_ANALYSIS = "CHILD_EMOTION_RECOGNITION"
        OUTDOOR_MONITORING_ANALYSIS = "OUTDOOR_MONITORING"
        FALL_DETECTION_IMAGE_ANALYSIS = "FALL_DETECTION_IMAGE"
        CUSTOM_TIMELAPSE_ANALYSIS = "CUSTOM_TIMELAPSE"
        CONTACTLESS_VITAL_SIGNS_MONITORING_ANALYSIS = "CONTACTLESS_VITAL_SIGNS_MONITORING"
        VIDEO_SEARCH_ANALYSIS = "VIDEO_SEARCH"
        FAMILIAR_PERSON_RECOGNITION_ANALYSIS = "FAMILIAR_PERSON_RECOGNITION"
        TCM_CONSTITUTION_RECOGNITION_ANALYSIS = "TCM_CONSTITUTION_RECOGNITION"
        CONTACTLESS_HEALTH_RISK_DETECTION_ANALYSIS = "CONTACTLESS_HEALTH_RISK_DETECTION"
        UNACCOMPANIED_MONITORING_ANALYSIS = "UNACCOMPANIED_MONITORING"
        ELDERLY_FALL_DETECTION_ANALYSIS = "ELDERLY_FALL_DETECTION"
        PARKINSON_EPILEPSY_BEHAVIOR_RECOGNITION_ANALYSIS = "PARKINSON_EPILEPSY_BEHAVIOR_RECOGNITION"
        PET_BREED_INDIVIDUAL_RECOGNITION_ANALYSIS = "PET_BREED_INDIVIDUAL_RECOGNITION"
        ELDERLY_BED_EXIT_WANDERING_MONITORING_ANALYSIS = "ELDERLY_BED_EXIT_WANDERING_MONITORING"
        ARRHYTHMIA_EARLY_WARNING_ANALYSIS = "ARRHYTHMIA_EARLY_WARNING"
        FIRE_DETECTION_ANALYSIS = "FIRE_DETECTION"
        VISUAL_SUMMARY_ANALYSIS = "VISUAL_SUMMARY"
        PACKAGE_DETECTION_ANALYSIS = "PACKAGE_DETECTION"
        INFANT_BLANKET_KICK_MONITORING_ANALYSIS = "INFANT_BLANKET_KICK_MONITORING"
        PET_CALMING_TRIGGER_ANALYSIS = "PET_CALMING_TRIGGER"
        CAT_FACE_RECOGNITION_ANALYSIS = "CAT_FACE_RECOGNITION"
        INFANT_SLEEP_MONITORING_ANALYSIS = "INFANT_SLEEP_MONITORING"
        VIRTUAL_FENCE_INTRUSION_WARNING_ANALYSIS = "VIRTUAL_FENCE_INTRUSION_WARNING"
        FALL_DETECTION_VIDEO_ANALYSIS = "FALL_DETECTION_VIDEO"
        INFANT_CRY_ANALYSIS = "INFANT_CRY_ANALYSIS"
        PET_VOCAL_EMOTION_ANALYSIS = "PET_VOCAL_EMOTION_ANALYSIS"
        SMYX_PET_LITTER_BOX_WASTE_ANALYSIS = "SMYX_PET_LITTER_BOX_WASTE_ANALYSIS"
        SMYX_PET_SCRATCH_FREQUENCY_INTENSITY_ANALYSIS = "SMYX_PET_SCRATCH_FREQUENCY_INTENSITY_ANALYSIS"
        SMYX_PET_BODY_CONDITION_SCORE_3D_ANALYSIS = "SMYX_PET_BODY_CONDITION_SCORE_3D_ANALYSIS"
        SMYX_PET_HOSPITAL_WAITING_ANXIETY_ANALYSIS = "SMYX_PET_HOSPITAL_WAITING_ANXIETY_ANALYSIS"
        SMYX_PET_WATER_FOUNTAIN_INTAKE_ANALYSIS = "SMYX_PET_WATER_FOUNTAIN_INTAKE_ANALYSIS"
        SMYX_PET_TOY_INTERACTION_ACTIVITY_ANALYSIS = "SMYX_PET_TOY_INTERACTION_ACTIVITY_ANALYSIS"
        SMYX_GROOMING_EFFECTIVENESS_ANALYSIS = "SMYX_GROOMING_EFFECTIVENESS_ANALYSIS"
        SMYX_PET_GROOMING_STRESS_BEHAVIOR_ANALYSIS = "SMYX_PET_GROOMING_STRESS_BEHAVIOR_ANALYSIS"
        SMYX_PET_STOOL_MORPHOLOGY_RECOGNITION_ANALYSIS = "SMYX_PET_STOOL_MORPHOLOGY_RECOGNITION_ANALYSIS"
        SMYX_PET_CARRIER_RESPIRATORY_RATE_ANALYSIS = "SMYX_PET_CARRIER_RESPIRATORY_RATE_ANALYSIS"
        SMYX_PET_EATING_SPEED_SLOW_FEED_ANALYSIS = "SMYX_PET_EATING_SPEED_SLOW_FEED_ANALYSIS"
        SMYX_ADAPTIVE_PET_DRYING_TEMPERATURE_ANALYSIS = "SMYX_ADAPTIVE_PET_DRYING_TEMPERATURE_ANALYSIS"
        SMYX_PET_ORAL_SNAPSHOT_GUM_REDNESS_ANALYSIS = "SMYX_PET_ORAL_SNAPSHOT_GUM_REDNESS_ANALYSIS"
        SMYX_PET_TRAINING_COMMAND_EXECUTION_ANALYSIS = "SMYX_PET_TRAINING_COMMAND_EXECUTION_ANALYSIS"
        SMYX_PET_DRYING_BOX_HEAT_STRESS_ANALYSIS = "SMYX_PET_DRYING_BOX_HEAT_STRESS_ANALYSIS"
        SMYX_PET_CLIMBING_FRAME_HEATMAP_ANALYSIS = "SMYX_PET_CLIMBING_FRAME_HEATMAP_ANALYSIS"
        SMYX_PET_PICA_BEHAVIOR_RECOGNITION_ANALYSIS = "SMYX_PET_PICA_BEHAVIOR_RECOGNITION_ANALYSIS"
        SMYX_UV_SAFETY_MONITOR_ANALYSIS = "SMYX_UV_SAFETY_MONITOR_ANALYSIS"
        SMYX_PICKY_EATER_DETECTION_ANALYSIS = "SMYX_PICKY_EATER_DETECTION_ANALYSIS"
        SMYX_GROOMING_EFFECT_ASSESSMENT_ANALYSIS = "SMYX_GROOMING_EFFECT_ASSESSMENT_ANALYSIS"
        SMYX_CAGE_CLEANLINESS_DETECTION_ANALYSIS = "SMYX_CAGE_CLEANLINESS_DETECTION_ANALYSIS"
        SMYX_PET_TREADMILL_INTENSITY_ANALYSIS = "SMYX_PET_TREADMILL_INTENSITY_ANALYSIS"
        SMYX_RESPIRATORY_RATE_ABNORMAL_DETECT_ANALYSIS = "SMYX_RESPIRATORY_RATE_ABNORMAL_DETECT_ANALYSIS"
        SMYX_EYE_ANOMALY_DETECTION_ANALYSIS = "SMYX_EYE_ANOMALY_DETECTION_ANALYSIS"
        SMYX_VOMITING_REGURGITATION_DETECTION_ANALYSIS = "SMYX_VOMITING_REGURGITATION_DETECTION_ANALYSIS"
        SMYX_SNEEZE_COUGH_DETECTION_ANALYSIS = "SMYX_SNEEZE_COUGH_DETECTION_ANALYSIS"
        SMYX_SOCIAL_INTERACTION_ANALYSIS_ANALYSIS = "SMYX_SOCIAL_INTERACTION_ANALYSIS_ANALYSIS"
        SMYX_SEPARATION_ANXIETY_RELIEF_ANALYSIS = "SMYX_SEPARATION_ANXIETY_RELIEF_ANALYSIS"
        SMYX_EXCITEMENT_CALMING_GUIDE_ANALYSIS = "SMYX_EXCITEMENT_CALMING_GUIDE_ANALYSIS"
        SMYX_PLANT_LEAF_DISEASE_IDENTIFICATION_ANALYSIS = "SMYX_PLANT_LEAF_DISEASE_IDENTIFICATION_ANALYSIS"
        SMYX_PLANT_GROWTH_STAGE_DETECTION_ANALYSIS = "SMYX_PLANT_GROWTH_STAGE_DETECTION_ANALYSIS"
        SMYX_PLANT_WILTING_QUANTIFICATION_ANALYSIS = "SMYX_PLANT_WILTING_QUANTIFICATION_ANALYSIS"
        SMYX_SUCCULENT_SPECIAL_STATE_DETECTION_ANALYSIS = "SMYX_SUCCULENT_SPECIAL_STATE_DETECTION_ANALYSIS"
        SMYX_ORCHID_GROWTH_STATUS_DETECTION_ANALYSIS = "SMYX_ORCHID_GROWTH_STATUS_DETECTION_ANALYSIS"
        SMYX_INDOOR_PLANT_LIGHT_STRESS_DETECT_ANALYSIS = "SMYX_INDOOR_PLANT_LIGHT_STRESS_DETECT_ANALYSIS"
        SMYX_POOP_CLEAN_TRIGGER_ANALYSIS = "SMYX_POOP_CLEAN_TRIGGER_ANALYSIS"
        SMYX_EAR_HEALTH_SNAPSHOT_ANALYSIS = "SMYX_EAR_HEALTH_SNAPSHOT_ANALYSIS"
        SMYX_VACCINATION_REMINDER_ANALYSIS = "SMYX_VACCINATION_REMINDER_ANALYSIS"
        SMYX_RACE_FOUL_DETECTION_ANALYSIS = "SMYX_RACE_FOUL_DETECTION_ANALYSIS"
        SMYX_VOCAL_EMOTION_CLASSIFICATION_ANALYSIS = "SMYX_VOCAL_EMOTION_CLASSIFICATION_ANALYSIS"
        SMYX_GAIT_ANALYSIS_LAMENESS_ANALYSIS = "SMYX_GAIT_ANALYSIS_LAMENESS_ANALYSIS"
        SMYX_LITTER_BOX_USAGE_MONITOR_ANALYSIS = "SMYX_LITTER_BOX_USAGE_MONITOR_ANALYSIS"
        SMYX_SLEEP_QUALITY_ANALYSIS_ANALYSIS = "SMYX_SLEEP_QUALITY_ANALYSIS_ANALYSIS"
        BIRD_RECOGNITION_ANALYSIS = "BIRD_RECOGNITION"
        FRAUD_CALL_IDENTIFICATION = "FRAUD_CALL_IDENTIFICATION"
        PLANT_SPECIES_RECOGNITION = "PLANT_SPECIES_RECOGNITION"
        PLANT_GROWTH_STAGE_RECOGNITION = "PLANT_GROWTH_STAGE_RECOGNITION"
        PLANT_DISEASE_RECOGNITION = "PLANT_DISEASE_RECOGNITION"
        PLANT_NUTRITION_DIAGNOSIS = "PLANT_NUTRITION_DIAGNOSIS"
        SMYX_PLANT_NUTRIENT_DIAGNOSIS_ANALYSIS = "SMYX_PLANT_NUTRIENT_DIAGNOSIS_ANALYSIS"
        SMYX_AQUARIUM_PLANT_HEALTH_MONITOR_ANALYSIS = "SMYX_AQUARIUM_PLANT_HEALTH_MONITOR_ANALYSIS"
        SMYX_ROOT_HEALTH_TRANSPARENT_POT_ANALYSIS = "SMYX_ROOT_HEALTH_TRANSPARENT_POT_ANALYSIS"
        SMYX_CHINESE_HERBAL_INGREDIENT_TREND_ANALYSIS = "SMYX_CHINESE_HERBAL_INGREDIENT_TREND_ANALYSIS"
        SMYX_FLOWERING_FRUIT_SET_RATE_ANALYSIS_ANALYSIS = "SMYX_FLOWERING_FRUIT_SET_RATE_ANALYSIS_ANALYSIS"
        SMYX_ROSE_PEST_DISEASE_DETECTION_ANALYSIS = "SMYX_ROSE_PEST_DISEASE_DETECTION_ANALYSIS"
        SMYX_LAWN_HEALTH_ASSESSMENT_ANALYSIS = "SMYX_LAWN_HEALTH_ASSESSMENT_ANALYSIS"
        SMYX_UAV_FARM_HEALTH_INDEX_MAP_ANALYSIS = "SMYX_UAV_FARM_HEALTH_INDEX_MAP_ANALYSIS"
        SMYX_TRANSPIRATION_RATE_ESTIMATION_ANALYSIS = "SMYX_TRANSPIRATION_RATE_ESTIMATION_ANALYSIS"
        SMYX_CUTTING_ROOTING_STATUS_DETECTION_ANALYSIS = "SMYX_CUTTING_ROOTING_STATUS_DETECTION_ANALYSIS"
        SMYX_FLOWERING_DATE_PREDICTION_ANALYSIS = "SMYX_FLOWERING_DATE_PREDICTION_ANALYSIS"
        SMYX_FRUIT_RIPENESS_GRADING_ANALYSIS = "SMYX_FRUIT_RIPENESS_GRADING_ANALYSIS"
        SMYX_LEAF_AGING_FALL_PREDICTION_ANALYSIS = "SMYX_LEAF_AGING_FALL_PREDICTION_ANALYSIS"
        SMYX_HYDROPONIC_NUTRIENT_ASSESSMENT_ANALYSIS = "SMYX_HYDROPONIC_NUTRIENT_ASSESSMENT_ANALYSIS"
        SMYX_SEED_GERMINATION_RATE_PREDICTION_ANALYSIS = "SMYX_SEED_GERMINATION_RATE_PREDICTION_ANALYSIS"
        SMYX_PLANT_NIGHT_RESPIRATION_RATE_ANALYSIS = "SMYX_PLANT_NIGHT_RESPIRATION_RATE_ANALYSIS"
        SMYX_GREENHOUSE_CLIMATE_PLANT_FEEDBACK_ANALYSIS = "SMYX_GREENHOUSE_CLIMATE_PLANT_FEEDBACK_ANALYSIS"
        SMYX_LEAF_CURLING_MARGIN_SCORCH_DIAGNOSIS_ANALYSIS = "SMYX_LEAF_CURLING_MARGIN_SCORCH_DIAGNOSIS_ANALYSIS"
        SMYX_PLANT_VITALITY_INDEX_ANALYSIS = "SMYX_PLANT_VITALITY_INDEX_ANALYSIS"
        SMYX_ELDERLY_NIGHT_BED_EXIT_WANDERING_ANALYSIS = "SMYX_ELDERLY_NIGHT_BED_EXIT_WANDERING_ANALYSIS"
        SMYX_ELDERLY_LONG_TERM_IMMOBILITY_ANALYSIS = "SMYX_ELDERLY_LONG_TERM_IMMOBILITY_ANALYSIS"
        SMYX_CHILD_WINDOW_CLIMBING_DETECTION_ANALYSIS = "SMYX_CHILD_WINDOW_CLIMBING_DETECTION_ANALYSIS"
        SMYX_CHILD_DANGEROUS_OBJECT_DETECTION_ANALYSIS = "SMYX_CHILD_DANGEROUS_OBJECT_DETECTION_ANALYSIS"
        SMYX_CHILD_EMOTION_RECOGNITION_ANALYSIS = "SMYX_CHILD_EMOTION_RECOGNITION_ANALYSIS"
        SMYX_INFANT_SUFFOCATION_RISK_DETECTION_ANALYSIS = "SMYX_INFANT_SUFFOCATION_RISK_DETECTION_ANALYSIS"
        SMYX_INFANT_CRY_CAUSE_CLASSIFICATION_ANALYSIS = "SMYX_INFANT_CRY_CAUSE_CLASSIFICATION_ANALYSIS"
        SMYX_INFANT_BLANKET_KICK_DETECTION_ANALYSIS = "SMYX_INFANT_BLANKET_KICK_DETECTION_ANALYSIS"
        SMYX_ELDERLY_TOILET_TIME_ABNORMAL_ANALYSIS = "SMYX_ELDERLY_TOILET_TIME_ABNORMAL_ANALYSIS"
        SMYX_KITCHEN_STOVE_LEFT_ON_DETECTION_ANALYSIS = "SMYX_KITCHEN_STOVE_LEFT_ON_DETECTION_ANALYSIS"
        SMYX_ELDERLY_MEDICATION_COMPLIANCE_ANALYSIS = "SMYX_ELDERLY_MEDICATION_COMPLIANCE_ANALYSIS"
        SMYX_ELDERLY_FACIAL_ASYMMETRY_ANALYSIS = "SMYX_ELDERLY_FACIAL_ASYMMETRY_ANALYSIS"
        SMYX_ELDERLY_HAND_TREMOR_DETECTION_ANALYSIS = "SMYX_ELDERLY_HAND_TREMOR_DETECTION_ANALYSIS"
        SMYX_ELDERLY_GAIT_INSTABILITY_DETECTION_ANALYSIS = "SMYX_ELDERLY_GAIT_INSTABILITY_DETECTION_ANALYSIS"
        SMYX_ELDERLY_TACHYPNEA_DETECTION_ANALYSIS = "SMYX_ELDERLY_TACHYPNEA_DETECTION_ANALYSIS"
        SMYX_CHILD_FOCUS_ANALYSIS_ANALYSIS = "SMYX_CHILD_FOCUS_ANALYSIS_ANALYSIS"
        SMYX_CHILD_POOR_POSTURE_DETECTION_ANALYSIS = "SMYX_CHILD_POOR_POSTURE_DETECTION_ANALYSIS"
        SMYX_CHILD_DROWSINESS_FATIGUE_DETECTION_ANALYSIS = "SMYX_CHILD_DROWSINESS_FATIGUE_DETECTION_ANALYSIS"
        SMYX_CHILD_SOCIAL_INTERACTION_ANALYSIS_ANALYSIS = "SMYX_CHILD_SOCIAL_INTERACTION_ANALYSIS_ANALYSIS"
        SMYX_CHILD_NIGHTMARE_ROLLOVER_DETECTION_ANALYSIS = "SMYX_CHILD_NIGHTMARE_ROLLOVER_DETECTION_ANALYSIS"
        SMYX_OFFICE_WORKER_POSTURE_WARNING_ANALYSIS = "SMYX_OFFICE_WORKER_POSTURE_WARNING_ANALYSIS"
        SMYX_ADULT_FACIAL_FATIGUE_STRESS_INDEX_ANALYSIS = "SMYX_ADULT_FACIAL_FATIGUE_STRESS_INDEX_ANALYSIS"
        SMYX_FACIAL_HRV_TREND_MONITORING_ANALYSIS = "SMYX_FACIAL_HRV_TREND_MONITORING_ANALYSIS"
        SMYX_DRIVER_BLINK_FATIGUE_DETECTION_ANALYSIS = "SMYX_DRIVER_BLINK_FATIGUE_DETECTION_ANALYSIS"
        SMYX_DRIVER_HEAD_POSE_ABNORMALITY_ANALYSIS = "SMYX_DRIVER_HEAD_POSE_ABNORMALITY_ANALYSIS"
        SMYX_DRIVER_FLUSHING_SWEAT_DETECTION_ANALYSIS = "SMYX_DRIVER_FLUSHING_SWEAT_DETECTION_ANALYSIS"
        SMYX_LIVING_ALONE_RHYTHM_ANOMALY_ANALYSIS = "SMYX_LIVING_ALONE_RHYTHM_ANOMALY_ANALYSIS"
        SMYX_THERMAL_FEVER_SCREENING_ANALYSIS = "SMYX_THERMAL_FEVER_SCREENING_ANALYSIS"
        SMYX_NEONATAL_JAUNDICE_SCREENING_ANALYSIS = "SMYX_NEONATAL_JAUNDICE_SCREENING_ANALYSIS"
        SMYX_INFANT_STOOL_COLOR_ABNORMALITY_ANALYSIS = "SMYX_INFANT_STOOL_COLOR_ABNORMALITY_ANALYSIS"
        SMYX_ELDERLY_DRINKING_FREQUENCY_ANALYSIS = "SMYX_ELDERLY_DRINKING_FREQUENCY_ANALYSIS"
        SMYX_ELDERLY_TV_SEDENTARY_REMINDER_ANALYSIS = "SMYX_ELDERLY_TV_SEDENTARY_REMINDER_ANALYSIS"
        SMYX_CHILD_OUTDOOR_ACTIVITY_MONITOR_ANALYSIS = "SMYX_CHILD_OUTDOOR_ACTIVITY_MONITOR_ANALYSIS"
        SMYX_PREGNANT_POSTURE_FATIGUE_DETECTION_ANALYSIS = "SMYX_PREGNANT_POSTURE_FATIGUE_DETECTION_ANALYSIS"
        SMYX_AUTISM_STEREOTYPED_BEHAVIOR_DETECT_ANALYSIS = "SMYX_AUTISM_STEREOTYPED_BEHAVIOR_DETECT_ANALYSIS"
        SMYX_ELDERLY_LONELINESS_DEPRESSION_ANALYSIS = "SMYX_ELDERLY_LONELINESS_DEPRESSION_ANALYSIS"
        SMYX_CHILD_SEPARATION_ANXIETY_DETECTION_ANALYSIS = "SMYX_CHILD_SEPARATION_ANXIETY_DETECTION_ANALYSIS"
        SMYX_WORKPLACE_STRESS_HEATMAP_ANALYSIS = "SMYX_WORKPLACE_STRESS_HEATMAP_ANALYSIS"
        SMYX_CLASSROOM_ENGAGEMENT_ANALYSIS_ANALYSIS = "SMYX_CLASSROOM_ENGAGEMENT_ANALYSIS_ANALYSIS"
        SMYX_FAMILY_CONFLICT_INTENSITY_DETECT_ANALYSIS = "SMYX_FAMILY_CONFLICT_INTENSITY_DETECT_ANALYSIS"
        SMYX_DEPRESSION_BEHAVIORAL_MARKERS_ANALYSIS = "SMYX_DEPRESSION_BEHAVIORAL_MARKERS_ANALYSIS"
        SMYX_ANXIETY_BEHAVIOR_RECOGNITION_ANALYSIS = "SMYX_ANXIETY_BEHAVIOR_RECOGNITION_ANALYSIS"
        SMYX_PUBLIC_PLACE_GROUP_EMOTION_INDEX_ANALYSIS = "SMYX_PUBLIC_PLACE_GROUP_EMOTION_INDEX_ANALYSIS"
        SMYX_TEEN_SCREEN_ADDICTION_DETECTION_ANALYSIS = "SMYX_TEEN_SCREEN_ADDICTION_DETECTION_ANALYSIS"
        SMYX_ELDERLY_NIGHTMARE_STARTLE_DETECT_ANALYSIS = "SMYX_ELDERLY_NIGHTMARE_STARTLE_DETECT_ANALYSIS"
        SMYX_EMPLOYEE_EMOTION_FLUCTUATION_HR_ANALYSIS = "SMYX_EMPLOYEE_EMOTION_FLUCTUATION_HR_ANALYSIS"
        SMYX_TRAUMA_STRESS_BEHAVIOR_DETECTION_ANALYSIS = "SMYX_TRAUMA_STRESS_BEHAVIOR_DETECTION_ANALYSIS"
        SMYX_ELDERLY_LONELINESS_COMFORT_ANALYSIS = "SMYX_ELDERLY_LONELINESS_COMFORT_ANALYSIS"
        SMYX_CHILD_HAPPY_MOMENT_CAPTURE_ANALYSIS = "SMYX_CHILD_HAPPY_MOMENT_CAPTURE_ANALYSIS"
        SMYX_FAMILY_CONFLICT_AFTERCARE_SUGGEST_ANALYSIS = "SMYX_FAMILY_CONFLICT_AFTERCARE_SUGGEST_ANALYSIS"
        SMYX_CHILD_BEDTIME_SOOTHING_ANALYSIS = "SMYX_CHILD_BEDTIME_SOOTHING_ANALYSIS"
        SMYX_COMMUTER_FATIGUE_CARE_ANALYSIS = "SMYX_COMMUTER_FATIGUE_CARE_ANALYSIS"
        SMYX_PREGNANCY_EMOTION_SOOTHING_ANALYSIS = "SMYX_PREGNANCY_EMOTION_SOOTHING_ANALYSIS"
        SMYX_DEMENTIA_CONFUSION_ORIENTATION_ANALYSIS = "SMYX_DEMENTIA_CONFUSION_ORIENTATION_ANALYSIS"
        SMYX_REHAB_MOTIVATION_ENCOURAGEMENT_ANALYSIS = "SMYX_REHAB_MOTIVATION_ENCOURAGEMENT_ANALYSIS"
        SMYX_FISH_ABNORMAL_SWIMMING_DETECTION_ANALYSIS = "SMYX_FISH_ABNORMAL_SWIMMING_DETECTION_ANALYSIS"
        SMYX_FISH_SURFACE_SYMPTOM_DETECTION_ANALYSIS = "SMYX_FISH_SURFACE_SYMPTOM_DETECTION_ANALYSIS"
        SMYX_FISH_RESPIRATORY_RATE_MONITOR_ANALYSIS = "SMYX_FISH_RESPIRATORY_RATE_MONITOR_ANALYSIS"
        SMYX_FISH_FEEDING_ACTIVITY_ANALYSIS = "SMYX_FISH_FEEDING_ACTIVITY_ANALYSIS"
        SMYX_FISH_ISOLATION_DETECTION_ANALYSIS = "SMYX_FISH_ISOLATION_DETECTION_ANALYSIS"
        SMYX_FISH_EGG_INCUBATION_STAGE_ANALYSIS = "SMYX_FISH_EGG_INCUBATION_STAGE_ANALYSIS"
        SMYX_FISH_FRY_GROWTH_MEASUREMENT_ANALYSIS = "SMYX_FISH_FRY_GROWTH_MEASUREMENT_ANALYSIS"
        SMYX_FISH_GASPING_AMMONIA_WARNING_ANALYSIS = "SMYX_FISH_GASPING_AMMONIA_WARNING_ANALYSIS"
        SMYX_FISH_COLOR_BRIGHTNESS_ASSESSMENT_ANALYSIS = "SMYX_FISH_COLOR_BRIGHTNESS_ASSESSMENT_ANALYSIS"
        SMYX_FISH_FLASHING_SCRAPING_DETECTION_ANALYSIS = "SMYX_FISH_FLASHING_SCRAPING_DETECTION_ANALYSIS"
        SMYX_REPTILE_THERMOREGULATION_BEHAVIOR_ANALYSIS = "SMYX_REPTILE_THERMOREGULATION_BEHAVIOR_ANALYSIS"
        SMYX_REPTILE_FEEDING_REFUSAL_VOMITING_ANALYSIS = "SMYX_REPTILE_FEEDING_REFUSAL_VOMITING_ANALYSIS"
        SMYX_REPTILE_TAIL_LOSS_DETECTION_ANALYSIS = "SMYX_REPTILE_TAIL_LOSS_DETECTION_ANALYSIS"
        SMYX_TURTLE_PNEUMONIA_SYMPTOM_DETECTION_ANALYSIS = "SMYX_TURTLE_PNEUMONIA_SYMPTOM_DETECTION_ANALYSIS"
        SMYX_SNAKE_STOMATITIS_DETECTION_ANALYSIS = "SMYX_SNAKE_STOMATITIS_DETECTION_ANALYSIS"
        SMYX_FROG_SKIN_MOISTURE_ASSESSMENT_ANALYSIS = "SMYX_FROG_SKIN_MOISTURE_ASSESSMENT_ANALYSIS"
        SMYX_REPTILE_CIRCADIAN_ACTIVITY_ANALYSIS = "SMYX_REPTILE_CIRCADIAN_ACTIVITY_ANALYSIS"
        SMYX_REPTILE_EXCREMENT_ANALYSIS_ANALYSIS = "SMYX_REPTILE_EXCREMENT_ANALYSIS_ANALYSIS"
        SMYX_EGG_INCUBATION_MONITORING_ANALYSIS = "SMYX_EGG_INCUBATION_MONITORING_ANALYSIS"
        PLANT_WILTING_MONITORING = "PLANT_WILTING_MONITORING"


path: skills/smyx_common/scripts/config.yaml
content: env: prod

ApiEnum:
  [REDACTED_SECRET]
  database-url:
  base-url-open-api: "https://open.lifeemergence.com/smyx-open-api"
  base-url-open-h5: "http://livemonitor.lifeemergence.com"
  base-url-health: "https://lifeemergence.com/jeecg-boot-xzgz"

ConstantEnum:
  is-debug: false
  app--id: x1a3s4nwy1s2r4se
  current--tentant-code: "XIAN_ZHAO_GAN_ZHI"
  feishu-app--id: cli_a93d769369badcb1
  feishu-app--[REDACTED_SECRET] ARK_CLAW


path: skills/smyx_common/scripts/dao.py
content: #!/usr/bin/env python3
"""
本地化轻量级数据库封装
使用SQLite + SQLAlchemy ORM
支持基础CRUD操作，通过继承BaseDao快速实现各表的Dao层
"""
import datetime
import sys
from enum import Enum
from typing import Any, Dict, List, Optional, Type, TypeVar
from sqlalchemy import create_engine, Column, Integer, String, DateTime, func, Select, Table, MetaData, select, or_
from sqlalchemy.orm import declarative_base
from sqlalchemy.orm import sessionmaker, Session
from sqlalchemy.sql.expression import text

from skills.smyx_common.scripts.config import ConstantEnum, ApiEnum

from skills.smyx_common.scripts.util import StringUtil, DatetimeUtil, FileUtil

from skills.smyx_common.scripts.base import BaseMixin, BaseDao

# 基础模型类
Base = declarative_base()

# 泛型类型，用于返回对应模型实例
T = TypeVar('T', bound=Base)

meta = MetaData()

DATABASE_URL = ApiEnum.DATABASE_URL


class BaseModelMixin(BaseMixin):

    @classmethod
    def load(cls, source: dict):
        """
        获取源枚举
        :param source: 源
        :return: User
        """
        column_names = cls.__table__.columns.keys()
        user_dict = {k: source.get(StringUtil.snake_to_camel(k)) for k in column_names}
        user_dict["create_time"] = DatetimeUtil.parse(user_dict["create_time"])
        user_dict["update_time"] = DatetimeUtil.parse(user_dict["update_time"])
        model = cls(**user_dict)
        return model


class Dao(BaseDao):
    """
    基础Dao类，提供通用的CRUD操作
    子类只需配置__model__和__tablename__即可使用
    """
    __model__: Type[T] = None  # 对应的模型类，子类必须配置
    __tablename__: str = None  # 表名，子类必须配置

    def get_db_path(self, db_path):
        import os

        cwd = os.getcwd()
        workspace = os.path.dirname(cwd)
        workspace = os.path.dirname(workspace)
        workspace = os.environ.get('OPENCLAW_WORKSPACE', workspace)
        parent_dir = os.path.join(workspace, "data")
        FileUtil.mkdir(parent_dir)
        db_path = os.path.join(parent_dir, db_path)

        return db_path

    def __init__(self, db_path: str = None):
        """
        初始化Dao
        :param db_path: SQLite数据库文件路径
        """

        if not db_path:
            db_path = "smyx-common-claw.db"
            db_path = self.get_db_path(db_path)

        self.engine = create_engine(f"sqlite:///{db_path}", echo=False)

        # 创建会话工厂
        self.SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=self.engine)
        # 初始化表结构
        self._create_tables()
        self._alter_tables()

    def _create_tables(self) -> None:
        """创建所有表结构"""
        Base.metadata.create_all(bind=self.engine)

    def _alter_tables(self) -> None:
        """创建所有表结构"""
        sql_statement = "ALTER TABLE sys_user ADD COLUMN source_id INT;"

        # 3. 执行语句
        try:
            with self.engine.connect() as connection:
                connection.execute(text(sql_statement))
                connection.commit()  # 对于数据定义语言(DDL)，需要显式提交
        except Exception as e:
            connection.rollback()
            if len(e.args) and "duplicate column name" in e.args[0]:
                pass
            else:
                raise

    def get_session(self) -> Session:
        """获取数据库会话"""
        return self.SessionLocal()

    def save(self, model) -> T:
        """
        创建新记录
        :param kwargs: 字段键值对
        :return: 创建的模型实例
        """

        try:
            return self.add(
                model
            )

        except Exception as e:
            return self.update(
                model
            )

    def add(self, model) -> T:
        """
        创建新记录
        :param kwargs: 字段键值对
        :return: 创建的模型实例
        """
        session = self.get_session()
        try:
            session.add(model)
            session.commit()
            session.refresh(model)
            return model
        finally:
            session.close()

    def create(self, **kwargs) -> T:
        """
        创建新记录
        :param kwargs: 字段键值对
        :return: 创建的模型实例
        """
        instance = self.__model__(**kwargs)
        return self.add(instance)

    def get_by_id(self, record_id: int) -> Optional[T]:
        """
        根据ID查询记录
        :param record_id: 记录ID
        :return: 模型实例或None
        """
        session = self.get_session()
        try:
            return session.query(self.__model__).filter(self.__model__.id == record_id).first()
        finally:
            session.close()

    def get_by_username(self, username: str) -> Optional[T]:
        """
        根据ID查询记录
        :param record_id: 记录ID
        :return: 模型实例或None
        """
        session = self.get_session()
        try:
            or_(
                self.__model__.del_flag == 0,
                self.__model__.del_flag.is_(None)  # 关键：使用 .is_(None) 来判断 SQL 的 NULL
            )
            return session.query(self.__model__).filter(self.__model__.username == username,
                                                        or_(
                                                            self.__model__.del_flag == 0,
                                                            self.__model__.del_flag.is_(None)
                                                            # 关键：使用 .is_(None) 来判断 SQL 的 NULL
                                                        )).first()
        finally:
            session.close()

    def list(self, filters: Optional[Dict[str, Any]] = None, limit: Optional[int] = None,
             offset: Optional[int] = None) -> List[T]:
        """
        查询记录列表
        :param filters: 过滤条件字典，如{"name": "张三", "age": 18}
        :param limit: 最大返回数量
        :param offset: 偏移量
        :return: 模型实例列表
        """
        session = self.get_session()
        try:
            query = session.query(self.__model__)
            # .where(self.__model__.id != 2, self.__model__.id == 1))

            if filters:
                for key, value in filters.items():
                    query = query.filter(getattr(self.__model__, key) == value)

            if offset:
                query = query.offset(offset)
            if limit:
                query = query.limit(limit)

            return query.all()
        finally:
            session.close()

    def update(self, model) -> Optional[T]:
        """
        更新记录
        :param record_id: 记录ID
        :param kwargs: 要更新的字段键值对
        :return: 更新后的模型实例或None
        """
        session = self.get_session()
        try:
            instance = session.query(self.__model__).filter(self.__model__.id == model.id).first()
            if not instance:
                return None

            column_names = self.__model__.__table__.columns.keys()

            for key in column_names:
                value = getattr(model, key)
                setattr(instance, key, value)

            session.commit()
            session.refresh(instance)
            return instance
        finally:
            session.close()

    def modify(self, record_id: int, **kwargs) -> Optional[T]:
        """
        更新记录
        :param record_id: 记录ID
        :param kwargs: 要更新的字段键值对
        :return: 更新后的模型实例或None
        """
        session = self.get_session()
        try:
            instance = session.query(self.__model__).filter(self.__model__.id == record_id).first()
            if not instance:
                return None

            for key, value in kwargs.items():
                setattr(instance, key, value)

            session.commit()
            session.refresh(instance)
            return instance
        finally:
            session.close()

    def update_by_username(self, username: str, **kwargs) -> Optional[T]:
        """
        更新记录
        :param username: 记录ID
        :param kwargs: 要更新的字段键值对
        :return: 更新后的模型实例或None
        """
        session = self.get_session()
        try:
            instance = session.query(self.__model__).filter(self.__model__.username == username).first()
            if not instance:
                return None

            for key, value in kwargs.items():
                setattr(instance, key, value)

            session.commit()
            session.refresh(instance)
            return instance
        finally:
            session.close()

    def delete(self, record_id: int) -> bool:
        """
        删除记录
        :param record_id: 记录ID
        :return: 删除成功返回True，失败返回False
        """
        session = self.get_session()
        try:
            instance = session.query(self.__model__).filter(self.__model__.id == record_id).first()
            if not instance:
                return False

            session.delete(instance)
            session.commit()
            return True
        finally:
            session.close()

    def count(self, filters: Optional[Dict[str, Any]] = None) -> int:
        """
        统计记录数量
        :param filters: 过滤条件字典
        :return: 记录数量
        """
        session = self.get_session()
        try:
            query = session.query(func.count(self.__model__.id))

            if filters:
                for key, value in filters.items():
                    query = query.filter(getattr(self.__model__, key) == value)

            return query.scalar()
        finally:
            session.close()


class User(Base, BaseModelMixin):
    """用户模型"""
    __tablename__ = "sys_user"

    id = Column(String(32), primary_key=True, index=True)
    source_id = Column(String(32), comment="源头id")
    username = Column(String(100), unique=True, index=True, nullable=False, comment="用户名")
    email = Column(String(45), unique=True, index=True, comment="邮箱")
    birthday = Column(DateTime, unique=True, index=True, comment="邮箱")
    sex = Column(Integer, comment="性别")
    age = Column(Integer, comment="年龄")
    [REDACTED_SECRET]), comment="token")
    open_token = Column(String(1000), comment="开放token")
    source = Column(String(50), comment="token")
    del_flag = Column(Integer, comment="是否删除", default=0)
    create_time = Column(DateTime, default=func.now(), comment="创建时间")
    update_time = Column(DateTime, default=func.now(), onupdate=func.now(), comment="更新时间")

    SourceEnum = ConstantEnum.SourceEnum


class UserDao(Dao):
    """用户Dao，继承BaseDao即可拥有所有基础CRUD功能"""
    __model__ = User
    __tablename__ = "users"


if __name__ == "__main__":
    pass


path: skills/smyx_common/scripts/skill.py
content: #!/usr/bin/env python3
import os
import sys
# import subprocess

from .config import ApiEnum as ApiEnumBase, ConstantEnum
from .base import BaseSkill
from .util import FileUtil

from .api_service import ApiService


class Skill(BaseSkill, ApiService):

    def __init__(self):
        super().__init__()


class AgentSkill(BaseSkill, ApiService):

    def __init__(self):
        super().__init__()

    def ai_chat(self, prompt: str, session_id: str = None, timeout: int = 120):
        """
        通过 subprocess 调用 openclaw agent 命令

        Args:
            prompt: 分析提示
            session_id: 会话 ID（可选）
            timeout: 超时时间（秒）

        Returns:
            分析结果或会话 ID
        """
        import uuid

        # 生成唯一会话 ID
        if not session_id:
            entry_script = sys.argv[0]
            abs_entry_script = os.path.abspath(entry_script)
            main_name = FileUtil.get_name(abs_entry_script)
            session_id = f"{main_name}--{uuid.uuid4()}"

        ConstantEnum.is_debug() and print(f"🤖 正在调用 openclaw agent (会话：{session_id})..., prompt:{prompt}")

        # 构建命令
        cmd = [
            # "openclaw",
            # "agent",
            # "-m", str(prompt),
            # "--session-id", session_id,
            # "--thinking", "minimal",
            # "--timeout", str(timeout)
        ]

        ConstantEnum.is_debug() and print(f"🤖 正在调用 openclaw agent 执行命令{' '.join(cmd)}")

        try:
            # 执行命令
            # result = subprocess.run(
            #     cmd,
            #     capture_output=True,
            #     text=True,
            #     timeout=timeout + 10
            # )

            result: dict = {}

            if result.stderr:
                ConstantEnum.is_debug() and print(f"🤖 正在调用 openclaw agent 执行错误:{result.stderr}")
                return

            output = result.stdout
            ConstantEnum.is_debug() and print(f"🤖 正在调用 openclaw agent 执行成功:{output}")

            return output

        # except subprocess.TimeoutExpired as e:
        #     ConstantEnum.is_debug() and print(f"🤖 正在调用 openclaw agent 超时（{timeout}秒），任务可能仍在后台运行:{e}")
        except Exception as e:
            ConstantEnum.is_debug() and print(f"🤖 正在调用 openclaw agent 执行错误:{e}")


skill = Skill()


path: skills/smyx_common/scripts/util.py
content: #!/usr/bin/env python3
import json
import os
import traceback

import requests
from .config import ApiEnum, ConstantEnum, sys, YamlUtil

from .base import BaseUtil
import time
import logging
from typing import Any, Callable, Optional, TypeVar, Dict
import pydash as _

if ConstantEnum.is_debug():
    import http.client

    # 【关键代码】开启调试模式
    http.client.HTTPConnection.debuglevel = 1
    # 可选：如果你希望日志更整洁，可以配合 logging 模块（否则打印会比较乱）
    import logging

    logging.basicConfig()
    logging.getLogger().setLevel(logging.DEBUG)
    requests_log = logging.getLogger("urllib3")
    requests_log.setLevel(logging.DEBUG)
    requests_log.propagate = True


class StringUtil(BaseUtil):

    @staticmethod
    def camel_to_snake(name):
        import re
        s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
        return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()

    @staticmethod
    def snake_to_pascal(name):
        import re
        name = re.sub(r'^([a-z])', lambda m: m.group(1).upper(), name)
        return re.sub(r'_([a-z])', lambda m: m.group(1).upper(), name)

    @staticmethod
    def snake_to_camel(name):
        import re
        # 逻辑：匹配 '_[a-z]' (下划线+小写字母)，将其替换为对应的大写字母（去掉下划线）
        return re.sub(r'_([a-z])', lambda m: m.group(1).upper(), name)


class FileUtil(BaseUtil):

    @staticmethod
    def get_fullname(path):
        try:
            return os.path.basename(path)
        except Exception as e:
            CommonUtil.trace_exception_stack(e)
            return ""

    @staticmethod
    def get_name(path):
        try:
            return os.path.splitext(os.path.basename(path))[0]
        except Exception as e:
            CommonUtil.trace_exception_stack(e)

    @staticmethod
    def get_ext(path):
        try:
            return os.path.splitext(os.path.basename(path))[1]
        except Exception as e:
            CommonUtil.trace_exception_stack(e)

    @staticmethod
    def open(path):
        try:
            return open(path, 'w', encoding='utf-8')
        except Exception as e:
            CommonUtil.trace_exception_stack(e)

    @staticmethod
    def mkdir(path):
        try:
            os.makedirs(path, exist_ok=True)
        except Exception as e:
            CommonUtil.trace_exception_stack(e)


class JsonUtil(BaseUtil):

    @staticmethod
    def stringify(json_obj, default_str=""):
        try:
            return json.dumps(json_obj, ensure_ascii=False, indent=2)
        except Exception as e:
            CommonUtil.trace_exception_stack(e)
            pass
        return default_str

    @staticmethod
    def parse(json_str, default_json={}):
        try:
            return json.loads(json_str)
        except Exception as e:
            # CommonUtil.trace_exception_stack(e)
            pass
        return default_json


class NumberUtil(BaseUtil):
    INT_MAX = 2147483647


class CommonUtil(BaseUtil):

    @staticmethod
    def trace_exception_stack(e):
        if ConstantEnum.is_debug():
            print(f"❌ 错误描述: {str(e)}, 堆栈跟踪:")
            traceback.print_stack()

    @staticmethod
    def polling(
            action: Callable[[], Any],
            check_condition: Callable[[Any], bool],
            on_success: Optional[Callable[[Any], None]] = None,
            on_retry: Optional[Callable[[Any, int], None]] = None,
            on_error: Optional[Callable[[Exception], None]] = None,
            interval: float = 1.0,
            max_attempts: int = 5,
            description: str = "轮询任务"
    ) -> Optional[Any]:
        """
        通用的轮询处理函数

        :param action:
            [必填] 执行动作的回调函数。
            例如：发送 HTTP 请求、查询数据库状态等。
            必须返回一个结果对象供 check_condition 使用。

        :param check_condition:
            [必填] 检查是否结束的回调函数。
            接收 action 的返回值，返回 True 表示“满足结束条件”，False 表示“继续轮询”。
            例如：lambda res: res.get('need_refresh') is False

        :param on_success:
            [可选] 当 check_condition 返回 True 时执行的回调（通常用于记录日志或处理最终数据）。

        :param on_retry:
            [可选] 当需要继续轮询时执行的回调。
            参数：(当前结果, 当前尝试次数)。可用于打印进度。

        :param on_error:
            [可选] 当 action 抛出异常时执行的回调。
            参数：(异常对象)。

        :param interval:
            每次轮询之间的等待时间（秒）。

        :param max_attempts:
            最大尝试次数，防止死循环。

        :param description:
            任务描述，用于日志输出。

        :return:
            如果成功，返回 action 的最后一次返回值；如果超时或失败，返回 None。
        """

        attempts = 0

        ConstantEnum.is_debug() and print(f"🚀 开始执行 [{description}]...")

        while attempts < max_attempts:
            attempts += 1

            try:
                # 1. 执行动作
                result = action()
                last_result = result

                # 2. 检查条件
                if check_condition(result):
                    ConstantEnum.is_debug() and print(
                        f"✅ [{description}] 成功！条件已满足 (尝试次数: {attempts}, 耗时{interval * attempts}秒)")
                    if on_success:
                        on_success(result)
                    return result

                # 3. 条件未满足，准备重试
                if on_retry:
                    on_retry(result, attempts)
                else:
                    # 默认日志行为
                    ConstantEnum.is_debug() and print(
                        f"⏳ [{description}] 条件未满足，{interval}秒后重试... ({attempts}/{max_attempts}, 耗时{interval * attempts}秒)")

                time.sleep(interval)

            except Exception as e:
                # 4. 异常处理
                if on_error:
                    on_error(e)
                else:
                    # 默认错误行为：打印错误并继续
                    logging.error(f"❌ [{description}] 发生异常: {e}")
                    ConstantEnum.is_debug() and print(f"⚠️ [{description}] 遇到错误，{interval}秒后重试...")

                time.sleep(interval)

        # 5. 超时处理
        ConstantEnum.is_debug() and print(f"⚠️ [{description}] 失败：达到最大尝试次数 ({max_attempts})，强制停止。")
        return None

    @staticmethod
    def is_windows():
        return sys.platform.startswith('win')

    @staticmethod
    def is_empty(data):
        # 1. 如果是 None (对应 JSON 的 null)
        if data is None:
            return True

        # 2. 如果是字典或列表，且长度为 0 (对应 {} 或 [])
        if isinstance(data, (dict, list)) and len(data) == 0:
            return True


from datetime import date, datetime


class DatetimeUtil(BaseUtil):
    FORMAT__DATETIME = "%Y-%m-%d %H:%M:%S"

    @staticmethod
    def now_str():
        return DatetimeUtil.format(DatetimeUtil.now())

    @staticmethod
    def today_str():
        return DatetimeUtil.format_date(DatetimeUtil.today())

    @staticmethod
    def now():
        return datetime.now()

    @staticmethod
    def today():
        return DatetimeUtil.now().replace(hour=0, minute=0, second=0, microsecond=0)

    @staticmethod
    def format(date):
        return date.strftime('%Y-%m-%d %H:%M:%S') if type(date) == datetime else date

    @staticmethod
    def format_date(date):
        return date.strftime('%Y-%m-%d') if type(date) == datetime else date

    @staticmethod
    def parse(date_str):
        if type(date_str) == int:
            return datetime.fromtimestamp(date_str)
        return datetime.strptime(date_str, DatetimeUtil.FORMAT__DATETIME) if type(date_str) == str else date_str

    @staticmethod
    def timestamp(date=datetime.now()):
        return int(date.timestamp() * 1000)


class RequestUtil(BaseUtil):
    BASE_URL = ApiEnum.BASE_URL_OPEN_API
    AUTHORIZATION_RETRY_COUNT_MAX = 3
    authorization_retry_count = 0

    @classmethod
    def http_post(cls, url, data=None, params=None, headers=None, *args, **argss):
        return cls.http_request("post", url, data=data, params=params, headers=headers, *args, **argss)

    @classmethod
    def http_put(cls, url, data=None, params=None, headers=None, *args, **argss):
        return cls.http_request("put", url, data=data, params=params, headers=headers, *args, **argss)

    @classmethod
    def http_delete(cls, url, data=None, params=None, headers=None, *args, **argss):
        return cls.http_request("delete", url, data=data, params=params, headers=headers, *args, **argss)

    @classmethod
    def http_get(cls, url, params=None, headers=None, *args, **argss):
        return cls.http_request("get", url, params=params, headers=headers, *args, **argss)

    @classmethod
    def http_request(cls, method, url, data=None, params=None, headers=None, options=None, *args,
                     timeout=ApiEnum.DEFAULT__REQUEST_TIMEOUT, **argss):
        def _get_or_create_user(username):
            _url = ApiEnum.BASE_URL_HEALTH + "/sys/phoneLogin"
            open_id = username
            _data = {
                "silent": 1,
                "register": 1,
                "openId": open_id,
                "mobile": username,
                "source": ConstantEnum.DEFAULT__SKILL_HUB_NAME
            }
            try:
                _response = requests.post(_url, json=_data)
                if _response.status_code == 200:
                    _response_json = _response.json()
                    if _response_json and _response_json.get("success"):
                        return _response_json and _response_json.get("result")
            except Exception as _e:
                CommonUtil.trace_exception_stack(_e)
            return {}

        try:
            headers = headers or {}
            if not url.startswith("https://") and not url.startswith("http://"):
                url = cls.BASE_URL + url
            headers['App-Id'] = ConstantEnum.APP__ID
            # ConstantEnum.CURRENT__USER_NAME = ConstantEnum.CURRENT__OPEN_ID = "ou_86fdd8e0d5f116c18a9dd550abefe6d2"
            current__user_name = ApiEnum.API_SECRET_KEY or ConstantEnum.CURRENT__USER_NAME or ConstantEnum.CURRENT__OPEN_ID
            found_user = None
            if (not ApiEnum.TOKEN or not ApiEnum.OPEN_TOKEN) and current__user_name:
                try:
                    from .dao import UserDao, User
                    user_dao = UserDao()
                    found_user = user_dao.get_by_username(current__user_name)
                    if found_user:
                        ApiEnum.[REDACTED_SECRET]
                        ApiEnum.OPEN_TOKEN = found_user.open_token
                    if not ApiEnum.TOKEN or not ApiEnum.OPEN_TOKEN:
                        new_current_user = _get_or_create_user(current__user_name)
                        if new_current_user:
                            ApiEnum.[REDACTED_SECRET]"token")
                            ApiEnum.OPEN_TOKEN = new_current_user.get("openToken")

                            current_user_info = new_current_user.get("userInfo")
                            if current_user_info:
                                current_user_info["token"] = new_current_user.get("token")
                                current_user_info["openToken"] = new_current_user.get(
                                    "openToken")
                                user_model = User.load(current_user_info)

                                user = user_dao.save(
                                    user_model
                                )

                except Exception as e:
                    CommonUtil.trace_exception_stack(e)
                    raise

            headers.setdefault("X-Access-Token", ApiEnum.TOKEN)
            headers.setdefault("X-Api-Key", ApiEnum.API_SECRET_KEY)
            headers.setdefault("Authorization", ApiEnum.OPEN_TOKEN)

            data = data or {}
            params = params or {}
            options = options or {}
            ConstantEnum.CURRENT__TENTANT_CODE and data.setdefault('tenantCode', ConstantEnum.CURRENT__TENTANT_CODE)
            ConstantEnum.DEFAULT__SKILL_HUB_NAME and data.setdefault('skillHubName',
                                                                     ConstantEnum.DEFAULT__SKILL_HUB_NAME)
            ConstantEnum.DEFAULT__SKILL_PLATFORM_NAME and data.setdefault('skillPlatform',
                                                                          ConstantEnum.DEFAULT__SKILL_PLATFORM_NAME)
            if current__user_name:
                data.setdefault('pnaUserName', current__user_name)

            if bool(options.get("dataAsParams")):
                params.update(data)

            # 安全打印：不打印完整 headers（避免巨大的 token 导致输出缓冲区溢出）
            # 同时处理 v 为 None 的情况，避免 len(None) 报错
            safe_headers = {}
            for k, v in headers.items():
                if v is None:
                    safe_headers[k] = "None"
                elif isinstance(v, (dict, list)):
                    safe_headers[k] = type(v).__name__
                elif len(v) > 30:
                    safe_headers[k] = v[:20] + "..."
                else:
                    safe_headers[k] = v
            ConstantEnum.is_debug() and print(f"🔄 请求拦截, URL:{url}", "method", method, "params", params, "data",
                                              data, "headers", safe_headers,
                                              "options", options,
                                              "timeout",
                                              timeout)
            response = requests.request(method, url, *args, json=data, params=params, headers=headers,
                                        timeout=int(timeout), **argss)
            response_text0 = response.text
            response_text = response_text0 if ConstantEnum.is_debug() else response
            status_code = response.status_code
            if current__user_name == "13800000000":
                status_code = 402
            if status_code == 401 and cls.authorization_retry_count < cls.AUTHORIZATION_RETRY_COUNT_MAX:
                # print(f"❌ 请求拦截, 鉴权失败:{response_text}, url:{url}", "method", method, "params", params,
                #       "data",
                #       data,
                #       "headers",
                #       headers,
                #       "timeout",
                #       timeout)
                ApiEnum.[REDACTED_SECRET] = None
                if found_user:
                    found_user.[REDACTED_SECRET] = None
                    user_dao.update(found_user)
                cls.authorization_retry_count += 1
                return cls.http_request(method, url, data, params, headers, options, *args, timeout=timeout, **argss)
            elif status_code == 402:
                ConstantEnum.is_debug() and print(f"❌ 请求拦截, 使用失败:{response_text}, url:{url}", "method", method,
                                                  "params", params,
                                                  "data",
                                                  data,
                                                  "headers",
                                                  headers,
                                                  "timeout",
                                                  timeout)
                return f'''⚠️ 因账户余额不足, 技能使用失败, 请按照如下步骤进行充值: 
👉 1. 先输入命令 "安装支付技能 smyx-payment", 等待安装完成. (如果已经安装支付技能过则忽略此步骤)
👉 2. 再输入命令 "技能账户充值", 然后跟随系统提示操作后即可继续使用技能.
{response_text0 or ""}
'''
                # f'请先输入命令 "技能账户充值", 请先确确保安装生命涌现支付技能 请先输入命令 "技能账户充值", 跟随系统提示进行充值后即可继续使用技能(). {response_text0 or ""}')
            elif status_code != 200:
                raise requests.exceptions.RequestException(
                    response, response=response)
            response_json = response.json()
            if not bool(response_json['success']):
                raise requests.exceptions.RequestException(
                    response, response=response)
            response_json_data = response_json.get("data", response_json.get("result"))
            response_json_data = response_json_data.get("records") if response_json_data and type(
                response_json_data) == dict and "records" in response_json_data else response_json_data
            ConstantEnum.is_debug() and print(f"✅ 请求拦截, 成功:{response_text}, url:{url}", "method", method,
                                              "params", params,
                                              "data",
                                              data,
                                              "headers",
                                              headers,
                                              "timeout",
                                              timeout)
            return response_json_data
        except Exception as e:
            CommonUtil.trace_exception_stack(e)
            response_text = _.get(e.args, '0.text')
            ConstantEnum.is_debug() and print(
                f"❌ 请求拦截, 失败: {e}, e.response.text: {response_text}, url:{url}",
                "method",
                method,
                "params",
                params,
                "data", data, "headers",
                "response", hasattr(e, 'response') and e.response,
                headers,
                "timeout",
                timeout)
            raise


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