headless_lms_credit_registration/
attention.rs1use chrono::{DateTime, Utc};
5use headless_lms_models::credit_registrations::{
6 AttentionRules, BlockingProblems, StoppedPhase, StuckThresholds,
7};
8use headless_lms_models::{
9 ModelResult, course_module_suotar_configurations, credit_registration_phase_state,
10 credit_registration_roster_schedules,
11};
12use sqlx::PgConnection;
13
14use crate::CreditRegistrationPhase;
15
16pub const PHASE_HEARTBEAT_INTERVAL_MULTIPLIER: i32 = 2;
18
19pub fn is_heartbeat_late(
22 last_heartbeat_at: Option<DateTime<Utc>>,
23 expected_interval_secs: i32,
24 paused_at: Option<DateTime<Utc>>,
25 now: DateTime<Utc>,
26) -> bool {
27 paused_at.is_none()
28 && last_heartbeat_at.is_some_and(|at| {
29 (now - at).num_seconds()
30 > i64::from(expected_interval_secs) * i64::from(PHASE_HEARTBEAT_INTERVAL_MULTIPLIER)
31 })
32}
33
34pub async fn attention_rules(
37 conn: &mut PgConnection,
38 account_linking_since: Option<DateTime<Utc>>,
39) -> ModelResult<AttentionRules> {
40 let now = Utc::now();
41 let stopped_phases = credit_registration_phase_state::get_all(conn)
42 .await?
43 .into_iter()
44 .filter(|row| {
45 row.paused_at.is_some()
46 || is_heartbeat_late(
47 row.last_heartbeat_at,
48 row.expected_interval_secs,
49 row.paused_at,
50 now,
51 )
52 })
53 .filter_map(|row| {
54 let phase = CreditRegistrationPhase::from_phase_name(&row.phase)?;
55 Some(StoppedPhase {
56 phase: row.phase,
57 owned_states: phase.owned_states().to_vec(),
58 sends_linking_emails: matches!(
59 phase,
60 CreditRegistrationPhase::EnrolmentDiscovery
61 | CreditRegistrationPhase::LinkEmails
62 ),
63 })
64 })
65 .collect();
66 let failing_course_codes = credit_registration_roster_schedules::get_failing_codes(conn)
67 .await?
68 .into_iter()
69 .map(|code| code.course_code)
70 .collect();
71 let misconfigured_module_ids =
72 course_module_suotar_configurations::get_module_ids_failing_config_check(conn).await?;
73 Ok(AttentionRules {
74 thresholds: StuckThresholds::CURRENT,
75 account_linking_since,
76 blocking: BlockingProblems {
77 stopped_phases,
78 failing_course_codes,
79 misconfigured_module_ids,
80 },
81 })
82}