• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

source-academy / backend / e42ae7988de8cb3e6b06aff697212329d78335dc-PR-1345

07 May 2026 09:20AM UTC coverage: 87.164% (-0.4%) from 87.546%
e42ae7988de8cb3e6b06aff697212329d78335dc-PR-1345

Pull #1345

github

RichDom2185
Redate migration files to maintain total order
Pull Request #1345: Add AI grading statistics/feedback and refined AI comment persistence workflow

280 of 352 new or added lines in 24 files covered. (79.55%)

3 existing lines in 2 files now uncovered.

4217 of 4838 relevant lines covered (87.16%)

6781.06 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.21
/lib/cadet_web/admin_controllers/admin_llm_stats_controller.ex
1
defmodule CadetWeb.AdminLLMStatsController do
2
  @moduledoc """
3
  Controller for per-assessment and per-question LLM usage statistics and feedback.
4
  """
5

6
  use CadetWeb, :controller
7
  require Logger
8

9
  alias Cadet.LLMStats
10

11
  @doc """
12
  GET /admin/llm-stats/:assessment_id
13
  Returns assessment-level LLM usage statistics with per-question breakdown.
14
  """
15
  def course_stats(conn, %{"course_id" => course_id}) do
16
    case parse_id(course_id) do
1✔
17
      {:ok, course_id} ->
18
        stats = LLMStats.get_course_statistics(course_id)
1✔
19
        json(conn, stats)
1✔
20

21
      :error ->
NEW
22
        conn |> put_status(:bad_request) |> text("Invalid course_id")
×
23
    end
24
  end
25

26
  def assessment_stats(conn, %{"course_id" => course_id, "assessment_id" => assessment_id}) do
27
    with {:ok, course_id} <- parse_id(course_id),
1✔
28
         {:ok, assessment_id} <- parse_id(assessment_id) do
1✔
29
      stats = LLMStats.get_assessment_statistics(course_id, assessment_id)
1✔
30
      json(conn, stats)
1✔
31
    else
NEW
32
      :error -> conn |> put_status(:bad_request) |> text("Invalid course_id or assessment_id")
×
33
    end
34
  end
35

36
  @doc """
37
  GET /admin/llm-stats/:assessment_id/:question_id
38
  Returns question-level LLM usage statistics.
39
  """
40
  def question_stats(conn, %{
41
        "course_id" => course_id,
42
        "assessment_id" => assessment_id,
43
        "question_id" => question_id
44
      }) do
45
    with {:ok, course_id} <- parse_id(course_id),
1✔
46
         {:ok, assessment_id} <- parse_id(assessment_id),
1✔
47
         {:ok, question_id} <- parse_id(question_id) do
1✔
48
      stats = LLMStats.get_question_statistics(course_id, assessment_id, question_id)
1✔
49
      json(conn, stats)
1✔
50
    else
51
      :error ->
52
        conn
53
        |> put_status(:bad_request)
NEW
54
        |> text("Invalid course_id, assessment_id, or question_id")
×
55
    end
56
  end
57

58
  @doc """
59
  GET /admin/llm-stats/:assessment_id/feedback
60
  Returns feedback for an assessment, optionally filtered by question_id query param.
61
  """
62
  def get_feedback(conn, params = %{"course_id" => course_id, "assessment_id" => assessment_id}) do
63
    with {:ok, course_id} <- parse_id(course_id),
2✔
64
         {:ok, assessment_id} <- parse_id(assessment_id),
2✔
65
         {:ok, question_id} <- parse_optional_id(Map.get(params, "question_id")) do
2✔
66
      feedback = LLMStats.get_feedback(course_id, assessment_id, question_id)
2✔
67
      json(conn, feedback)
2✔
68
    else
69
      :error ->
70
        conn
71
        |> put_status(:bad_request)
NEW
72
        |> text("Invalid course_id, assessment_id, or question_id")
×
73
    end
74
  end
75

76
  @doc """
77
  POST /admin/llm-stats/:assessment_id/feedback
78
  Submits new feedback for the LLM feature on an assessment (optionally for a specific question).
79
  """
80
  def submit_feedback(
81
        conn,
82
        params = %{"course_id" => course_id, "assessment_id" => assessment_id}
83
      ) do
84
    with {:ok, course_id} <- parse_id(course_id),
2✔
85
         {:ok, assessment_id} <- parse_id(assessment_id),
2✔
86
         {:ok, question_id} <- parse_optional_id(Map.get(params, "question_id")) do
2✔
87
      user = conn.assigns[:current_user]
2✔
88

89
      attrs = %{
2✔
90
        course_id: course_id,
91
        user_id: user.id,
2✔
92
        assessment_id: assessment_id,
93
        question_id: question_id,
94
        rating: Map.get(params, "rating"),
95
        body: Map.get(params, "body")
96
      }
97

98
      case LLMStats.submit_feedback(attrs) do
2✔
99
        {:ok, _feedback} ->
100
          conn
101
          |> put_status(:created)
102
          |> json(%{message: "Feedback submitted successfully"})
1✔
103

104
        {:error, changeset} ->
105
          Logger.error("Failed to submit LLM feedback: #{inspect(changeset.errors)}")
1✔
106

107
          conn
108
          |> put_status(:bad_request)
109
          |> json(%{error: "Failed to submit feedback"})
1✔
110
      end
111
    else
112
      :error ->
113
        conn
114
        |> put_status(:bad_request)
NEW
115
        |> text("Invalid course_id, assessment_id, or question_id")
×
116
    end
117
  end
118

119
  defp parse_id(id) when is_integer(id), do: {:ok, id}
1✔
120

121
  defp parse_id(id) when is_binary(id) do
122
    case Integer.parse(id) do
15✔
123
      {parsed, ""} -> {:ok, parsed}
15✔
NEW
124
      _ -> :error
×
125
    end
126
  end
127

128
  defp parse_optional_id(nil), do: {:ok, nil}
2✔
129
  defp parse_optional_id(id), do: parse_id(id)
2✔
130
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc