跳转到主要内容
本文面向已完成工作空间创建、文件类别配置和审核规则库设置的用户,演示如何在现有配置基础上,通过 API 完成日常的文件上传、字段抽取和智能审核流程。 如果您还没有配置过 DocFlow,请先阅读 AP 审单场景(从零开始)

01 场景说明

工作空间、文件类别、审核规则库属于一次性基础配置,完成后即可持续复用。在日常 AP 审单业务中,只需通过 API 重复以下三步:
  1. 上传文件:将新的单据(发票、采购合同、入库单、验收单)上传至工作空间
  2. 获取抽取结果:等待系统完成分类识别与字段抽取,获取结构化数据
  3. 智能审核:绑定已有规则库,提交审核任务并获取审核结论
本文演示的完整流程如下图所示: AP 审单日常处理流程

02 先决条件

在运行本文代码之前,您需要准备:
  1. 认证信息:从 TextIn 控制台 获取 x-ti-app-idx-ti-secret-code
  2. workspace_id:已创建的工作空间 ID(查看方式见下方)
  3. repo_id:已配置的审核规则库 ID(查看方式见下方)
  4. 待处理文件:本次需要处理的单据,例如发票、采购合同、入库单、验收单

如何获取 workspace_id

第一步:在左侧工作空间列表中,将鼠标悬停在目标空间名称上,点击出现的「设置」按钮。 获取 workspace_id 第一步 第二步:进入空间的「基础信息」页,右侧「空间ID」字段即为 workspace_id,点击复制图标可直接复制。 获取 workspace_id 第二步

如何获取 repo_id

第一步:进入目标工作空间后,点击右上角「智能审核」按钮。 获取 repo_id 第一步 第二步:在智能审核页面中,点击顶部「规则库」标签页。 获取 repo_id 第二步 第三步:规则库列表的「规则库ID」列即为 repo_id 获取 repo_id 第三步

03 代码结构说明

本示例只包含日常处理所需的三个步骤,代码量比从零开始版本少约 60%。

API 调用函数

函数(Python)方法(Java)对应 API 端点说明
upload_fileuploadFilePOST /file/upload上传待处理文件,返回 batch_number
submit_review_tasksubmitReviewTaskPOST /review/task/submit提交审核任务,返回审核 task_id

逐步代码说明

步骤 1:上传待处理文件

将四份单据逐一上传至工作空间,系统根据已配置的文件类别自动完成分类识别,并返回 batch_number
upload_targets = [
    os.path.join(FILES_DIR, "sample_invoice.pdf"),
    os.path.join(FILES_DIR, "sample_contract.pdf"),
    os.path.join(FILES_DIR, "sample_inbound.pdf"),
    os.path.join(FILES_DIR, "sample_acceptance.pdf"),
]
batch_numbers = [upload_file(WORKSPACE_ID, p) for p in upload_targets]

步骤 2:获取抽取结果

对每个 batch_number 轮询 file/fetch 接口,等待识别完成后获取各文档的分类和字段抽取结果,并从结果中收集 task_id 供后续审核使用。
raw_results = []
for batch_number in batch_numbers:
    result = wait_for_result(WORKSPACE_ID, batch_number)
    raw_results.append(result)
    display_result(result)

extract_task_ids = [r.get("task_id") for r in raw_results if r.get("task_id")]

步骤 3:提交审核任务并获取结果

将所有文件的 task_id 传入 review/task/submit 接口,绑定已有规则库提交审核;审核完成后轮询获取每条规则的通过/不通过结论及 AI 依据。
task_name = f"AP审核_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
review_task_id = submit_review_task(WORKSPACE_ID, task_name, REPO_ID, extract_task_ids)

review_result = wait_for_review(WORKSPACE_ID, review_task_id)
display_review_result(review_result)

04 完整示例代码

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
DocFlow AP 审单场景示例(已完成配置版)

适用于工作空间、文件类别、审核规则库均已配置完毕的场景。
流程:
  1. 上传待处理文件(发票、采购合同、入库单、验收单)
  2. 轮询获取抽取结果,展示分类与字段抽取结果
  3. 提交审核任务
  4. 轮询获取审核结果,展示审核结论

依赖:
  pip install requests

使用前请先填写下方配置项。
"""

import os
import time
from datetime import datetime

import requests

# ============================================================
# 配置项 — 请替换为您的实际值
# ============================================================
APP_ID        = "your-app-id"        # TextIn 控制台中的 x-ti-app-id
SECRET_CODE   = "your-secret-code"   # TextIn 控制台中的 x-ti-secret-code

WORKSPACE_ID = "your-workspace-id"  # 已创建的工作空间 ID
REPO_ID      = "your-repo-id"       # 已配置的审核规则库 ID

BASE_URL = "https://docflow.textin.com"

# 待处理文件目录(默认指向内置示例文件,可替换为您自己的文件路径)
FILES_DIR = os.path.join(
    os.path.dirname(os.path.abspath(__file__)),
    "..", "sample_files", "ap_review"
)

# ============================================================
# 工具辅助函数
# ============================================================

def _headers() -> dict:
    return {
        "x-ti-app-id":      APP_ID,
        "x-ti-secret-code": SECRET_CODE,
    }


def _check(resp: requests.Response, action: str) -> dict:
    """校验响应状态,返回解析后的 JSON;失败时抛出 RuntimeError。"""
    data = resp.json()
    if data.get("code") != 200:
        raise RuntimeError(f"{action} 失败(code={data.get('code')}): {data}")
    return data


def _mime(file_path: str) -> str:
    """根据文件扩展名返回 MIME 类型。"""
    ext = os.path.splitext(file_path)[1].lower()
    return {
        ".png":  "image/png",
        ".jpg":  "image/jpeg",
        ".jpeg": "image/jpeg",
        ".pdf":  "application/pdf",
        ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
        ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
    }.get(ext, "application/octet-stream")


def display_result(file_result: dict):
    """格式化输出文件的分类结果和字段抽取结果。"""
    print("\n" + "=" * 60)
    print(f"文件名   : {file_result.get('name')}")
    print(f"分类结果 : {file_result.get('category') or '未分类'}")
    data = file_result.get("data") or {}
    fields = data.get("fields") or []
    if fields:
        print("\n── 基本信息字段 ────────────────────────")
        for f in fields:
            print(f"  {f.get('key', ''):<25s}: {f.get('value', '')}")
    tables = data.get("tables") or []
    for table in tables:
        tname = table.get("tableName", "")
        t_items = table.get("items") or []
        if t_items:
            print(f"\n── 表格[{tname}] ──────────────────────")
            for row_idx, row in enumerate(t_items, start=1):
                cells = "  |  ".join(f"{c.get('key')}={c.get('value', '')}" for c in row)
                print(f"  第{row_idx}行: {cells}")


def display_review_result(review_result: dict):
    """格式化输出审核任务的结论和各规则审核结果。"""
    STATUS_MAP = {
        0: "未审核", 1: "审核通过", 2: "审核失败",
        3: "审核中", 4: "审核不通过", 5: "识别中",
        6: "排队中", 7: "识别失败",
    }
    RISK_MAP = {10: "高风险", 20: "中风险", 30: "低风险"}

    stats = review_result.get("statistics", {})
    print("\n" + "=" * 60)
    print(f"审核任务状态  : {STATUS_MAP.get(review_result.get('status'), '未知')}")
    print(f"规则通过数    : {stats.get('pass_count', 0)}")
    print(f"规则不通过数  : {stats.get('failure_count', 0)}")

    for group in review_result.get("groups", []):
        print(f"\n── 规则组:{group.get('group_name')} ───────────────────")
        for rt in group.get("review_tasks", []):
            result_text = STATUS_MAP.get(rt.get("review_result"), "未知")
            risk_text   = RISK_MAP.get(rt.get("risk_level"), "未知")
            icon = "✓" if rt.get("review_result") == 1 else "✗"
            print(f"  {icon} [{risk_text}] {rt.get('rule_name')}: {result_text}")
            reasoning = rt.get("reasoning", "")
            if reasoning:
                print(f"    依据: {reasoning[:100]}{'...' if len(reasoning) > 100 else ''}")


# ============================================================
# 步骤 1:上传待处理文件
# REST API: POST /api/app-api/sip/platform/v2/file/upload
# ============================================================

def upload_file(workspace_id: str, file_path: str) -> str:
    """上传待处理文件至工作空间,返回 batch_number。"""
    url = f"{BASE_URL}/api/app-api/sip/platform/v2/file/upload"
    with open(file_path, "rb") as f:
        resp = requests.post(
            url,
            params={"workspace_id": workspace_id},
            files={"file": (os.path.basename(file_path), f, _mime(file_path))},
            headers=_headers(),
            timeout=60,
        )
    batch_number = _check(resp, "上传文件")["result"]["batch_number"]
    print(f"[步骤1] 文件上传成功  name={os.path.basename(file_path)}"
          f"  batch_number={batch_number}")
    return batch_number


# ============================================================
# 步骤 2:轮询等待抽取结果
# REST API: GET /api/app-api/sip/platform/v2/file/fetch
# ============================================================

def wait_for_result(
    workspace_id: str,
    batch_number: str,
    timeout: int = 120,
    interval: int = 3,
) -> dict:
    """
    轮询直至文件识别完成,返回文件结果对象(含 task_id)。

    recognition_status: 0=待识别, 1=成功, 2=失败
    """
    url = f"{BASE_URL}/api/app-api/sip/platform/v2/file/fetch"
    params = {"workspace_id": workspace_id, "batch_number": batch_number}
    deadline = time.time() + timeout
    print(f"[步骤2] 等待处理结果(batch_number={batch_number})...", end="", flush=True)
    while time.time() < deadline:
        resp = requests.get(url, params=params, headers=_headers(), timeout=30)
        data = _check(resp, "获取处理结果")
        files = data.get("result", {}).get("files", [])
        if files:
            status = files[0].get("recognition_status")
            if status == 1:
                print(" 完成")
                return files[0]
            elif status == 2:
                raise RuntimeError(f"文件处理失败: {files[0].get('failure_causes')}")
        print(".", end="", flush=True)
        time.sleep(interval)
    raise TimeoutError(f"等待处理结果超时({timeout}s)")


# ============================================================
# 步骤 3:提交审核任务
# REST API: POST /api/app-api/sip/platform/v2/review/task/submit
# ============================================================

def submit_review_task(
    workspace_id: str,
    name: str,
    repo_id: str,
    extract_task_ids: list,
) -> str:
    """提交审核任务,返回审核任务 task_id。"""
    url = f"{BASE_URL}/api/app-api/sip/platform/v2/review/task/submit"
    payload = {
        "workspace_id":     workspace_id,
        "name":             name,
        "repo_id":          repo_id,
        "extract_task_ids": extract_task_ids,
    }
    resp = requests.post(url, json=payload, headers=_headers(), timeout=30)
    task_id = _check(resp, "提交审核任务")["result"]["task_id"]
    print(f"[步骤3] 审核任务提交成功  task_id={task_id}")
    return task_id


# ============================================================
# 步骤 4:轮询等待审核结果
# REST API: POST /api/app-api/sip/platform/v2/review/task/result
# ============================================================

def wait_for_review(
    workspace_id: str,
    task_id: str,
    timeout: int = 300,
    interval: int = 5,
) -> dict:
    """
    轮询直至审核任务完成,返回审核结果对象。

    终态: 1=审核通过, 2=审核失败, 4=审核不通过, 7=识别失败
    """
    url = f"{BASE_URL}/api/app-api/sip/platform/v2/review/task/result"
    payload = {"workspace_id": workspace_id, "task_id": task_id}
    deadline = time.time() + timeout
    print(f"[步骤4] 等待审核结果(task_id={task_id})...", end="", flush=True)
    while time.time() < deadline:
        resp = requests.post(url, json=payload, headers=_headers(), timeout=30)
        data = _check(resp, "获取审核结果")
        result = data.get("result", {})
        if result.get("status") in (1, 2, 4, 7):
            print(" 完成")
            return result
        print(".", end="", flush=True)
        time.sleep(interval)
    raise TimeoutError(f"等待审核结果超时({timeout}s)")


# ============================================================
# 主流程
# ============================================================

def main():
    print("=" * 60)
    print("  DocFlow AP 审单场景示例(已完成配置版)")
    print("=" * 60)
    print(f"工作空间: {WORKSPACE_ID}")
    print(f"规则库:   {REPO_ID}")

    # ----------------------------------------------------------
    # 步骤 1:上传待处理文件
    # ----------------------------------------------------------
    print("\n开始上传待处理文件...")
    upload_targets = [
        os.path.join(FILES_DIR, "sample_invoice.pdf"),
        os.path.join(FILES_DIR, "sample_contract.pdf"),
        os.path.join(FILES_DIR, "sample_inbound.pdf"),
        os.path.join(FILES_DIR, "sample_acceptance.pdf"),
    ]
    batch_numbers = [upload_file(WORKSPACE_ID, p) for p in upload_targets]

    # ----------------------------------------------------------
    # 步骤 2:轮询获取抽取结果并展示
    # ----------------------------------------------------------
    print("\n开始获取处理结果...")
    raw_results = []
    for batch_number in batch_numbers:
        result = wait_for_result(WORKSPACE_ID, batch_number)
        raw_results.append(result)
        display_result(result)

    # ----------------------------------------------------------
    # 步骤 3:提交审核任务
    # ----------------------------------------------------------
    print("\n开始审核...")
    task_name = f"AP审核_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
    extract_task_ids = [r.get("task_id") for r in raw_results if r.get("task_id")]
    review_task_id = submit_review_task(WORKSPACE_ID, task_name, REPO_ID, extract_task_ids)

    # ----------------------------------------------------------
    # 步骤 4:轮询获取审核结果并展示
    # ----------------------------------------------------------
    review_result = wait_for_review(WORKSPACE_ID, review_task_id)
    display_review_result(review_result)


if __name__ == "__main__":
    main()

05 完整示例代码下载

完整可运行代码(含 Python、Java 两个版本)已内置在文档仓库的 examples/ 目录下:
examples/
├── python/
│   ├── ap_review_configured.py   # Python 完整示例(已完成配置版)
│   ├── requirements.txt
│   └── README.md
├── java/
│   ├── src/main/java/com/docflow/
│   │   └── ApReviewConfigured.java   # Java 完整示例(已完成配置版)
│   ├── pom.xml
│   └── README.md
└── sample_files/
    └── ap_review/
        ├── sample_invoice.pdf
        ├── sample_contract.pdf
        ├── sample_inbound.pdf
        └── sample_acceptance.pdf

Python 示例

查看 Python 完整示例代码

Java 示例

查看 Java 完整示例代码

06 运行示例

环境要求:Python 3.8+1. 安装依赖
cd examples/python
pip install -r requirements.txt
2. 填写配置打开 ap_review_configured.py,填写文件顶部的配置项:
APP_ID       = "your-app-id"        # x-ti-app-id
SECRET_CODE  = "your-secret-code"   # x-ti-secret-code
WORKSPACE_ID = "your-workspace-id"  # 已创建的工作空间 ID
REPO_ID      = "your-repo-id"       # 已配置的审核规则库 ID
3. 运行
python ap_review_configured.py
运行成功后,可登录 DocFlow Web 页面,在对应工作空间下直观查看每份文件的分类、字段抽取结果和智能审核结果,便于与代码输出对照验证。

预期控制台输出

============================================================
  DocFlow AP 审单场景示例(已完成配置版)
============================================================
工作空间: <workspace_id>
规则库:   <repo_id>

开始上传待处理文件...
[步骤1] 文件上传成功  name=sample_invoice.pdf  batch_number=<batch_number>
[步骤1] 文件上传成功  name=sample_contract.pdf  batch_number=<batch_number>
[步骤1] 文件上传成功  name=sample_inbound.pdf  batch_number=<batch_number>
[步骤1] 文件上传成功  name=sample_acceptance.pdf  batch_number=<batch_number>

开始获取处理结果...
[步骤2] 等待处理结果(batch_number=<batch_number>)..... 完成

============================================================
文件名   : sample_invoice.pdf
分类结果 : 国内票-数电票

── 基本信息字段 ────────────────────────
  价税合计                  : ¥50000.00
  税前金额                  : ¥44247.79
  发票号码                  : 25312000000123456789
  开票日期                  : 2025年12月01日
  购买方名称                : 上海合合信息科技股份有限公司
  销售方名称                : 上海一二三有限公司
  ...

[步骤2] 等待处理结果(batch_number=<batch_number>)... 完成

============================================================
文件名   : sample_contract.pdf
分类结果 : 采购合同

── 基本信息字段 ────────────────────────
  合同编号                  : CG-123456
  甲方全称                  : 上海合合信息科技股份有限公司
  乙方全称                  : 上海一二三有限公司
  ...

[步骤2] 等待处理结果(batch_number=<batch_number>)... 完成

============================================================
文件名   : sample_inbound.pdf
分类结果 : 入库单

── 基本信息字段 ────────────────────────
  收货单编号                : GR-2025-03-0201
  仓库/库位                : 上海总部仓库/A区-03
  ...

[步骤2] 等待处理结果(batch_number=<batch_number>)... 完成

============================================================
文件名   : sample_acceptance.pdf
分类结果 : 验收单

── 基本信息字段 ────────────────────────
  乙方施工人                : 胡工
  验收说明                  : 根据双方签订的《合同》内容,乙方已完成了合同内约定的...
  ...

开始审核...
[步骤3] 审核任务提交成功  task_id=<task_id>
[步骤4] 等待审核结果(task_id=<task_id>).... 完成

============================================================
审核任务状态  : 审核不通过
规则通过数    : 4
规则不通过数  : 1

── 规则组:AP审单合规性检查 ───────────────────
  ✓ [高风险] 发票金额与合同金额一致性: 审核通过
  ✓ [高风险] 入库数量与合同数量一致性: 审核通过
  ✓ [中风险] 验收单签署完整性: 审核通过
  ✗ [中风险] 发票开票日期合理性: 审核不通过
    依据: 发票开票日期(2025年12月01日)晚于入库日期,不符合先到货后开票的时序要求
  ✓ [低风险] 供应商名称一致性: 审核通过
    ...

07 结果说明

抽取结果

处理完成后,每份文件将返回分类结果和字段抽取结果。字段抽取结果位于 data.fields[],表格字段位于 data.items[][],每个字段包含 keyvalue 及坐标 position(可用于原文高亮回显)。 以下为各样本文件的实际接口返回(来自 file/fetch,省略了部分 position 坐标和字段):

sample_invoice.pdf

{
  "name": "sample_invoice.pdf",
  "format": "pdf",
  "category": "国内票-数电票",
  "recognition_status": 1,
  "duration_ms": 4832,
  "data": {
    "fields": [
      { "key": "价税合计",           "value": "¥50000.00" },
      { "key": "税前金额",           "value": "¥44247.79" },
      { "key": "税额",              "value": "3347.79" },
      { "key": "发票号码",           "value": "25312000000123456789" },
      { "key": "开票日期",           "value": "2025年12月01日" },
      { "key": "购买方名称",          "value": "上海合合信息科技股份有限公司" },
      { "key": "购买方纳税人识别号",   "value": "91310110791485269J" },
      { "key": "销售方名称",          "value": "上海一二三有限公司" },
      { "key": "销售方纳税人识别号",   "value": "91310111111111111D" }
    ],
    "items": [
      [
        { "key": "商品名称", "value": "*计算机网络设备*交换机" },
        { "key": "规格型号", "value": "RG-S6510-48VS8CQ" },
        { "key": "单位",    "value": "台" },
        { "key": "数量",    "value": "1" },
        { "key": "单价",    "value": "25752.21" },
        { "key": "金额",    "value": "25752.21" },
        { "key": "税率",    "value": "13%" },
        { "key": "税额",    "value": "3347.79" }
      ]
    ],
    "stamps": [],
    "handwritings": []
  }
}
{
  "name": "sample_contract.pdf",
  "format": "pdf",
  "category": "采购合同",
  "recognition_status": 1,
  "data": {
    "fields": [
      { "key": "合同编号",     "value": "CG-123456" },
      { "key": "合同名称",     "value": "合合信息采购合同" },
      { "key": "甲方全称",     "value": "上海合合信息科技股份有限公司" },
      { "key": "乙方全称",     "value": "上海一二三有限公司" },
      { "key": "含税总金额(大写)", "value": "伍万元整" },
      { "key": "税率",        "value": "13%" },
      { "key": "账期",        "value": "60日" },
      { "key": "签订日期",     "value": "2025年9月25日" }
    ]
  }
}
{
  "name": "sample_inbound.pdf",
  "format": "pdf",
  "category": "入库单",
  "recognition_status": 1,
  "data": {
    "fields": [
      { "key": "收货单编号",   "value": "GR-2025-03-0201" },
      { "key": "仓库/库位",   "value": "上海总部仓库/A区-03" }
    ]
  }
}
{
  "name": "sample_acceptance.pdf",
  "format": "pdf",
  "category": "验收单",
  "recognition_status": 1,
  "data": {
    "fields": [
      { "key": "乙方施工人",   "value": "胡工" },
      { "key": "验收说明",     "value": "根据双方签订的《合同》内容,乙方已完成了合同内约定的软件到货、资料移交、安装、调试、测试、试运行等相关工作..." }
    ]
  }
}

审核结果

审核完成后,可从 review/task/result 接口获取以下信息:
  • status:任务整体状态(1=审核通过,4=审核不通过,2=审核失败)
  • statistics:规则通过数、不通过数汇总
  • groups[].review_tasks[]:每条规则的详细审核结果,包含:
    • review_result:该规则的审核结论(1=通过,4=不通过)
    • reasoning:AI 给出的审核依据说明
    • anchors:依据在原文中的坐标位置(可用于高亮回显)
{
  "task_id": "31415926",
  "task_name": "AP审核",
  "status": 1,
  "statistics": { "pass_count": 6, "failure_count": 0, "error_count": 0 },
  "groups": [
    {
      "group_name": "供应商一致性校验",
      "review_tasks": [
        {
          "rule_name": "供应商一致性",
          "risk_level": 10,
          "review_result": 1,
          "reasoning": "发票销售方名称"上海一二三有限公司"与采购合同乙方全称一致,纳税人识别号 91310111111111111D 匹配,审核通过。"
        },
        {
          "rule_name": "发票与合同金额匹配",
          "risk_level": 10,
          "review_result": 1,
          "reasoning": "发票价税合计 ¥50000.00 与采购合同含税总金额"伍万元整"一致,审核通过。"
        },
        {
          "rule_name": "发票购买方与合同甲方一致",
          "risk_level": 20,
          "review_result": 1,
          "reasoning": "发票购买方"上海合合信息科技股份有限公司"与采购合同甲方全称一致,审核通过。"
        }
      ]
    },
    {
      "group_name": "入库与合同一致性",
      "review_tasks": [
        {
          "rule_name": "入库数量与合同数量匹配",
          "risk_level": 10,
          "review_result": 1,
          "reasoning": "入库单实收数量与采购合同约定数量一致,审核通过。"
        },
        {
          "rule_name": "入库单号关联合同号",
          "risk_level": 20,
          "review_result": 1,
          "reasoning": "入库单对应合同号 CG-123456 与采购合同编号一致,审核通过。"
        }
      ]
    },
    {
      "group_name": "验收合规性",
      "review_tasks": [
        {
          "rule_name": "验收单与入库单物料一致",
          "risk_level": 20,
          "review_result": 1,
          "reasoning": "验收单合同编号与入库单对应合同号均为 CG-123456,物料关联一致,审核通过。"
        }
      ]
    }
  ]
}