Files
ONE-OS/_scripts/generate_customer_rules_docx.py
王冕 a27e3b8e43 feat: sync full workspace including web modules, docs, and configurations to Gitea
Optimized the root .gitignore to exclude virtual environments, node modules,
and temp folders to ensure clean and lightweight version tracking.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-09 18:12:25 +08:00

345 lines
14 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""Generate 客户分级与黑名单规则.docx with tables and basic typography."""
from pathlib import Path
from docx import Document
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.oxml import OxmlElement
from docx.oxml.ns import qn
from docx.shared import Inches, Pt, RGBColor
def set_cell_shading(cell, fill: str) -> None:
"""fill: hex without # e.g. EAEAEA"""
tc = cell._tc
tcPr = tc.get_or_add_tcPr()
shd = OxmlElement("w:shd")
shd.set(qn("w:fill"), fill)
shd.set(qn("w:val"), "clear")
tcPr.append(shd)
def set_table_borders(table) -> None:
tbl = table._tbl
tblPr = tbl.tblPr
if tblPr is None:
tblPr = OxmlElement("w:tblPr")
tbl.insert(0, tblPr)
borders = OxmlElement("w:tblBorders")
for edge in ("top", "left", "bottom", "right", "insideH", "insideV"):
el = OxmlElement(f"w:{edge}")
el.set(qn("w:val"), "single")
el.set(qn("w:sz"), "4")
el.set(qn("w:space"), "0")
el.set(qn("w:color"), "333333")
borders.append(el)
tblPr.append(borders)
def add_heading_doc(doc, text: str, level: int) -> None:
p = doc.add_heading(text, level=level)
for run in p.runs:
run.font.name = "PingFang SC"
run._element.rPr.rFonts.set(qn("w:eastAsia"), "PingFang SC")
def add_para(doc, text: str, bold: bool = False) -> None:
p = doc.add_paragraph()
run = p.add_run(text)
run.font.name = "PingFang SC"
run._element.rPr.rFonts.set(qn("w:eastAsia"), "PingFang SC")
run.font.size = Pt(10.5)
if bold:
run.bold = True
def add_bullet_list(doc, items: list) -> None:
for t in items:
p = doc.add_paragraph(style="List Bullet")
run = p.add_run(t)
run.font.name = "PingFang SC"
run._element.rPr.rFonts.set(qn("w:eastAsia"), "PingFang SC")
run.font.size = Pt(10.5)
def add_table(doc, headers: list, rows: list, header_fill: str = "EAEAEA") -> None:
table = doc.add_table(rows=1 + len(rows), cols=len(headers))
table.style = "Table Grid"
set_table_borders(table)
hdr_cells = table.rows[0].cells
for i, h in enumerate(headers):
hdr_cells[i].text = h
set_cell_shading(hdr_cells[i], header_fill)
for p in hdr_cells[i].paragraphs:
p.alignment = WD_ALIGN_PARAGRAPH.LEFT
for r in p.runs:
r.bold = True
r.font.size = Pt(9.5)
r.font.name = "PingFang SC"
r._element.rPr.rFonts.set(qn("w:eastAsia"), "PingFang SC")
for ri, row in enumerate(rows):
cells = table.rows[ri + 1].cells
for ci, val in enumerate(row):
cells[ci].text = str(val)
for p in cells[ci].paragraphs:
for r in p.runs:
r.font.size = Pt(9.5)
r.font.name = "PingFang SC"
r._element.rPr.rFonts.set(qn("w:eastAsia"), "PingFang SC")
doc.add_paragraph()
def main() -> None:
out = Path.home() / "Desktop" / "客户分级与黑名单规则.docx"
doc = Document()
section = doc.sections[0]
section.page_height = Inches(11.69)
section.page_width = Inches(8.27)
section.left_margin = Inches(0.75)
section.right_margin = Inches(0.75)
section.top_margin = Inches(0.85)
section.bottom_margin = Inches(0.85)
t = doc.add_paragraph()
t.alignment = WD_ALIGN_PARAGRAPH.CENTER
r = t.add_run("客户分级ABC、风险标签与黑名单规则")
r.bold = True
r.font.size = Pt(17)
r.font.name = "PingFang SC"
r._element.rPr.rFonts.set(qn("w:eastAsia"), "PingFang SC")
st = doc.add_paragraph()
st.alignment = WD_ALIGN_PARAGRAPH.CENTER
rr = st.add_run("版本:草案 · 适用于 ToB 合同、收款与开票场景(可按业务微调阈值)")
rr.font.size = Pt(9.5)
rr.font.color.rgb = RGBColor(0x55, 0x55, 0x55)
rr.font.name = "PingFang SC"
rr._element.rPr.rFonts.set(qn("w:eastAsia"), "PingFang SC")
doc.add_paragraph()
add_heading_doc(doc, "一、客户分级ABC", level=1)
add_heading_doc(doc, "1. 分级目的", level=2)
add_bullet_list(
doc,
[
"资源投入:授信、账期、折扣、专属服务与响应时效。",
"风控A 类可放宽流程C 类收紧合同与收款条款。",
"统计:报表、提成、续约策略分层。",
],
)
add_heading_doc(doc, "2. 维度与计分(建议每年或每季复核)", level=2)
add_table(
doc,
["维度", "说明", "权重示例"],
[
["贡献度", "近 12 个月含税回款、毛利、合同金额", "35%"],
["履约与回款", "逾期次数、最长逾期天数、DSO", "25%"],
["合作稳定性", "合作月数、续约、纠纷/索赔次数", "20%"],
["战略价值", "品牌、标杆案例、增量潜力、独家合作", "20%"],
],
)
add_para(doc, "可将各维度标准化为 0100 分,加权得综合分,再划档;或直接按「硬性门槛 + 一票否决」定级。")
add_heading_doc(doc, "3. 等级定义与标准(示例)", level=2)
add_table(
doc,
["等级", "综合分(示例)", "典型特征"],
[
["A", "≥ 80", "回款好、贡献高或战略客户;近 12 个月无严重逾期、无未结重大纠纷"],
["B", "5079", "正常合作;偶有短逾期已结清;贡献中等"],
["C", "< 50", "新客观察期、小额长尾、或回款/纠纷问题较多"],
],
)
add_heading_doc(doc, "4. 硬性调整(建议写死规则)", level=2)
add_bullet_list(
doc,
[
"近 12 个月出现单次逾期 > 60 天或累计逾期 ≥ 3 次:最高不超过 B。",
"存在未结法律诉讼或重大合规调查:暂定为 C直至结案。",
"新签约 90 天内且无足够交易数据:默认 C 或 B观察标签单独标「新客观察」。",
],
)
add_heading_doc(doc, "5. 分级权益与约束(示例)", level=2)
add_table(
doc,
["项目", "A", "B", "C"],
[
["标准账期", "可按合同约定略优", "标准", "缩短或预付比例提高"],
["授信额度", "可申请较高额度", "中等", "低或零,款到发货"],
["价格/折扣", "可申请专项政策", "标准政策", "原则上无额外折扣"],
["合同审批", "可简化部分条款", "标准", "法务/财务加签"],
["服务响应", "优先 SLA", "标准", "标准(不承诺加急)"],
],
)
add_heading_doc(doc, "二、风险标签", level=1)
add_heading_doc(doc, "1. 设计原则", level=2)
add_bullet_list(
doc,
[
"标签≠等级:同一客户可有多个标签;黑名单与部分标签可联动自动拦截。",
"来源:系统规则自动打标 + 人工标注;重要标签变更留痕。",
"有效期部分标签可设过期时间如「短期资金链紧张」6 个月复核)。",
],
)
add_para(doc, "建议字段:标签代码、名称、级别、来源、备注、生效时间。")
add_heading_doc(doc, "2. 信用与回款", level=2)
add_table(
doc,
["标签代码", "名称", "级别", "典型触发"],
[
["CR-01", "逾期预警", "", "当前存在逾期或近 6 个月有逾期"],
["CR-02", "严重拖欠", "", "逾期 > 30 天未结或历史坏账记录"],
["CR-03", "频繁改账期", "", "一年内申请延长账期 ≥ 3 次"],
],
)
add_heading_doc(doc, "3. 合规与法律", level=2)
add_table(
doc,
["标签代码", "名称", "级别", "典型触发"],
[
["CP-01", "资质存疑", "", "证照过期、抬头与付款主体不一致"],
["CP-02", "涉诉/仲裁", "", "与我方或第三方重大未结诉讼"],
["CP-03", "制裁/高风险地区", "", "名单筛查命中(按公司合规清单)"],
],
)
add_heading_doc(doc, "4. 经营与履约", level=2)
add_table(
doc,
["标签代码", "名称", "级别", "典型触发"],
[
["OP-01", "频繁争议", "", "合同争议、扣款争议次数超阈值"],
["OP-02", "车辆/资产高风险", "中–高", "违章、事故、骗保嫌疑等业务规则(按公司业务定义)"],
["OP-03", "信息不实", "", "虚假联系人、空头公司等核实结果"],
],
)
add_heading_doc(doc, "5. 操作与流程", level=2)
add_table(
doc,
["标签代码", "名称", "级别", "典型触发"],
[
["PR-01", "开票异常", "", "频繁退票、抬头变更异常"],
["PR-02", "多头签约", "", "同一实控人多主体分散签约"],
],
)
add_para(doc, "级别建议:低 / 中 / 高 / 致命(致命仅建议与黑名单联动)。", bold=True)
add_heading_doc(doc, "6. 标签与系统行为(示例)", level=2)
add_table(
doc,
["级别", "建议动作"],
[
["", "仅展示与报表,不拦截"],
["", "合同/大额订单增加审批节点;降低默认授信"],
["", "禁止新增授信;新合同须法务/财务双签;可限制开票额度"],
["致命", "与黑名单规则对齐,禁止新业务或仅允许收尾"],
],
)
add_heading_doc(doc, "三、黑名单规则", level=1)
add_heading_doc(doc, "1. 黑名单分级(建议)", level=2)
add_table(
doc,
["级别", "名称", "含义"],
[
["L1", "观察名单", "重点监控,限制部分权限"],
["L2", "业务冻结", "禁止新签合同、新增订单/车辆,允许履约收尾与收款"],
["L3", "全面禁止", "禁止新业务;按法务意见处理尾款与争议"],
],
)
add_heading_doc(doc, "2. 进入条件(由风控委员会或授权人最终裁定)", level=2)
add_para(doc, "进入 L2业务冻结——示例", bold=True)
add_bullet_list(
doc,
[
"恶意拖欠:应付账款逾期 > 90 天且经书面催收无效。",
"欺诈或伪造:伪造资质、虚构交易、骗取发票或补贴等查实。",
"重大违约:单方毁约造成损失达约定金额或内部红线。",
"司法:被列为失信被执行人,或对我方提起恶意诉讼并造成实质风险。",
"合规:命中内外部黑名单库、制裁名单且公司政策要求停止合作。",
],
)
add_para(doc, "进入 L3全面禁止——示例", bold=True)
add_bullet_list(
doc,
[
"L2 情形加重且法务/高管批准。",
"刑事立案或监管机构明确要求终止合作。",
],
)
add_para(doc, "进入 L1观察名单——示例", bold=True)
add_bullet_list(
doc,
[
"连续 2 次逾期 > 15 天。",
"存在未结争议金额超过阈值。",
"高风险标签(如 CP-02、CR-02未解除超过 90 天。",
],
)
add_heading_doc(doc, "3. 系统与业务规则(落地)", level=2)
add_bullet_list(
doc,
[
"客户主数据:标记黑名单级别、原因代码、生效日、操作人、附件(催收记录、判决书等)。",
"合同L2/L3 禁止新建合同;进行中合同可走「例外审批」仅允许极少数变更(如收款账户)。",
"订单/开票L1 可设单笔限额L2/L3 禁止新开票或仅允许红冲/收尾类单据(按税务合规)。",
"消息:销售、运营界面显著提示,避免误接单。",
],
)
add_heading_doc(doc, "4. 移出与复议", level=2)
add_bullet_list(
doc,
[
"L1满足连续 6 个月无新增逾期、争议结案,由销售发起,财务复核后可移除。",
"L2/L3须还清欠款或达成书面和解 + 法务/风控签字L3 须高管或委员会批准。",
"全程保留审计日志;禁止个人擅自移出。",
],
)
add_heading_doc(doc, "5. 与 ABC、标签的联动建议", level=2)
add_bullet_list(
doc,
[
"进入 L2/L3 → 客户等级强制为 C或显示「黑名单-冻结」覆盖原等级)。",
"标签 CR-02、CP-02、CP-03 在确认后自动建议进入 L1 或触发 L2 审批流。",
"A 类客户进入黑名单须升级审批(防止误伤大客户)。",
],
)
add_heading_doc(doc, "四、模块数据模型提示(可选)", level=1)
add_bullet_list(
doc,
[
"客户表abc_level、blacklist_level、blacklist_reason_code、blacklist_since、abc_reviewed_at。",
"客户标签表多对多tag_id、sourcerule/manual、severity、expires_at。",
"规则引擎表(可选):规则 ID、条件 JSON、动作打标 / 调级 / 推审批)。",
],
)
foot = doc.add_paragraph()
rr2 = foot.add_run(
"本文档由 ONE-OS 项目辅助生成,正式使用前请经法务、财务与业务负责人审定阈值与审批链。"
)
rr2.font.size = Pt(9)
rr2.font.color.rgb = RGBColor(0x44, 0x44, 0x44)
rr2.font.name = "PingFang SC"
rr2._element.rPr.rFonts.set(qn("w:eastAsia"), "PingFang SC")
doc.save(out)
print(f"Wrote: {out}")
if __name__ == "__main__":
main()