添加批量行情解析API和分页功能修复

This commit is contained in:
龙大 2026-04-17 00:52:40 +08:00
parent 446cd32484
commit 9e237c88a4
2 changed files with 179 additions and 1 deletions

View File

@ -0,0 +1,2 @@
from app.models.deal_info import DealInfo
from app.models.models import User, Collection

View File

@ -1,5 +1,5 @@
# 资讯API路由 # 资讯API路由
from fastapi import APIRouter, Depends, HTTPException, Query from fastapi import APIRouter, Depends, HTTPException, Query, Body
from sqlalchemy.orm import Session, joinedload from sqlalchemy.orm import Session, joinedload
from typing import List, Optional from typing import List, Optional
from pydantic import BaseModel from pydantic import BaseModel
@ -1082,3 +1082,179 @@ def get_seek_list_all(
content=result, content=result,
headers={"X-Total-Pages": str(total_pages), "X-Total-Count": str(total_count)} headers={"X-Total-Pages": str(total_pages), "X-Total-Count": str(total_count)}
) )
def parse_deals_locally(text: str, default_packaging: str = '', default_date: str = '', default_platform: str = ''):
"""本地正则解析批量行情文本"""
import re
from datetime import datetime
results = []
# 尝试从文本中提取日期(可能出现在标题或时间戳中)
# 格式如: 3月29日, 2026年3月29日, 2026-03-29
date_patterns = [
r'(\d{1,2})月(\d{1,2})日',
r'(\d{4})年(\d{1,2})月(\d{1,2})日',
r'(\d{4})-(\d{1,2})-(\d{1,2})'
]
extracted_date = None
for pattern in date_patterns:
match = re.search(pattern, text)
if match:
try:
if len(match.groups()) == 2:
# 3月29日 - 使用当前年份
extracted_date = f"2026-{int(match.group(1)):02d}-{int(match.group(2)):02d}"
elif len(match.groups()) == 3:
if int(match.group(1)) > 2000:
# 2026年3月29日
extracted_date = f"{match.group(1)}-{int(match.group(2)):02d}-{int(match.group(3)):02d}"
else:
# 3月29日格式
extracted_date = f"2026-{int(match.group(1)):02d}-{int(match.group(2)):02d}"
break
except:
pass
# 默认使用今天
default_date = datetime.now().strftime('%Y-%m-%d')
deal_date = extracted_date or default_date
lines = text.strip().split('\n')
for line in lines:
line = line.strip()
if not line or 'J0' not in line:
continue
# 提取冠字号 J0 + 8-9位数字
serial_match = re.search(r'J0(\d{8,9})', line)
if not serial_match:
continue
serial_num = serial_match.group(1)
if len(serial_num) == 9:
serial_num = serial_num[:8]
serial = 'J0' + serial_num
# 提取价格 ¥xxx,xxx 或 xxx,xxx必须在J0之后
serial_pos = line.find(serial)
after_serial = line[serial_pos + len(serial):]
price_match = re.search(r'[¥¥]?\s*([1-9][\d,]{2,})', after_serial)
if not price_match:
continue
price = int(price_match.group(1).replace(',', ''))
# 提取卖家(价格后面的中文字符)
after_price_pos = after_serial.find(price_match.group(0)) + len(price_match.group(0))
after_price = after_serial[after_price_pos:]
seller_match = re.search(r'([^\d\s¥¥]+?)(?:\s*$|\s*\n)', after_price)
seller = seller_match.group(1).strip() if seller_match else ''
# 分类判断
digits = serial_num
d = digits
category = '通货'
if not any(c in d for c in ['1','2','3','4','5','7']): category = '圆圆号'
elif not any(c in d for c in ['2','3','4','5','7']): category = '倒置号'
elif not any(c in d for c in ['1','2','3','4','7']): category = '金马王'
elif not any(c in d for c in ['2','3','4','7']): category = '金马号'
elif not any(c in d for c in ['1','2','4','5','7']): category = '金山王'
elif not any(c in d for c in ['1','2','4','7']): category = '天马王'
elif not any(c in d for c in ['2','4','5','7']): category = '金山号'
elif not any(c in d for c in ['2','4','7']): category = '天马号'
elif not any(c in d for c in ['1','3','4','5','7']): category = '朦胧王'
elif not any(c in d for c in ['3','4','5','7']): category = '朦胧号'
elif not any(c in d for c in ['1','3','4','7']): category = '如意号'
elif not any(c in d for c in ['3','4','7']): category = '钻石号'
elif not any(c in d for c in ['4','7']): category = '永恒号'
elif '4' not in d: category = '带7号'
# 提取评级机构 PCGS/PMG/ACG/爱藏
grade = ''
grading_company = ''
packaging = '单张'
if 'PC69' in line or 'PC68' in line or 'PC67' in line:
grade_match = re.search(r'PC(6[789]|5\d?)', line)
grade = 'PC' + grade_match.group(1) if grade_match else ''
grading_company = 'PCGS'
elif 'PMG68' in line or 'PMG67' in line:
grade_match = re.search(r'PMG(6[789]|5\d?)', line)
grade = 'PMG' + grade_match.group(1) if grade_match else ''
grading_company = 'PMG'
elif 'ACG' in line:
grade_match = re.search(r'ACG(6[789]|5\d?)', line)
grade = 'ACG' + grade_match.group(1) if grade_match else ''
grading_company = 'ACG'
elif '爱藏67+' in line:
grade = '67+'
grading_company = '爱藏'
elif '爱藏67' in line:
grade = '67'
grading_company = '爱藏'
# 判断包装类型
packaging = '单张'
# 如果传入了默认包装类型,先使用默认
if default_packaging:
packaging = default_packaging
# 简化识别:带"刀"字=标百,带"标"字=标十
if '' in line:
packaging = '标百'
elif '' in line:
packaging = '标十'
# 尾号判断如果冠字号尾号是01/11/21/31/41/51/61/71/81/91且有刀/标字样,基本确认是标百
if len(serial_num) >= 2:
tail = serial_num[-2:]
if tail in ['01', '11', '21', '31', '41', '51', '61', '71', '81', '91']:
if '' in line or ('' in line and packaging == '单张'):
packaging = '标百'
packaging = '标百'
# 如果没有刀/标字样但尾号是01且没有其他特征可能是标百
if packaging == '单张' and len(serial_num) >= 2:
tail = serial_num[-2:]
if tail == '01':
# 检查是否在特定语境下
packaging = '标百'
# 计算尾号和大小号
tail_number = ''
size_type = ''
if packaging == '标十' and len(serial_num) >= 2:
tail_number = serial_num[-2:]
size_type = tail_number in ['01','11','21','31','41','51'] and '小号' or '大号'
elif packaging == '标百' and len(serial_num) >= 3:
tail_number = serial_num[-3:]
size_type = tail_number in ['101','201','301','401','501'] and '小号' or '大号'
results.append({
'serial': serial,
'price': price,
'category': category,
'seller': seller,
'packaging': packaging,
'grade': grade,
'grading_company': grading_company,
'deal_date': deal_date or default_date, # 成交时间
'entry_date': default_date, # 录入时间
'is_graded': bool(grade),
'tail_number': tail_number, # 尾号
'size_type': size_type, # 大小号
'platform': default_platform # 平台
})
return results
@router.post("/batch-parse-local")
async def batch_parse_deals_local(request: dict = Body(...)):
"""本地正则解析批量行情文本无需AI"""
text = request.get('text', '')
default_packaging = request.get('defaultPackaging', '')
default_date = request.get('defaultDate', '')
default_platform = request.get('defaultPlatform', '')
results = parse_deals_locally(text, default_packaging, default_date, default_platform)
return {"success": True, "data": results}