91 lines
4.2 KiB
Python
91 lines
4.2 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""导入王芳2026H1微信账单,剔除与支付宝/邮储卡重复的支出"""
|
|
import json, pymysql
|
|
from datetime import datetime
|
|
cfg = json.load(open('db_config.json'))
|
|
conn = pymysql.connect(host=cfg['host'], port=cfg['port'], user=cfg['user'],
|
|
password=cfg.get('password'), database=cfg['database'], charset='utf8mb4')
|
|
cur = conn.cursor()
|
|
MEMBER = 3
|
|
|
|
# 已导入的支付宝+邮储卡支出集合 (同日+同金额)
|
|
cur.execute("""SELECT amount, recorded_date FROM variable_expenses
|
|
WHERE member_id=3 AND recorded_date BETWEEN '2026-01-01' AND '2026-06-30'
|
|
AND (note LIKE '%[支付宝]%' OR note LIKE '%[邮储卡]%')""")
|
|
imported = {(d.strftime('%Y-%m-%d'), round(float(a), 2)) for a, d in cur.fetchall()}
|
|
print(f"已导入支付宝+邮储卡: {len(imported)}笔")
|
|
|
|
d = json.load(open('/tmp/wx_juaner.json'))
|
|
def fp(s): return float(s)
|
|
|
|
REFUND_STATES = ('已全额退款', '对方已退还', '已退款', '已退款¥')
|
|
def is_refunded(st):
|
|
return any(x in st for x in REFUND_STATES)
|
|
|
|
def cat_of(peer, goods):
|
|
s = peer + '|' + (goods or '')
|
|
if any(k in s for k in ['超市','生鲜','便利店','水果','餐饮','烧烤','龙虾','包子','蜜雪','瑞幸','金拱门','餐厅','食堂','食品','桃酥','米糕','奶茶','蛋糕','饭店','卷' ,'鱼店','粮油','菜']):
|
|
return '餐饮/食品'
|
|
if any(k in s for k in ['滴滴','12306','铁路','交通','停车','地铁','火车','中石化','加油','顺风','轨道','导航','共享单车']):
|
|
return '交通'
|
|
if any(k in s for k in ['药','医院','诊所','口腔','门诊','健康','体检']):
|
|
return '医疗'
|
|
if any(k in s for k in ['母婴','奶粉','尿不湿','童装','早教','幼教','园']):
|
|
return '母婴'
|
|
if any(k in s for k in ['服装','女装','百货','鞋','淘宝','拼多多','快团团','旗舰店','饰品','电商','服饰']):
|
|
return '购物'
|
|
if any(k in s for k in ['美发','美容','护肤','理容']):
|
|
return '美容美发'
|
|
if any(k in s for k in ['缴费','水电','话费','燃气','物业']):
|
|
return '生活缴费'
|
|
if any(k in s for k in ['游泳','健身','运动','乐刻']):
|
|
return '运动'
|
|
if any(k in s for k in ['酒店','旅游','门票','住宿']):
|
|
return '旅游'
|
|
if any(k in s for k in ['美团','饿了么','外卖','三快','拉扎斯']):
|
|
return '餐饮/食品'
|
|
if any(k in s for k in ['转账','红包']):
|
|
return '转账/红包'
|
|
return '其他'
|
|
|
|
exp_c = 0; exp_s = 0.0; dup = 0; dup_amt = 0.0; cat_tot = {}
|
|
for r in d['records']:
|
|
direction = r[4].strip()
|
|
if direction != '支出':
|
|
continue
|
|
if is_refunded(r[7].strip()):
|
|
continue
|
|
amt = fp(r[5]); dt = datetime.strptime(r[0][:19], '%Y-%m-%d %H:%M:%S')
|
|
key = (r[0][:10], round(amt, 2))
|
|
if key in imported: # 跨平台重复,不重复计数
|
|
dup += 1; dup_amt += amt
|
|
continue
|
|
peer = r[2]; goods = r[3]
|
|
c = cat_of(peer, goods)
|
|
note = f"[微信] 支出|{r[1].strip()}|{peer}|{goods or '/'}|{r[6].strip()}"
|
|
cur.execute("INSERT INTO variable_expenses (member_id,category,amount,recorded_date,note) VALUES (%s,%s,%s,%s,%s)",
|
|
(MEMBER, c, amt, dt.date(), note))
|
|
exp_c += 1; exp_s += amt; cat_tot[c] = cat_tot.get(c, 0) + amt
|
|
|
|
inc_c = 0; inc_s = 0.0
|
|
for r in d['records']:
|
|
direction = r[4].strip()
|
|
if direction != '收入':
|
|
continue
|
|
if is_refunded(r[7].strip()):
|
|
continue
|
|
amt = fp(r[5]); dt = datetime.strptime(r[0][:19], '%Y-%m-%d %H:%M:%S')
|
|
source = r[2] if r[2].strip() else r[1].strip()
|
|
note = f"[微信] 收入|{r[1].strip()}|{r[2]}"
|
|
cur.execute("INSERT INTO income_records (member_id,source,amount,frequency,recorded_date,note) VALUES (%s,%s,%s,%s,%s,%s)",
|
|
(MEMBER, source, amt, '一次性', dt.date(), note))
|
|
inc_c += 1; inc_s += amt
|
|
|
|
conn.commit()
|
|
print(f"微信支出导入: {exp_c}笔 ¥{exp_s:.2f}")
|
|
print(f"微信支出去重排除: {dup}笔 ¥{dup_amt:.2f}")
|
|
print(f"微信收入导入: {inc_c}笔 ¥{inc_s:.2f}")
|
|
print("支出分类:", {k: round(v, 2) for k, v in sorted(cat_tot.items(), key=lambda x: -x[1])})
|
|
conn.close()
|