27 KiB
27 KiB
三层数据库架构重构计划
一、项目背景与目标
现状分析
- 已有三层架构: L1A(原始JSON) → L2(结构化事实/维度表) → L3(特征集市)
- 主要问题:
- 数据库文件命名不统一(L1A.sqlite, L2_Main.sqlite, L3_Features.sqlite)
- JSON中存在两种Round数据格式(leetify含经济数据, classic含xyz坐标), 目前通过
data_source_type标记但未完全统一Schema - web/services层包含大量数据处理逻辑(feature_service.py 2257行, stats_service.py 1113行), 应下沉到数据库构建层
- L2_Builder.py单体文件1470行,缺乏模块化
重构目标
- 标准化命名: 统一数据库文件为
L1.db,L2.db,L3.db - Schema优化: 设计统一Round数据表结构,支持多数据源差异化字段
- 逻辑下沉: 将聚合计算从web/services迁移至database层的processor模块
- 模块化解耦: 建立sub-processor模式,按功能域拆分处理器
- 预留L1B: 为未来Demo直接解析管道预留目录结构
二、目录结构重构
2.1 标准化三层目录
database/
├── L1/
│ ├── L1.db # 标准化命名(原L1A.sqlite)
│ ├── L1_Builder.py # 数据入库脚本(原L1A_Builder.py)
│ └── README.md
├── L1B/ # 预留未来Demo解析管道
│ └── README.md # 说明此目录用途及预留原因
├── L2/
│ ├── L2.db # 标准化命名(原L2_Main.sqlite)
│ ├── L2_Builder.py # 主构建器(重构,瘦身)
│ ├── schema.sql # 优化后的统一Schema
│ ├── processors/ # 新建:子处理器模块目录
│ │ ├── __init__.py
│ │ ├── match_processor.py # 比赛基础信息处理
│ │ ├── player_processor.py # 玩家统计处理
│ │ ├── round_processor.py # Round数据统一处理
│ │ ├── economy_processor.py # 经济数据处理(leetify)
│ │ ├── event_processor.py # 事件流处理(kill/bomb等)
│ │ └── spatial_processor.py # 空间坐标处理(classic)
│ └── README.md
├── L3/
│ ├── L3.db # 标准化命名(原L3_Features.sqlite)
│ ├── L3_Builder.py # 主构建器(重构)
│ ├── schema.sql # 保持现有L3 schema
│ ├── processors/ # 新建:特征计算模块
│ │ ├── __init__.py
│ │ ├── basic_processor.py # 基础特征(avg rating/kd/kast)
│ │ ├── sta_processor.py # 稳定性时间序列特征
│ │ ├── bat_processor.py # 对抗能力特征
│ │ ├── hps_processor.py # 高压场景特征
│ │ ├── ptl_processor.py # 手枪局特征
│ │ ├── side_processor.py # T/CT阵营特征
│ │ ├── util_processor.py # 道具使用特征
│ │ ├── eco_processor.py # 经济效率特征
│ │ └── pace_processor.py # 节奏侵略性特征
│ └── README.md
├── original_json_schema/ # 保持不变
└── Force_Rebuild.py # 更新引用新路径
三、L2层Schema优化
3.1 Round数据统一Schema设计
核心思路: 设计包含所有字段的统一表结构,根据data_source_type选择性填充
3.1.1 fact_rounds表增强
CREATE TABLE IF NOT EXISTS fact_rounds (
match_id TEXT,
round_num INTEGER,
-- 公共字段(两种数据源均有)
winner_side TEXT CHECK(winner_side IN ('CT', 'T', 'None')),
win_reason INTEGER,
win_reason_desc TEXT,
duration REAL,
ct_score INTEGER,
t_score INTEGER,
-- Leetify专属字段
ct_money_start INTEGER, -- 仅leetify
t_money_start INTEGER, -- 仅leetify
begin_ts TEXT, -- 仅leetify
end_ts TEXT, -- 仅leetify
-- Classic专属字段
end_time_stamp TEXT, -- 仅classic
final_round_time INTEGER, -- 仅classic
pasttime INTEGER, -- 仅classic
-- 数据源标记(继承自fact_matches)
data_source_type TEXT CHECK(data_source_type IN ('leetify', 'classic', 'unknown')),
PRIMARY KEY (match_id, round_num),
FOREIGN KEY (match_id) REFERENCES fact_matches(match_id) ON DELETE CASCADE
);
3.1.2 fact_round_events表增强
CREATE TABLE IF NOT EXISTS fact_round_events (
event_id TEXT PRIMARY KEY,
match_id TEXT,
round_num INTEGER,
event_type TEXT CHECK(event_type IN ('kill', 'bomb_plant', 'bomb_defuse', 'suicide', 'unknown')),
event_time INTEGER,
-- Kill相关字段
attacker_steam_id TEXT,
victim_steam_id TEXT,
assister_steam_id TEXT,
flash_assist_steam_id TEXT,
trade_killer_steam_id TEXT,
weapon TEXT,
is_headshot BOOLEAN DEFAULT 0,
is_wallbang BOOLEAN DEFAULT 0,
is_blind BOOLEAN DEFAULT 0,
is_through_smoke BOOLEAN DEFAULT 0,
is_noscope BOOLEAN DEFAULT 0,
-- Classic空间数据(xyz坐标)
attacker_pos_x INTEGER, -- 仅classic
attacker_pos_y INTEGER, -- 仅classic
attacker_pos_z INTEGER, -- 仅classic
victim_pos_x INTEGER, -- 仅classic
victim_pos_y INTEGER, -- 仅classic
victim_pos_z INTEGER, -- 仅classic
-- Leetify评分影响
score_change_attacker REAL, -- 仅leetify
score_change_victim REAL, -- 仅leetify
twin REAL, -- 仅leetify (team win probability)
c_twin REAL, -- 仅leetify
twin_change REAL, -- 仅leetify
c_twin_change REAL, -- 仅leetify
-- 数据源标记
data_source_type TEXT CHECK(data_source_type IN ('leetify', 'classic', 'unknown')),
FOREIGN KEY (match_id, round_num) REFERENCES fact_rounds(match_id, round_num) ON DELETE CASCADE
);
3.1.3 fact_round_player_economy表增强
CREATE TABLE IF NOT EXISTS fact_round_player_economy (
match_id TEXT,
round_num INTEGER,
steam_id_64 TEXT,
side TEXT CHECK(side IN ('CT', 'T')),
-- Leetify经济数据(仅leetify)
start_money INTEGER,
equipment_value INTEGER,
main_weapon TEXT,
has_helmet BOOLEAN,
has_defuser BOOLEAN,
has_zeus BOOLEAN,
round_performance_score REAL,
-- Classic装备快照(仅classic, JSON存储)
equipment_snapshot_json TEXT, -- Classic的equiped字段序列化
-- 数据源标记
data_source_type TEXT CHECK(data_source_type IN ('leetify', 'classic', 'unknown')),
PRIMARY KEY (match_id, round_num, steam_id_64),
FOREIGN KEY (match_id, round_num) REFERENCES fact_rounds(match_id, round_num) ON DELETE CASCADE
);
3.2 Force Buy修复
在fact_round_player_economy表中确保:
start_money和equipment_value字段类型为INTEGER- 处理器中正确解析leetify的
bron_equipment和player_bron_crash
四、L2 Processor模块化设计
4.1 架构模式
L2_Builder.py (主控制器, ~300行)
↓ 调用
processors/
├── match_processor.py # 处理fact_matches, fact_match_teams
├── player_processor.py # 处理dim_players, fact_match_players
├── round_processor.py # 统一调度round数据处理
│ ├── 内部调用 economy_processor
│ ├── 内部调用 event_processor
│ └── 内部调用 spatial_processor
├── economy_processor.py # 专门处理leetify经济数据
├── event_processor.py # 处理kill/bomb事件
└── spatial_processor.py # 处理classic坐标数据
4.2 Processor接口规范
每个processor模块提供标准接口:
class XxxProcessor:
@staticmethod
def process(match_data: MatchData, conn: sqlite3.Connection) -> bool:
"""
Args:
match_data: 统一的MatchData对象(包含所有原始数据)
conn: L2数据库连接
Returns:
bool: 处理成功返回True
"""
pass
4.3 核心Processor功能分配
match_processor.py
- 职责: 处理比赛主表和队伍信息
- 输入:
MatchData.data_match的main字段 - 输出: 写入
fact_matches,fact_match_teams - 关键逻辑:
- 提取main字段的40+基础信息
- 解析group1/group2队伍信息
- 存储treat_info_raw等原始JSON
- 设置data_source_type标记
player_processor.py
- 职责: 处理玩家维度表和比赛统计
- 输入:
MatchData.data_match的group_1/group_2玩家列表, data_vip - 输出: 写入
dim_players,fact_match_players,fact_match_players_t,fact_match_players_ct - 关键逻辑:
- 合并fight/fight_t/fight_ct三个字段
- 处理VIP+高级统计(kast, awp_kill等)
- 计算utility usage(从round details累加)
- UPSERT dim_players(避免重复)
round_processor.py (调度器)
- 职责: 作为Round数据的统一入口,根据data_source_type分发
- 输入:
MatchData.data_leetify或MatchData.data_round_list - 输出: 调度其他processor处理
- 关键逻辑:
if match_data.data_source_type == 'leetify': economy_processor.process_leetify(...) event_processor.process_leetify_events(...) elif match_data.data_source_type == 'classic': event_processor.process_classic_events(...) spatial_processor.process_positions(...)
economy_processor.py
- 职责: 处理leetify的经济数据
- 输入:
data_leetify['leetify_data']['round_stat'] - 输出: 写入
fact_round_player_economy,fact_rounds的经济字段 - 关键逻辑:
- 解析bron_equipment(装备列表)
- 解析player_bron_crash(起始金钱)
- 计算equipment_value
event_processor.py
- 职责: 处理击杀/炸弹事件
- 输入: leetify的show_event或classic的all_kill
- 输出: 写入
fact_round_events - 关键逻辑:
- 生成event_id(UUID)
- 区分event_type: kill/bomb_plant/bomb_defuse
- leetify: 提取killer_score_change, victim_score_change, twin变化
- classic: 提取attacker/victim的pos(x,y,z)
spatial_processor.py
- 职责: 处理classic的空间数据
- 输入:
data_round_list['round_list']的pos字段 - 输出: 更新
fact_round_events的坐标字段 - 关键逻辑:
- 提取attacker.pos.x/y/z
- 提取victim.pos.x/y/z
- 为未来热力图/战术板分析做准备
五、L3 Processor模块化设计
5.1 现状与问题
现状:
- L3_Builder.py目前委托给
web.services.feature_service.FeatureService.rebuild_all_features() - feature_service.py包含2257行代码,混杂大量特征计算逻辑
目标:
- 将特征计算逻辑完全迁移到
database/L3/processors/ - feature_service仅保留查询和缓存逻辑
- 按FeatureRDD.md的6大维度+基础特征建立processor
5.2 Processor模块划分
basic_processor.py
- 职责: 计算基础统计特征(0-42个指标)
- 数据源:
fact_match_players - 特征示例:
basic_avg_rating: AVG(rating)basic_avg_kd: AVG(kills/deaths)basic_headshot_rate: SUM(headshot_count)/SUM(kills)basic_first_kill_rate: SUM(first_kill)/(SUM(first_kill)+SUM(first_death))
- 实现方式: SQL聚合 + 简单Python计算
sta_processor.py (稳定性时间序列)
- 职责: 计算STA维度特征
- 数据源:
fact_match_players,fact_matches(按start_time排序) - 特征示例:
sta_last_30_rating: 近30局平均ratingsta_win_rating,sta_loss_rating: 胜/败局分组ratingsta_rating_volatility: STDDEV(last 10 ratings)sta_fatigue_decay: 同日后期比赛vs前期比赛性能下降
- 实现方式: pandas时间序列分析
bat_processor.py (对抗能力)
- 职责: 计算BAT维度特征
- 数据源:
fact_round_events(击杀关系网络),fact_match_players - 特征示例:
bat_kd_diff_high_elo: 对最高elo对手的KD差bat_avg_duel_win_rate: 1v1对决胜率bat_win_rate_close/mid/far: 不同距离对枪胜率(需classic坐标)
- 实现方式: 对手关系矩阵构建 + 条件聚合
hps_processor.py (高压场景)
- 职责: 计算HPS维度特征
- 数据源:
fact_rounds,fact_round_events,fact_match_players - 特征示例:
hps_clutch_win_rate_1v1/1v2/1v3_plus: 残局胜率hps_match_point_win_rate: 赛点表现hps_pressure_entry_rate: 连败后首杀率hps_comeback_kd_diff: 翻盘时KD提升
- 实现方式: 识别特殊场景(赛点/连败/残局) + 条件统计
ptl_processor.py (手枪局)
- 职责: 计算PTL维度特征
- 数据源:
fact_rounds(round_num=1,13),fact_round_events - 特征示例:
ptl_pistol_win_rate: 手枪局胜率ptl_pistol_kd: 手枪局KDptl_pistol_multikills: 手枪局多杀次数ptl_pistol_util_efficiency: 道具辅助击杀率
- 实现方式: 过滤round_num + 武器类型判断
side_processor.py (T/CT阵营)
- 职责: 计算T/CT维度特征
- 数据源:
fact_match_players_t,fact_match_players_ct - 特征示例:
side_rating_t,side_rating_ct: 分阵营ratingside_kd_diff_ct_t: CT-T的KD差side_first_kill_rate_t/ct: 分阵营首杀率side_plants_t,side_defuses_ct: 下包/拆包数
- 实现方式: 分表聚合 + 差值计算
util_processor.py (道具使用)
- 职责: 计算UTIL维度特征
- 数据源:
fact_match_players(util_xxx_usage字段) - 特征示例:
util_avg_nade_dmg: 平均手雷伤害util_avg_flash_time: 平均致盲时长util_usage_rate: 道具使用频率
- 实现方式: 简单聚合
eco_processor.py (经济效率)
- 职责: 计算ECO维度特征
- 数据源:
fact_round_player_economy(仅leetify数据) - 特征示例:
eco_avg_damage_per_1k: 每1000元造成的伤害eco_rating_eco_rounds: ECO局ratingeco_kd_ratio: 经济局KD
- 实现方式: 经济分段 + 性能关联
- 注意: 仅leetify数据源可用
pace_processor.py (节奏侵略性)
- 职责: 计算PACE维度特征
- 数据源:
fact_round_events(event_time) - 特征示例:
pace_avg_time_to_first_contact: 平均首次交火时间pace_opening_kill_time: 开局击杀速度pace_trade_kill_rate: 补枪速率rd_phase_kill_early/mid/late_share: 早/中/后期击杀占比
- 实现方式: 事件时间戳分析
5.3 L3_Builder重构结构
# L3_Builder.py (瘦身至~150行)
from database.L3.processors import (
basic_processor,
sta_processor,
bat_processor,
hps_processor,
ptl_processor,
side_processor,
util_processor,
eco_processor,
pace_processor
)
def rebuild_all_features():
conn_l2 = sqlite3.connect(L2_DB_PATH)
conn_l3 = sqlite3.connect(L3_DB_PATH)
players = get_all_players(conn_l2)
for player in players:
features = {}
# 调用各processor
features.update(basic_processor.calculate(player, conn_l2))
features.update(sta_processor.calculate(player, conn_l2))
features.update(bat_processor.calculate(player, conn_l2))
features.update(hps_processor.calculate(player, conn_l2))
features.update(ptl_processor.calculate(player, conn_l2))
features.update(side_processor.calculate(player, conn_l2))
features.update(util_processor.calculate(player, conn_l2))
features.update(eco_processor.calculate(player, conn_l2))
features.update(pace_processor.calculate(player, conn_l2))
# 写入L3
upsert_player_features(conn_l3, player['steam_id_64'], features)
conn_l2.close()
conn_l3.close()
六、Web Services解耦
6.1 迁移策略
原则: Web层只做查询和缓存,不做计算
feature_service.py重构
- 保留功能:
get_player_features(steam_id): 从L3查询get_players_list(): 分页查询
- 移除功能(迁移到L3 processors):
rebuild_all_features()→ L3_Builder.py- 所有
_calculate_xxx()方法 → L3/processors/xxx_processor.py
stats_service.py重构
- 保留功能:
get_player_basic_stats(): 简单查询L2get_match_details(): 查询比赛详情
- 优化功能:
get_team_stats_summary(): 改为查询L2 VIEW(新建聚合视图)- 复杂聚合逻辑移至L2 processors或创建数据库VIEW
6.2 新建L2 VIEW
在database/L2/schema.sql中新增:
-- 玩家全场景统计视图
CREATE VIEW IF NOT EXISTS v_player_all_stats AS
SELECT
steam_id_64,
COUNT(DISTINCT match_id) as total_matches,
AVG(rating) as avg_rating,
AVG(kd_ratio) as avg_kd,
AVG(kast) as avg_kast,
SUM(kills) as total_kills,
SUM(deaths) as total_deaths,
SUM(assists) as total_assists,
SUM(mvp_count) as total_mvps
FROM fact_match_players
GROUP BY steam_id_64;
-- 地图维度统计视图
CREATE VIEW IF NOT EXISTS v_map_performance AS
SELECT
fmp.steam_id_64,
fm.map_name,
COUNT(*) as matches_on_map,
AVG(fmp.rating) as avg_rating,
AVG(fmp.kd_ratio) as avg_kd,
SUM(CASE WHEN fmp.is_win THEN 1 ELSE 0 END) * 1.0 / COUNT(*) as win_rate
FROM fact_match_players fmp
JOIN fact_matches fm ON fmp.match_id = fm.match_id
GROUP BY fmp.steam_id_64, fm.map_name;
七、数据流与交叉引用
7.1 数据流示意图
原始数据(output_arena/*/iframe_network.json)
↓
【L1层】L1.db: raw_iframe_network (1张表)
└─ match_id (PK)
└─ content (JSON全文)
↓
【L2层】L2.db: 9张核心表
├─ dim_players (玩家维度, 75个字段)
├─ dim_maps (地图维度)
├─ fact_matches (比赛主表, 50+字段)
├─ fact_match_teams (队伍信息)
├─ fact_match_players (玩家比赛统计, 100+字段)
├─ fact_match_players_t/ct (分阵营统计)
├─ fact_rounds (回合主表, 统一Schema)
├─ fact_round_events (事件流, 统一Schema)
└─ fact_round_player_economy (经济快照, 统一Schema)
↓
【L3层】L3.db: 特征集市
├─ dm_player_features (玩家画像, 150+特征)
└─ fact_match_features (单场特征快照, 可选)
7.2 JSON→L2字段映射表
| JSON路径 | L2表 | L2字段 | 数据源 | 处理器 |
|---|---|---|---|---|
data.main.match_code |
fact_matches | match_code | 公共 | match_processor |
data.main.map |
fact_matches | map_name | 公共 | match_processor |
data.group_1[].fight.rating |
fact_match_players | rating | 公共 | player_processor |
data.group_1[].fight_t.kill |
fact_match_players_t | kills | 公共 | player_processor |
data.<steamid>.kast |
fact_match_players | kast | VIP | player_processor |
leetify_data.round_stat[].t_money_group |
fact_rounds | t_money_start | leetify | economy_processor |
leetify_data.round_stat[].bron_equipment |
fact_round_player_economy | equipment_value | leetify | economy_processor |
leetify_data.round_stat[].show_event[].kill_event |
fact_round_events | weapon, is_headshot | leetify | event_processor |
leetify_data.round_stat[].show_event[].killer_score_change |
fact_round_events | score_change_attacker | leetify | event_processor |
round_list[].all_kill[].attacker.pos.x |
fact_round_events | attacker_pos_x | classic | spatial_processor |
round_list[].c4_event[] |
fact_round_events | event_type='bomb_plant' | classic | event_processor |
7.3 L2→L3特征映射表
| L3特征字段 | 数据源(L2表) | 计算逻辑 | 处理器 |
|---|---|---|---|
basic_avg_rating |
fact_match_players.rating | AVG() | basic_processor |
basic_headshot_rate |
fact_match_players | SUM(headshot_count)/SUM(kills) | basic_processor |
sta_last_30_rating |
fact_match_players + fact_matches.start_time | ORDER BY start_time LIMIT 30 | sta_processor |
sta_rating_volatility |
fact_match_players.rating | STDDEV(last_10_ratings) | sta_processor |
bat_kd_diff_high_elo |
fact_match_players + fact_match_teams.group_origin_elo | 对最高elo对手的击杀-被杀 | bat_processor |
hps_clutch_win_rate_1v1 |
fact_round_events + fact_rounds.winner_side | 识别1v1场景+胜负统计 | hps_processor |
ptl_pistol_win_rate |
fact_rounds(round_num=1,13) + fact_match_players | 手枪局胜率 | ptl_processor |
side_kd_diff_ct_t |
fact_match_players_ct.kd_ratio - fact_match_players_t.kd_ratio | 阵营KD差 | side_processor |
eco_avg_damage_per_1k |
fact_round_player_economy.equipment_value + fact_match_players.damage_total | damage/equipment_value*1000 | eco_processor |
pace_opening_kill_time |
fact_round_events.event_time (first kill) | AVG(首次击杀时间) | pace_processor |
八、实施步骤
Phase 1: 目录与命名标准化 (1-2小时)
- 重命名数据库文件:
database/L1A/L1A.sqlite→database/L1/L1.dbdatabase/L2/L2_Main.sqlite→database/L2/L2.dbdatabase/L3/L3_Features.sqlite→database/L3/L3.db
- 重命名Builder脚本:
L1A_Builder.py→L1_Builder.py
- 更新所有引用路径:
web/config.pyForce_Rebuild.py- 各Builder脚本内部路径
- 创建processor目录结构:
mkdir database/L2/processors mkdir database/L3/processors touch database/L2/processors/__init__.py touch database/L3/processors/__init__.py - 创建L1B预留目录:
- 创建
database/L1B/README.md说明用途
- 创建
Phase 2: L2 Schema优化 (2-3小时)
- 修改
database/L2/schema.sql:- 更新
fact_rounds增加leetify/classic差异字段 - 更新
fact_round_events增加坐标和评分字段 - 更新
fact_round_player_economy增加data_source_type和equipment_snapshot_json - 新增VIEW:
v_player_all_stats,v_map_performance
- 更新
- 验证Schema兼容性:
- 创建测试数据库执行新Schema
- 确认外键约束和CHECK约束正常
Phase 3: L2 Processor开发 (8-10小时)
按依赖顺序开发:
- match_processor.py (1h):
- 从L2_Builder.py提取
_parse_base_info()逻辑 - 实现
process(match_data, conn)接口
- 从L2_Builder.py提取
- player_processor.py (2h):
- 提取
_parse_players_base(),_parse_players_vip() - 合并fight/fight_t/fight_ct
- 处理dim_players UPSERT
- 提取
- round_processor.py (0.5h):
- 实现数据源分发逻辑
- economy_processor.py (2h):
- 解析leetify bron_equipment
- 计算equipment_value
- 写入fact_round_player_economy
- event_processor.py (2h):
- 统一处理leetify和classic的kill事件
- 提取bomb_plant/defuse事件
- 生成UUID event_id
- spatial_processor.py (1h):
- 提取classic的xyz坐标
- 关联到fact_round_events
- L2_Builder.py重构 (1.5h):
- 瘦身至~300行
- 调用各processor
- 实现错误处理和日志
Phase 4: L3 Processor开发 (12-15小时)
- basic_processor.py (1.5h):
- 实现42个基础特征计算
- SQL聚合+pandas处理
- sta_processor.py (2h):
- 时间序列分析
- 滑动窗口计算
- bat_processor.py (2.5h):
- 对手关系网络构建
- 对决矩阵分析
- hps_processor.py (2.5h):
- 场景识别(残局/赛点/连败)
- 条件统计
- ptl_processor.py (1h):
- 手枪局过滤
- 武器类型判断
- side_processor.py (1.5h):
- T/CT分表聚合
- 差值计算
- util_processor.py (0.5h):
- 简单聚合
- eco_processor.py (1h):
- 经济分段逻辑
- 性能关联
- pace_processor.py (1.5h):
- 事件时间戳分析
- 时间窗口划分
- L3_Builder.py重构 (1h):
- 调度各processor
- 批量更新dm_player_features
Phase 5: Web Services解耦 (4-5小时)
- feature_service.py瘦身 (2h):
- 移除所有计算逻辑
- 保留查询功能
- 更新单元测试
- stats_service.py优化 (1.5h):
- 改用L2 VIEW查询
- 简化聚合逻辑
- 路由层适配 (1h):
- 更新
web/routes/players.py等 - 确认profile页面正常渲染
- 更新
- 缓存策略 (0.5h):
- 考虑L3特征的缓存机制
Phase 6: 测试与验证 (3-4小时)
- 单元测试:
- 为每个processor编写测试用例
- Mock数据验证输出
- 集成测试:
- 完整运行L1→L2→L3 pipeline
- 对比重构前后特征值
- 数据质量校验:
- 运行
verify_L2.py - 检查字段覆盖率
- 运行
- 性能测试:
- 测量pipeline耗时
- 优化SQL查询
Phase 7: 文档与交付 (2小时)
- 更新README.md:
- 新的目录结构
- Processor模块说明
- 编写Processor README:
database/L2/processors/README.mddatabase/L3/processors/README.md
- API文档更新:
- web/services API变更说明
- Schema映射表:
- 生成完整的JSON→L2→L3字段映射Excel
九、风险与注意事项
9.1 数据一致性
- 风险: 重构过程中Schema变化可能导致旧数据不兼容
- 缓解:
- 使用
Force_Rebuild.py全量重建 - 保留L1原始数据,随时可回溯
- 使用
9.2 性能影响
- 风险: Processor模块化可能增加函数调用开销
- 缓解:
- 批量处理(一次处理多个match)
- 使用executemany()优化INSERT
- 关键路径使用SQL聚合而非Python循环
9.3 Leetify vs Classic覆盖率
- 风险: 部分特征(如eco, spatial)仅单数据源可用
- 缓解:
- 在processor中判断data_source_type
- 不可用特征标记为NULL
- 文档中明确标注依赖
9.4 Web服务中断
- 风险: feature_service重构可能影响线上功能
- 缓解:
- 先完成L2/L3 processor,再改web层
- 使用特性开关(feature flag)
- 灰度发布
十、预期成果
10.1 目录结构清晰
database/
├── L1/ # 统一命名
├── L1B/ # 预留清晰
├── L2/ # 模块化processors
├── L3/ # 模块化processors
└── Force_Rebuild.py
10.2 Schema完备性
- Round数据统一Schema,支持leetify和classic差异字段
- 清晰的data_source_type标记
- 完整的外键和约束
10.3 代码可维护性
- L2_Builder.py从1470行降至~300行
- L3_Builder.py从委托web服务改为调度本地processors
- web/services从4000+行降至~1000行
10.4 可扩展性
- 新增特征只需添加processor模块
- 新增数据源只需扩展Schema和processor
- L1B预留未来Demo解析管道
10.5 文档完整性
- JSON→L2→L3完整映射表
- 每个processor的功能和依赖说明
- 数据流示意图
十一、后续优化方向
11.1 性能优化
- 考虑L2/L3的materialized view(SQLite不原生支持,可手动实现)
- 增量更新机制(当前为全量重建)
- 并行处理多个match
11.2 功能扩展
- L1B层完整设计(Demo解析)
- 更多L3特征(FeatureRDD.md中的Phase 5内容)
- 实时特征更新API
11.3 工具增强
- 可视化Schema关系图
- Processor依赖图生成
- 自动化数据质量报告
总结
本计划提供了从目录结构、Schema设计、代码重构到测试交付的完整路径。核心目标是:
- 标准化: 统一命名和目录结构
- 模块化: 按功能域拆分processor
- 解耦: 将计算逻辑从web层下沉到database层
- 可扩展: 为未来数据源和特征预留扩展点
预计总工时: 35-40小时,可分阶段实施,每个Phase独立可验证。