Source code for src.core.routes.reports

# Copyright (C) 2025 Raccoon Survey org
# This file is part of Raccoon Survey.
# Raccoon Survey is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License v3 as published by
# the Free Software Foundation.
# See the LICENSE file distributed with this program for details.

from __future__ import annotations

from datetime import datetime

from flask import Blueprint, jsonify, request

from src.core.middlewares.rbac import role_required
from src.core.services import reports_service

bp = Blueprint("reports", __name__)


def _parse_iso_dt(value: str | None) -> datetime | None:
    if not value:
        return None

    try:
        return datetime.fromisoformat(value)
    except ValueError:
        raise ValueError("invalid_datetime_format")  # noqa: B904


[docs] @bp.get("/surveys/<int:survey_id>/summary") @role_required("admin", "rrhh") def survey_summary(survey_id: int) -> tuple[dict, int]: """Aggregate responses for a survey, optionally filtered by team and date range. Args: survey_id (int): ID of the survey. Returns: tuple[dict, int]: Formatted summary and status code. """ team_id_raw = request.args.get("team_id") date_from_str = request.args.get("date_from") date_to_str = request.args.get("date_to") team_id = int(team_id_raw) if team_id_raw is not None else None date_from = _parse_iso_dt(date_from_str) date_to = _parse_iso_dt(date_to_str) try: data = reports_service.get_survey_summary( survey_id, team_id=team_id, date_from=date_from, date_to=date_to ) return jsonify(data), 200 except LookupError as e: return jsonify({"message": str(e)}), 404 except ValueError as e: return jsonify({"message": str(e)}), 400 except Exception as e: return jsonify({"message": str(e)}), 500
[docs] @bp.get("/teams/<int:team_id>/summary") @role_required("admin", "rrhh") def team_summary(team_id: int) -> tuple[dict, int]: """Aggregate responses for a team, optionally filtered by survey and date range. Args: team_id (int): ID of the team. Returns: tuple[dict, int]: Formatted summary and status code. """ survey_id_raw = request.args.get("survey_id") date_from_str = request.args.get("date_from") date_to_str = request.args.get("date_to") survey_id = int(survey_id_raw) if survey_id_raw is not None else None date_from = _parse_iso_dt(date_from_str) date_to = _parse_iso_dt(date_to_str) try: data = reports_service.get_team_summary( team_id, survey_id=survey_id, date_from=date_from, date_to=date_to ) return jsonify(data), 200 except LookupError as e: return jsonify({"message": str(e)}), 404 except ValueError as e: return jsonify({"message": str(e)}), 400 except Exception as e: return jsonify({"message": str(e)}), 500
[docs] @bp.get("/export") @role_required("admin", "rrhh") def export_reports() -> tuple[str, int, dict]: """Export survey or team summary as CSV. Returns: tuple[str, int, dict]: CSV content, status code, and headers. """ survey_id_raw = request.args.get("survey_id") team_id_raw = request.args.get("team_id") date_from_str = request.args.get("date_from") date_to_str = request.args.get("date_to") survey_id = int(survey_id_raw) if survey_id_raw is not None else None team_id = int(team_id_raw) if team_id_raw is not None else None date_from = _parse_iso_dt(date_from_str) date_to = _parse_iso_dt(date_to_str) try: if survey_id is not None: summary = reports_service.get_survey_summary( survey_id, team_id=team_id, date_from=date_from, date_to=date_to ) elif team_id is not None: summary = reports_service.get_team_summary( team_id, survey_id=survey_id, date_from=date_from, date_to=date_to ) else: return jsonify({"message": "either survey_id or team_id is required"}), 400 csv_content = reports_service.export_summary_csv(summary=summary) filename = ( f"survey_{survey_id}_summary.csv" if survey_id is not None else f"team_{team_id}_summary.csv" ) return ( csv_content, 200, { "Content-Type": "text/csv; charset=utf-8", "Content-Disposition": f"attachment; filename={filename}", }, ) except LookupError as e: return jsonify({"message": str(e)}), 404 except ValueError as e: return jsonify({"message": str(e)}), 400 except Exception as e: return jsonify({"message": str(e)}), 500