1use utoipa::ToSchema;
5
6use crate::library::credit_registration::enrolment_check_schedule::{
7 EnrolmentCheckGroup, EnrolmentCheckSource,
8};
9use crate::prelude::*;
10
11pub const RETENTION_DAYS: i64 = 400;
14
15pub const VERY_LATE_SECS: i64 = 60 * 60;
17
18#[derive(Debug, Clone, PartialEq)]
19pub struct NewEnrolmentCheckOutcome {
20 pub credit_registration_id: Uuid,
21 pub course_module_id: Uuid,
22 pub enrolment_check_group: EnrolmentCheckGroup,
23 pub enrolment_check_step: Option<i32>,
24 pub source: EnrolmentCheckSource,
25 pub due_at: Option<DateTime<Utc>>,
26 pub checked_at: DateTime<Utc>,
27 pub previous_checked_at: Option<DateTime<Utc>>,
28 pub is_enrolment_found: bool,
29 pub enrolled_at: Option<DateTime<Utc>>,
30}
31
32pub async fn insert(conn: &mut PgConnection, new: &NewEnrolmentCheckOutcome) -> ModelResult<Uuid> {
34 let id = sqlx::query_scalar!(
35 r#"
36INSERT INTO credit_registration_enrolment_check_outcomes (
37 credit_registration_id,
38 course_module_id,
39 enrolment_check_group,
40 enrolment_check_step,
41 source,
42 due_at,
43 checked_at,
44 previous_checked_at,
45 is_enrolment_found,
46 enrolled_at
47 )
48VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
49RETURNING id
50 "#,
51 new.credit_registration_id,
52 new.course_module_id,
53 new.enrolment_check_group as EnrolmentCheckGroup,
54 new.enrolment_check_step,
55 new.source as EnrolmentCheckSource,
56 new.due_at,
57 new.checked_at,
58 new.previous_checked_at,
59 new.is_enrolment_found,
60 new.enrolled_at,
61 )
62 .fetch_one(conn)
63 .await?;
64 Ok(id)
65}
66
67pub async fn delete_older_than(
69 conn: &mut PgConnection,
70 cutoff: DateTime<Utc>,
71 limit: i64,
72) -> ModelResult<u64> {
73 let res = sqlx::query!(
74 r#"
75DELETE FROM credit_registration_enrolment_check_outcomes
76WHERE id IN (
77 SELECT id
78 FROM credit_registration_enrolment_check_outcomes
79 WHERE checked_at < $1
80 ORDER BY checked_at
81 LIMIT $2
82 )
83 "#,
84 cutoff,
85 limit,
86 )
87 .execute(conn)
88 .await?;
89 Ok(res.rows_affected())
90}
91
92#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema)]
94pub struct EnrolmentCheckLateness {
95 pub enrolment_check_group: EnrolmentCheckGroup,
96 pub enrolment_check_step: i32,
97 pub check_count: i64,
98 pub p50_late_secs: f64,
99 pub p95_late_secs: f64,
100 pub max_late_secs: f64,
101 pub very_late_count: i64,
103}
104
105pub async fn get_lateness_since(
108 conn: &mut PgConnection,
109 since: DateTime<Utc>,
110) -> ModelResult<Vec<EnrolmentCheckLateness>> {
111 let res = sqlx::query_as!(
112 EnrolmentCheckLateness,
113 r#"
114SELECT enrolment_check_group AS "enrolment_check_group!",
115 enrolment_check_step AS "enrolment_check_step!",
116 COUNT(*) AS "check_count!",
117 PERCENTILE_CONT(0.5) WITHIN GROUP (
118 ORDER BY late_secs
119 ) AS "p50_late_secs!",
120 PERCENTILE_CONT(0.95) WITHIN GROUP (
121 ORDER BY late_secs
122 ) AS "p95_late_secs!",
123 MAX(late_secs) AS "max_late_secs!",
124 COUNT(*) FILTER (
125 WHERE late_secs > $2
126 ) AS "very_late_count!"
127FROM (
128 SELECT enrolment_check_group,
129 enrolment_check_step,
130 GREATEST(EXTRACT(EPOCH FROM checked_at - due_at), 0)::double precision AS late_secs
131 FROM credit_registration_enrolment_check_outcomes
132 WHERE checked_at >= $1
133 AND source = 'schedule'
134 AND due_at IS NOT NULL
135 AND enrolment_check_step IS NOT NULL
136 ) checks
137GROUP BY enrolment_check_group,
138 enrolment_check_step
139ORDER BY enrolment_check_group,
140 enrolment_check_step
141 "#,
142 since,
143 VERY_LATE_SECS as f64,
144 )
145 .fetch_all(conn)
146 .await?;
147 Ok(res)
148}
149
150#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema)]
152pub struct EnrolmentCheckFindings {
153 pub enrolment_check_group: EnrolmentCheckGroup,
154 pub enrolment_check_step: Option<i32>,
156 pub source: EnrolmentCheckSource,
157 pub check_count: i64,
158 pub found_count: i64,
159 pub p50_detection_secs: Option<f64>,
162 pub p95_detection_secs: Option<f64>,
163}
164
165pub async fn get_findings_since(
167 conn: &mut PgConnection,
168 since: DateTime<Utc>,
169) -> ModelResult<Vec<EnrolmentCheckFindings>> {
170 let res = sqlx::query_as!(
171 EnrolmentCheckFindings,
172 r#"
173SELECT enrolment_check_group AS "enrolment_check_group!",
174 enrolment_check_step,
175 source AS "source!",
176 COUNT(*) AS "check_count!",
177 COUNT(*) FILTER (
178 WHERE is_enrolment_found
179 ) AS "found_count!",
180 PERCENTILE_CONT(0.5) WITHIN GROUP (
181 ORDER BY EXTRACT(EPOCH FROM checked_at - enrolled_at)::double precision
182 ) FILTER (
183 WHERE is_enrolment_found
184 AND enrolled_at IS NOT NULL
185 ) AS p50_detection_secs,
186 PERCENTILE_CONT(0.95) WITHIN GROUP (
187 ORDER BY EXTRACT(EPOCH FROM checked_at - enrolled_at)::double precision
188 ) FILTER (
189 WHERE is_enrolment_found
190 AND enrolled_at IS NOT NULL
191 ) AS p95_detection_secs
192FROM credit_registration_enrolment_check_outcomes
193WHERE checked_at >= $1
194GROUP BY enrolment_check_group,
195 enrolment_check_step,
196 source
197ORDER BY enrolment_check_group,
198 source,
199 enrolment_check_step NULLS LAST
200 "#,
201 since,
202 )
203 .fetch_all(conn)
204 .await?;
205 Ok(res)
206}
207
208#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema)]
210pub struct EnrolmentCheckPopulation {
211 pub enrolment_check_group: EnrolmentCheckGroup,
212 pub enrolment_check_step: Option<i32>,
214 pub row_count: i64,
215 pub never_checked_count: i64,
217}
218
219pub async fn get_population(conn: &mut PgConnection) -> ModelResult<Vec<EnrolmentCheckPopulation>> {
221 let res = sqlx::query_as!(
222 EnrolmentCheckPopulation,
223 r#"
224SELECT enrolment_check_group AS "enrolment_check_group!",
225 enrolment_check_step,
226 COUNT(*) AS "row_count!",
227 COUNT(*) FILTER (
228 WHERE enrolment_checked_at IS NULL
229 ) AS "never_checked_count!"
230FROM credit_registrations
231WHERE state IN (
232 'no_usable_enrolment',
233 'ready_to_submit',
234 'resolving_enrolment',
235 'failed_retryable'
236 )
237 AND enrolment_check_anchor_at IS NOT NULL
238 AND superseded_by_id IS NULL
239 AND deleted_at IS NULL
240GROUP BY enrolment_check_group,
241 enrolment_check_step
242ORDER BY enrolment_check_group,
243 enrolment_check_step NULLS LAST
244 "#,
245 )
246 .fetch_all(conn)
247 .await?;
248 Ok(res)
249}