3. 催收数据模型设计:客户画像表、逾期订单表、催收行为表、催收结果表

催收数据模型是催收系统的核心资产,它决定了催收策略的精准度、催收效率以及后续数据分析的深度。在 Spark 批流一体架构下,数据模型需要兼顾离线批处理的高吞吐与实时流处理的低延迟。本章围绕四个核心主题表展开:客户画像表、逾期订单表、催收行为表和催收结果表。

设计原则:催收数据模型遵循“宽表 + 拉链表 + 事件表”的混合模式。客户画像表采用宽表存储最新快照;逾期订单表采用拉链表记录状态变更历史;催收行为表和催收结果表采用事件表追加写入,便于 Spark Structured Streaming 实时消费。

3.1 客户画像表 (customer_profile)

客户画像表是催收分析的起点,聚合了客户的基础信息、信用评分、历史行为标签和实时风险等级。该表以 customer_id 为主键,每天由 Spark 批处理任务全量刷新。

字段名 类型 说明
customer_id STRING 客户唯一标识,主键
age_group STRING 年龄分段:20-30, 30-40, 40-50, 50+
credit_score INT 外部征信评分(300-850)
internal_risk_level STRING 内部风险等级:A/B/C/D
total_overdue_days INT 历史累计逾期天数
max_overdue_amount DECIMAL(18,2) 历史单笔最大逾期金额
contact_phone_count INT 有效联系人数量
last_contact_date DATE 最近一次联系日期
profile_version BIGINT 数据版本号,用于乐观锁
Spark 优化点:客户画像表在 Spark 中采用 bucketBy(64, "customer_id") 分桶存储,配合 sortBy("internal_risk_level"),在后续 Join 逾期订单表时可显著减少 Shuffle 数据量。

3.2 逾期订单表 (overdue_order)

逾期订单表记录每一笔订单的逾期状态变化,采用拉链表设计,支持时间旅行查询。每个订单从逾期开始到结清或核销,会生成多条记录,通过 effective_date 和 expiration_date 标记有效期。

-- 逾期订单拉链表 DDL 示例 (Spark SQL)
CREATE TABLE overdue_order (
    order_id          STRING,
    customer_id       STRING,
    loan_amount       DECIMAL(18,2),
    overdue_principal DECIMAL(18,2),
    overdue_days      INT,
    overdue_stage     STRING,  -- M1, M2, M3, M4+
    effective_date    DATE,
    expiration_date   DATE,
    is_current        BOOLEAN
)
USING parquet
PARTITIONED BY (dt STRING)
CLUSTERED BY (customer_id) INTO 128 BUCKETS;
逾期阶段 逾期天数范围 催收策略倾向
M1 1-30 天 短信/IVR 提醒,AI 外呼
M2 31-60 天 人工电话催收,风险预警
M3 61-90 天 高级催收员介入,法务准备
M4+ 90 天以上 委外催收,诉讼核销

3.3 催收行为表 (collection_action)

催收行为表是事件驱动的流水表,记录每一次催收触达行为,包括触达方式、时间、内容摘要和关联的催收员。该表由 Spark Structured Streaming 实时写入,同时供离线分析使用。

// Spark Structured Streaming 写入催收行为表示例
Dataset<Row> actions = spark
    .readStream()
    .format("kafka")
    .option("subscribe", "collection_actions")
    .load()
    .selectExpr(
        "CAST(value AS STRING) as json",
        "CAST(timestamp AS TIMESTAMP) as action_time"
    )
    .select(
        functions.from_json(
            functions.col("json"),
            actionSchema
        ).alias("data"),
        functions.col("action_time")
    )
    .select("data.*", "action_time")
    .writeStream()
    .format("parquet")
    .option("checkpointLocation", "/data/checkpoint/collection_action")
    .partitionBy("action_date")
    .start();
字段名 类型 说明
action_id STRING 行为唯一 ID (UUID)
order_id STRING 关联订单
action_type STRING SMS, IVR, MANUAL_CALL, EMAIL, VISIT
contact_result STRING CONTACTED, NO_ANSWER, BUSY, INVALID
collector_id STRING 催收员 ID(AI 外呼为 'AI_AGENT')
action_duration_sec INT 通话时长/交互时长(秒)
action_date STRING 分区字段,格式 yyyyMMdd

3.4 催收结果表 (collection_result)

催收结果表记录每一次催收行为的最终结果,包括承诺还款金额、实际还款金额、还款日期等。该表与催收行为表通过 action_id 关联,形成行为-结果一对一的闭环。

数据一致性注意:催收结果表通常由催收系统在还款入账后异步写入,可能存在延迟。Spark 作业在关联行为表与结果表时,应使用 LEFT JOIN 并设置合理的 watermark,避免因结果延迟导致数据丢失。
催收数据模型核心表关系图 客户画像表 customer_profile 逾期订单表 overdue_order 催收行为表 collection_action 催收结果表 collection_result 数据流方向 ① 客户画像 → 逾期订单 (风险等级关联) ② 逾期订单 → 催收行为 (催收任务生成) ③ 催收行为 → 催收结果 (action_id 关联) ④ 催收结果 → 客户画像 (反馈更新风险等级)

3.5 模型关联与 Spark 实践

四个核心表通过外键形成星型模型,客户画像表作为维度表,逾期订单表作为事实表,催收行为表和催收结果表作为明细事件表。在 Spark 中,典型的 ETL 流程如下:

  1. 每日批处理:使用 Spark SQL 将客户画像表与逾期订单表 Join,生成催收任务宽表,写入催收行为表。
  2. 实时流处理:Spark Structured Streaming 消费 Kafka 中的催收行为事件,写入催收行为表分区。
  3. 结果回写:催收结果表通过异步消息队列更新,Spark 微批次作业每 5 分钟拉取结果,更新逾期订单表的 overdue_stage 和客户画像表的 internal_risk_level。
-- Spark SQL 催收任务宽表生成示例
CREATE OR REPLACE TEMP VIEW collection_task AS
SELECT
    p.customer_id,
    p.internal_risk_level,
    o.order_id,
    o.overdue_principal,
    o.overdue_days,
    o.overdue_stage,
    CURRENT_TIMESTAMP() AS task_create_time
FROM customer_profile p
INNER JOIN overdue_order o
    ON p.customer_id = o.customer_id
WHERE o.is_current = TRUE
  AND o.overdue_days > 0;
性能建议:在 Spark 中 Join 客户画像表与逾期订单表时,建议使用 broadcast hash join(画像表通常小于 500MB),避免 Shuffle。如果画像表较大,可考虑使用 sort merge join 并确保两表按 customer_id 分桶。