""" 日报查询 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)