ai_member_xiaoxi/scripts/xiaolong_sheet_update.py
2026-06-05 08:00:01 +08:00

242 lines
8.4 KiB
Python

#!/usr/bin/env python3
"""
小龙 sheet update: match phones via XXTEA encryption, fill H (UID), D (trial count), I (reg date), J (channel)
"""
import sys
sys.path.insert(0, '/root/.openclaw/workspace/scripts')
from phone_encrypt import encrypt_phone
import psycopg2
import json
import requests
import time
FEISHU_TOKEN = "t-g10464c0UK5L67JVXSDDT3EWM4DPLSDY5C7R7NS6"
SPREADSHEET_TOKEN = "NoZqsFi47hIOHEt9j8WcfRtbnug"
SHEET_ID = "qJF4I"
# PostgreSQL connection
PG_CONFIG = {
"host": "bj-postgres-16pob4sg.sql.tencentcdb.com",
"port": 28591,
"user": "ai_member",
"password": "LdfjdjL83h3h3^$&**YGG*",
"database": "vala_bi",
}
def get_sheet_data():
"""Read all data from the sheet"""
url = f"https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/{SPREADSHEET_TOKEN}/values/{SHEET_ID}!A3:J2502?valueRenderOption=ToString"
headers = {"Authorization": f"Bearer {FEISHU_TOKEN}"}
r = requests.get(url, headers=headers)
data = r.json()
values = data.get("data", {}).get("valueRange", {}).get("values", [])
return values
def write_cell_range(range_str, values_list):
"""Write values to a range"""
url = f"https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/{SPREADSHEET_TOKEN}/values"
headers = {
"Authorization": f"Bearer {FEISHU_TOKEN}",
"Content-Type": "application/json"
}
body = {
"valueRange": {
"range": f"{SHEET_ID}!{range_str}",
"values": values_list
}
}
r = requests.put(url, headers=headers, json=body)
resp = r.json()
if resp.get("code") != 0:
print(f" ERROR writing {range_str}: {resp}")
else:
print(f" OK: {range_str}")
return resp
def main():
# Step 1: Read sheet data
print("Reading sheet data...")
rows = get_sheet_data()
print(f"Got {len(rows)} rows")
# Parse rows with phones
phone_rows = [] # (row_num, phone, existing_uid, existing_d, existing_i, existing_j)
for i, row in enumerate(rows):
row_num = i + 3
if len(row) < 5:
continue
phone = row[4].strip() if row[4] else ""
if not phone or len(phone) != 11 or not phone.isdigit():
continue
uid = row[7].strip() if len(row) > 7 and row[7] else ""
d_val = row[3] if len(row) > 3 and row[3] else ""
i_val = row[8].strip() if len(row) > 8 and row[8] else ""
j_val = row[9].strip() if len(row) > 9 and row[9] else ""
phone_rows.append((row_num, phone, uid, d_val, i_val, j_val))
print(f"Found {len(phone_rows)} rows with valid phones")
# Separate into two groups:
# Group A: H is "未注册" or empty → need to match and fill H, D, I, J
# Group B: H has valid numeric UID but D is empty → need to fill D, I, J
group_a = [] # need phone matching
group_b = [] # already have UID, need D/I/J
for row_num, phone, uid, d_val, i_val, j_val in phone_rows:
if not uid or uid == "未注册":
group_a.append((row_num, phone, uid, d_val, i_val, j_val))
elif uid.isdigit():
if not d_val or not i_val or not j_val:
group_b.append((row_num, phone, uid, d_val, i_val, j_val))
print(f"Group A (need phone match): {len(group_a)}")
print(f"Group B (have UID, need D/I/J): {len(group_b)}")
# Step 2: Encrypt all phones and query PostgreSQL
all_phones_a = [p[1] for p in group_a]
all_uids_b = [p[2] for p in group_b]
conn = psycopg2.connect(**PG_CONFIG)
cur = conn.cursor()
# Map: encrypted_phone -> account_id
enc_to_uid = {}
if all_phones_a:
enc_list = [encrypt_phone(p) for p in all_phones_a]
phone_to_enc = dict(zip(all_phones_a, enc_list))
# Query accounts
placeholders = ",".join(["%s"] * len(enc_list))
cur.execute(
f"SELECT id, tel_encrypt FROM bi_vala_app_account WHERE tel_encrypt IN ({placeholders}) AND status=1 AND deleted_at IS NULL",
enc_list
)
for row in cur.fetchall():
uid, tel_enc = row
enc_to_uid[tel_enc] = uid
print(f"Matched {len(enc_to_uid)} phones in bi_vala_app_account")
# For matched accounts, get registration date and download channel
matched_uids = list(enc_to_uid.values())
uid_to_info = {}
if matched_uids:
placeholders2 = ",".join(["%s"] * len(matched_uids))
cur.execute(
f"SELECT id, created_at::date, download_channel FROM bi_vala_app_account WHERE id IN ({placeholders2}) AND status=1 AND deleted_at IS NULL",
matched_uids
)
for row in cur.fetchall():
uid_to_info[row[0]] = (str(row[1]) if row[1] else "", row[2] or "")
# Step 3: Get trial lesson counts for ALL accounts (both groups)
all_uids = list(set(
[enc_to_uid[encrypt_phone(p[1])] for p in group_a if encrypt_phone(p[1]) in enc_to_uid] +
[p[2] for p in group_b]
))
uid_to_trial_count = {}
if all_uids:
placeholders3 = ",".join(["%s"] * len(all_uids))
cur.execute(
f"SELECT account_id, COUNT(*) FROM bi_user_course_detail WHERE account_id IN ({placeholders3}) AND expire_time IS NULL AND deleted_at IS NULL GROUP BY account_id",
all_uids
)
for row in cur.fetchall():
uid_to_trial_count[row[0]] = row[1]
cur.close()
conn.close()
print(f"Trial counts: {len(uid_to_trial_count)} accounts have trials")
print(f"Account info: {len(uid_to_info)} accounts have reg date/channel")
# Step 4: Build write batches
# For Group A: write H (UID), D (trial count), I (reg date), J (channel)
# For Group B: write D (trial count), I (reg date), J (channel)
writes_h = [] # (row, value)
writes_d = [] # (row, value)
writes_i = [] # (row, value)
writes_j = [] # (row, value)
matched_count = 0
for row_num, phone, old_uid, old_d, old_i, old_j in group_a:
enc = encrypt_phone(phone)
uid = enc_to_uid.get(enc)
if uid:
matched_count += 1
uid_str = str(uid)
# H column: UID
writes_h.append((row_num, uid_str))
# D column: trial count
trial = uid_to_trial_count.get(uid, 0)
writes_d.append((row_num, str(trial) if trial else ""))
# I column: reg date
info = uid_to_info.get(uid, ("", ""))
writes_i.append((row_num, info[0]))
# J column: channel
writes_j.append((row_num, info[1]))
# If not matched, skip - don't write anything
for row_num, phone, uid_str, old_d, old_i, old_j in group_b:
uid = int(uid_str)
# D column: trial count
trial = uid_to_trial_count.get(uid, 0)
writes_d.append((row_num, str(trial) if trial else ""))
# I column: reg date
info = uid_to_info.get(uid, ("", ""))
writes_i.append((row_num, info[0]))
# J column: channel
writes_j.append((row_num, info[1]))
print(f"\nMatched {matched_count} new phones")
print(f"Total H writes: {len(writes_h)}")
print(f"Total D writes: {len(writes_d)}")
print(f"Total I writes: {len(writes_i)}")
print(f"Total J writes: {len(writes_j)}")
# Step 5: Write in batches (consecutive rows per column)
def write_batch(writes, col_letter):
if not writes:
return
# Sort by row number
writes.sort(key=lambda x: x[0])
# Group consecutive rows
i = 0
while i < len(writes):
start = writes[i][0]
vals = []
j = i
while j < len(writes) and writes[j][0] == start + (j - i):
vals.append([writes[j][1]])
j += 1
end = start + len(vals) - 1
range_str = f"{col_letter}{start}:{col_letter}{end}"
write_cell_range(range_str, vals)
i = j
time.sleep(0.05)
print("\nWriting H column...")
write_batch(writes_h, "H")
print("\nWriting D column...")
write_batch(writes_d, "D")
print("\nWriting I column...")
write_batch(writes_i, "I")
print("\nWriting J column...")
write_batch(writes_j, "J")
print("\n=== SUMMARY ===")
print(f"Phones matched: {matched_count}")
print(f"H (UID) written: {len(writes_h)}")
print(f"D (trial count) written: {len(writes_d)}")
print(f"I (reg date) written: {len(writes_i)}")
print(f"J (channel) written: {len(writes_j)}")
if __name__ == "__main__":
main()