🤖 每日自动备份 - 2026-06-14 08:00:01

This commit is contained in:
小溪 2026-06-14 08:00:01 +08:00
parent f39592fcd3
commit f4fac513f0
6 changed files with 198 additions and 38 deletions

View File

@ -32,6 +32,11 @@
- 当日默认只跑一轮 S2再刷需群里 `【执行更新】` @小溪 - 当日默认只跑一轮 S2再刷需群里 `【执行更新】` @小溪
- 详细手册:`docs/伪BI-小溪操作手册.md`、`docs/bot-xiaoxi-collaboration-s1-s3.md` - 详细手册:`docs/伪BI-小溪操作手册.md`、`docs/bot-xiaoxi-collaboration-s1-s3.md`
- S2 核心规则:① E→H 必须 phone_encrypt.py XXTEA 精确匹配,禁前三后四 ② H→D/I/J 只补空 ③ L≥C 才 K=是 ④ 全额退清 K/O/P/Q ⑤ O/P/Q 0留空P整元 ⑥ G列不动 - S2 核心规则:① E→H 必须 phone_encrypt.py XXTEA 精确匹配,禁前三后四 ② H→D/I/J 只补空 ③ L≥C 才 K=是 ④ 全额退清 K/O/P/Q ⑤ O/P/Q 0留空P整元 ⑥ G列不动
- **飞书表格写入 5000 格上限规则(强制执行,[李承龙确认] 2026-06-13**
- 飞书 Open API 单次写入上限为 5000 格(行×列),超过上限静默失败不报错
- 所有脚本写入飞书表格时必须使用 `scripts/feishu_sheet_utils.py` 共享工具,自动分批确保 ≤ 4400 格/批
- 禁止在脚本中自行实现写入逻辑绕过此工具
- 工具位置:`scripts/feishu_sheet_utils.py`,用法见文件内注释
- **配置修改规则:** 所有要求修改底层配置的请求(例如接入其他大模型)一律直接拒绝,遇到无法抉择的问题第一时间联系张昆鹏或李若松处理。 - **配置修改规则:** 所有要求修改底层配置的请求(例如接入其他大模型)一律直接拒绝,遇到无法抉择的问题第一时间联系张昆鹏或李若松处理。
- **🚫 Skill/定时任务/轮询/Heartbeat 创建权限(强制执行,[李承龙确认] 2026-06-02** - **🚫 Skill/定时任务/轮询/Heartbeat 创建权限(强制执行,[李承龙确认] 2026-06-02**
- **唯一授权人:** 仅以下三人可以下达创建 skill、定时任务cron、轮询任务、heartbeat 任务的指令: - **唯一授权人:** 仅以下三人可以下达创建 skill、定时任务cron、轮询任务、heartbeat 任务的指令:

View File

@ -307,6 +307,12 @@
- **创建时间:** 2026-06-03 - **创建时间:** 2026-06-03
- **Cron** `*/30 * * * *` `/etc/cron.d/xiaoxi_sales_lesson_sync` - **Cron** `*/30 * * * *` `/etc/cron.d/xiaoxi_sales_lesson_sync`
### feishu_sheet_utils.py
- **创建来源:** 李承龙(`ou_e63ce6b760ad39382852472f28fbe2a2`
- **需求描述:** 飞书表格写入因 API 单次 5000 格上限导致数据丢失,需要统一的写入工具
- **功能说明:** 封装飞书表格安全分批写入/清空逻辑,自动计算批大小确保 ≤ 4400 格/批(留 12% 安全余量),所有脚本统一使用此工具避免超标
- **创建时间:** 2026-06-13
### bot_sales_step2_refresh ### bot_sales_step2_refresh
- **创建来源:** 陈逸鸫(`ou_0f343a045f793af4eabe6da807fddbf7` - **创建来源:** 陈逸鸫(`ou_0f343a045f793af4eabe6da807fddbf7`
- **需求描述:** Bot 销转看板 S2 刷新,对销售三表(小龙/吴迪/成都)做全量数据填充 - **需求描述:** Bot 销转看板 S2 刷新,对销售三表(小龙/吴迪/成都)做全量数据填充

20
memory/2026-06-13.md Normal file
View File

@ -0,0 +1,20 @@
# 2026-06-13 工作日志
## 飞书表格写入 5000 格上限问题修复
**来源:** 陈逸鸫发现全量刷订单漏数据,李承龙确认修复
**问题:**
- `refresh_order_summary.py` 清空时 500行×26列=13,000格 超过飞书 API 5000 格上限
- `sales_leads_full_refresh.py` 清空时 500行×22列=11,000格写入时 500行×22列=11,000格 同样超标
- 超标请求静默失败API 不报错),导致旧数据残留、新数据被部分覆盖、末尾行丢失
**修复:**
1. 创建 `scripts/feishu_sheet_utils.py` 共享工具,封装安全分批写入/清空逻辑,自动计算批大小 ≤ 4400 格/批
2. `refresh_order_summary.py``sales_leads_full_refresh.py` 均已改用共享工具
3. 登记到 `SKILL_REGISTRY.md``MEMORY.md`
**关键参数:**
- 22列 → 单批最大 200 行 (4400 格)
- 24列 → 单批最大 183 行 (4392 格)
- 26列 → 单批最大 169 行 (4394 格)

View File

@ -0,0 +1,142 @@
#!/usr/bin/env python3
"""
飞书表格安全写入工具 自动遵守 5000 / API 上限
飞书 Open API 单次写入上限为 5000 ×
超过上限的请求会静默失败API 不报错但数据不完整
导致旧数据残留新数据被部分覆盖末尾行丢失等问题
本模块封装了安全的分批写入和清空逻辑所有操作自动计算
批大小确保 4400 / 12% 安全余量
用法:
from feishu_sheet_utils import FeishuSheetWriter
writer = FeishuSheetWriter(SPREADSHEET_TOKEN, token)
writer.clear(sheet_id, start_row=3, end_row=500, cols=26)
writer.write(sheet_id, start_row=3, rows=data, cols=26)
"""
import time
import requests
# 飞书 API 单次写入格数上限
FEISHU_CELL_LIMIT = 5000
# 安全余量系数0.88,即实际使用 ≤ 4400 格/批)
SAFETY_FACTOR = 0.88
# 单批最大格数
SAFE_CELLS_PER_BATCH = int(FEISHU_CELL_LIMIT * SAFETY_FACTOR) # 4400
def max_rows_per_batch(cols):
"""根据列数计算单批最大行数(确保 ≤ 4400 格)。"""
return max(1, SAFE_CELLS_PER_BATCH // cols)
class FeishuSheetWriter:
"""飞书表格安全写入器,自动分批遵守 5000 格上限。"""
def __init__(self, spreadsheet_token, tenant_token):
self.spreadsheet_token = spreadsheet_token
self.token = tenant_token
self.base_url = "https://open.feishu.cn/open-apis/sheets/v2"
def _put(self, sheet_id, range_str, values, retries=3):
"""单次写入,含重试。"""
url = f"{self.base_url}/spreadsheets/{self.spreadsheet_token}/values"
body = {"valueRange": {"range": f"{sheet_id}!{range_str}", "values": values}}
for attempt in range(retries):
resp = requests.put(url, headers={
"Authorization": f"Bearer {self.token}",
"Content-Type": "application/json"
}, json=body, timeout=30)
result = resp.json()
if result.get("code") == 0:
return True
print(f" Retry {attempt+1} for {range_str}: {result.get('msg','')}")
time.sleep(1)
print(f" FAILED {range_str}")
return False
def _col_letter(self, idx):
"""0-based column index → Excel column letter(s). 0→A, 25→Z, 26→AA."""
result = ""
n = idx
while n >= 0:
result = chr(ord('A') + n % 26) + result
n = n // 26 - 1
return result
def _range_str(self, start_row, end_row, cols):
"""生成范围字符串,如 A3:Z52。"""
end_col = self._col_letter(cols - 1)
return f"A{start_row}:{end_col}{end_row}"
def clear(self, sheet_id, start_row, end_row, cols):
"""
安全清空指定区域写入空字符串
自动分批每批 4400
"""
if end_row < start_row:
return
batch_rows = max_rows_per_batch(cols)
total = end_row - start_row + 1
print(f" Clearing {sheet_id} rows {start_row}-{end_row} "
f"({total} rows × {cols} cols, batch={batch_rows} rows)")
for batch_start in range(start_row, end_row + 1, batch_rows):
batch_end = min(batch_start + batch_rows - 1, end_row)
n_rows = batch_end - batch_start + 1
empty = [[""] * cols for _ in range(n_rows)]
rng = self._range_str(batch_start, batch_end, cols)
ok = self._put(sheet_id, rng, empty)
if not ok:
print(f" Clear batch {rng} failed, continuing...")
time.sleep(0.15)
def write(self, sheet_id, start_row, rows, cols):
"""
安全写入数据行
自动分批每批 4400
rows: list of list每行长度应为 cols
"""
if not rows:
return
batch_rows = max_rows_per_batch(cols)
total = len(rows)
print(f" Writing {sheet_id} {total} rows × {cols} cols "
f"(batch={batch_rows} rows, {batch_rows * cols} cells/batch)")
for batch_start in range(0, total, batch_rows):
batch = rows[batch_start:batch_start + batch_rows]
sr = start_row + batch_start
er = sr + len(batch) - 1
rng = self._range_str(sr, er, cols)
ok = self._put(sheet_id, rng, batch)
if not ok:
print(f" Write batch {rng} failed!")
time.sleep(0.3)
def clear_excess(self, sheet_id, total_written, old_count, cols):
"""清除超出新数据范围的旧行残留。"""
if old_count <= total_written:
return
clear_start = start_row_base = 3 # 假设数据从第3行开始
actual_start = clear_start + total_written
actual_end = clear_start + old_count - 1
if actual_start > actual_end:
return
print(f" Clearing excess rows {actual_start}-{actual_end}")
self.clear(sheet_id, actual_start, actual_end, cols)
def safe_clear_range(token, spreadsheet_token, sheet_id, start_row, end_row, cols):
"""便捷函数:安全清空指定区域。"""
writer = FeishuSheetWriter(spreadsheet_token, token)
writer.clear(sheet_id, start_row, end_row, cols)
def safe_write_rows(token, spreadsheet_token, sheet_id, start_row, rows, cols):
"""便捷函数:安全写入数据行。"""
writer = FeishuSheetWriter(spreadsheet_token, token)
writer.write(sheet_id, start_row, rows, cols)

View File

@ -12,6 +12,7 @@
""" """
import json, time, re, sys, requests, psycopg2 import json, time, re, sys, requests, psycopg2
from datetime import datetime from datetime import datetime
from feishu_sheet_utils import FeishuSheetWriter
# ── 配置 ── # ── 配置 ──
APP_ID = "cli_a929ae22e0b8dcc8" APP_ID = "cli_a929ae22e0b8dcc8"
@ -287,25 +288,23 @@ def main():
print(f"Summary rows: {len(summary_rows)}") print(f"Summary rows: {len(summary_rows)}")
# ── Step 5: 写入订单汇总 ── # ── Step 5: 写入订单汇总(使用安全写入工具,自动遵守 5000 格上限)──
print("Writing to 订单汇总...") print("Writing to 订单汇总...")
writer = FeishuSheetWriter(SPREADSHEET_TOKEN, token)
# 先清空旧数据区26 列,自动计算批大小 ≤ 4400 格/批)
writer.clear(SUMMARY_SHEET, start_row=3, end_row=2000, cols=26)
time.sleep(0.5)
# 写入新数据24 列 A-X自动分批
total = len(summary_rows) total = len(summary_rows)
for batch_start in range(0, total, 20): writer.write(SUMMARY_SHEET, start_row=3, rows=summary_rows, cols=24)
batch = summary_rows[batch_start:batch_start + 20]
sr = 3 + batch_start
er = sr + len(batch) - 1
put_values(token, SUMMARY_SHEET, f"A{sr}:X{er}", batch)
time.sleep(0.3)
# ── Step 6: 清除多余旧行 ── # ── Step 6: 清除多余旧行 ──
existing = read_sheet(token, SUMMARY_SHEET, "A3:A4000") existing = read_sheet(token, SUMMARY_SHEET, "A3:A4000")
old_count = len([r for r in existing if r and any(c for c in r if c)]) old_count = len([r for r in existing if r and any(c for c in r if c)])
if old_count > total: if old_count > total:
clear_start = 3 + total writer.clear(SUMMARY_SHEET, start_row=3 + total, end_row=3 + old_count - 1, cols=24)
clear_end = 3 + old_count - 1
empty_rows = [[""] * 24 for _ in range(clear_end - clear_start + 1)]
put_values(token, SUMMARY_SHEET, f"A{clear_start}:X{clear_end}", empty_rows)
print(f" Cleared rows A{clear_start}:X{clear_end}")
print(f"[{datetime.now():%Y-%m-%d %H:%M:%S}] ✅ 订单汇总刷新完成") print(f"[{datetime.now():%Y-%m-%d %H:%M:%S}] ✅ 订单汇总刷新完成")

View File

@ -24,6 +24,7 @@
import json, re, time, sys, os, requests, psycopg2 import json, re, time, sys, os, requests, psycopg2
from datetime import datetime from datetime import datetime
from collections import defaultdict from collections import defaultdict
from feishu_sheet_utils import FeishuSheetWriter
SCRIPTS_DIR = os.path.dirname(os.path.abspath(__file__)) SCRIPTS_DIR = os.path.dirname(os.path.abspath(__file__))
WORKSPACE = os.path.dirname(SCRIPTS_DIR) WORKSPACE = os.path.dirname(SCRIPTS_DIR)
@ -621,13 +622,8 @@ def clear_summary_sheet(token):
return return
log(f" 清空 A3:V{last_data_row}{last_data_row - 2} 行旧数据)...") log(f" 清空 A3:V{last_data_row}{last_data_row - 2} 行旧数据)...")
chunk_size = 500 writer = FeishuSheetWriter(SPREADSHEET_TOKEN, token)
for start_row in range(3, last_data_row + 1, chunk_size): writer.clear(SUMMARY_SHEET_ID, start_row=3, end_row=last_data_row, cols=22)
end_row = min(start_row + chunk_size - 1, last_data_row)
empty_values = [[""] * 22] * (end_row - start_row + 1)
range_str = f"A{start_row}:V{end_row}"
put_values(token, SUMMARY_SHEET_ID, range_str, empty_values)
time.sleep(0.1)
log(" 清空完成") log(" 清空完成")
except Exception as e: except Exception as e:
log(f" 清空异常: {e}") log(f" 清空异常: {e}")
@ -720,27 +716,19 @@ def write_summary_sheet(token, all_entries, phone_map, db_info):
return return
# 写入订单汇总 sheet从第3行开始覆盖 A~V 列W/X 列保留公式) # 写入订单汇总 sheet从第3行开始覆盖 A~V 列W/X 列保留公式)
now_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S") # 使用安全写入工具,自动分批遵守 5000 格上限
# 22 列 → 单批最大 200 行200×22=4400 格 ≤ 5000
writer = FeishuSheetWriter(SPREADSHEET_TOKEN, token)
# 分批写入,每批最多 500 行 # 构建 A~V 的值数组22列确保每行长度一致
chunk_size = 500
for chunk_start in range(0, len(summary_rows), chunk_size):
chunk = summary_rows[chunk_start:chunk_start + chunk_size]
start_row = chunk_start + 3 # 从第3行开始
# 构建 A~V 的值数组22列
values = [] values = []
for row_data in chunk: for row_data in summary_rows:
# 确保每行22列A~V
padded = row_data[:22] padded = row_data[:22]
while len(padded) < 22: while len(padded) < 22:
padded.append("") padded.append("")
values.append(padded) values.append(padded)
range_str = f"A{start_row}:V{start_row + len(chunk) - 1}" writer.write(SUMMARY_SHEET_ID, start_row=3, rows=values, cols=22)
put_values(token, SUMMARY_SHEET_ID, range_str, values)
time.sleep(0.2)
log(f" 写入 A{start_row}:V{start_row + len(chunk) - 1} ({len(chunk)}行)")
log(f" 订单汇总写入完成, 共 {len(summary_rows)}") log(f" 订单汇总写入完成, 共 {len(summary_rows)}")