feat(djapi): 新增日报查询 API(news/reports + news/events)及文档
- api/report/ 包:query(连库+SQL)/ views(2 视图)/ serializers(OpenAPI)/ tests(17 单测) - urls.py 注册 news/reports/、news/events/;settings.py SPECTACULAR 加「日报」tag - .env.example 补 NEWS_DB_* 占位配置;README/continuation.md 更新 - docs/news_report_api.md 使用手册;CLAUDE*.md 修正 CLI 路径为 finance/ 前缀
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
"""
|
||||
日报查询 API 视图。
|
||||
|
||||
- GET /api/news/reports/ 日报查询(默认最近 24 小时;传 id 返回单份详情含事件)
|
||||
- GET /api/news/events/ 跨日报重要事件聚合(最近 N 天 importance >= 阈值)
|
||||
"""
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from django.utils import timezone
|
||||
from drf_spectacular.utils import OpenApiParameter, extend_schema
|
||||
from rest_framework.decorators import api_view
|
||||
from rest_framework.response import Response
|
||||
|
||||
from . import query as report_query
|
||||
from .serializers import (
|
||||
ImportantEventSerializer,
|
||||
ReportDetailSerializer,
|
||||
ReportListSerializer,
|
||||
)
|
||||
|
||||
_REPORT_TYPES = ("finance", "intl")
|
||||
_SECTIONS = ("xwlb", "news", "cninfo", "intl")
|
||||
|
||||
|
||||
def _parse_date_param(request, name):
|
||||
"""解析 YYYY-MM-DD 参数,非法格式抛 ValueError。"""
|
||||
raw = request.GET.get(name)
|
||||
if not raw:
|
||||
return None
|
||||
try:
|
||||
return datetime.strptime(raw, "%Y-%m-%d").date()
|
||||
except ValueError:
|
||||
raise ValueError(f"{name} 格式错误,应为 YYYY-MM-DD")
|
||||
|
||||
|
||||
@extend_schema(
|
||||
parameters=[
|
||||
OpenApiParameter(name='report_type', type=str, required=False,
|
||||
description='日报类型:finance | intl(默认两者)'),
|
||||
OpenApiParameter(name='start_date', type=str, required=False,
|
||||
description='起始日期 YYYY-MM-DD(默认:当前时间往前 24 小时)'),
|
||||
OpenApiParameter(name='end_date', type=str, required=False,
|
||||
description='结束日期 YYYY-MM-DD(默认今天)'),
|
||||
OpenApiParameter(name='id', type=int, required=False,
|
||||
description='日报 id,指定时返回单份详情(含事件,按板块/序号排序)'),
|
||||
],
|
||||
responses={200: ReportDetailSerializer},
|
||||
description='AI 财经日报查询:默认返回最近 24 小时的日报列表(每天每类型取最新一份);'
|
||||
'传 id 返回单份详情含事件',
|
||||
tags=['日报'],
|
||||
)
|
||||
@api_view(['GET'])
|
||||
def news_reports(request):
|
||||
try:
|
||||
report_id = request.GET.get('id')
|
||||
if report_id is not None:
|
||||
report_id = int(report_id)
|
||||
if report_id <= 0:
|
||||
raise ValueError("id 必须为正整数")
|
||||
report_type = request.GET.get('report_type')
|
||||
if report_type and report_type not in _REPORT_TYPES:
|
||||
raise ValueError("report_type 仅支持 finance / intl")
|
||||
start_date = _parse_date_param(request, 'start_date')
|
||||
end_date = _parse_date_param(request, 'end_date')
|
||||
except ValueError as e:
|
||||
return Response({'error': str(e)}, status=400)
|
||||
|
||||
now = timezone.now()
|
||||
start_date = start_date or (now - timedelta(hours=24)).date()
|
||||
end_date = end_date or now.date()
|
||||
|
||||
try:
|
||||
data = report_query.fetch_reports(
|
||||
report_type=report_type,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
report_id=report_id,
|
||||
)
|
||||
except Exception as e:
|
||||
return Response({'error': f'查询失败: {e}'}, status=500)
|
||||
|
||||
if report_id is not None:
|
||||
if data is None:
|
||||
return Response({'error': f'日报 id={report_id} 不存在'}, status=404)
|
||||
return Response(data)
|
||||
return Response(data)
|
||||
|
||||
|
||||
def _get_int_param(request, name, default, lo, hi):
|
||||
"""解析整数参数并校验范围,非法抛 ValueError。"""
|
||||
raw = request.GET.get(name)
|
||||
if raw is None:
|
||||
return default
|
||||
try:
|
||||
value = int(raw)
|
||||
except ValueError:
|
||||
raise ValueError(f"{name} 必须为整数")
|
||||
if not (lo <= value <= hi):
|
||||
raise ValueError(f"{name} 需在 {lo}~{hi} 之间")
|
||||
return value
|
||||
|
||||
|
||||
@extend_schema(
|
||||
parameters=[
|
||||
OpenApiParameter(name='days', type=int, required=False, default=7,
|
||||
description='最近 N 天(1~365)'),
|
||||
OpenApiParameter(name='importance', type=int, required=False, default=4,
|
||||
description='最低重要度(1~5)'),
|
||||
OpenApiParameter(name='report_type', type=str, required=False,
|
||||
description='日报类型:finance | intl(默认两者)'),
|
||||
OpenApiParameter(name='section', type=str, required=False,
|
||||
description='板块:xwlb | news | cninfo | intl(默认全部)'),
|
||||
OpenApiParameter(name='limit', type=int, required=False, default=100,
|
||||
description='返回条数上限(1~500)'),
|
||||
],
|
||||
responses={200: ImportantEventSerializer(many=True)},
|
||||
description='跨日报重要事件聚合:最近 N 天 importance >= 阈值的事件,'
|
||||
'按重要度、日期降序',
|
||||
tags=['日报'],
|
||||
)
|
||||
@api_view(['GET'])
|
||||
def news_events(request):
|
||||
try:
|
||||
days = _get_int_param(request, 'days', 7, 1, 365)
|
||||
importance = _get_int_param(request, 'importance', 4, 1, 5)
|
||||
limit = _get_int_param(request, 'limit', 100, 1, 500)
|
||||
report_type = request.GET.get('report_type')
|
||||
if report_type and report_type not in _REPORT_TYPES:
|
||||
raise ValueError("report_type 仅支持 finance / intl")
|
||||
section = request.GET.get('section')
|
||||
if section and section not in _SECTIONS:
|
||||
raise ValueError("section 仅支持 xwlb / news / cninfo / intl")
|
||||
except ValueError as e:
|
||||
return Response({'error': str(e)}, status=400)
|
||||
|
||||
try:
|
||||
data = report_query.fetch_important_events(
|
||||
days=days,
|
||||
importance=importance,
|
||||
report_type=report_type,
|
||||
section=section,
|
||||
limit=limit,
|
||||
)
|
||||
except Exception as e:
|
||||
return Response({'error': f'查询失败: {e}'}, status=500)
|
||||
return Response(data)
|
||||
Reference in New Issue
Block a user