ai-settlement-pro

AI驱动的智能结算助手,支持话题词精准识别、自然语言规则解析、规则确认流程、多种结算模式(达标瓜分/排名赛/混合模式),数据本地处理保障安全。触发词:「一组结算」「帮我结算」「请结算」「结算活动」。

Safety Notice

This listing is from the official public ClawHub registry. Review SKILL.md and referenced scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "ai-settlement-pro" with this command: npx skills add luxieng030124-max/jiesuan

AI智能结算助手 Pro v2.1.1

融合AI规则解析与高效数据处理的专业结算工具,让复杂活动结算变得简单智能。


⚠️ 执行规则(最高优先级,必须遵守)

核心原则:本Skill在文档末尾内嵌了完整可执行的结算引擎代码,AI必须直接复制该代码块到执行环境中运行,严禁自行手写替代代码。

强制要求

  1. 必须使用内嵌的结算引擎代码:文档末尾"结算引擎代码"章节包含完整实现,所有结算逻辑(数据加载、条件过滤、金额计算、CSV导出)均已就绪,不得另行实现。
  2. 禁止手动计算奖金:不得用 round()、手写除法或任何自定义方式计算每人奖金,必须调用内嵌代码中的 _distribute_equal()run_settlement()
  3. 禁止自定义精度处理:内嵌代码已内置 Decimal 高精度算法,直接使用即可,不得重复实现。
  4. AI只需构造奖池配置:读懂规则后,构造 AwardPool 列表传入 run_settlement(),其他全部由引擎处理。
# 正确执行方式:
# 1. 复制文档末尾"完整结算引擎代码"代码块,全部执行
# 2. 根据活动规则构造 pools 列表
# 3. 调用 run_settlement(file_path, pools, ...)
# 4. 调用 export_to_csv() 导出结果

AI行为检查清单(每次结算前自查)

  • 已将文档末尾的完整引擎代码块复制到执行环境并运行?
  • 未自行实现任何结算逻辑(包括数据加载、金额计算)?
  • 未使用 round() 计算奖金?
  • 金额计算通过内嵌代码的 _distribute_equal() 函数完成?
  • 已验证 每人金额 × 人数 == 奖池金额

🚫 AI执行约束(所有操作均受此章节约束)

以下约束适用于 AI 在整个结算过程中的所有行为,包括代码生成、计算、输出。违反任何一条均视为执行错误。

0. 开始结算前:必须完整列出所有奖池

在构造任何代码之前,AI必须先从用户描述中提取并列出全部奖池,格式如下:

📋 我理解本次活动共有 X 个奖池:
  奖池1: [名称] - [金额] - [类型:达标瓜分/排名奖/权重分配] - [达标条件]
  奖池2: [名称] - [金额] - [类型] - [条件]
  ...
  总奖金合计: [所有奖池金额之和]

请确认是否遗漏?确认后我将开始构造结算代码。
  • ✅ 必须逐一列出,等待用户确认后再执行
  • ❌ 禁止跳过此步骤直接写代码
  • ❌ 禁止只构造部分奖池(如只写瓜分,遗漏榜单奖)
  • ❌ 如果所有奖池金额之和与用户说的总奖金不符,必须询问用户

1. 严格按文档算法执行

  • ✅ 仅使用本文档明确描述的算法和逻辑
  • ❌ 禁止自行添加任何「优化」、「修正」、「调整」逻辑,即使 AI 认为这样更「合理」
  • ❌ 禁止在未告知用户的情况下更改计算方式

2. 瓜分奖金额必须完全相等

  • ✅ 同一奖池内,所有达标者奖金数值完全相同(字符串完全一致)
  • ❌ 禁止以任何形式让某一个人的金额与其他人不同,包括:
    • 给「第一个人」多分几分钱来消化差额
    • 给「最后一个人」少分钱来凑整
    • 以「精度误差修正」为由调整任何人的金额
  • ❌ 禁止在代码中出现类似 results[0].award += remainder 的逻辑

3. 不添加文档未提及的功能

  • ✅ 代码实现与文档描述的逻辑保持完全一致
  • ❌ 禁止添加文档未提及的过滤条件、排序规则、特殊处理逻辑
  • ❌ 禁止「顺手」修复 AI 认为的潜在问题(如自动过滤异常数据行)

4. 有疑问时必须询问用户

  • ✅ 规则存在歧义 → 列出歧义点并询问用户,等待明确答复后再执行
  • ✅ 数据字段无法匹配 → 告知用户并询问对应关系,不得猜测
  • ✅ 奖池金额无法整除 → 自动执行方案B(保留最少小数位使每人金额相同),并在输出时告知用户保留了几位小数
  • ❌ 禁止在任何不确定的情况下自行假设并执行

🎯 核心能力

能力说明
🧠 AI规则解析用自然语言描述规则,AI自动转换为结算逻辑
🏷️ 话题词识别精准匹配视频标题中的话题词,支持且/或逻辑
✅ 规则确认流程返回规则理解供用户确认,可多轮修正
📊 多模式结算达标瓜分、排名/榜单奖、混合多奖池并行、权重分配
📂 多格式数据源CSV 和 Excel (xlsx/xls),支持多 Sheet 合并
⚡ 高效处理本地Python处理,万级数据秒级完成
🛡️ 数据安全原始数据不上传,仅规则描述发送至AI
🔧 健壮可靠超时自动重试、分级错误提示、断点恢复

🚀 快速开始

方式一:自然语言直接结算(推荐)

请帮我结算这个活动:
总奖金2万元,发布作品≥5条且播放量≥3万,必须携带话题#春节活动的作者等额瓜分

快捷触发词:发送 「一组结算」 可直接进入结算流程,无需其他前缀。 同样有效的触发词:「帮我结算」「请结算」「结算活动」「一组结算」

话题词说明

  • 可以是任意话题,如:#春节活动#新品发布#挑战赛#品牌联名
  • 系统会自动从你的描述中提取 # 开头的话题词
  • 支持多个话题词组合(且/或关系)

方式二:分步操作

# 步骤1:打开结算助手
打开AI结算助手

# 步骤2:描述规则(包含话题词要求)
规则:播放量≥3万,同时携带话题 #春节活动 和 #新年优惠 的作者瓜分2万奖金

# 注意:话题词可以是任意内容,根据你的活动需求自定义

# 步骤3:确认规则
查看AI返回的规则理解,确认或修改

# 步骤4:上传数据
[上传Excel/CSV文件,必须包含视频标题字段]

# 步骤5:获取结果
下载结算结果

💬 自然语言规则示例

✅ 达标瓜分模式

总奖金10万元,完成量≥100的用户等额瓜分奖池
设置两个奖池:
- 基础奖池5万:完成量≥50的用户瓜分
- 进阶奖池5万:完成量≥100的用户瓜分

⚠️ 分档瓜分:必须询问是否互斥

当规则包含多档奖池(如1档/2档/3档)时,在规则确认阶段必须主动询问用户档位是否互斥,不得自行假设,不得跳过询问直接结算。

询问时机与格式

在返回规则理解供用户确认时,若检测到多档奖池结构,必须在确认信息末尾加入以下问题

❓ 档位关系确认(必须回答后才能继续):
   您的规则包含多档奖池,请确认档位关系:
   A. 【累进叠加】满足高档条件的作者同时获得所有低档奖励
      例:发7条 → 同时获得1档+2档+3档奖励
   B. 【互斥独享】每位作者只能获得其满足的最高档奖励
      例:发7条 → 只获得3档奖励
   请回复 A 或 B,或直接说明。

根据用户回答处理

用户选A(累进叠加):各档独立判断资格,结算代码对每档独立循环,不使用 elif

# ✅ 累进写法:各档独立判断
for tier in [tier1, tier2, tier3]:
    qualified = [a for a in authors if a.valid_videos >= tier.min_videos]
    per_person = tier.pool / len(qualified)
    for a in qualified:
        a.awards[tier.name] = per_person
作者有效视频数参与1档(≥2条)参与2档(≥4条)参与3档(≥7条)总奖金
2条1档金额
4条1档+2档
7条1档+2档+3档

用户选B(互斥独享):每位作者只取满足的最高档,结算代码使用 elif 链从高到低判断:

# ✅ 互斥写法:从高档到低档判断
for a in authors:
    if a.valid_videos >= tier3.min_videos:
        a.awards['奖励'] = tier3.pool / qualified3_count
    elif a.valid_videos >= tier2.min_videos:
        a.awards['奖励'] = tier2.pool / qualified2_count
    elif a.valid_videos >= tier1.min_videos:
        a.awards['奖励'] = tier1.pool / qualified1_count

✅ 带话题词要求的瓜分模式

支持任意话题词,动态配置

总奖金2万元,发布作品≥5条且播放量≥3万,必须携带话题 #你的话题 的作者等额瓜分
奖池5万元,播放量≥10万,同时携带话题 #话题A 和 #话题B 的作者瓜分
总奖金3万,作品≥3条,携带话题 #话题1 或 #话题2 的用户瓜分

话题词规则说明

  • 🎯 动态配置:支持任意话题词,用户自定义(如 #春节活动、#新品发布、#品牌联名 等)
  • 精准匹配:话题词必须完全匹配,例如 #春节 不会匹配 #春节活动
  • 🔗 且关系(AND):要求同时携带所有指定话题词(关键词:且/和/同时/都/AND)
  • 🔗 或关系(OR):要求至少携带一个话题词(关键词:或/OR,或无明确关键词)
  • 📝 自动识别:用 # 开头标识话题词,AI自动提取和识别逻辑关系

话题词格式要求

  • ✅ 必须以 # 开头
  • ✅ 后面跟任意文字(中文、英文、数字均可)
  • ✅ 遇到空格、逗号、或特殊符号自动截断
  • ✅ 示例:#春节活动#NewYear#2024挑战

✅ 排名赛模式

按销售额降序排名:
- 第1名:10000元
- 第2-3名:各5000元
- 第4-10名:各2000元

✅ 混合不互斥模式

双奖池并行:
1. 达标奖池5万:播放量≥1万的作者平分
2. 排名奖池5万:按点赞数排名前20名,第1-5名5000元,第6-10名3000元,第11-20名1500元

✅ 权重分配模式

总奖金8万元,按播放量权重比例分配

🔢 精确瓜分算法(必须遵守)

核心要求:每人金额必须严格相等,不允许有人多有人少。奖池金额必须被彻底瓜分,不多不少。

算法约束(与上方「AI执行约束」共同生效)

约束说明
✅ 所有达标者金额数值完全相同同一奖池内,每人奖金字符串必须一致
❌ 禁止「差额补给第一人」results[0].award += 0.01 绝对禁止
❌ 禁止自行截断后丢弃差额截断后差额不能悄悄归零,必须报告用户
❌ 禁止以「修正精度误差」为由调整任何人金额精度问题应通过方案选择解决,不是代码补丁

精度问题的唯一合法处理方案(方案B)

当奖池金额无法被参与人数整除时(如 30000 / 17),AI必须自动执行方案B,并在输出结果时告知用户

⚠️ 精度提示:奖池 30,000 元 / 17 人 无法以2位小数等额整除
   已自动执行方案B:保留最少小数位数使每人金额完全相同
   每人 1764.705882 元(6位小数),17人合计精确等于 30,000 元
   → 所有人金额相同,奖池金额完全分完

禁止截断到2位小数后丢弃差额(即方案A不允许使用)。 ✅ 始终自动执行方案B:找到使 per × n == total 成立的最小小数位数。

每人金额相等的正确实现

直接均分时保留足够小数位数,确保每人金额完全相同:

from decimal import Decimal, ROUND_HALF_UP, getcontext

def distribute_equal(total_yuan, n):
    """
    等额瓜分:每人金额严格相同
    - 先尝试精确整除
    - 若能整除(如 5000 / 10 = 500.00),保留两位小数
    - 若不能整除(如 5000 / 17),保留足够位数使每人金额完全相同
      并验证 per_person * n == total_yuan
    """
    getcontext().prec = 28
    total = Decimal(str(total_yuan))
    count = Decimal(n)
    per_person = total / count  # 精确除法

    # 检查是否能整除到两位小数
    per_2 = per_person.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
    if per_2 * count == total:
        return float(per_2), 2  # 返回(每人金额, 小数位数)

    # 否则找到能让 per * n == total 的最小小数位数(最多10位)
    for digits in range(3, 11):
        fmt = '0.' + '0' * digits
        per_d = per_person.quantize(Decimal(fmt), rounding=ROUND_HALF_UP)
        if per_d * count == total:
            return float(per_d), digits

    # 极端情况:直接返回高精度值,在表格中标注
    return float(per_person), -1

# 示例
# distribute_equal(5000, 10)  → (500.0, 2)  每人500.00元
# distribute_equal(5000, 17)  → 需要找到能被整除的精度
#   5000/17 = 294.117647058823...  → 若无法整除,按原始精确值记录,备注「无法等额整除」

若奖池无法等额整除

统一执行方案B:找到使 per × n == total 成立的最小小数位数,保持所有人金额完全相同,奖池金额精确分完。

  • ❌ 不得截断到2位小数后丢弃差额
  • ❌ 不得询问用户是否接受截断(方案A已废弃)
  • ✅ 在输出结果时注明保留的小数位数,让用户知晓

验证规则

输出结果前必须验证:

  • 每人金额 × 人数 == 奖池金额(精确验证,不允许浮点误差)
  • 验证失败则不允许输出,必须重新计算或报错

🔧 工作流程

┌─────────────────────────────────────────────────────┐
│  用户输入自然语言规则                                  │
│  "总奖金2万,播放量≥3万,携带话题#春节活动的作者瓜分"     │
└──────────────────────┬──────────────────────────────┘
                       ↓
┌─────────────────────────────────────────────────────┐
│  AI自动提取话题词并解析规则 → 结构化配置               │
│  {                                                   │
│    mode: "guaranteed",                               │
│    pool: 20000,                                      │
│    condition: {field: "播放量", op: ">=", value: 30000},│
│    topic_rule: {topics: ["#春节活动"], logic: "OR"}  │
│  }                                                   │
└──────────────────────┬──────────────────────────────┘
                       ↓
┌─────────────────────────────────────────────────────┐
│  返回规则理解给用户确认                                │
│  ═══════════════════════════════════════════        │
│  📋 规则理解确认                                      │
│  【奖池1】达标瓜分奖池                                │
│    💰 奖池金额: 20,000元                             │
│    ✅ 达标条件: 播放量 >= 30,000                     │
│    🏷️ 话题词要求: #春节活动                          │
│  ❓ 请确认以上规则理解是否正确?                       │
│  ═══════════════════════════════════════════        │
└──────────────────────┬──────────────────────────────┘
                       ↓
┌─────────────────────────────────────────────────────┐
│  用户确认/调整AI理解                                  │
│  • "对,但需要同时携带#春节活动和#新年优惠"            │
│    → AI更新配置 → 再次返回确认                        │
│  • "确认无误" → 进入下一步                            │
└──────────────────────┬──────────────────────────────┘
                       ↓
┌─────────────────────────────────────────────────────┐
│  上传数据文件(Excel/CSV)                            │
│  本地处理,数据不上传                                 │
└──────────────────────┬──────────────────────────────┘
                       ↓
┌─────────────────────────────────────────────────────┐
│  执行结算                                             │
│  - 数据聚合(按作者ID汇总)                            │
│  - 话题词检查(精准匹配)                              │
│  - 条件筛选                                           │
│  - 金额计算                                           │
│  - 结果生成                                           │
└──────────────────────┬──────────────────────────────┘
                       ↓
┌─────────────────────────────────────────────────────┐
│  输出结果                                            │
│  - 结算摘要(统计信息)                                │
│  - 详细名单(Excel下载)                              │
│  - 异常记录(如有)                                   │
└─────────────────────────────────────────────────────┘

🏷️ 话题词识别详解

动态配置 - 支持任意话题词

话题词不是固定的,可以根据活动需求自定义配置。系统会自动从用户输入的规则中提取话题词。

支持的话题词示例

  • #春节活动 - 节日主题
  • #新品发布 - 产品活动
  • #品牌联名 - 营销活动
  • #挑战赛2024 - 竞赛活动
  • #GameEvent - 英文话题
  • #攻略 - 简短话题
  • 任意以 # 开头的文字

精准匹配规则

话题词必须完全匹配,部分匹配无效:

要求话题词视频标题是否匹配说明
#春节"最强攻略 #春节 活动"✅ 匹配完整包含#春节
#春节"最强攻略 #春节活动 指南"❌ 不匹配#春节活动 != #春节
#春节"活动 #春节 和 #春节活动"✅ 匹配包含完整的#春节
#新品 + #发布(且)"#新品 #发布 预告"✅ 匹配两个都有
#新品 + #发布(且)"#新品 预告视频"❌ 不匹配缺少#发布
#新品 + #发布(或)"#新品 预告视频"✅ 匹配至少有一个

逻辑关系识别

且关系 (AND) - 关键词触发:

  • "且"、"和"、"同时"、"都"、"AND"
  • 示例:同时携带#春节和#活动

或关系 (OR) - 关键词触发:

  • "或"、"OR"
  • 默认(无明确关键词时)
  • 示例:携带#话题A或#话题B

话题词提取规则

系统使用正则表达式自动提取:

# 匹配模式:# 后跟非特殊字符
topics = re.findall(r'#[^#\s,,和或]+', rule_text)

会被识别为话题词

  • #春节活动 → 提取 #春节活动
  • #NewYear2024 → 提取 #NewYear2024
  • #挑战 → 提取 #挑战

不会被识别

  • ❌ 没有 # 符号的文字
  • # 后直接跟空格、逗号等

多视频检查策略

作者只要有任意一个视频满足话题词要求即可:

作者A发布了3个视频:
  视频1: "日常vlog"              → 无话题词
  视频2: "#春节活动 攻略"        → 包含#春节活动 ✅
  视频3: "游戏实况"              → 无话题词

要求: 携带话题#春节活动
结果: ✅ 作者A符合要求(视频2包含)

常见应用场景

活动类型话题词示例规则示例
节日营销#春节#中秋携带话题#春节的作者瓜分
产品推广#新品发布#限时特惠同时携带#新品发布和#优惠的作者
品牌活动#品牌挑战赛#品牌联名携带#品牌挑战赛或#品牌联名
游戏活动#攻略#教学同时携带#攻略和#教学
UGC征集#创作挑战#用户故事携带#创作挑战的用户

📋 数据格式要求

支持的文件格式

  • ✅ Excel (.xlsx, .xls)
  • ✅ CSV (.csv,推荐UTF-8编码)

必需字段

字段类型说明示例
用户ID唯一标识作者ID、用户ID、账号
用户名称显示名称作者名称、用户昵称
视频标题话题词匹配用视频标题、标题
数据字段结算依据播放量、销售额、完成量
排序字段排名赛使用点赞数、销售额

示例数据结构

视频数据

作者ID作者名称视频ID视频标题播放量点赞数
123456张三789"#金铲铲 最强阵容"500001000
123456张三790"游戏攻略"30000500
789012李四791"#金铲铲 #攻略"800002000

聚合后结算

作者ID作者名称作品数累计播放量累计点赞符合话题词
123456张三2800001500✅ (视频789)
789012李四1800002000✅ (视频791)

📝 版本历史

v2.1.1 (2026-05-09)

  • 新增「AI执行约束」章节:严格禁止 AI 自行添加「优化」逻辑、修改计算方式、添加文档未说明功能
  • 强化瓜分算法约束:明确要求所有达标者奖金完全相同,明确禁止「差额补给某人」、「少给某人」、「自行处理差额」
  • 规范精度无法整除时的处理:统一执行方案B(保留最少小数位使金额整除),废除方案A(截断后丢弃差额)
  • 明确有疑问时必须询问用户:规则歧义、字段不匹配、奖池无法整除均必须等待用户决策

v2.1.0 (2026-05-08)

  • 支持 Excel (xlsx/xls):新增 openpyxl 读取,自动检测编码,支持多 Sheet 合并
  • 多奖池并行SettlementEngine 支持同时计算任意数量奖池(达标瓜分 + 榜单奖混合)
  • 榜单/排名奖:新增 SettlementMode.RANKING,支持分段档位(第1名/第2-3名/...)固定金额或瓜分
  • 多条件达标conditions 字段支持 AND 逻辑多条件(如作品数≥5 且 播放量≥3万)
  • 精度修复export_to_csv 金额列不再用 :.2f 截断,完整保留精确小数
  • 直播数据支持:新增 live_duration/live_count/live_sales 字段聚合
  • 命令行参数扩展:支持 sheet名称--all-sheets 参数

v2.0.2 (2026-05-08)

  • 新增强制执行规则章节:明确要求AI必须调用 settlement_engine.py,禁止手写替代代码
  • 禁止 round() 精度陷阱:明确标注常见错误写法,防止AI第一次计算时忽略精度要求
  • 完整执行流程示例:提供可直接复制执行的4步代码,含错误写法对比
  • AI自查清单:结算前必须完成的5项检查,确保不遗漏关键步骤
  • 命令行快速执行:补充命令行调用示例,AI可直接用 python settlement_engine.py 完成结算

v2.0.1 (2026-05-07)

  • ✅ 修复精确瓜分算法:彻底解决浮点舍入导致总额不等于奖池的问题
  • ✅ 明确分档瓜分(累进档位)规则:各档独立判断,高档位作者可同时参与低档奖励
  • ✅ 新增触发词「一组结算」,可直接启动结算流程
  • 📝 补充分档瓜分示例和代码规范

v1.1.0 (2026-04-21)

  • ✅ 新增话题词精准匹配功能
    • 支持精准识别视频标题中的话题词(如#金铲铲)
    • 支持且/或逻辑关系(AND/OR)
    • 自动区分相似话题词(#金铲铲 vs #金铲铲之战)
  • ✅ 新增规则确认流程
    • AI解析后返回格式化的规则理解
    • 用户可多轮修正直到确认
    • 避免因理解偏差导致的结算错误
  • 📊 优化数据加载逻辑,支持读取视频标题字段

v1.0.0 (2026-04-11)

  • ✅ 初始版本发布
  • ✅ 支持自然语言规则解析
  • ✅ 四种结算模式:达标瓜分、排名赛、混合不互斥、权重分配
  • ✅ 超时自动重试机制
  • ✅ 数据本地处理,保障安全
  • ✅ 专业Excel输出格式

💡 设计哲学

让结算变得简单,让数据变得安全,让结果变得专业。

核心原则

  1. AI优先:用自然语言替代复杂配置
  2. 本地处理:数据不出本地,安全保障
  3. 健壮可靠:超时重试,容错恢复
  4. 专业输出:格式规范,直接可用

🧪 结算引擎代码(AI直接使用,无需查找外部文件)

⚠️ AI执行结算时,必须将以下完整代码块全部复制到执行环境中运行,禁止自行手写任何替代实现。

完整结算引擎代码(直接复制执行)

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""AI智能结算引擎 v2.1.1 - 直接复制此代码块到执行环境"""

import csv, re, os
from decimal import Decimal, ROUND_HALF_UP, getcontext
from typing import Dict, List, Any, Optional, Tuple
from dataclasses import dataclass, field
from enum import Enum

getcontext().prec = 28

class SettlementMode(Enum):
    GUARANTEED = "guaranteed"
    RANKING    = "ranking"
    WEIGHTED   = "weighted"

@dataclass
class TopicRule:
    topics: List[str]
    logic: str = "OR"
    def check(self, title: str) -> bool:
        if not self.topics: return True
        matches = [bool(re.search(r'(?:^|[\s,,。!!??;;::、])' + re.escape(t) + r'(?:$|[\s,,。!!??;;::、])', title)) for t in self.topics]
        return all(matches) if self.logic == "AND" else any(matches)

@dataclass
class RankingTier:
    rank_start: int
    rank_end:   int
    amount:     float = 0.0
    pool:       float = 0.0

@dataclass
class AwardPool:
    name:          str
    amount:        float
    mode:          SettlementMode
    condition:     Optional[Dict] = None
    ranking_field: Optional[str]  = None
    ranking_tiers: Optional[List[RankingTier]] = None
    weight_field:  Optional[str]  = None
    topic_rule:    Optional[TopicRule] = None
    conditions:    Optional[List[Dict]] = None

@dataclass
class AuthorData:
    author_id:    str
    author_name:  str
    videos:       int = 0
    total_plays:  int = 0
    total_likes:  int = 0
    video_titles: List[str] = field(default_factory=list)
    live_duration: int = 0
    live_count:    int = 0
    live_sales:    int = 0
    extra: Dict[str, Any] = field(default_factory=dict)

@dataclass
class SettlementResult:
    author_id:    str
    author_name:  str
    videos:       int
    total_plays:  int
    total_likes:  int
    awards:       Dict[str, str]
    total_amount: str

def _distribute_equal(total_yuan: float, n: int) -> str:
    """
    等额瓜分核心函数。保证所有人金额完全相同,奖池金额精确分完。
    ❌ 禁止用 round() 替代此函数。
    """
    total = Decimal(str(total_yuan)); count = Decimal(n); per = total / count
    for digits in range(2, 11):
        per_r = per.quantize(Decimal('0.' + '0' * digits), rounding=ROUND_HALF_UP)
        if per_r * count == total:
            return str(per_r)
    return str(per.quantize(Decimal('0.0000000001'), rounding=ROUND_HALF_UP))

FIELD_ALIASES = {
    'author_id':     ['作者ID', '用户ID', '账号ID', 'authorId', 'uid'],
    'author_name':   ['作者名称(最新)', '作者名称', '用户名称', '昵称', 'authorName'],
    'title':         ['视频标题', '标题', 'title'],
    'plays':         ['视频累计外显播放次数', '播放量', '播放次数', 'plays'],
    'likes':         ['视频累计外显点赞次数', '点赞量', '获赞数', 'likes'],
    'live_duration': ['直播时长', '直播累计时长', 'liveDuration'],
    'live_count':    ['开播场次', '直播场次', 'liveCount'],
    'live_sales':    ['直播销售额', '销售额', 'liveSales'],
}

def _resolve(row, key):
    for alias in FIELD_ALIASES.get(key, [key]):
        if alias in row: return row[alias]
    return None

def _num(val):
    if val is None or val == '': return 0
    try: return int(float(str(val).replace(',', '')))
    except: return 0

def _load_csv(fp):
    for enc in ('utf-8-sig', 'gbk', 'utf-8'):
        try:
            with open(fp, 'r', encoding=enc) as f:
                return {os.path.splitext(os.path.basename(fp))[0]: list(csv.DictReader(f))}
        except: continue
    return {}

def _load_excel(fp, sheet_name, all_sheets):
    try: import openpyxl
    except ImportError: raise ImportError("读取Excel需要安装openpyxl:pip install openpyxl")
    wb = openpyxl.load_workbook(fp, read_only=True, data_only=True)
    target = wb.sheetnames if all_sheets else ([sheet_name] if sheet_name else [wb.sheetnames[0]])
    result = {}
    for sname in target:
        ws = wb[sname]; rows_iter = ws.iter_rows(values_only=True)
        header = [str(h).strip() if h is not None else '' for h in next(rows_iter, [])]
        result[sname] = [{header[i]: (row[i] if i < len(row) else None) for i in range(len(header))} for row in rows_iter]
    wb.close(); return result

def load_file(fp, sheet_name=None, all_sheets=False):
    ext = os.path.splitext(fp)[1].lower()
    if ext == '.csv': return _load_csv(fp)
    elif ext in ('.xlsx', '.xls', '.xlsm'): return _load_excel(fp, sheet_name, all_sheets)
    else: raise ValueError(f"不支持的格式: {ext}")

def _aggregate(rows):
    authors = {}
    for row in rows:
        aid = str(_resolve(row, 'author_id') or '').strip()
        if not aid: continue
        if aid not in authors: authors[aid] = AuthorData(author_id=aid, author_name='')
        a = authors[aid]
        name = str(_resolve(row, 'author_name') or '').strip()
        if name: a.author_name = name
        a.videos += 1; a.total_plays += _num(_resolve(row, 'plays')); a.total_likes += _num(_resolve(row, 'likes'))
        a.live_duration += _num(_resolve(row, 'live_duration')); a.live_count += _num(_resolve(row, 'live_count')); a.live_sales += _num(_resolve(row, 'live_sales'))
        t = str(_resolve(row, 'title') or '').strip()
        if t: a.video_titles.append(t)
        for k, v in row.items():
            if k not in a.extra: a.extra[k] = v
    return authors

def _merge(base, new_authors):
    for aid, data in new_authors.items():
        if aid in base:
            a = base[aid]
            if data.author_name: a.author_name = data.author_name
            a.videos += data.videos; a.total_plays += data.total_plays; a.total_likes += data.total_likes
            a.live_duration += data.live_duration; a.live_count += data.live_count; a.live_sales += data.live_sales
            a.video_titles += data.video_titles; a.extra.update(data.extra)
        else: base[aid] = data

FMAP = {
    '播放量': 'total_plays', '播放': 'total_plays', '点赞': 'total_likes', '获赞': 'total_likes',
    '直播时长': 'live_duration', '开播场次': 'live_count', '销售额': 'live_sales', '作品数': 'videos',
}

def _topic_ok(a, rule): return not rule or any(rule.check(t) for t in a.video_titles)

def _cond_ok(a, pool):
    for cond in (pool.conditions or ([pool.condition] if pool.condition else [])):
        attr = FMAP.get(cond.get('field', ''), cond.get('field', ''))
        actual = getattr(a, attr, None) or a.extra.get(attr, 0)
        op = cond.get('op', '>='); val = cond.get('value', 0)
        if op in ('>=', '≥'): ok = actual >= val
        elif op == '>': ok = actual > val
        elif op in ('<=', '≤'): ok = actual <= val
        elif op == '<': ok = actual < val
        elif op == '==': ok = actual == val
        else: ok = False
        if not ok: return False
    return True

def run_settlement(file_path, pools, sheet_name=None, all_sheets=False, extra_file=None):
    """
    主结算函数。pools 由 AI 根据活动规则构造。
    - file_path: CSV 或 Excel
    - all_sheets=True: 合并所有 Sheet
    - extra_file: 第二个底表文件(数据合并后统一结算)
    """
    authors = {}
    for rows in load_file(file_path, sheet_name, all_sheets).values():
        _merge(authors, _aggregate(rows))
    if extra_file:
        for rows in load_file(extra_file).values():
            _merge(authors, _aggregate(rows))

    results = {aid: SettlementResult(aid, a.author_name, a.videos, a.total_plays, a.total_likes, {}, '0')
               for aid, a in authors.items()}

    for pool in pools:
        if pool.mode == SettlementMode.GUARANTEED:
            q = [aid for aid, a in authors.items() if _topic_ok(a, pool.topic_rule) and _cond_ok(a, pool)]
            if q:
                per = _distribute_equal(pool.amount, len(q))
                for aid in q: results[aid].awards[pool.name] = per

        elif pool.mode == SettlementMode.RANKING and pool.ranking_field and pool.ranking_tiers:
            cands = [a for a in authors.values() if _topic_ok(a, pool.topic_rule) and _cond_ok(a, pool)]
            attr = FMAP.get(pool.ranking_field, pool.ranking_field)
            cands.sort(key=lambda a: (getattr(a, attr, 0) or a.extra.get(attr, 0)), reverse=True)
            for tier in pool.ranking_tiers:
                tc = cands[tier.rank_start - 1: tier.rank_end]
                if not tc: continue
                per = _distribute_equal(tier.pool, len(tc)) if tier.pool > 0 else _distribute_equal(tier.amount, 1)
                for a in tc: results[a.author_id].awards[pool.name] = per

        elif pool.mode == SettlementMode.WEIGHTED and pool.weight_field:
            cands = [a for a in authors.values() if _topic_ok(a, pool.topic_rule) and _cond_ok(a, pool)]
            attr = FMAP.get(pool.weight_field, pool.weight_field)
            tw = sum(getattr(a, attr, 0) or a.extra.get(attr, 0) for a in cands)
            if tw > 0:
                pd = Decimal(str(pool.amount)); twd = Decimal(str(tw))
                for a in cands:
                    w = Decimal(str(getattr(a, attr, 0) or a.extra.get(attr, 0)))
                    award = (pd * w / twd).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
                    if award > 0: results[a.author_id].awards[pool.name] = str(award)

    for r in results.values():
        tot = sum(Decimal(v) for v in r.awards.values())
        r.total_amount = str(tot.normalize() if tot != 0 else Decimal('0'))

    winners = sorted([r for r in results.values() if Decimal(r.total_amount) > 0],
                     key=lambda x: Decimal(x.total_amount), reverse=True)
    stats = {
        'total_authors': len(authors), 'qualified_authors': len(winners),
        'total_videos': sum(r.videos for r in winners), 'total_plays': sum(r.total_plays for r in winners),
        'total_likes': sum(r.total_likes for r in winners),
        'total_award': str(sum(Decimal(r.total_amount) for r in winners).normalize()),
        'pools': [{'name': p.name, 'amount': p.amount, 'mode': p.mode.value} for p in pools],
    }
    return winners, stats

def export_to_csv(results, output_path, stats):
    """导出结算结果,金额保留完整精度,不截断。"""
    pool_names = []
    for r in results:
        for k in r.awards:
            if k not in pool_names: pool_names.append(k)
    header = ['序号', '作者ID', '作者名称', '发布作品数', '累计播放量', '累计获赞'] + [f'{p}(元)' for p in pool_names] + ['总奖金(元)']
    with open(output_path, 'w', newline='', encoding='utf-8-sig') as f:
        w = csv.writer(f); w.writerow(header)
        for idx, r in enumerate(results, 1):
            row = [idx, r.author_id, r.author_name, r.videos, r.total_plays, r.total_likes]
            row += [r.awards.get(p, '') for p in pool_names] + [r.total_amount]
            w.writerow(row)
        summary = ['汇总', '', '', stats['total_videos'], stats['total_plays'], stats['total_likes']]
        for p in pool_names:
            summary.append(str(sum(Decimal(r.awards[p]) for r in results if p in r.awards).normalize()))
        summary.append(stats['total_award']); w.writerow(summary)

def print_summary(results, stats):
    print('=' * 80 + '\n结算结果摘要\n' + '=' * 80)
    for p in stats.get('pools', []): print(f"  - {p['name']}: {p['amount']:,.2f}元 ({p['mode']})")
    print(f"\n  获奖: {stats['qualified_authors']}人 / 参与: {stats['total_authors']}人  总奖金: {stats['total_award']}元\n" + '-' * 80)
    print(f"{'序号':<5}{'作者ID':<16}{'作者名称':<16}{'作品数':<8}{'播放量':<12}{'总奖金'}")
    for i, r in enumerate(results[:10], 1):
        print(f"{i:<5}{r.author_id:<16}{r.author_name:<16}{r.videos:<8}{r.total_plays:<12}{r.total_amount}")
    print('=' * 80)

如何构造 pools(每次活动必须重新构造,示例仅供参考)

⚠️ 以下示例代码是模板,不是固定配置。AI每次结算前必须根据用户描述的实际规则重新构造 pools,奖池数量、类型、金额、条件均可任意组合。

pools 支持的奖池类型(可任意数量、任意组合):

类型SettlementMode适用场景
达标瓜分GUARANTEED满足条件的人等额平分奖池
排名/榜单奖RANKING按指定字段排名,不同名次固定金额或瓜分
权重分配WEIGHTED按某字段权重比例分配奖池

示例(仅参考,实际 pools 由 AI 根据当次活动规则构造):

# ============================================================
# 以下仅为示例,AI必须根据实际活动规则重新构造 pools
# 奖池数量不限,类型可任意混合
# ============================================================

# 场景示例A:单个达标瓜分
pools = [
    AwardPool(
        name="瓜分奖池",
        amount=20000,
        mode=SettlementMode.GUARANTEED,
        conditions=[{'field': '播放量', 'op': '>=', 'value': 30000}],
    ),
]

# 场景示例B:多档瓜分(累进叠加,高档同时获得低档奖励)
pools = [
    AwardPool(name="1档奖池", amount=10000, mode=SettlementMode.GUARANTEED,
              conditions=[{'field': '作品数', 'op': '>=', 'value': 2}]),
    AwardPool(name="2档奖池", amount=20000, mode=SettlementMode.GUARANTEED,
              conditions=[{'field': '作品数', 'op': '>=', 'value': 5}]),
    AwardPool(name="3档奖池", amount=30000, mode=SettlementMode.GUARANTEED,
              conditions=[{'field': '作品数', 'op': '>=', 'value': 10}]),
]

# 场景示例C:瓜分 + 话题词过滤 + 榜单奖混合
pools = [
    AwardPool(
        name="基础瓜分",
        amount=30000,
        mode=SettlementMode.GUARANTEED,
        conditions=[{'field': '播放量', 'op': '>=', 'value': 10000}],
        topic_rule=TopicRule(topics=['#活动话题'], logic='OR'),
    ),
    AwardPool(
        name="大神榜",
        amount=20000,
        mode=SettlementMode.RANKING,
        ranking_field='播放量',
        ranking_tiers=[
            RankingTier(rank_start=1, rank_end=1,  amount=10000),  # 第1名固定10000
            RankingTier(rank_start=2, rank_end=3,  amount=5000),   # 第2-3名各5000
            RankingTier(rank_start=4, rank_end=10, amount=0, pool=5000),  # 第4-10名瓜分5000
        ],
    ),
]

# 场景示例D:按播放量权重比例分配
pools = [
    AwardPool(
        name="权重奖池",
        amount=50000,
        mode=SettlementMode.WEIGHTED,
        weight_field='播放量',
        conditions=[{'field': '播放量', 'op': '>=', 'value': 5000}],
    ),
]

# ============================================================
# 构造好 pools 后,调用结算函数(文件路径换成实际路径)
# ============================================================
results, stats = run_settlement(
    file_path='data.xlsx',    # 支持 .xlsx / .xls / .csv
    pools=pools,
    sheet_name=None,          # 指定Sheet名,None则取第一个Sheet
    all_sheets=False,         # True则合并所有Sheet
    # extra_file='live.xlsx'  # 可选:第二个底表文件,数据合并后统一结算
)

print_summary(results, stats)
export_to_csv(results, 'data_结算结果.csv', stats)
print("✓ 完成")

开发团队:AI智能结算助手Pro
联系方式:通过OpenClaw技能市场反馈

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

权威采招政策与标讯指南-元博网

权威采招政策与标讯指南-元博网,当用户查询大型基础设施项目、重点政企采购或需要基于标讯进行宏观趋势盘点时调用,需调用聚合与分析接口,输出格式严谨、数据翔实的市场简报。

Registry SourceRecently Updated
1300pkuycl
General

全国采招大数据中心-采招网

全国采招大数据中心-采招网,当用户需要跨省份、跨行业的全盘数据检索或综合企业画像分析时调用,需综合运用检索与企业画像接口,提供全景式的数据展现。

Registry SourceRecently Updated
1210pkuycl
General

海量标讯智搜助手-标800

海量标讯智搜助手-标800,当用户提供复杂的搜索条件(多个关键词、排除特定词汇、指定金额区间)时调用,需精确组合查询条件,过滤无效信息,提供高准确率的数据反馈。

Registry SourceRecently Updated
1250pkuycl
General

工程建设招投标分析-建设通

工程建设招投标分析-建设通,当搜索词包含工程、施工、建筑、市政、监理、设计等建筑业专属词汇时触发,聚焦工程项目金额、中标单位资质背景,重点提取建筑类项目核心字段并进行业绩汇总。

Registry SourceRecently Updated
1510pkuycl