Skill: 18072937735/smyx-staff-absence-detection-analysis (version 1.0.4)

--- name: [REDACTED_SECRET] description: "Real-time monitoring of personnel on-duty status in specific areas based on computer vision and human pose estimation, automatically detects abnormal statuses such as leaving posts and absent from work, supports custom threshold settings, and triggers early warning immediately when abnormality is detected. | 人员离岗实时监测技能，基于计算机视觉与人体姿态估计算法，实时监测特定区域内人员的在岗状态，自动判断离岗、缺岗等异常状态，支持自定义判定阈值，异常发生立即触发预警，适用于工厂车间、监控室、服务窗口等岗位监管场景" version: "1.0.1" --- # Staff Absence Detection Skill | 人员离岗实时监测技能 Tailored specifically for post management supervision scenarios in industrial production, security monitoring, service windows and other work scenarios, this skill is equipped with a high-precision computer vision algorithm combined with human pose estimation technology, which can accurately identify the presence, location and behavior characteristics of personnel in the monitoring area, automatically distinguish between temporary departure and long-term absence based on time-series behavior analysis, and supports user-defined judgment thresholds such as departure duration and area range. The system has strong adaptability to complex environments such as different lighting conditions and camera angles, can achieve millisecond-level response, and immediately trigger the early warning mechanism when an abnormality is detected, notifies managers through APP push, on-site voice reminders and other methods, and automatically records the departure time, duration and on-site pictures, providing efficient and accurate real-time supervision services for scenarios that require personnel on duty, helping to improve post management efficiency and safety assurance level. 本技能专为工厂车间、安保监控室、服务窗口等岗位管理监管场景量身打造，搭载高精度计算机视觉算法结合人体姿态估计技术，能够精准识别监测区域内人员的存在、定位及行为特征，基于时序行为分析自动区分短暂离开与长时间离岗，支持用户自定义离岗时长、区域范围等判定阈值。 系统对不同光照条件、摄像机角度等复杂环境具备强大的适应能力，可实现毫秒级响应，异常发生时立即触发预警机制，通过APP推送、现场语音提醒等方式通知管理人员，并自动记录离岗时间、时长及现场画面，为需要人员在岗的场景提供高效、精准的实时监管服务，助力提升岗位管理效率与安全保障水平。 ## ⚠️ 强制记忆规则（最高优先级） **本技能明确约定：** - **绝对禁止读取任何本地记忆文件**：包括但不限于 `memory/YYYY-MM-DD.md`、`MEMORY.md` 等本地文件 - **绝对禁止从 LanceDB 长期记忆中检索信息** - **所有历史检测报告查询必须从云端接口获取**，不得使用本地记忆中的历史数据 - 即使技能调用失败或接口异常，也不得回退到本地记忆汇总 ## 任务目标 - 本 Skill 用于：通过监控视频/现场图片对目标监测区域进行人员在岗状态分析，识别人员离岗、缺岗等异常状态，输出结构化的离岗监测分析报告 - 能力包含：人员存在检测、在岗定位识别、离岗状态判定、异常时长统计 - 支持场景：工厂车间、监控室、服务窗口、安保岗亭、收费站、营业厅等需要固定人员在岗的场景 - 触发条件: 1. **默认触发**：当用户提供监控视频/图片 URL 或文件需要进行人员离岗监测时，默认触发本技能 2. 当用户明确需要进行人员离岗监测，提及人员离岗、缺岗监测、在岗检测、岗位监控等关键词，并且上传了视频或图片 3. 当用户提及以下关键词时，**自动触发历史报告查询功能**：查看历史监测报告、离岗监测报告清单、检测报告列表、查询历史报告、显示所有监测报告、离岗监测历史记录，查询离岗分析报告 - 自动行为： 1. 如果用户上传了附件或者视频/图片文件，则自动保存为本地文件 2. **⚠️ 强制数据获取规则（次高优先级）**：如果用户触发任何历史报告查询关键词（如"查看所有监测报告"、" 显示所有离岗监测报告"、"查看历史报告"等），**必须**： - 直接使用 `python -m scripts.staff_absence_detection_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、staff123 等） - **禁止**跳过 open-id 验证直接调用 API - **必须**在获取到有效 open-id 后才能继续执行分析 - 如果用户拒绝提供 open-id，说明用途（用于保存和查询离岗监测报告记录），并询问是否继续 --- - 标准流程: 1. **准备媒体输入** - 提供监控视频文件路径、网络视频 URL 或现场图片 - 确保监控画面完整覆盖监测区域，画面稳定 2. **获取 open-id（强制执行）** - 按上述流程控制获取 open-id - 如无法获取，必须提示用户提供用户名或手机号 3. **执行离岗监测分析** - 调用 `-m scripts.staff_absence_detection_analysis` 处理素材（**必须在技能根目录下运行脚本**） - 参数说明: - `--input`: 本地视频/图片文件路径 - `--url`: 网络视频/图片 URL 地址（API 服务自动下载） - `--media-type`: 媒体类型，可选值：video/image，默认 video - `--confidence-threshold`: 置信度阈值，低于该分值不输出，默认 0.5 - `--absence-threshold`: 离岗判定阈值（秒），超过该时长判定为异常离岗，默认 300 秒（5分钟） - `--open-id`: 当前用户的 open-id（必填，按上述流程获取） - `--list`: 显示离岗监测历史分析报告列表清单（可以输入起始日期参数过滤数据范围） - `--api-key`: API 访问密钥（可选） - `--api-url`: API 服务地址（可选，使用默认值） - `--detail`: 输出详细程度（basic/standard/json，默认 json） - `--output`: 结果输出文件路径（可选） 4. **查看分析结果** - 接收结构化的人员离岗监测报告 - 包含：检测基本信息、在岗状态、异常离岗判定、离岗时长统计 ## 资源索引 - 必要脚本：见 [scripts/staff_absence_detection_analysis.py](scripts/staff_absence_detection_analysis.py)(用途：调用 API 进行人员离岗监测，本地文件上传，网络 URL 由 API 服务自动下载) - 配置文件：见 [scripts/config.py](scripts/config.py)(用途：配置 API 地址、默认参数和媒体格式限制) - 领域参考：见 [references/api_doc.md](references/api_doc.md)(何时读取：需要了解 API 接口详细规范和错误码时) ## 注意事项 - 仅在需要时读取参考文档，保持上下文简洁 - 支持格式：视频支持 mp4/avi/mov 格式，图片支持 jpg/png/jpeg 格式，最大 10MB - API 密钥可选，如果通过参数传入则必须确保调用鉴权成功，否则忽略鉴权 - 分析结果仅供岗位管理参考，具体处置请结合实际管理制度 - 禁止临时生成脚本，只能用技能本身的脚本 - 传入的网络地址参数，不需要下载本地，默认地址都是公网地址，api 服务会自动下载 - 当显示历史监测报告清单的时候，从数据 json 中提取字段 reportImageUrl 作为超链接地址，使用 Markdown 表格格式输出，包含" 报告名称"、"检测时间"、"异常离岗次数"、"点击查看"四列，其中"报告名称"列使用`人员离岗监测分析报告-{记录id}`形式拼接, " 点击查看"列使用 `[🔗 查看报告](reportImageUrl)` 格式的超链接，用户点击即可直接跳转到对应的完整报告页面。 - 表格输出示例： | 报告名称 | 检测时间 | 异常离岗次数 | 点击查看 | |----------|----------|--------------|----------| | 人员离岗监测分析报告-20260415103000001 | 2026-04-15 10:30:00 | 2 | [🔗 查看报告](https://example.com/report?id=xxx) | ## 使用示例 ```bash # 监测本地监控视频（以下只是示例，禁止直接使用openclaw-control-ui 作为 open-id） python -m scripts.staff_absence_detection_analysis --input /path/to/monitor.mp4 --media-type video --absence-threshold 300 --open-id openclaw-control-ui # 监测现场图片（以下只是示例，禁止直接使用openclaw-control-ui 作为 open-id） python -m scripts.staff_absence_detection_analysis --input /path/to/monitor.jpg --media-type image --confidence-threshold 0.6 --open-id openclaw-control-ui # 监测网络监控视频（以下只是示例，禁止直接使用openclaw-control-ui 作为 open-id） python -m scripts.staff_absence_detection_analysis --url https://example.com/monitor.mp4 --media-type video --absence-threshold 180 --open-id openclaw-control-ui # 显示历史监测报告/显示监测报告清单列表/显示历史离岗监测报告（自动触发关键词：查看历史检测报告、历史报告、检测报告清单等） python -m scripts.staff_absence_detection_analysis --list --open-id openclaw-control-ui # 输出精简报告 python -m scripts.staff_absence_detection_analysis --input video.mp4 --media-type video --open-id your-open-id --detail basic # 保存结果到文件 python -m scripts.staff_absence_detection_analysis --input video.mp4 --media-type video --open-id your-open-id --output result.json ```

path: references/api_doc.md
content: # 人员离岗实时监测 API 接口文档

## 接口概览

人员离岗实时监测功能依赖云端 API 服务进行计算机视觉分析，以下是 API 接口规范说明。

## 认证方式

- API Key 通过请求头 `Authorization: Bearer {api_key}` 传递
- 未配置 API Key 时使用默认共享配额

## 主要接口

### 1. 离岗监测分析接口

**URL**: `{base_url}/api/v1/ai-analysis/personnel-leave-post-detection`

**Method**: `POST`

**请求参数**:

- `input_type`: `file` 或 `url`
- `media_type`: `video` 或 `image`
- `confidence_threshold`: 置信度阈值，默认 0.5
- `absence_threshold`: 离岗判定阈值（秒），默认 300
- `scene_code`: `PERSONNEL_LEAVE_POST_MONITORING`（固定值）
- `open_id`: 用户标识

**响应格式**: JSON

```json
{
  "code": 0,
  "message": "success",
  "data": {
    "id": "request-id",
    "detection_time": "2026-04-15 10:30:00",
    "detection": {
      "post_status": "leave_post",
      "abnormal_absence_count": 2,
      "total_absence_duration": 650,
      "status_stats": [
        {
          "status": "on_duty",
          "count": 1,
          "total_duration": 1200
        },
        {
          "status": "leave_post",
          "count": 2,
          "total_duration": 650
        },
        {
          "status": "temporary_leave",
          "count": 1,
          "total_duration": 80
        }
      ]
    }
  }
}
```

### 2. 查询历史报告列表接口

**URL**: `{base_url}/api/v1/ai-analysis/list`

**Method**: `GET`

**请求参数**:

- `scene_code`: `PERSONNEL_LEAVE_POST_MONITORING`
- `open_id`: 用户标识
- `start_time`: 起始时间（可选）
- `end_time`: 结束时间（可选）

### 3. 获取报告详情接口

**URL**: `{base_url}/api/v1/ai-analysis/detail/{request_id}`

**Method**: `GET`

## 错误码说明

| 错误码  | 说明      |
|------|---------|
| 0    | 成功      |
| 1001 | 参数错误    |
| 1002 | 文件格式不支持 |
| 1003 | 文件过大    |
| 2001 | 认证失败    |
| 2002 | 配额不足    |
| 3001 | 分析失败    |
| 4001 | 记录不存在   |

## 注意事项

1. 最大支持文件大小：10MB
2. 支持视频格式：mp4、avi、mov
3. 支持图片格式：jpg、png、jpeg
4. 分析时间根据文件大小不同，通常在 3-30 秒


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__MEDIA_TYPE = "video"
    DEFAULT__CONFIDENCE_THRESHOLD = 0.5
    DEFAULT__ABSENCE_THRESHOLD = 300  # 默认离岗阈值 300秒(5分钟)

    @classmethod
    def init(cls, config=None):
        super().init(config)
        ConstantEnumParent.DEFAULT__SCENE_CODE = SceneCodeEnum.PERSONNEL_LEAVE_POST_MONITORING.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/staff_absence_detection_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

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


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 SUPPORTED_FORMATS:
        raise ValueError(f"不支持的文件格式，支持的格式: {', '.join(SUPPORTED_FORMATS)}")

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

    return True


def analyze_media(input_path=None, url=None, media_type=None, confidence_threshold=None, absence_threshold=None,
                  api_url=None, api_key=None,
                  output_level=None):
    """调用API进行人员离岗检测"""
    if not input_path and not url:
        raise ValueError("必须提供本地媒体路径(--input)或网络媒体URL(--url)")

    # 设置参数
    if media_type:
        ConstantEnum.DEFAULT__MEDIA_TYPE = media_type
    if confidence_threshold:
        ConstantEnum.DEFAULT__CONFIDENCE_THRESHOLD = confidence_threshold
    if absence_threshold:
        ConstantEnum.DEFAULT__ABSENCE_THRESHOLD = absence_threshold

    try:
        input_path = input_path or url
        # 携带额外参数
        params = {}
        if confidence_threshold:
            params["confidence_threshold"] = confidence_threshold
        if absence_threshold:
            params["absence_threshold"] = absence_threshold
        return skill.get_output_analysis(input_path, params)

    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):
    # if not open_id:
    #     raise ValueError("必须提供本用户的OpenId/UserId")

    try:
        output_content = skill.get_output_analysis_list(open_id=open_id)
        return output_content

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


def get_analysis_export_url(request_id=None):
    """调用API分析视频"""
    if not request_id:
        return ""
    return ApiEnum.DETAIL_EXPORT_URL + request_id


def format_result(result, output_level="standard", media_type="video", confidence_threshold=0.5, absence_threshold=300):
    """格式化输出结果"""
    category_map = {
        "on_duty": "在岗",
        "leave_post": "异常离岗",
        "temporary_leave": "短暂离开"
    }

    if output_level == "json":
        result_id = None
        if result is not None:
            result_json = result
            result_id = result_json.get('id', {})
            result_json = json.dumps(result_json.get('postDetectionResponse', {}), ensure_ascii=False, indent=2)
        else:
            return "⚠️ 暂无分析结果"
        return f"""
📊 人员离岗监测分析结构化结果
{result_json}
""", result_id
    elif output_level == "basic":
        # 精简输出
        data = result.get('data', {})
        detection = data.get('detection', {})
        return f"""
📊 人员离岗监测报告
{'=' * 40}
监测区域在岗状态: {category_map.get(detection.get('post_status', 'on_duty'), detection.get('post_status'))}
异常离岗次数: {detection.get('abnormal_absence_count', 0)} 次
累计离岗时长: {detection.get('total_absence_duration', 0)} 秒
        """
    elif output_level == "standard":
        # 标准输出
        data = result.get('data', {})
        detection = data.get('detection', {})

        objects = "\n".join([
            f"  📍 {category_map.get(obj.get('status'), obj.get('status'))}: {obj.get('count', 0)} 段，累计时长: {obj.get('total_duration', 0)} 秒"
            for obj in detection.get('status_stats', [])])

        return f"""
📊 人员离岗实时监测分析报告
{'=' * 50}
⏰ 检测时间: {data.get('detection_time', '未知')}
📹 素材类型: {media_type}
🎯 置信度阈值: {confidence_threshold}
⏱️  离岗判定阈值: {absence_threshold} 秒

🔍 监测结果:
  当前在岗状态: {category_map.get(detection.get('post_status', 'on_duty'), detection.get('post_status'))}
  异常离岗总次数: {detection.get('abnormal_absence_count', 0)} 次
  累计异常离岗时长: {detection.get('total_absence_duration', 0)} 秒

  各状态统计:
{objects if objects else '  ✅ 未检测到异常离岗记录'}
{'=' * 50}
> 注：本报告仅供岗位管理参考，具体处置请结合实际管理制度。
        """
    else:
        # 完整输出（JSON格式）
        return json.dumps(result, ensure_ascii=False, indent=2)


def main():
    parser = argparse.ArgumentParser(description="人员离岗实时监测工具")
    parser.add_argument("--input", help="本地视频/图片文件路径")
    parser.add_argument("--url", help="网络视频/图片的URL地址")
    parser.add_argument("--media-type", choices=["video", "image"], default=ConstantEnum.DEFAULT__MEDIA_TYPE,
                        help="媒体类型：video(视频流/视频文件), image(图片)，默认 video")
    parser.add_argument("--confidence-threshold", type=float, default=ConstantEnum.DEFAULT__CONFIDENCE_THRESHOLD,
                        help="置信度阈值，低于该分值不输出，默认 0.5")
    parser.add_argument("--absence-threshold", type=int, default=ConstantEnum.DEFAULT__ABSENCE_THRESHOLD,
                        help="离岗判定阈值，超过该时长(秒)判定为异常离岗，默认 300")
    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_media(
            input_path=args.input,
            url=args.url,
            media_type=args.media_type,
            confidence_threshold=args.confidence_threshold,
            absence_threshold=args.absence_threshold,
            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={}, *args, **argss):
        response = self.get_analysis(
            input_path, params,
            *args, **argss
        )

        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={}, *args, **argss):
        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)
            }

        open_id = argss.pop('open_id', None)
        # if not open_id:  ## ARK_CLAW ##
        #     return "⚠️ 错误：缺少 open_id 参数"
        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

        open_id = argss.pop('open_id', None)
        # if not open_id:  ## ARK_CLAW ##
        #     return "⚠️ 错误：缺少 open_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 "⚠️ 暂无分析报告记录"


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(open_id=open_id)
        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"
        SMYX_REPTILE_SHEDDING_PROGRESS_ANALYSIS = "SMYX_REPTILE_SHEDDING_PROGRESS_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)  ## ARK_CLAW ##
            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:
                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)  ## ARK_CLAW ##
                return f'''⚠️ 因账户余额不足, 技能使用失败, 请按照如下步骤进行充值: 
👉 1. 先输入命令 "安装支付技能 smyx-payment", 等待安装完成. (如果已经安装支付技能过则忽略此步骤)
👉 2. 再输入命令 "技能账户充值", 然后跟随系统提示操作后即可继续使用技能.
{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)  ## ARK_CLAW ##
            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", headers,
                "timeout",
                timeout)  ## ARK_CLAW ##
            raise


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