Complete architecture reference for the A3 insurance platform — file locations, data flow, conventions, microservice map, and fullstack patterns
日本語の概要は準備中です。原文の説明を表示しています。
Process vehicle insurance Excel data using Pandas - file handling, data cleaning, merging, validation. Use when processing Excel/CSV files, handling data imports, implementing business rules (negative premiums, zero commissions), debugging data pipelines, or optimizing Pandas performance. Keywords: data_processor.py, Excel, CSV, Pandas, merge, deduplication, date normalization.
インストールする前に、エージェントに与えられる指示の中身を確認できます。
版本: v1.0 最后更新: 2025-11-08 适用场景: Excel 数据处理、数据清洗、文件合并、数据验证
本 Skill 提供车险签单数据处理后端的完整实现指南,作为 analyzing-auto-insurance-data Skill 的后端补充。涵盖数据流转、核心处理逻辑、业务规则实现、性能优化和错误处理。
核心价值:
关键文件位置:
┌─────────────────────────────────────────────────────────────────┐
│ Excel 文件上传到 data/ 目录 │
└────────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ scan_and_process_new_files() │
│ - 扫描 data/*.xlsx, data/*.xls │
│ - 批量处理所有新文件 │
└────────────────────────────┬────────────────────────────────────┘
│
▼
┌───────────────────────────────────────┐
│ process_new_excel(excel_path) │
│ - 读取 Excel 文件 (pd.read_excel) │
│ - 返回原始 DataFrame │
└───────────────┬───────────────────────┘
│
▼
┌───────────────────────────────────────┐
│ _clean_data(df) │
│ - 删除空行 │
│ - 日期格式标准化 │
│ - 数值类型转换 │
│ - 缺失值填充为空字符串 │
└───────────────┬───────────────────────┘
│
▼
┌───────────────────────────────────────┐
│ merge_with_existing(new_df) │
│ - 读取现有 CSV (如存在) │
│ - pd.concat() 合并新旧数据 │
│ - 根据保单号+投保确认时间去重 │
└───────────────┬───────────────────────┘
│
▼
┌───────────────────────────────────────┐
│ save_merged_data(df) │
│ - 保存为 车险清单_2025年10-11月_合并.csv │
│ - 编码: utf-8-sig (支持Excel打开) │
└───────────────┬───────────────────────┘
│
▼
┌───────────────────────────────────────┐
│ 移动到 data/processed/ 目录 │
│ - 重命名: 原文件名_processed_时间戳.xlsx │
│ - 避免重复处理 │
└───────────────────────────────────────┘
输入文件命名规范:
data/*.xlsx 或 data/*.xls车险清单_2025年10月.xlsx处理后文件命名:
data/processed/{原文件名}_processed_{YYYYMMDD_HHMMSS}.xlsxdata/processed/车险清单_2025年10月_processed_20251108_143025.xlsx合并文件命名:
车险清单_2025年10-11月_合并.csvutf-8-sig (带 BOM,Excel 可直接打开)去重逻辑 (data_processor.py:172-177):
# 根据保单号和投保确认时间去重,保留最新记录
merged_df = merged_df.drop_duplicates(
subset=['保单号', '投保确认时间'],
keep='last'
)
为什么选择这两个字段:
保单号: 业务唯一标识投保确认时间: 同一保单可能有批改记录,需要保留时间维度keep='last' 的原因:
keep='last' 优先保留新数据process_new_excel(excel_path) - Excel 文件处理功能: 读取 Excel 文件并返回清洗后的 DataFrame
实现细节:
def process_new_excel(self, excel_path):
print(f"正在处理Excel文件: {excel_path}")
# 读取 Excel 文件
df = pd.read_excel(excel_path)
print(f" 读取成功: {len(df)} 行, {len(df.columns)} 列")
# 数据清洗
df = self._clean_data(df)
return df
注意事项:
pd.read_excel(excel_path, dtype=str) - 会导致数值计算失败_clean_data() 中转换scan_and_process_new_files() 已处理异常常见问题:
xlrd.biffh.XLRDErrorPermissionErrorMemoryError_clean_data(df) - 数据清洗功能: 标准化数据格式,确保符合 CSV 规范
清洗步骤:
df = df.dropna(how='all')
date_columns = ['刷新时间', '投保确认时间', '保险起期']
for col in date_columns:
if col in df.columns:
df[col] = pd.to_datetime(df[col], errors='coerce')
errors='coerce': 无法解析的日期转为 NaT (Not a Time)2025-11-08, 2025/11/08, 20251108 等常见日期问题:
44500 → 自动转换为 2021-10-15"2025年11月8日" → errors='coerce' 会转为 NaTNaT,后续填充为空字符串numeric_columns = ['签单/批改保费', '签单数量', '手续费', '手续费含税', '增值税']
for col in numeric_columns:
if col in df.columns:
df[col] = pd.to_numeric(df[col], errors='coerce')
errors='coerce': 无法解析的数值转为 NaN"1,234.56" → 1234.56 (移除千分位逗号)常见数值问题:
-5000.00 (退保/批改) → 保留负数,不做修改0.00 → 合法值,不做修改"五万" → 转为 NaNdf = df.fillna('')
NaN / NaT 填充为空字符串 ''null 值value === '' 来识别缺失⚠️ 重要: 日期列在此步骤会被转为空字符串,需要在后续查询时重新转换:
df['投保确认时间'] = pd.to_datetime(df['投保确认时间'], errors='coerce')
merge_with_existing(new_df) - 数据合并功能: 将新数据与现有 CSV 合并,去重并统计
实现细节:
def merge_with_existing(self, new_df):
if self.merged_csv.exists():
print(f"读取现有数据: {self.merged_csv}")
existing_df = pd.read_csv(self.merged_csv, encoding='utf-8-sig')
# 合并数据
merged_df = pd.concat([existing_df, new_df], ignore_index=True)
# 去重 - 根据保单号和投保确认时间
if '保单号' in merged_df.columns and '投保确认时间' in merged_df.columns:
merged_df = merged_df.drop_duplicates(
subset=['保单号', '投保确认时间'],
keep='last'
)
print(f" 合并完成: {len(existing_df)} + {len(new_df)} = {len(merged_df)} 行")
else:
print(f" 未找到现有数据,创建新文件")
merged_df = new_df
return merged_df
关键参数解析:
encoding='utf-8-sig': 读取带 BOM 的 UTF-8 文件ignore_index=True: 重新生成 0-N 的连续索引keep='last': 保留重复记录中的最后一条性能优化建议:
# ❌ 不推荐: 逐行追加(O(n²) 复杂度)
for _, row in new_df.iterrows():
existing_df = existing_df.append(row)
# ✅ 推荐: 一次性合并(O(n) 复杂度)
merged_df = pd.concat([existing_df, new_df], ignore_index=True)
内存优化:
pd.read_csv(chunksize=10000) 分块读取del existing_df, new_dfscan_and_process_new_files() - 批量处理功能: 扫描 data 目录,批量处理所有新 Excel 文件
实现流程:
excel_files = list(self.data_dir.glob('*.xlsx')) + list(self.data_dir.glob('*.xls'))
pathlib.Path.glob() 查找文件.xlsx 和 .xls 两种格式all_new_data = []
for excel_file in excel_files:
try:
# 处理单个文件
df = self.process_new_excel(excel_file)
all_new_data.append(df)
# 处理完成后移动到已处理目录
processed_dir = self.data_dir / 'processed'
processed_dir.mkdir(exist_ok=True)
new_path = processed_dir / f"{excel_file.stem}_processed_{datetime.now().strftime('%Y%m%d_%H%M%S')}{excel_file.suffix}"
excel_file.rename(new_path)
print(f" 文件已移动: {new_path}")
except Exception as e:
print(f" 处理失败: {e}")
异常处理策略:
try-except 捕获单文件异常print(f"处理失败: {e}")if all_new_data:
# 合并所有新数据
combined_new = pd.concat(all_new_data, ignore_index=True)
# 与现有数据合并
final_df = self.merge_with_existing(combined_new)
# 保存
self.save_merged_data(final_df)
print(f"数据更新完成!")
触发方式:
python backend/data_processor.pyPOST /api/refresh (见 api_server.py:21-37)业务场景:
签单/批改保费 < 0 (例如: -5000.00)处理策略:
# ✅ 保留负数,不做修改
df[col] = pd.to_numeric(df[col], errors='coerce')
查询时的处理:
sum(),负数会自动抵消正数>= 50 的记录(见 data_processor.py:507-509)# 保单件数: 过滤小额/负保费
period_data = period_data[period_data['签单/批改保费'] >= 50]
daily_stats = period_data.groupby('weekday_index').size()
注意事项:
abs() 取绝对值 - 会导致退保也计入正保费业务场景:
处理策略:
# ✅ 保留 0 值,不做修改
commission_day = sum_float(day_df['手续费含税']) # 0 也会被累加
验证逻辑:
if commission > 0: - 会忽略合法的 0 值支持的日期格式:
2025-11-08 (推荐)2025/11/082025110844500 (自动转换)标准化代码:
df['投保确认时间'] = pd.to_datetime(df['投保确认时间'], errors='coerce')
API 返回格式:
# 统一转换为 YYYY-MM-DD 格式
latest_date.strftime('%Y-%m-%d') # '2025-11-08'
时区处理:
pd.to_datetime(..., utc=True)填充规则:
df = df.fillna('') # 统一填充为空字符串
各字段缺失值处理:
| 字段类型 | 缺失值处理 | 查询时处理 |
|---|---|---|
| 日期字段 | NaT → '' | pd.to_datetime(..., errors='coerce') 重新转换 |
| 数值字段 | NaN → '' | pd.to_numeric(..., errors='coerce') 重新转换 |
| 文本字段 | None → '' | 直接使用空字符串 |
查询时的重新转换 (必需):
# 读取 CSV 后必须重新转换日期
df = pd.read_csv(self.merged_csv, encoding='utf-8-sig')
df['投保确认时间'] = pd.to_datetime(df['投保确认时间'], errors='coerce')
为什么需要重新转换:
pd.read_csv() 默认将日期读为字符串❌ 低效: 逐行迭代
for _, row in df.iterrows():
if row['签单/批改保费'] >= 50:
count += 1
✅ 高效: 向量化过滤
count = len(df[df['签单/批改保费'] >= 50])
性能提升: 100x - 1000x
❌ 低效: 链式过滤(每次创建新 DataFrame)
df = df[df['签单/批改保费'] >= 50]
df = df[df['是否续保'] == '是']
df = df[df['三级机构'] == '达州']
✅ 高效: 合并条件
mask = (df['签单/批改保费'] >= 50) & \
(df['是否续保'] == '是') & \
(df['三级机构'] == '达州')
df = df[mask]
内存节省: 避免创建 3 个中间 DataFrame
low_memory=False 处理大文件# ✅ 推荐: 大文件读取时指定
df = pd.read_csv(self.merged_csv, encoding='utf-8-sig', low_memory=False)
说明:
low_memory=True (默认): 分块读取,可能导致数据类型推断不一致low_memory=False: 一次性读取,确保类型一致(适用于 <1GB 文件)existing_df = pd.read_csv(...)
new_df = pd.read_excel(...)
merged_df = pd.concat([existing_df, new_df])
# ✅ 立即释放内存
del existing_df, new_df
dtype 指定数据类型(适用于已知列)# ✅ 指定类型可减少内存占用
dtype_dict = {
'保单号': 'string',
'签单/批改保费': 'float32', # 而非 float64
'签单数量': 'int32' # 而非 int64
}
df = pd.read_csv(..., dtype=dtype_dict)
内存节省: float64 → float32 减少 50% 内存
# ✅ 超大文件(>1GB)使用分块
chunk_size = 10000
chunks = []
for chunk in pd.read_csv(..., chunksize=chunk_size):
# 处理每个分块
chunk = chunk[chunk['签单/批改保费'] >= 50]
chunks.append(chunk)
df = pd.concat(chunks, ignore_index=True)
文件大小分级:
pd.read_csv() / pd.read_excel()low_memory=Falsedtype 指定类型chunksize 分块处理当前项目实践:
low_memory=False 确保类型一致当前架构: 单进程顺序处理
可优化方向:
from concurrent.futures import ProcessPoolExecutor
def process_file(excel_file):
df = pd.read_excel(excel_file)
df = _clean_data(df)
return df
# ✅ 多进程并行处理
with ProcessPoolExecutor(max_workers=4) as executor:
results = executor.map(process_file, excel_files)
all_new_data = list(results)
注意事项:
if __name__ == '__main__': 保护try:
df = self.process_new_excel(excel_file)
all_new_data.append(df)
except Exception as e:
print(f" 处理失败: {e}")
# ✅ 不中断,继续处理下一个文件
捕获的异常类型:
FileNotFoundError: 文件不存在PermissionError: 文件被占用(Windows)xlrd.biffh.XLRDError: Excel 文件损坏MemoryError: 内存不足pd.errors.ParserError: CSV 解析失败@app.route('/api/refresh', methods=['POST'])
def refresh_data():
try:
processor.scan_and_process_new_files()
return jsonify({
'success': True,
'message': '数据刷新成功',
'latest_date': processor.get_latest_date()
})
except Exception as e:
return jsonify({
'success': False,
'message': f'数据刷新失败: {str(e)}'
}), 500
统一响应格式:
{ success: true, data: {...} }{ success: false, message: "错误信息" }, HTTP 500当前日志级别:
print(): 输出到 stdout (终端/日志文件)logging 模块)日志内容:
print(f"正在处理Excel文件: {excel_path}") # INFO
print(f" 读取成功: {len(df)} 行, {len(df.columns)} 列") # INFO
print(f" 数据清洗完成") # INFO
print(f" 处理失败: {e}") # ERROR
改进建议 (使用 logging 模块):
import logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [%(levelname)s] %(message)s',
handlers=[
logging.FileHandler('backend/backend.log'),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
# 使用
logger.info(f"正在处理Excel文件: {excel_path}")
logger.error(f"处理失败: {e}", exc_info=True) # 记录完整堆栈
API 错误提示设计:
| 错误场景 | HTTP 状态码 | 错误消息 | 用户操作建议 |
|---|---|---|---|
| 未找到数据 | 404 | "未找到数据" | 请先上传 Excel 文件 |
| 文件格式错误 | 500 | "Excel 文件格式不正确" | 请检查文件格式是否为 .xlsx/.xls |
| 必填列缺失 | 500 | "缺少必填列: 保单号" | 请确保 Excel 包含所有必填列 |
| 日期格式错误 | 500 | "投保确认时间格式不正确" | 请使用 YYYY-MM-DD 格式 |
| 内存不足 | 500 | "文件过大,内存不足" | 请分批上传较小的文件 |
前端展示建议:
if (!response.success) {
// ✅ 用户友好的提示
toast.error(response.message)
// ❌ 不友好的提示
console.error(response.message)
}
from pathlib import Path
from data_processor import DataProcessor
# 初始化处理器
processor = DataProcessor(
data_dir='data',
staff_mapping_file='业务员机构团队归属.json'
)
# 批量处理新文件
processor.scan_and_process_new_files()
# 输出示例:
# ==========================================
# 找到 2 个Excel文件
# 正在处理Excel文件: data/车险清单_2025年10月.xlsx
# 读取成功: 1234 行, 45 列
# 数据清洗完成
# 合并完成: 5678 + 1234 = 6912 行
# 文件已移动: data/processed/车险清单_2025年10月_processed_20251108_143025.xlsx
# 正在处理Excel文件: data/车险清单_2025年11月.xlsx
# 读取成功: 890 行, 45 列
# 数据清洗完成
# 合并完成: 6912 + 890 = 7802 行
# 文件已移动: data/processed/车险清单_2025年11月_processed_20251108_143030.xlsx
# 数据更新完成!
from pathlib import Path
import pandas as pd
from data_processor import DataProcessor
processor = DataProcessor()
# 步骤 1: 处理单个 Excel
excel_path = Path('data/车险清单_2025年10月.xlsx')
df = processor.process_new_excel(excel_path)
# 步骤 2: 检查数据
print(f"处理后数据: {len(df)} 行")
print(f"列名: {df.columns.tolist()}")
# 步骤 3: 合并到现有数据
merged_df = processor.merge_with_existing(df)
# 步骤 4: 保存
processor.save_merged_data(merged_df)
print("单文件处理完成!")
import pandas as pd
# 读取合并后的数据
df = pd.read_csv('车险清单_2025年10-11月_合并.csv', encoding='utf-8-sig', low_memory=False)
# ===== 数据质量检查 =====
# 1. 检查必填列是否存在
required_cols = ['保单号', '业务员', '投保确认时间', '签单/批改保费']
missing_cols = [col for col in required_cols if col not in df.columns]
if missing_cols:
print(f"❌ 缺少必填列: {missing_cols}")
else:
print(f"✅ 必填列完整")
# 2. 检查保单号唯一性
df['投保确认时间'] = pd.to_datetime(df['投保确认时间'], errors='coerce')
duplicates = df[df.duplicated(subset=['保单号', '投保确认时间'], keep=False)]
if len(duplicates) > 0:
print(f"⚠️ 发现 {len(duplicates)} 条重复记录")
else:
print(f"✅ 无重复记录")
# 3. 检查日期范围
latest_date = df['投保确认时间'].max()
earliest_date = df['投保确认时间'].min()
print(f"📅 日期范围: {earliest_date.date()} ~ {latest_date.date()}")
# 4. 检查保费分布
print(f"💰 保费统计:")
print(f" - 最小值: {df['签单/批改保费'].min():.2f}")
print(f" - 最大值: {df['签单/批改保费'].max():.2f}")
print(f" - 平均值: {df['签单/批改保费'].mean():.2f}")
print(f" - 负保费记录数: {len(df[df['签单/批改保费'] < 0])}")
# 5. 检查业务员映射覆盖率
staff_in_data = set(df['业务员'].dropna().unique())
staff_in_mapping = set() # 从映射文件中提取
coverage = len(staff_in_mapping & staff_in_data) / len(staff_in_data) * 100
print(f"👥 业务员映射覆盖率: {coverage:.1f}%")
使用 pathlib 处理路径
# ✅ 推荐
from pathlib import Path
project_root = Path(__file__).parent.parent
csv_path = project_root / '车险清单_2025年10-11月_合并.csv'
# ❌ 不推荐
import os
csv_path = os.path.join(os.getcwd(), '车险清单_2025年10-11月_合并.csv')
读取 CSV 时始终指定 encoding
# ✅ 推荐
df = pd.read_csv(csv_path, encoding='utf-8-sig')
# ❌ 不推荐(可能乱码)
df = pd.read_csv(csv_path)
查询前重新转换日期类型
# ✅ 推荐
df = pd.read_csv(csv_path, encoding='utf-8-sig')
df['投保确认时间'] = pd.to_datetime(df['投保确认时间'], errors='coerce')
# ❌ 不推荐(日期运算会报错)
df = pd.read_csv(csv_path, encoding='utf-8-sig')
df['投保确认时间'].max() # TypeError: '>' not supported
使用向量化操作
# ✅ 推荐
count = len(df[df['签单/批改保费'] >= 50])
# ❌ 不推荐
count = 0
for _, row in df.iterrows():
if row['签单/批改保费'] >= 50:
count += 1
异常捕获不中断批量处理
# ✅ 推荐
for file in files:
try:
process(file)
except Exception as e:
logger.error(f"处理失败: {e}")
continue # 继续处理下一个
# ❌ 不推荐(一个文件失败导致全部中断)
for file in files:
process(file) # 未捕获异常
链式赋值警告
# ❌ 会触发 SettingWithCopyWarning
df[df['签单/批改保费'] >= 50]['是否续保'] = '是'
# ✅ 使用 .loc
df.loc[df['签单/批改保费'] >= 50, '是否续保'] = '是'
日期比较前未转换
# ❌ 日期是字符串,比较结果错误
df = pd.read_csv(csv_path)
df = df[df['投保确认时间'] > '2025-11-01'] # 字符串比较
# ✅ 先转换为日期
df['投保确认时间'] = pd.to_datetime(df['投保确认时间'], errors='coerce')
df = df[df['投保确认时间'] > pd.to_datetime('2025-11-01')]
忽略 errors='coerce'
# ❌ 无效日期会抛出异常
df['投保确认时间'] = pd.to_datetime(df['投保确认时间'])
# ✅ 无效日期转为 NaT
df['投保确认时间'] = pd.to_datetime(df['投保确认时间'], errors='coerce')
使用 append() 追加行 (Pandas 1.4+ 已弃用)
# ❌ 已弃用,且性能差
for _, row in df.iterrows():
new_df = new_df.append(row)
# ✅ 使用 concat
new_df = pd.concat([new_df, df], ignore_index=True)
keep='last'✅ 适用:
❌ 不适用:
analyzing-auto-insurance-data)api-endpoint-design)vue-component-dev)文档维护者: Claude Code AI Assistant 创建日期: 2025-11-08 下次审查: 2025-11-22
まだレビューはありません。使ってみた感想をお寄せください。
概要と使いどころ
Complete architecture reference for the A3 insurance platform — file locations, data flow, conventions, microservice map, and fullstack patterns
日本語の概要は準備中です。原文の説明を表示しています。
Drafts U.S. commercial real estate access and indemnity (right-of-entry) agreements for pre-closing due diligence. Covers license grants, non-invasive vs invasive testing gates, insurance/endorsement requirements, indemnity with discovery carve-outs, restoration and lien remedies, confidentiality, and anti-indemnity guardrails. Trigger: access agreement, right of entry, due diligence access, Phase I/II, invasive testing, pre-PSA site inspection.
日本語の概要は準備中です。原文の説明を表示しています。
当用户需要对意外险投保问卷进行专业、结构化的核保审查,提取职业类别、工作环境、作业内容、危险活动参与情况、交通出行习惯、特殊工作暴露及其他可能影响意外险承保判断的高风险信息,并生成适合意外险核保、补问流转和风险分层处理的结构化分析结果时使用本 skill。
日本語の概要は準備中です。原文の説明を表示しています。
Inspect, schema, and fill true AcroForm PDFs using sciClaw's pdf_form_inspect, pdf_form_schema, and pdf_form_fill tools. Use when: (1) checking whether a PDF is a real fillable AcroForm, (2) listing form field names, types, and choices, (3) filling a supported form from structured values, (4) preparing reviewable insurance, legal, administrative, or clinical form drafts, (5) reporting skipped fields or unused input keys after a fill. Do not use for scanned PDFs, OCR tasks, or XFA-only forms.
日本語の概要は準備中です。原文の説明を表示しています。
Guide actuarial analysis for insurance pricing. TRIGGERS - Use when user needs help with actuarial-analysis related tasks.
日本語の概要は準備中です。原文の説明を表示しています。
Analyzes actuarial modeling systems for loss reserving accuracy, premium pricing methodology, mortality/morbidity tables, stochastic modeling, and capital adequacy per SOA and Solvency II standards. USE THIS SKILL WHEN: - You need to review or audit actuarial models (reserving, pricing, capital) - Someone asks about loss triangle analysis or reserve adequacy - You are evaluating IBNR calculations, chain ladder methods, or Bornhuetter-Ferguson - A project involves insurance pricing, GLM rating models, or ratemaking - You need to assess Solvency II SCR calculations or RBC compliance - Someone mentions actuarial opinions, ASOP compliance, or SOA standards - You are reviewing stochastic models, ESG configurations, or DFA frameworks - A codebase uses actuarial libraries (chainladder, lifetables, ChainLadder R package) TRIGGER PHRASES: "actuarial", "loss reserving", "IBNR", "chain ladder", "premium pricing", "mortality table", "Solvency II", "capital adequacy", "ratemaking", "GLM pricing", "risk-based capital", "reserve analysis", "actuarial opinion"
日本語の概要は準備中です。原文の説明を表示しています。