Skill: scnet-sugon/scnet-ocr (version 1.0.5)

--- name: scnet-ocr description: 将图片中的文字、通用文字识别, 居民身份证, 银行卡, 社保卡, 户口本, 出生医学证明, 往来港澳通行证, 往来台湾通行证, 台湾居民来往大陆通行证, 港澳居民来往内地通行证, 营业执照, 社会团体法人登记证书, 工会法人资格证书, 宗教活动场所登记证, 民办非企业单位登记证书, 事业单位法人证书, 统一社会信用代码证书, 增值税发票, 增值税卷票, 出租车发票, 火车票, 航空运输电子客票行程单, 机动车销售统一发票, 定额发票, 过路过桥费发票, 医疗门诊发票, 税收完税证明, 船票, 非税票据, 通用机打发票, 汽车票等信息识别并提取出来。本技能应在用户需要 OCR 识别图片中的文字，或识别通用文字识别, 居民身份证, 银行卡, 社保卡, 户口本, 出生医学证明, 往来港澳通行证, 往来台湾通行证, 台湾居民来往大陆通行证, 港澳居民来往内地通行证, 营业执照, 社会团体法人登记证书, 工会法人资格证书, 宗教活动场所登记证, 民办非企业单位登记证书, 事业单位法人证书, 统一社会信用代码证书, 增值税发票, 增值税卷票, 出租车发票, 火车票, 航空运输电子客票行程单, 机动车销售统一发票, 定额发票, 过路过桥费发票, 医疗门诊发票, 税收完税证明, 船票, 非税票据, 通用机打发票, 汽车票时使用。 version: 1.0.5 author: SCNet license: MIT tags: - OCR - 证件识别 - 发票识别 - 文字提取 required_env_vars: - SCNET_API_KEY optional_env_vars: - SCNET_API_BASE primary_credential: SCNET_API_KEY dependencies: - python3 - requests input: - ocrType : 识别类型，可选值见下文 - filePath : 待识别图片的本地路径 output: 结构化的 JSON 数据，包含识别结果和置信度 --- # Sugon-Scnet 通用 OCR 技能 本技能封装了 Sugon-Scnet 通用 OCR 服务，通过单一接口即可调用 31 种识别能力，高效提取文字及票据信息。 ## 功能特性 - **通用文字识别**：提取图片中的全部文字，支持横竖版及坐标定位。 - **个人证照**：识别居民身份证, 银行卡, 社保卡, 户口本, 出生医学证明, 往来港澳通行证, 往来台湾通行证, 台湾居民来往大陆通行证, 港澳居民来往内地通行证。 - **行业资质**：识别营业执照, 社会团体法人登记证书, 工会法人资格证书, 宗教活动场所登记证, 民办非企业单位登记证书, 事业单位法人证书, 统一社会信用代码证书。 - **财务票据**：覆盖增值税发票, 增值税卷票, 出租车发票, 火车票, 航空运输电子客票行程单, 机动车销售统一发票, 定额发票, 过路过桥费发票, 医疗门诊发票, 税收完税证明, 船票, 非税票据, 通用机打发票, 汽车票，自动提取关键字段。 ## 前置配置 > **⚠️ 重要**：使用前需要申请 Scnet API Token ### 申请 API Token 1. 访问 [Scnet 官网](https://www.scnet.cn) 注册/登录 2. 在控制台申请 API 密钥（格式：[REDACTED_SECRET]） 3. 复制密钥备用 ### 配置 Token **手动配置（推荐）** 1. 在技能目录下创建 `config/.env` 文件，内容如下： ```ini # ===== Sugon-Scnet OCR API 配置 ===== # 申请地址：https://www.scnet.cn SCNET_API_KEY=your_scnet_api_key_here # API 基础地址（一般无需修改） SCNET_API_BASE=https://api.scnet.cn/api/llm/v1 ``` 2. 添加：`SCNET_API_KEY=你的密钥` 3. 设置文件权限为 600（仅所有者可读写） **⚠️ 安全警告**：切勿将 API Key 直接粘贴到聊天对话中，否则可能被记录或泄露。 ### Token 更新 Token 过期后调用会返回 401 或 403 错误。更新方法：重新申请 Token 并替换 config/.env 中的 SCNET_API_KEY。 ### 依赖安装 本技能需要 Python 3.6+ 和 requests 库。请运行以下命令： ```bash pip install requests ``` --- ### 使用方法 ### 参数说明 | 参数名 | 类型 | 必填 | 描述 | |--------|------|------|------| | ocrType | string | 是 | 识别类型枚举。必须为以下之一：<br>• GENERAL（通用文字）<br>• ID_CARD（身份证）<br>• BANK_CARD（银行卡）<br>• SOCIAL_SECURITY_CARD（社保卡）<br>• HOUSEHOLD_REGISTER（户口本）<br>• BIRTH_CERTIFICATE（出生医学证明）<br>• HK_MACAU_PASS（往来港澳通行证）<br>• TAIWAN_PASS（往来台湾通行证）<br>• TAIWAN_MAINLAND_PASS（台湾居民来往大陆通行证）<br>• HK_MAINLAND_PASS（港澳居民来往内地通行证）<br>• BUSINESS_LICENSE（营业执照）<br>• SOCIAL_ORG_REG（社会团体法人登记证书）<br>• TRADE_UNION_REG（工会法人资格证书）<br>• RELIGIOUS_ACTIVITY_REG（宗教活动场所登记证）<br>• PRIVATE_NON_ENTERPRISE_REG（民办非企业单位登记证书）<br>• INSTITUTION_LEGAL_REG（事业单位法人证书）<br>• UNIFIED_SOCIAL_CREDIT_REG（统一社会信用代码证书）<br>• VAT_INVOICE（增值税发票）<br>• VAT_ROLL_INVOICE（增值税卷票）<br>• TAXI_INVOICE（出租车发票）<br>• TRAIN_TICKET（火车票）<br>• AIRPORT_TICKET（航空运输电子客票行程单）<br>• VEHICLE_SALE_INVOICE（机动车销售统一发票）<br>• QUOTA_INVOICE（定额发票）<br>• TOLL_INVOICE（过路过桥费发票）<br>• MEDICAL_INVOICE（医疗门诊发票）<br>• TAX_CERTIFICATE（税收完税证明）<br>• SHIP_TICKET（船票）<br>• NON_TAX_BILL（非税票据）<br>• GENERAL_MACHINE_INVOICE（通用机打发票）<br>• BUS_TICKET（汽车票） | | filePath | string | 是 | 待识别图片的本地绝对路径。支持 jpg、png、pdf 等常见格式。 | ### 命令行调用示例 ```bash python .claude/skills/scnet-ocr/scripts/main.py VAT_INVOICE /path/to/invoice.jpg ``` ### 在 AI 对话中使用 用户可以说： - “帮我识别这张身份证，图片在 /Users/name/Downloads/id.jpg” - “提取这张发票的信息，路径是 /Users/name/Downloads/fapiao.png” - “OCR 这个图片里的文字，图片在 /Users/name/Desktop/text.png” AI 会根据 description 中的关键词自动触发本技能。 ### AI 调用建议 为避免触发 API 速率限制（10 QPS），请串行调用本技能，即等待前一个识别完成后再发起下一个请求。 如果使用 OpenClaw 的 exec 工具，建议设置 timeout 或 yieldMs 参数，让命令同步执行，避免多个命令同时运行导致并发。 ### 配置选项 编辑 `config/.env` 文件： | 变量名 | 默认值 | 说明 | |--------|--------|------| | SCNET_API_KEY | 必需 | Scnet API 密钥 | | SCNET_API_BASE | https://api.scnet.cn/api/llm/v1 | API 基础地址（一般无需修改） | ### 输出 - 标准输出：识别结果的 JSON 数据，结构与 API 文档一致，位于 `data` 字段内。 - 识别结果位于 data[0].result[0].elements 中，具体字段取决于 ocrType。 - 错误信息：如果发生错误，会输出以 `错误:` 开头的友好提示。 ### 注意事项 - 本技能调用的 OCR API 有 10 QPS 的速率限制。 - 如果遇到 429 错误，请等待 2-3 秒后重试，不要连续发起请求。 - 建议在调用前确保图片已准备就绪，避免因网络问题导致重复调用。 ### 故障排除 | 问题 | 解决方案 | |------|----------| | 配置文件不存在 | 创建 config/.env 并填入 Token（参考前置配置） | | API Key 无效/过期 | 重新申请 Token 并更新 `.env` 文件 | | 文件不存在 | 检查提供的文件路径是否正确 | | 网络连接失败 | 检查网络连接或防火墙设置 | | 不支持的文件类型 | 确保文件扩展名为允许的类型（参考 API 文档） | | 401/403/Unauthorized | Token 无效或过期，重新申请并配置 | | 429 Too Many Requests | 请求过于频繁，技能会自动等待并重试（最多 3 次）。若持续失败，请降低调用频率或联系服务方提高限额。 |

path: assets/templates/fields-summary.md
content: # 各识别类型的字段说明（elements 内容）

根据 ocrType 不同，返回的 `elements` 对象包含以下字段：

## GENERAL (通用文字)
- `width`: 图像宽度（像素）
- `height`: 图像高度（像素）
- `angle`: 图像旋转角度（度）
- `text`: 文字识别结果（List<Object>）
  - `text`: 文字条内容
  - `text_class`: 文本类别标识，1是竖向文本，2是横向文本
  - `anglenet_class`: 角度分类标识
  - `x`: 文本块左上角X坐标
  - `y`: 文本块左上角Y坐标
  - `width`: 文本块宽度（像素）
  - `height`: 文本块高度（像素）
  - `pos`: 文本块四边形坐标（左上、右上、右下、左下），List<Array>
  - `confidences`: 文字条置信度
  - `chars`: 字符识别结果（List<Object>）
    - `pos`: 字符四点坐标，List<Array>
    - `text`: 识别的字符

## ID_CARD (大陆身份证)
- `name`: 姓名
- `gender`: 性别
- `nation`: 民族
- `bornDate`: 出生日期
- `address`: 住址
- `IDNumber`: 公民身份号码
- `issueInstitution`: 签发机关
- `validityPeriod`: 有效期

## BANK_CARD (银行卡)
- `bankName`: 银行名称
- `cardNumber`: 卡号
- `validThru`: 有效期
- `cardHolder`: 持卡人

## SOCIAL_SECURITY_CARD (社保卡)
- `name`: 姓名
- `gender`: 性别
- `nation`: 民族
- `bornDate`: 出生日期
- `socialSecurityNumber`: 社会保障号码
- `cardNumber`: 社会保障卡号
- `issueDate`: 发卡日期
- `bankCardNumber`: 银行卡号
- `validityPeriod`: 有效期限
- `issueInstitution`: 发卡机关

## HOUSEHOLD_REGISTER (户口本)
- `title`: 标题
- `houseNo`: 户号
- `householdType`: 户别
- `fullName`: 姓名
- `householdRelation`: 户主或与户主关系
- `formerName`: 曾用名
- `gender`: 性别
- `birthPlace`: 出生地
- `nation`: 民族
- `nativePlace`: 籍贯
- `birthDate`: 出生日期
- `otherAddress`: 本市(县)其他住址
- `religion`: 宗教信仰
- `idNumber`: 公民身份证件编号
- `height`: 身高
- `bloodType`: 血型
- `educationLevel`: 文化程度
- `maritalStatus`: 婚姻状况
- `militaryService`: 兵役状况
- `workUnit`: 服务处所
- `occupation`: 职业
- `moveInCityDate`: 何时何地迁来本市
- `moveInAddressDate`: 何时何地迁来本址
- `registerDate`: 登记日期

## BIRTH_CERTIFICATE (出生医学证明)
- `title`: 标题
- `newbornName`: 新生儿姓名
- `gender`: 性别
- `birthTime`: 出生时间
- `gestationalWeeks`: 出生孕周
- `birthWeight`: 出生体重
- `birthLength`: 出生身长
- `birthPlace`: 出生地点
- `medicalInstitutionName`: 医疗机构名称
- `motherName`: 母亲姓名
- `motherAge`: 母亲年龄
- `motherNationality`: 母亲国籍
- `motherEthnicGroup`: 母亲民族
- `motherAddress`: 母亲住址
- `motherIdType`: 母亲身份证件类别
- `motherIdNumber`: 母亲身份证件号码
- `fatherName`: 父亲姓名
- `fatherAge`: 父亲年龄
- `fatherNationality`: 父亲国籍
- `fatherEthnicGroup`: 父亲民族
- `fatherAddress`: 父亲住址
- `fatherIdType`: 父亲身份证件类别
- `fatherIdNumber`: 父亲身份证件号码
- `issuingAuthority`: 签发机构
- `issueDate`: 签发日期
- `certificateNumber`: 证书编号

## HK_MACAU_PASS (往来港澳通行证)
- `title`: 标题
- `documentNumber`: 证件号码
- `chineseName`: 中文姓名
- `englishName`: 英文姓名
- `birthDate`: 出生日期
- `gender`: 性别
- `validityPeriod`: 有效期限
- `issuingAuthority`: 签发机关
- `issuingPlace`: 签发地点
- `machineReadableCode`: 机读码

## TAIWAN_PASS (往来台湾通行证)
- `title`: 标题
- `documentNumber`: 证件号码
- `chineseName`: 中文姓名
- `englishName`: 英文姓名
- `birthDate`: 出生日期
- `gender`: 性别
- `validityPeriod`: 有效期限
- `issuingAuthority`: 签发机关
- `issuingPlace`: 签发地点
- `machineReadableCode`: 机读码

## TAIWAN_MAINLAND_PASS (台湾居民来往大陆通行证)
- `title`: 标题
- `chineseName`: 中文姓名
- `englishName`: 英文姓名
- `birthDate`: 出生日期
- `gender`: 性别
- `validityPeriod`: 有效期限
- `issuingAuthority`: 签发机关
- `issuingPlace`: 签发地点
- `documentNumber`: 证件号码
- `issuingTimes`: 签发次数

## HK_MAINLAND_PASS (港澳居民来往内地通行证)
- `title`: 标题
- `chineseName`: 中文姓名
- `englishName`: 英文姓名
- `birthDate`: 出生日期
- `gender`: 性别
- `nationality`: 国籍
- `validityPeriod`: 有效期限
- `issuingAuthority`: 签发机关
- `documentNumber`: 证件号码
- `renewalTimes`: 换证次数

## BUSINESS_LICENSE (营业执照)
- `title`: 标题
- `socialCreditCode`: 统一社会信用代码
- `name`: 名称
- `capital`: 注册资本
- `type`: 类型
- `date`: 成立日期
- `directorType`: 负责人类型
- `director`: 负责人
- `businessTerm`: 有效日期至
- `businessScope`: 经营范围
- `address`: 住所

## SOCIAL_ORG_REG (社会团体法人登记证书)
- `title`: 标题
- `name`: 名称
- `directorType`: 负责人类型
- `director`: 负责人
- `scope`: 业务范围
- `regionAct`: 活动地域
- `capital`: 注册资金
- `address`: 住所
- `businessUnit`: 业务主管单位
- `code`: 统一社会信用代码
- `due`: 有效期限
- `issueUnit`: 发证机关
- `issueDate`: 发证日期

## TRADE_UNION_REG (工会法人资格证书)
- `title`: 标题
- `name`: 工会名称
- `address`: 住所
- `directorType`: 负责人类型
- `director`: 负责人
- `issueUnit`: 发证机关
- `due`: 有效期
- `code`: 社会统一信用代码
- `issueDate`: 发证日期

## RELIGIOUS_ACTIVITY_REG (宗教活动场所登记证)
- `title`: 标题
- `name`: 名称
- `religion`: 教别
- `orgNature`: 类别
- `directorType`: 负责人类型
- `director`: 负责人
- `address`: 地址
- `code`: 统一社会信用代码
- `religionNo`: 宗场证字
- `issueUnit`: 发证机关
- `issueDate`: 发证日期

## PRIVATE_NON_ENTERPRISE_REG (民办非企业单位登记证书)
- `title`: 标题
- `name`: 名称
- `directorType`: 负责人类型
- `director`: 法定代表人
- `address`: 住所
- `capital`: 开办资金
- `scope`: 业务范围
- `businessUnit`: 业务主管单位
- `code`: 统一社会信用代码
- `due`: 有效期限
- `issueUnit`: 发证机关
- `issueDate`: 发证日期

## INSTITUTION_LEGAL_REG (事业单位法人证书)
- `title`: 标题
- `name`: 名称
- `directorType`: 负责人类型
- `director`: 法定代表人
- `scope`: 宗旨和业务范围
- `resource`: 经费来源
- `capital`: 开办资金
- `address`: 住所
- `organizer`: 举办单位
- `code`: 统一社会信用代码
- `due`: 有效期

## UNIFIED_SOCIAL_CREDIT_REG (统一社会信用代码证书)
- `title`: 标题
- `name`: 机构名称
- `orgNature`: 机构性质
- `address`: 机构地址
- `directorType`: 负责人类型
- `director`: 负责人
- `code`: 统一社会信用代码
- `due`: 有效期至
- `issueDate`: 颁发日期

## VAT_INVOICE (增值税发票)
- `title`: 发票名称
- `invoiceCode`: 发票代码
- `invoiceNo`: 发票号码
- `printedCode`: 机打代码
- `printedNo`: 机打号码
- `checkCode`: 校验码
- `machineCode`: 机器编号
- `invoiceDate`: 开票日期
- `passwordArea`: 密码区
- `buyerName`: 购方名称
- `buyerCode`: 购方纳税人识别号
- `buyerAddressAndPhone`: 购方地址及电话
- `buyerBankAndAccount`: 购方开户行及账号
- `sellerName`: 销售方名称
- `sellerCode`: 销售方纳税人识别号
- `sellerAddressAndPhone`: 销售方地址及电话
- `sellerBankAndAccount`: 销售方开户行及账号
- `preTaxTotalAmount`: 税前合计金额
- `totalTaxAmount`: 合计税额
- `totalAmountUpper`: 价税合计(大写)
- `totalAmountLower`: 价税合计(小写)
- `invoiceForm`: 联次
- `remarks`: 备注
- `payee`: 收款人
- `checker`: 复核
- `drawer`: 开票人
- `goodsDetails`: 发票商品明细
  - `goodsName`: 货物服务名称
  - `specification`: 规格
  - `unit`: 单位
  - `quantity`: 数量
  - `unitPrice`: 单价
  - `itemAmount`: 金额
  - `taxRate`: 税率
  - `taxAmount`: 税额

## VAT_ROLL_INVOICE (增值税卷票)
- `title`: 发票名称
- `invoiceCode`: 发票代码
- `invoiceNo`: 发票号码
- `printedNo`: 机打号码
- `machineCode`: 机器编号
- `invoiceDate`: 开票日期
- `payee`: 收款员
- `buyerName`: 购方名称
- `buyerCode`: 购方纳税人识别号
- `sellerName`: 销售方名称
- `sellerCode`: 销售方纳税人识别号
- `totalAmountUpper`: 价税合计(大写)
- `totalAmountLower`: 价税合计(小写)
- `checkCode`: 校验码
- `goodsDetails`: 发票商品明细
  - `goodsName`: 项目
  - `unitPrice`: 单价
  - `quantity`: 数量
  - `itemAmount`: 金额

## TAXI_INVOICE (出租车发票)
- `title`: 标题
- `invoiceCode`: 发票代码
- `invoiceNo`: 发票号码
- `vehicleNo`: 车号
- `certificateNo`: 证号
- `date`: 日期
- `boardingTime`: 上车时间
- `alightingTime`: 下车时间
- `amount`: 金额
- `actualAmount`: 实收金额

## TRAIN_TICKET (火车票)
- `title`: 标题
- `ticketNo`: 车票编号
- `departStation`: 起始站
- `destinationStation`: 终止站
- `trainNo`: 车次
- `departDate`: 发车日期
- `departTime`: 发车时间
- `seatPostion`: 座位号
- `seatNo`: 座次
- `ticketPrice`: 票价
- `passengerName`: 旅客姓名
- `identifyId`: 身份证号
- `invoiceNo`: 发票号码
- `invoiceDate`: 开票日期
- `preTaxAmount`: 税前金额
- `taxRate`: 税率
- `taxAmount`: 税额
- `elecTicketNo`: 电子客票号
- `originInvoiceNo`: 原发票号码
- `buyerName`: 购买方名称
- `socialCreditCode`: 统一社会信用代码
- `refundTag`: 退票标识
- `replaceTag`: 换开标识
- `otherInfo`: 其他信息

## AIRPORT_TICKET (航空运输电子客票行程单)
- `title`: 标题
- `domesticTag`: 国内国际标识
- `gpBillNo`: GP单号
- `issueStatus`: 开具状态
- `invoiceNo`: 发票号码
- `serialNumber`: 印刷序号
- `passengerName`: 旅客姓名
- `identifyIdNo`: 身份证号码
- `endorsement`: 签注
- `openMark`: OPEN标示
- `refundMark`: 退票费标示
- `ticketPrice`: 票价
- `fuleDischarge`: 燃油附加费
- `civilAviationFund`: 民航发展基金
- `taxRate`: 增值税税率
- `taxAmount`: 增值税税额
- `otherTaxes`: 其他税费
- `totalAmount`: 合计金额
- `ticketNo`: 电子客票号码
- `checkCode`: 验证码
- `reminderInfo`: 提示信息
- `insuranceCharge`: 保险费
- `salesOutletCode`: 销售网点代号
- `issueUnit`: 填开单位
- `issueDate`: 填开日期
- `buyerName`: 购买方名称
- `taxPayerCode`: 纳税人识别号
- `airTransportRoutes`: 行程信息
  - `departPlace`: 始发地
  - `destinationPlace`: 目的地
  - `carrier`: 承运人
  - `flightNo`: 航班号
  - `seatLevel`: 座位等级
  - `departDate`: 航班日期
  - `departTime`: 航班时间
  - `ticketLevel`: 客票级别
  - `ticketEffectiveDate`: 客票生效日期
  - `ticketExpirationDate`: 客票失效日期
  - `freeLuggage`: 免费行李

## VEHICLE_SALE_INVOICE (机动车销售统一发票)
- `title`: 发票名称
- `invoiceForm`: 发票联次
- `invoiceCode`: 发票代码
- `invoiceNo`: 发票号码
- `issueDate`: 开票日期
- `printedCode`: 机打代码
- `printedNo`: 机打号码
- `machineCode`: 机器编号
- `taxControlCode`: 税控码
- `buyerName`: 购方名称
- `buyerTaxId`: 购方纳税人识别号
- `buyerCode`: 购买方身份证或组织机构代码
- `vehicleType`: 车辆类型
- `brandModel`: 厂牌型号
- `originalPlace`: 产地
- `qualifiedNo`: 合格证号
- `importCertificateNo`: 进口证明书号
- `commodityInspectionNo`: 商检单号
- `engineNo`: 发动机号码
- `vehicleIdentificationNo`: 车辆识别代号
- `totalAmountUpper`: 价税合计(大写)
- `totalAmountLower`: 价税合计(小写)
- `sellerName`: 销售方名称
- `sellerTaxId`: 销售方纳税人识别号
- `sellerAddressAndPhone`: 销售方地址及电话
- `sellerBankAndAccount`: 销售方开户行及账号
- `taxRate`: 增值税税率
- `taxAmount`: 增值税税额
- `taxAuthorityName`: 主管税务机关名称
- `taxAuthorityCode`: 主管税务机关代码
- `preTaxAmount`: 不含税价
- `taxPaymentVoucher`: 完税凭证号码
- `tonnage`: 吨位
- `maxCapacity`: 限乘人数
- `drawer`: 开票人
- `remark`: 备注

## QUOTA_INVOICE (定额发票)
- `title`: 发票名称
- `invoiceCode`: 发票代码
- `invoiceNo`: 发票代码
- `amountUpper`: 发票号码
- `amountLower`: 开票日期

## TOLL_INVOICE (过路过桥费发票)
- `title`: 标题
- `invoiceCode`: 发票代码
- `invoiceNo`: 发票代码
- `invoiceDate`: 开票日期
- `entranceLocation`: 入口
- `exitLocation`: 出口
- `exitTime`: 出口时间
- `totalAmountLower`: 合计金额(小写)

## MEDICAL_INVOICE (医疗门诊发票)
- `title`: 标题
- `invoiceCode`: 发票代码
- `invoiceNo`: 发票号码
- `invoiceDate`: 开票日期
- `checkCode`: 校验码
- `payerName`: 交款人
- `payerAccount`: 交款人统一社会信用代码
- `payeeName`: 收款单位
- `totalAmountUpper`: 合计金额(大写)
- `totalAmountLower`: 合计金额(小写)

## TAX_CERTIFICATE (税收完税证明)
- `title`: 标题
- `verifyCode`: 验证码
- `billNo`: 票据号码
- `fillDate`: 填发日期
- `taxAuthority`: 税务机关
- `taxCode`: 纳税人识别号
- `taxPayerName`: 纳税人名称
- `totalAmountUpper`: 合计金额(大写)
- `totalAmountLower`: 合计金额(小写)
- `issuer`: 填票人
- `remarks`: 备注
- `taxItems`: 纳税明细
  - `originBillNo`: 原凭证号
  - `taxType`: 税种
  - `itemName`: 品目名称
  - `taxPeriod`: 税款所属时期
  - `treasuryDate`: 入(退)库日期
  - `actualPayAmount`: 实缴(退)金额

## SHIP_TICKET (船票)
- `title`: 标题
- `invoiceCode`: 发票代码
- `invoiceNo`: 发票号码
- `departureDate`: 开船日期
- `departureTime`: 开船时间
- `departStation`: 起始站
- `destinationStation`: 终止站
- `totalAmountLower`: 合计金额(小写)
- `passengerName`: 旅客姓名

## NON_TAX_BILL (非税票据)
- `title`: 标题
- `invoiceCode`: 发票代码
- `invoiceNo`: 发票号码
- `payerName`: 交款人
- `payerSocialCreditCode`: 交款人统一社会信用代码
- `checkCode`: 校验码
- `invoiceDate`: 开票日期
- `totalAmountUpper`: 合计金额(大写)
- `totalAmountLower`: 合计金额(小写)
- `payeeCompany`: 收款单位
- `checker`: 复核人
- `payee`: 收款人
- `itemDetails`: 项目明细
  - `itemCode`: 项目编码
  - `itemName`: 项目名称
  - `itemUnit`: 单位
  - `itemCount`: 数量
  - `itemCharge`: 标准
  - `itemAmt`: 金额
  - `itemRemark`: 备注

## GENERAL_MACHINE_INVOICE (通用机打发票)
- `title`: 标题
- `invoiceCode`: 发票代码
- `invoiceNo`: 发票号码
- `invoiceDate`: 开票日期
- `checkCode`: 验证码
- `buyerName`: 购方名称
- `buyerCode`: 购方纳税人识别号
- `buyerAddressAndPhone`: 购方地址及电话
- `buyerBankAndAccount`: 购方开户行及账号
- `sellerName`: 销售方名称
- `sellerCode`: 销售方纳税人识别号
- `sellerAddressAndPhone`: 销售方地址及电话
- `sellerBankAndAccount`: 销售方开户行及账号
- `preTaxTotalAmount`: 税前合计金额
- `totalTaxAmount`: 合计税额
- `totalAmountUpper`: 价税合计(大写)
- `totalAmountLower`: 价税合计(小写)
- `remarks`: 备注
- `payee`: 收款人
- `checker`: 复核
- `drawer`: 开票人
- `goodsDetails`: 发票商品明细
  - `goodsName`: 货物服务名称
  - `specification`: 规格型号
  - `unit`: 单位
  - `quantity`: 数量
  - `unitPrice`: 单价
  - `itemAmount`: 金额
  - `taxRate`: 税率
  - `taxAmount`: 税额

## BUS_TICKET (汽车票)
- `title`: 标题
- `invoiceCode`: 发票代码
- `invoiceNo`: 发票号码
- `invoiceDate`: 开票日期
- `departureDate`: 开车日期
- `departureTime`: 开车时间
- `departStation`: 起始站
- `destinationStation`: 终止站
- `totalAmountLower`: 合计金额(小写)
- `passengerName`: 旅客姓名

path: CHANGELOG.md
content: # 变更日志

## [1.0.0] - 2025-03-12

### 新增
- 初始版本发布
- 支持 10 种 OCR 识别类型：通用文字、身份证、银行卡、营业执照、增值税发票、增值税卷票、出租车发票、火车票、航空行程单、机动车销售统一发票
- 从 `.env` 文件读取 API Key
- 友好的配置检测和 Token 过期提示
- 输出识别结果为 JSON 格式

## [1.0.1] - 2025-03-26

### 优化
重试机制：

    在 recognize_with_retry 函数中，遇到 429 状态码时，采用指数退避策略等待（默认初始1秒，每次重试翻倍，如1,2,4秒），最多重试3次。

    若 API 响应中包含 retry_after 字段，则优先使用该值作为等待时间。

    重试过程中输出友好提示到 stderr，不影响正常 JSON 输出。

避免并发：

    该机制确保了单个脚本实例在遇到限流时不会立刻退出，而是内部等待后重试。这减少了 AI 代理在外部发起新请求的冲动（因为脚本尚未结束，AI 会等待结果）。

    如果 AI 仍可能并发启动多个脚本实例，你还可以在技能描述（SKILL.md）中明确建议用户不要同时发起多个 OCR 请求。

兼容性：

    所有修改均向后兼容，不改变原有功能，仅增加重试逻辑。

## [1.0.2] - 2025-04-10

### 优化
- 支持 31 种 OCR 识别类型：通用文字识别, 居民身份证, 银行卡, 社保卡, 户口本, 出生医学证明, 往来港澳通行证, 往来台湾通行证, 台湾居民来往大陆通行证, 港澳居民来往内地通行证, 营业执照, 社会团体法人登记证书, 工会法人资格证书, 宗教活动场所登记证, 民办非企业单位登记证书, 事业单位法人证书, 统一社会信用代码证书, 增值税发票, 增值税卷票, 出租车发票, 火车票, 航空运输电子客票行程单, 机动车销售统一发票, 定额发票, 过路过桥费发票, 医疗门诊发票, 税收完税证明, 船票, 非税票据, 通用机打发票, 汽车票
- 从 `.env` 文件读取 API Key
- 友好的配置检测和 Token 过期提示
- 输出识别结果为 JSON 格式


## [1.0.3] - 2025-04-13

### 优化
- update channelTag

## [1.0.4] - 2025-04-15

### 优化
- update ClawHub 的安全扫描

## [1.0.5] - 2025-04-16

### 优化
- update ClawHub 的安全扫描V1

path: LICENSE.txt
content: MIT License

Copyright (c) 2025 Sugon-Scnet OCR Skill Contributors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

path: README.md
content: # scnet-ocr



## Getting started

To make it easy for you to get started with GitLab, here's a list of recommended next steps.

Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!

## Add your files

* [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
* [Add files using the command line](https://docs.gitlab.com/topics/git/add_files/#add-files-to-a-git-repository) or push an existing Git repository with the following command:

```
cd existing_repo
git remote add origin https://github.com/SCNet-sugon/scnet-ocr.git
git branch -M main
git push -uf origin main
```


## Collaborate with your team

* [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
* [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
* [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
* [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
* [Set auto-merge](https://docs.gitlab.com/user/project/merge_requests/auto_merge/)

## Test and Deploy

Use the built-in continuous integration in GitLab.

* [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/)
* [Analyze your code for known vulnerabilities with Static Application Security Testing (SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
* [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
* [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
* [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)

***

# Editing this README

When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thanks to [makeareadme.com](https://www.makeareadme.com/) for this template.

## Suggestions for a good README

Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.

## Name
Choose a self-explaining name for your project.

## Description
Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.

## Badges
On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.

## Visuals
Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.

## Installation
Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.

## Usage
Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.

## Support
Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.

## Roadmap
If you have ideas for releases in the future, it is a good idea to list them in the README.

## Contributing
State if you are open to contributions and what your requirements are for accepting them.

For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.

You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.

## Authors and acknowledgment
Show your appreciation to those who have contributed to the project.

## License
For open source projects, say how it is licensed.

## Project status
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.


path: references/api-docs.md
content: # Sugon-Scnet OCR API 文档摘要

## 接口地址
`POST https://api.scnet.cn/api/llm/v1/ocr/recognize`

## 请求头
- `Content-Type: multipart/form-data`
- `Authorization: Bearer <你的 API Key>`

## 请求参数（表单）
| 参数名  | 类型 | 必填 | 描述                                   |
| ------- | ---- | ---- | -------------------------------------- |
| file    | File | 是   | 需要识别的图片文件                     |
| ocrType | str  | 是   | 识别类型枚举，详见 SKILL.md 参数说明   |

## 响应结构
```json
{
  "code": "0",
  "msg": "success",
  "data": [
    {
      "traceId": "202604010000001",
      "originalFilename": "通用文字示例.png",
      "cosPath": "/ocr/202604/01/通用文字示例.png",
      "result": [
        {
          "imageBase64": "",
          "offset": 0,
          "confidence": 0.9668,
          "width": 989,
          "height": 1200,
          "angle": 0,
          "text": [
            {
              "pos": [
                [
                  117,
                  14
                ],
                [
                  931,
                  18
                ],
                [
                  931,
                  63
                ],
                [
                  117,
                  58
                ]
              ],
              "width": 814,
              "height": 44,
              "text_class": "2",
              "x": 117,
              "y": 14,
              "anglenet_class": "0",
              "text": "喜欢欣赏",
              "chars": [
                {
                  "pos": [
                    [
                      468.01,
                      16.08
                    ],
                    [
                      514.78,
                      16.35
                    ],
                    [
                      514.78,
                      60.88
                    ],
                    [
                      468.01,
                      60.61
                    ]
                  ],
                  "text": "喜"
                },
                {
                  "pos": [
                    [
                      514.78,
                      16.35
                    ],
                    [
                      554.68,
                      16.58
                    ],
                    [
                      554.68,
                      61.11
                    ],
                    [
                      514.78,
                      60.88
                    ]
                  ],
                  "text": "欢"
                },
                {
                  "pos": [
                    [
                      554.68,
                      16.58
                    ],
                    [
                      602.83,
                      16.86
                    ],
                    [
                      602.83,
                      61.39
                    ],
                    [
                      554.68,
                      61.11
                    ]
                  ],
                  "text": "欣"
                },
                {
                  "pos": [
                    [
                      602.83,
                      16.86
                    ],
                    [
                      635.85,
                      17.05
                    ],
                    [
                      635.85,
                      61.58
                    ],
                    [
                      602.83,
                      61.39
                    ]
                  ],
                  "text": "赏"
                }
              ]
            }
          ]
        }
      ]
    }
  ]
}
```
## 错误码
- `401 / 403: Token 无效或过期`
- `其他 4xx/5xx: 请检查请求参数或联系服务商`
- `业务错误码（如 code 非 0）：见返回的 msg 字段`

## 注意事项
- `支持单张图片、PDF 或多页压缩包（自动解压识别）`
- `识别结果位于 data[0].result[0].elements 中`
- `不同 ocrType 返回的 elements 字段不同，详见 assets/templates/fields-summary.md`


path: scripts/main.py
content: #!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
Sugon-Scnet 通用 OCR 技能主脚本
接收命令行参数：ocrType filePath
输出：识别结果的 JSON
"""

import os
import sys
import json
import time
import requests
import mimetypes
from pathlib import Path

# 获取技能根目录（脚本所在目录的上一级）
SKILL_ROOT = Path(__file__).parent.parent.absolute()
ENV_FILE = SKILL_ROOT / "config" / ".env"

# --- 新增：重试配置 ---
MAX_RETRIES = 3            # 最大重试次数
RETRY_BACKOFF_FACTOR = 2   # 退避因子，每次重试等待时间翻倍
INITIAL_RETRY_DELAY = 1    # 初始等待时间（秒）
# --------------------

def load_config():
    """从环境变量或 .env 文件加载配置，环境变量优先"""
    config = {}

    # 1. 如果 config/.env 存在，先加载其中的变量
    if ENV_FILE.exists():
        with open(ENV_FILE, 'r', encoding='utf-8') as f:
            for line in f:
                line = line.strip()
                if not line or line.startswith('#'):
                    continue
                if '=' in line:
                    key, value = line.split('=', 1)
                    key = key.strip()
                    value = value.strip().strip('"').strip("'")
                    config[key] = value

    # 2. 从系统环境变量读取，覆盖 .env 中的同名变量
    env_api_key = os.environ.get('SCNET_API_KEY')
    if env_api_key:
        config['SCNET_API_KEY'] = env_api_key

    env_api_base = os.environ.get('SCNET_API_BASE')
    if env_api_base:
        config['SCNET_API_BASE'] = env_api_base

    # 3. 设置默认值
    config.setdefault('SCNET_API_BASE', 'https://api.scnet.cn/api/llm/v1')

    # 4. 检查必要配置
    [REDACTED_SECRET]'SCNET_API_KEY', '')
    if not api_key or api_key == 'your_scnet_api_key_here':
        error_msg = (
            "\n===============================================\n"
            "Scnet API Key 未配置\n"
            "===============================================\n"
            "⚠️ 安全警告：切勿在聊天中粘贴 API Key！\n\n"
            "请通过以下任一方式设置 SCNET_API_KEY：\n\n"
            "1. 环境变量（推荐）：\n"
            "   export SCNET_API_KEY='你的真实密钥'\n\n"
            "2. 配置文件：\n"
            f"   mkdir -p {SKILL_ROOT}/config\n"
            f"   echo 'SCNET_API_KEY=你的真实密钥' > {ENV_FILE}\n"
            f"   chmod 600 {ENV_FILE}\n"
        )
        sys.exit(error_msg)

    return config

def recognize_with_retry(ocr_type, file_path, config, retry_count=0):
    """
    带重试机制的 OCR 识别函数。
    当遇到 429 (Too Many Requests) 时，自动等待后重试。
    """
    api_base = config['SCNET_API_BASE']
    [REDACTED_SECRET]'SCNET_API_KEY']
    url = f"{api_base}/ocr/recognize"

    # 检查文件是否存在
    if not os.path.isfile(file_path):
        sys.exit(f"错误: 文件不存在 - {file_path}")

    # 自动检测 MIME 类型
    mime_type, _ = mimetypes.guess_type(file_path)
    if mime_type is None:
        mime_type = 'application/octet-stream'

    headers = {
        'Authorization': f'Bearer {api_key}'
    }

    try:
        with open(file_path, 'rb') as f:
            files = {
                'file': (os.path.basename(file_path), f, mime_type)
            }
            data = {
                'ocrType': ocr_type,
                'channelTag': "scnetSkills"
            }
            response = requests.post(url, headers=headers, data=data, files=files, timeout=60)
    except Exception as e:
        sys.exit(f"网络请求失败: {str(e)}")

    # --- 新增：处理 429 速率限制 ---
    if response.status_code == 429:
        if retry_count >= MAX_RETRIES:
            sys.exit(f"错误: 请求被限流 (429)，已达到最大重试次数 {MAX_RETRIES}。请稍后再试。")

        # 尝试从响应中获取重试等待时间（如果有）
        retry_after = INITIAL_RETRY_DELAY * (RETRY_BACKOFF_FACTOR ** retry_count)
        try:
            error_data = response.json()
            # 某些 API 会在响应中返回 retry_after 字段
            if 'retry_after' in error_data:
                retry_after = int(error_data['retry_after'])
        except:
            pass

        # 输出友好提示到 stderr（不影响 JSON 输出）
        sys.stderr.write(f"⚠️ 请求过于频繁，等待 {retry_after} 秒后重试... (第 {retry_count+1}/{MAX_RETRIES} 次重试)\n")
        time.sleep(retry_after)

        # 递归重试
        return recognize_with_retry(ocr_type, file_path, config, retry_count + 1)
    # ---------------------------------

    if response.status_code != 200:
        # 针对 401/403 给出明确提示
        if response.status_code in (401, 403):
            error_msg = (
                "\n===============================================\n"
                "Scnet API Token 无效或已过期\n"
                "===============================================\n"
                f"HTTP 状态码: {response.status_code}\n\n"
                "⚠️ 安全警告：切勿在聊天中粘贴 API Key！\n\n"
                "解决方法：\n"
                "1. 访问 https://www.scnet.cn 重新申请 Token\n"
                "2. 手动更新配置文件：\n"
                f"   编辑 {ENV_FILE}\n"
                "   设置 SCNET_API_KEY=你的新密钥\n"
                "3. 或设置环境变量：export SCNET_API_KEY='你的新密钥'\n"
            )
            sys.exit(error_msg)
        else:
            sys.exit(f"HTTP 错误 {response.status_code}: {response.text}")

    try:
        result = response.json()
    except Exception:
        sys.exit(f"响应不是有效的 JSON: {response.text}")

    # 检查业务状态码
    if result.get('code') != '0':
        sys.exit(f"API 错误 {result.get('code')}: {result.get('msg')}")

    # 输出 data 部分（识别结果）
    data = result.get('data', [])

    # 移除每个识别项中的 confidence 字段（优化点）
    for file_result in data:
        if 'result' in file_result and isinstance(file_result['result'], list):
            for item in file_result['result']:
                item.pop('confidence', None)

    # 输出处理后的数据
    print(json.dumps(data, ensure_ascii=False, indent=2))

def main():
    if len(sys.argv) != 3:
        print("用法: python main.py <ocrType> <filePath>")
        print("ocrType 可选值: GENERAL, ID_CARD, BANK_CARD, SOCIAL_SECURITY_CARD, HOUSEHOLD_REGISTER, BIRTH_CERTIFICATE, HK_MACAU_PASS, TAIWAN_PASS, TAIWAN_MAINLAND_PASS, HK_MAINLAND_PASS, BUSINESS_LICENSE, SOCIAL_ORG_REG, TRADE_UNION_REG, RELIGIOUS_ACTIVITY_REG, PRIVATE_NON_ENTERPRISE_REG, INSTITUTION_LEGAL_REG, UNIFIED_SOCIAL_CREDIT_REG, VAT_INVOICE, VAT_ROLL_INVOICE, TAXI_INVOICE, TRAIN_TICKET, AIRPORT_TICKET, VEHICLE_SALE_INVOICE, QUOTA_INVOICE, TOLL_INVOICE, MEDICAL_INVOICE, TAX_CERTIFICATE, SHIP_TICKET, NON_TAX_BILL, GENERAL_MACHINE_INVOICE, BUS_TICKET")
        sys.exit(1)

    ocr_type = sys.argv[1]
    file_path = sys.argv[2]

    config = load_config()
    # 调用带重试的识别函数
    recognize_with_retry(ocr_type, file_path, config)

if __name__ == '__main__':
    main()


path: skill.yaml
content: name: scnet-ocr
version: 1.0.5
description: 将图片中的文字、通用文字识别, 居民身份证, 银行卡, 社保卡, 户口本, 出生医学证明, 往来港澳通行证, 往来台湾通行证, 台湾居民来往大陆通行证, 港澳居民来往内地通行证, 营业执照, 社会团体法人登记证书, 工会法人资格证书, 宗教活动场所登记证, 民办非企业单位登记证书, 事业单位法人证书, 统一社会信用代码证书, 增值税发票, 增值税卷票, 出租车发票, 火车票, 航空运输电子客票行程单, 机动车销售统一发票, 定额发票, 过路过桥费发票, 医疗门诊发票, 税收完税证明, 船票, 非税票据, 通用机打发票, 汽车票等信息识别并提取出来。本技能应在用户需要 OCR 识别图片中的文字，或识别通用文字识别, 居民身份证, 银行卡, 社保卡, 户口本, 出生医学证明, 往来港澳通行证, 往来台湾通行证, 台湾居民来往大陆通行证, 港澳居民来往内地通行证, 营业执照, 社会团体法人登记证书, 工会法人资格证书, 宗教活动场所登记证, 民办非企业单位登记证书, 事业单位法人证书, 统一社会信用代码证书, 增值税发票, 增值税卷票, 出租车发票, 火车票, 航空运输电子客票行程单, 机动车销售统一发票, 定额发票, 过路过桥费发票, 医疗门诊发票, 税收完税证明, 船票, 非税票据, 通用机打发票, 汽车票时使用。
author: SCNet
license: MIT
tags:
  - ocr
  - 文字提取
required_env_vars:
  - SCNET_API_KEY
optional_env_vars:
  - SCNET_API_BASE
primary_credential: SCNET_API_KEY
# 增加项目主页，解决 missing homepage 警告
homepage: https://github.com/SCNet-sugon/scnet-ocr.git  # 请替换为实际仓库地址
dependencies:
  - python3
  - requests

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