1+ import json
12import math
23import re
34import time
1112
1213import requests
1314from membership .models import Group , Member , member_group
15+ from messages .message import send_message
16+ from messages .models import Message , MessageTemplate
1417from multiaccessy .models import PhysicalAccessEntry
1518from PIL import Image
1619from quiz .models import QuizAnswer , QuizQuestion
@@ -1164,6 +1167,19 @@ def is_task_delegation_enabled(member_id: int) -> bool:
11641167 return pref .selected_options == "true"
11651168
11661169
1170+ def consecutive_ignored_tasks (last_assigned_tasks : list [TaskDelegationLogSimple ]) -> list [TaskDelegationLogSimple ]:
1171+ """Return the leading run of ignored tasks (newest first), skipping still-pending and re-rolled tasks."""
1172+ result = []
1173+ for task in last_assigned_tasks :
1174+ if task .status in ("assigned" , "not_done_rerolled" ):
1175+ continue
1176+ if task .status == "ignored" :
1177+ result .append (task )
1178+ else :
1179+ break
1180+ return result
1181+
1182+
11671183def member_recently_received_task (ctx : TaskContext ) -> bool :
11681184 # If a member has completed a task recently, don't assign them another one too soon.
11691185 # The wait depends on the size of the task
@@ -1183,10 +1199,11 @@ def member_recently_received_task(ctx: TaskContext) -> bool:
11831199 # It's then no longer assigned or completed, but we still don't want to assign them another task too soon.
11841200 #
11851201 # If the member explicitly wanted a new task, we allow assigning a new task sooner.
1186- if (
1187- ctx .time - last_assigned_task .created_at < timedelta (hours = 14 )
1188- and last_assigned_task .status != "not_done_rerolled"
1189- ):
1202+ # If the member has been ignoring tasks, increase the delay by 3x.
1203+ base_delay = timedelta (hours = 14 )
1204+ if len (consecutive_ignored_tasks (ctx .member .last_assigned_tasks )) >= 3 :
1205+ base_delay *= 3
1206+ if ctx .time - last_assigned_task .created_at < base_delay and last_assigned_task .status != "not_done_rerolled" :
11901207 return True
11911208
11921209 return False
@@ -1800,6 +1817,16 @@ def delegate_task_for_member(
18001817 logger .info (f"Member { member_id } received a task recently; skipping delegation" )
18011818 return None
18021819
1820+ # If 3 tasks have been ignored in a row since the last preference question, ask again.
1821+ if not force and slack_interaction is None :
1822+ if should_ask_delegation_preference (member_id , ctx .member .last_assigned_tasks ):
1823+ member = db_session .get (Member , member_id )
1824+ if member :
1825+ ask_delegation_preference_question (member )
1826+ db_session .commit ()
1827+ logger .info (f"Queued delegation preference question for member { member_id } " )
1828+ return None
1829+
18031830 if picked_card is None :
18041831 picked_card = select_card_for_member (ctx , ignore_reasons )
18051832
@@ -2010,6 +2037,24 @@ def clear_pending_question(member_id: int) -> None:
20102037 redis_connection .delete (cache_key )
20112038
20122039
2040+ def should_ask_delegation_preference (member_id : int , last_assigned_tasks : list [TaskDelegationLogSimple ]) -> bool :
2041+ """Return True if 3 tasks have been ignored in a row since the last delegation preference message was sent."""
2042+ streak = consecutive_ignored_tasks (last_assigned_tasks )
2043+ if len (streak ) < 3 :
2044+ return False
2045+ last_message_date = db_session .execute (
2046+ select (Message .created_at )
2047+ .where (
2048+ Message .member_id == member_id ,
2049+ Message .template == MessageTemplate .TASK_DELEGATION_PREFERENCE .value ,
2050+ Message .status != Message .FAILED ,
2051+ )
2052+ .order_by (Message .created_at .desc ())
2053+ .limit (1 )
2054+ ).scalar_one_or_none ()
2055+ return last_message_date is None or streak [2 ].created_at > last_message_date
2056+
2057+
20132058def ask_room_preference_question (
20142059 member : Member ,
20152060 slack_client : WebClient ,
@@ -2179,3 +2224,44 @@ def ask_skill_level_question(
21792224 channel = slack_response .get ("channel" , "" )
21802225 ts = slack_response .get ("ts" , "" )
21812226 return (channel , ts )
2227+
2228+
2229+ def ask_delegation_preference_question (member : Member ) -> None :
2230+ """Queue a Slack message asking the member whether they want to continue receiving task suggestions."""
2231+ text = (
2232+ f"Hi { member .firstname } ! We've noticed you haven't responded to the last few task suggestions. "
2233+ f"Would you like to continue receiving task suggestions?"
2234+ )
2235+
2236+ blocks : list [Block ] = [
2237+ DividerBlock (),
2238+ SectionBlock (text = MarkdownTextObject (text = text )),
2239+ ActionsBlock (
2240+ block_id = "task_delegation_preference" ,
2241+ elements = [
2242+ ButtonElement (
2243+ text = PlainTextObject (text = "Yes, keep sending tasks" , emoji = True ),
2244+ action_id = "task_delegation_preference_yes" ,
2245+ value = "yes" ,
2246+ style = "primary" ,
2247+ ),
2248+ ButtonElement (
2249+ text = PlainTextObject (text = "No, stop sending tasks" , emoji = True ),
2250+ action_id = "task_delegation_preference_no" ,
2251+ value = "no" ,
2252+ style = "danger" ,
2253+ ),
2254+ ],
2255+ ),
2256+ ]
2257+
2258+ send_message (
2259+ template = MessageTemplate .TASK_DELEGATION_PREFERENCE ,
2260+ member = member ,
2261+ db_session = db_session ,
2262+ recipient = member .email ,
2263+ recipient_type = "slack" ,
2264+ subject = "Continue receiving task suggestions?" ,
2265+ body = json .dumps ([block .to_dict () for block in blocks ]),
2266+ )
2267+ logger .info (f"Queued delegation preference question for member #{ member .member_number } " )
0 commit comments