Skip to content

Develop#430

Merged
ucswift merged 3 commits into
masterfrom
develop
Jul 18, 2026
Merged

Develop#430
ucswift merged 3 commits into
masterfrom
develop

Conversation

@ucswift

@ucswift ucswift commented Jul 17, 2026

Copy link
Copy Markdown
Member

Description

This PR significantly expands the Resgrid chatbot's natural language capabilities with new query intents, improved custom state resolution, and text-based RSVP/poll response handling.

New Chatbot Intents & Handlers

  • Who's available? (who_available) — Lists personnel whose status and staffing classify as available to respond.
  • Units available? (units_available) — Lists units currently classified as available/in service.
  • Who's on/responding/dispatched to a call? — Three distinct intents:
    • call_responders — Personnel and units currently en route or on scene for a call.
    • call_dispatched — The full dispatch list (personnel, groups, roles, units) for a call.
  • My calls (my_calls) / Unit calls (unit_calls) — Active calls the user or a named unit was dispatched to.
  • My schedule (my_schedule) — User's shifts plus RSVP'd calendar events for a given day.
  • Poll (create_poll) — Admin-only yes/no poll broadcast to all department members, with confirmation before sending.

Reworked Call Response Flow

  • Bare phrases like "responding," "omw," "not responding," and "not going" now resolve against the user's most recent dispatch (including group and role dispatches), rather than requiring an explicit call reference.
  • Call responses map through department-configured custom statuses via BaseType, falling back to built-in ActionTypes.
  • Negative responses ("not responding"/"not going") set the appropriate declined status.

Custom State Resolution Overhaul

  • New CustomStateMatcher service standardizes state lookup by ID, name, BaseType, and numeric selection across status, staffing, and unit status handlers.
  • Status and staffing commands now resolve through department custom states first, with built-in enum values as fallbacks.
  • Unit status respects unit-type-specific state sets when configured.

Text-Based RSVP & Poll Responses

  • New CalendarRsvp message type and TextResponsePromptService record RSVP prompts when calendar notifications are sent.
  • Calendar notifications and reminders now append "Reply YES or NO." for RSVP-type events.
  • TextResponseResolver resolves bare YES/NO replies against recent unanswered polls and calendar RSVPs, with a numbered disambiguation flow when multiple items are pending.
  • SMS notifications added for calendar events when the user has SMS enabled.

Other Changes

  • Department config support for AllowDispatchViaChatbot, RequireConfirmationForStatusChange, and SessionTtlMinutes.
  • Conversation engine prompts now list department-specific custom state options (numbered) for status and staffing selection.
  • Keyword classifier pattern matching reordered for priority correctness (patterns checked before candidates).
  • Updated help text, localized resources (9 languages), and comprehensive test coverage for all new functionality.

Summary by CodeRabbit

  • New Features
    • Expanded chatbot commands for availability, responders, dispatched personnel, personal calls, unit calls, schedules, and polls.
    • Added YES/NO responses for polls and calendar RSVP prompts through supported messaging channels.
    • Added clearer status and call-response handling, including negative responses and recent-call references.
    • Added a document details page with improved viewing, downloading, editing, and deletion controls.
  • Bug Fixes
    • Improved document access restrictions, file-name handling, upload reliability, and certification uploads.
    • Dispatch broadcasts now target only newly selected recipients.
    • Chatbot sessions can use department-configured expiration times.

@request-info

request-info Bot commented Jul 17, 2026

Copy link
Copy Markdown

Thanks for opening this, but we'd appreciate a little more information. Could you update it with more details?

@Resgrid-Bot

This comment has been minimized.

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: bbec99c3-5070-482a-b954-e3d129752cde

📥 Commits

Reviewing files that changed from the base of the PR and between 939bd97 and 0b2c2b9.

⛔ Files ignored due to path filters (14)
  • Tests/Resgrid.Tests/Chatbot/CallRespondersActionHandlerTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Chatbot/ChatbotAvailabilityIntentClassifierTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Chatbot/ChatbotDeptConfigAndSessionTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Chatbot/ChatbotHandlerTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Chatbot/ChatbotTextResponseResolverTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Chatbot/MyCallsActionHandlerTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Chatbot/MyScheduleActionHandlerTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Framework/FileHelperTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Models/CallQueueItemTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Models/DocumentTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Services/CalendarServiceTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Services/CommunicationServiceTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Web/DocumentPageTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Web/UploadFormEncodingTests.cs is excluded by !**/Tests/**
📒 Files selected for processing (35)
  • .gitignore
  • Core/Resgrid.Chatbot.NLU/Providers/KeywordIntentClassifier.cs
  • Core/Resgrid.Chatbot/Handlers/CallRespondersActionHandler.cs
  • Core/Resgrid.Chatbot/Handlers/MyCallsActionHandler.cs
  • Core/Resgrid.Chatbot/Handlers/MyScheduleActionHandler.cs
  • Core/Resgrid.Chatbot/Handlers/PollCreateHandler.cs
  • Core/Resgrid.Chatbot/Handlers/RespondToCallHandler.cs
  • Core/Resgrid.Chatbot/Handlers/StaffingActionHandler.cs
  • Core/Resgrid.Chatbot/Interfaces/IChatbotSessionManager.cs
  • Core/Resgrid.Chatbot/Interfaces/IChatbotSessionStore.cs
  • Core/Resgrid.Chatbot/Services/ChatbotIngressService.cs
  • Core/Resgrid.Chatbot/Services/ChatbotSessionManager.cs
  • Core/Resgrid.Chatbot/Services/RedisSessionStore.cs
  • Core/Resgrid.Chatbot/Services/TextResponseResolver.cs
  • Core/Resgrid.Framework/FileHelper.cs
  • Core/Resgrid.Model/Messages/TextResponsePromptMetadata.cs
  • Core/Resgrid.Model/Queue/CallQueueItem.cs
  • Core/Resgrid.Services/CalendarService.cs
  • Core/Resgrid.Services/CommunicationService.cs
  • Core/Resgrid.Services/TextResponsePromptService.cs
  • Web/Resgrid.Web.Services/Controllers/v4/CallsController.cs
  • Web/Resgrid.Web/Areas/User/Controllers/DispatchController.cs
  • Web/Resgrid.Web/Areas/User/Controllers/DocumentsController.cs
  • Web/Resgrid.Web/Areas/User/Controllers/ProfileController.cs
  • Web/Resgrid.Web/Areas/User/Models/Documents/ViewDocumentView.cs
  • Web/Resgrid.Web/Areas/User/Views/Documents/EditDocument.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Documents/Index.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Documents/ViewDocument.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Profile/AddCertification.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Profile/EditCertification.cshtml
  • Web/Resgrid.Web/Helpers/ClaimsAuthorizationHelper.cs
  • Web/Resgrid.Web/wwwroot/js/app/internal/documents/resgrid.documents.newdocument.js
  • Workers/Resgrid.Workers.Framework/Logic/CalendarNotifierLogic.cs
  • Workers/Resgrid.Workers.Framework/Logic/CallBroadcast.cs
  • Workers/Resgrid.Workers.Framework/Logic/NotificationBroadcastLogic.cs
💤 Files with no reviewable changes (1)
  • Core/Resgrid.Chatbot/Handlers/StaffingActionHandler.cs
🚧 Files skipped from review as they are similar to previous changes (13)
  • Core/Resgrid.Services/TextResponsePromptService.cs
  • Workers/Resgrid.Workers.Framework/Logic/NotificationBroadcastLogic.cs
  • Core/Resgrid.Services/CalendarService.cs
  • Core/Resgrid.Chatbot/Handlers/MyCallsActionHandler.cs
  • Core/Resgrid.Chatbot/Handlers/PollCreateHandler.cs
  • Core/Resgrid.Chatbot.NLU/Providers/KeywordIntentClassifier.cs
  • Workers/Resgrid.Workers.Framework/Logic/CalendarNotifierLogic.cs
  • Core/Resgrid.Chatbot/Handlers/MyScheduleActionHandler.cs
  • Core/Resgrid.Chatbot/Handlers/CallRespondersActionHandler.cs
  • Core/Resgrid.Services/CommunicationService.cs
  • Core/Resgrid.Chatbot/Handlers/RespondToCallHandler.cs
  • Core/Resgrid.Chatbot/Services/ChatbotIngressService.cs
  • Core/Resgrid.Chatbot/Services/TextResponseResolver.cs

📝 Walkthrough

Walkthrough

This PR expands chatbot intent recognition and handlers, adds multi-turn poll and calendar RSVP responses, supports per-session TTLs, filters selected call broadcast recipients, and updates document and certification upload workflows with authorization, sanitization, and safer file handling.

Changes

Chatbot response expansion

Layer / File(s) Summary
Intent contracts and classification
Core/Resgrid.Chatbot.NLU/..., Core/Resgrid.Chatbot/Models/..., Core/Resgrid.Chatbot/Services/...
Adds availability, call, polling, and schedule intents with expanded keyword/OpenAI mappings and localized responses.
Conversation and session flow
Core/Resgrid.Chatbot/Services/..., Core/Resgrid.Chatbot/Interfaces/...
Adds parameter collection, pending YES/NO target selection, department-configured session TTLs, and per-session Redis/in-memory expiration.
Status and query handlers
Core/Resgrid.Chatbot/Handlers/..., Core/Resgrid.Chatbot/ChatbotModule.cs
Adds availability, responder, dispatch, call-list, schedule, and poll handlers while updating status and call-response resolution. DispatchCallHandler now honors department dispatch configuration.

Poll and calendar RSVP responses

Layer / File(s) Summary
Text-response resolution
Core/Resgrid.Chatbot/Services/TextResponseResolver.cs, Core/Resgrid.Model/Messages/...
Discovers pending poll and calendar RSVP prompts, validates targets, and records answers.
Calendar RSVP delivery
Core/Resgrid.Services/..., Workers/Resgrid.Workers.Framework/...
Adds RSVP prompt persistence, YES/NO notification text, SMS delivery paths, and worker integration after successful notifications.

Document and broadcast updates

Layer / File(s) Summary
Selected dispatch broadcasting
Core/Resgrid.Model/Queue/..., Web/.../CallsController.cs, Workers/.../CallBroadcast.cs
Carries selected user, group, unit, and role IDs through call queue items and filters dispatches before broadcast processing.
Document and certification handling
Web/Resgrid.Web/..., Core/Resgrid.Framework/FileHelper.cs
Adds document viewing and authorization paths, sanitizes descriptions and filenames, supports async cancellable uploads, and enables multipart certification forms.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

  • Resgrid/Core#428: Overlaps with the keyword classifier and call-response handling paths.
  • Resgrid/Core#427: Also changes ChatbotIngressService session and routing behavior.
  • Resgrid/Core#393: Provides related chatbot NLU and intent-handling framework changes.

Suggested reviewers: github-actions

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.96% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is too generic and does not describe the chatbot and web changes in this PR. Replace it with a concise, specific summary such as "Expand chatbot intents and add text response support".
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch develop

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 17

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Workers/Resgrid.Workers.Framework/Logic/CalendarNotifierLogic.cs (1)

98-136: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Wrap the entity notification path in the worker’s try-catch.

The added helper calls can throw from communication or data access, but this entire branch remains outside the attendee branch’s error handling, allowing Process() to escape without a failure tuple.

As per coding guidelines, worker Process() methods must wrap processing in try-catch, log via Logging.LogException(), and return a failure tuple on error.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Workers/Resgrid.Workers.Framework/Logic/CalendarNotifierLogic.cs` around
lines 98 - 136, The entity notification branch in Process(), including
SendCalendarReminderAsync and GetAllGroupsForDepartmentAsync calls, must be
enclosed in the worker’s try-catch. On any exception, log it through
Logging.LogException() and return the established failure tuple instead of
allowing Process() to escape; preserve the existing notification and
MarkAsNotifiedAsync flow on success.

Source: Coding guidelines

🟡 Minor comments (5)
Core/Resgrid.Chatbot/Services/ConversationEngine.cs-292-305 (1)

292-305: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Localize the custom-state prompt headings.

These branches bypass ChatbotResources, so every non-English session receives an English heading whenever custom states are configured. Add resource keys and pass the culture-specific heading into BuildOptionsPrompt.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Core/Resgrid.Chatbot/Services/ConversationEngine.cs` around lines 292 - 305,
Localize the headings in the SetStatus and SetStaffing branches of the
conversation flow by adding corresponding ChatbotResources keys for each prompt
and passing the culture-specific values to BuildOptionsPrompt. Preserve the
existing custom-state lookup and option handling, and use the session/request
culture already established by the surrounding code.
Core/Resgrid.Chatbot/Handlers/DispatchCallHandler.cs-47-47 (1)

47-47: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Move the new English response fragments into localized resources.

  • Core/Resgrid.Chatbot/Handlers/DispatchCallHandler.cs#L47-L47: localize the disabled-dispatch response.
  • Core/Resgrid.Chatbot/Handlers/RespondToCallHandler.cs#L77-L80: localize the entire negative call-response format.
  • Core/Resgrid.Chatbot/Handlers/StatusActionHandler.cs#L65-L65: localize the “your status” confirmation label.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Core/Resgrid.Chatbot/Handlers/DispatchCallHandler.cs` at line 47, Move the
hard-coded English chatbot responses into the existing localized resource
system: replace the disabled-dispatch text in DispatchCallHandler, the complete
negative call-response format in RespondToCallHandler, and the “your status”
confirmation label in StatusActionHandler with resource lookups, adding the
corresponding resource entries and preserving each response’s current formatting
and behavior.
Core/Resgrid.Chatbot/Handlers/HelpActionHandler.cs-139-139 (1)

139-139: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Mention calendar RSVP prompts here too.

Bare YES or NO can also resolve a pending calendar RSVP, not only the latest poll.

Proposed wording
-				+ "YES or NO: answers your latest poll\n"
+				+ "YES or NO: answers your latest poll or RSVP prompt\n"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Core/Resgrid.Chatbot/Handlers/HelpActionHandler.cs` at line 139, Update the
help text in HelpActionHandler to mention that a bare YES or NO also answers a
pending calendar RSVP, alongside the existing latest-poll behavior.
Core/Resgrid.Chatbot/Handlers/CallDispatchedActionHandler.cs-92-114 (1)

92-114: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Indicate truncated group, role, and unit results.

These sections silently hide entries beyond MaxLinesPerSection, unlike personnel. Append Msg_AndMore with the omitted count to avoid presenting an incomplete dispatch list as complete.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Core/Resgrid.Chatbot/Handlers/CallDispatchedActionHandler.cs` around lines 92
- 114, Update the group, role, and unit sections in CallDispatchedActionHandler
so each calculates omitted entries beyond MaxLinesPerSection and appends
ChatbotResources.Get("Msg_AndMore", culture) with that count, matching the
existing personnel truncation behavior. Preserve the current entries and section
output when no results are omitted.
Core/Resgrid.Chatbot/Handlers/CallDispatchedActionHandler.cs-43-60 (1)

43-60: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Treat filler-only call references as unspecified.

Inputs such as “the call” are sent to CallReferenceResolver, producing Call_NoMatch instead of selecting the sole active call. Normalize call, the call, and similar filler values before resolution, as the responder handler does.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Core/Resgrid.Chatbot/Handlers/CallDispatchedActionHandler.cs` around lines 43
- 60, Normalize filler-only values such as “call” and “the call” to an
unspecified reference before the reference check in the call-selection flow.
Reuse the responder handler’s existing normalization behavior or helper, then
let empty results follow the sole-active-call fallback; keep valid references
routed through CallReferenceResolver.ResolveAsync.
🧹 Nitpick comments (2)
Core/Resgrid.Chatbot/Services/ChatbotIngressService.cs (1)

52-69: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the repository’s Service Locator pattern for the newly added service dependencies.

Both changes add constructor injection despite the repository-specific dependency-resolution rule.

  • Core/Resgrid.Chatbot/Services/ChatbotIngressService.cs#L52-L69: resolve ITextResponseResolver through Bootstrapper.GetKernel().Resolve<ITextResponseResolver>().
  • Core/Resgrid.Chatbot/Services/ConversationEngine.cs#L16-L20: resolve ICustomStateService through Bootstrapper.GetKernel().Resolve<ICustomStateService>().

As per coding guidelines, “Use Service Locator pattern via Bootstrapper.GetKernel().Resolve<T>() to resolve dependencies explicitly in constructors, rather than constructor injection.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Core/Resgrid.Chatbot/Services/ChatbotIngressService.cs` around lines 52 - 69,
Replace constructor injection for ITextResponseResolver in ChatbotIngressService
with Bootstrapper.GetKernel().Resolve<ITextResponseResolver>(), removing the
corresponding constructor parameter and assignment. Apply the same change in
Core/Resgrid.Chatbot/Services/ConversationEngine.cs at lines 16-20 for
ICustomStateService: resolve it through
Bootstrapper.GetKernel().Resolve<ICustomStateService>() instead of injecting it.

Source: Coding guidelines

Core/Resgrid.Chatbot/Handlers/AvailabilityActionHandler.cs (1)

61-77: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Index action logs and user states before iterating.

Lines 69-70 scan both department-wide collections for every user, making this path quadratic. Build dictionaries keyed by UserId once before the loop.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Core/Resgrid.Chatbot/Handlers/AvailabilityActionHandler.cs` around lines 61 -
77, Update the availability flow before the user iteration to build dictionaries
keyed by UserId from lastActionLogs and userStates, then replace the per-user
FirstOrDefault lookups in the foreach over allUsers with dictionary lookups
while preserving null and missing-entry behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Core/Resgrid.Chatbot.NLU/Providers/KeywordIntentClassifier.cs`:
- Around line 202-210: Update the positive and negative responder patterns in
the keyword classifier to pass extracted call references through CleanReference
before constructing the callRef parameter. Apply this consistently to the
callRef extraction callbacks in the relevant respond_to_call rules, while
leaving callId handling unchanged.

In `@Core/Resgrid.Chatbot/Handlers/AvailabilityActionHandler.cs`:
- Around line 24-45: Replace constructor injection with
Bootstrapper.GetKernel().Resolve<T>() assignments for all dependencies in
Core/Resgrid.Chatbot/Handlers/AvailabilityActionHandler.cs lines 24-45,
including all six services; resolve the three handler services explicitly in
Core/Resgrid.Chatbot/Handlers/UnitsAvailableActionHandler.cs lines 22-34;
resolve calls and units services explicitly in
Core/Resgrid.Chatbot/Handlers/MyCallsActionHandler.cs lines 26-33; resolve
message, department, and profile services explicitly in
Core/Resgrid.Chatbot/Handlers/PollCreateHandler.cs lines 28-40; and resolve
message, calendar, and authorization services explicitly in
Core/Resgrid.Chatbot/Services/TextResponseResolver.cs lines 19-29. Remove the
corresponding constructor parameters while preserving the existing private
dependency fields.

In `@Core/Resgrid.Chatbot/Handlers/CallRespondersActionHandler.cs`:
- Around line 71-76: Update the action-log selection in
CallRespondersActionHandler to fetch each resource’s current department state
before filtering responders. Compare those current states against the
call-scoped records and retain only records whose destination is this call,
rather than selecting the latest entry solely from GetActionLogsForCallAsync;
apply the same logic to the corresponding block around the later occurrence.
- Around line 29-50: Replace constructor injection with explicit
Bootstrapper.GetKernel().Resolve<T>() resolution for all six services in
Core/Resgrid.Chatbot/Handlers/CallRespondersActionHandler.cs#L29-L50, all three
services in
Core/Resgrid.Chatbot/Handlers/CallDispatchedActionHandler.cs#L22-L34, and all
three services in
Core/Resgrid.Chatbot/Handlers/MyScheduleActionHandler.cs#L24-L36. In
Core/Resgrid.Services/CalendarService.cs#L28-L46, resolve
ITextResponsePromptService explicitly instead of using an optional constructor
parameter; in Core/Resgrid.Services/TextResponsePromptService.cs#L14-L19,
resolve IMessageService through the kernel. Preserve each constructor’s existing
initialization behavior while applying the required Service Locator pattern.

In `@Core/Resgrid.Chatbot/Handlers/DispatchCallHandler.cs`:
- Around line 26-31: Replace optional constructor service parameters with
explicit Bootstrapper.GetKernel().Resolve<T>() resolution: in
Core/Resgrid.Chatbot/Handlers/DispatchCallHandler.cs lines 26-31, always resolve
IChatbotDepartmentConfigService; in
Core/Resgrid.Chatbot/Handlers/RespondToCallHandler.cs lines 29-37, always
resolve the custom-state, group, and role services; and in
Core/Resgrid.Chatbot/Handlers/StatusActionHandler.cs lines 18-23, always resolve
the department configuration service. Update each constructor’s field assignment
while preserving the existing handler behavior.

In `@Core/Resgrid.Chatbot/Handlers/MyCallsActionHandler.cs`:
- Around line 116-135: The GetActiveCallsWithDispatchesAsync method must not
truncate active calls with Take(MaxActiveCallsToScan), since older dispatched
calls can be missed. Replace the limited retrieval with a dispatch-filtered
service query, or process all active calls using bounded concurrency, while
preserving the existing forUnits-specific PopulateCallData options and result
behavior.

In `@Core/Resgrid.Chatbot/Handlers/MyScheduleActionHandler.cs`:
- Line 53: Update MyScheduleActionHandler date parsing and formatting to
consistently use the session culture rather than CultureInfo.InvariantCulture or
process culture. Apply this to the dateLabel formatting and the additional
date-related locations at the indicated symbols, ensuring localized weekday
names and date input are accepted while preserving the existing response
behavior.
- Around line 84-99: The calendar retrieval in the schedule handler currently
excludes multi-day events because it requires both endpoints within the window.
Update GetAllCalendarItemsForDepartmentInRangeAsync or the underlying query it
uses to apply overlap semantics, selecting items where Start is before windowEnd
and End is on or after windowStart, while preserving the existing
windowStartUtc/windowEndUtc inputs and attendee/date filtering.

In `@Core/Resgrid.Chatbot/Handlers/PollCreateHandler.cs`:
- Around line 83-99: Carry the department boundary through the poll-response
flow: in Core/Resgrid.Chatbot/Handlers/PollCreateHandler.cs lines 83-99, persist
session.DepartmentId in machine-readable recipient metadata; in
Core/Resgrid.Chatbot/Services/TextResponseResolver.cs lines 31-63, filter
discovered poll recipients to matching departmentId metadata; and in lines
89-109, revalidate that metadata before saving the response.

In `@Core/Resgrid.Chatbot/Handlers/RespondToCallHandler.cs`:
- Around line 121-149: Update the dispatch timestamp aggregation in the handler
so group and role dispatches are evaluated even when a direct dispatch exists;
remove the dispatch-null gating around the group and role checks while
preserving their existing membership and role filtering, then take the maximum
applicable timestamp across direct, group, and role dispatches.

In `@Core/Resgrid.Chatbot/Handlers/StaffingActionHandler.cs`:
- Around line 116-118: Remove the positional fallback in the staffing-level
resolution logic that indexes the active custom levels with staffingId. Treat
staffingId as a UserStateTypes value only, and return no match when the existing
ID/name resolution does not succeed, preserving the active-level filtering for
valid resolution paths.

In `@Core/Resgrid.Chatbot/Services/ChatbotIngressService.cs`:
- Around line 254-255: Update the session flow around GetOrCreateSessionAsync so
the effective department TTL is applied before the session expiry check and
LastActivity refresh. Pass the department-configured TTL into retrieval, or set
it before invoking GetOrCreateSessionAsync, while preserving the existing
session state behavior for valid sessions.

In `@Core/Resgrid.Chatbot/Services/RedisSessionStore.cs`:
- Around line 35-38: Update PruneExpiredAsync in RedisSessionStore to use
ChatbotSession.IsExpired() for each entry when no explicit cutoff is supplied,
instead of applying the global default cutoff. Preserve the explicit-cutoff
behavior when a cutoff is provided, and retain GetTtl for Redis expiration.

In `@Core/Resgrid.Services/CalendarService.cs`:
- Around line 585-598: Use the boolean result from SendCalendarAsync in
CalendarService.SendCalendarPromptAsync and record the RSVP prompt only when
delivery is accepted. Apply the same guard to SendNotificationAsync in
CalendarNotifierLogic, and retain its send result in NotificationBroadcastLogic
before skipping prompt recording when it is false; update all three specified
sites accordingly.

In `@Core/Resgrid.Services/CommunicationService.cs`:
- Around line 580-590: Update the SMS send guards in the notification and
calendar paths around _smsService.SendNotificationAsync to require
MobileNumberVerified.IsContactMethodAllowedForSending() in addition to the
existing profile check. Apply the same verification condition to both affected
paths, preserving the current send and exception-logging behavior for allowed
contacts.

In `@Core/Resgrid.Services/TextResponsePromptService.cs`:
- Around line 29-50: Update the prompt creation flow in
TextResponsePromptService around SaveMessageAsync to reuse or upsert an existing
unexpired CalendarRsvp prompt keyed by calendarItem.CalendarItemId and userId,
using the same metadata. Ensure repeated invitations, updates, reminders, and
worker executions do not create duplicate active prompts, while still creating a
prompt when no matching unexpired record exists.

In `@Workers/Resgrid.Workers.Framework/Logic/NotificationBroadcastLogic.cs`:
- Around line 63-74: Wrap the RSVP enrichment call in the calendar-event branch
of the notification broadcast flow with exception handling so failures from
GetCalendarItemByIdAsync do not abort delivery of the original notification.
Catch the exception, log it through Logging.LogException(), and continue
processing without RSVP text or item enrichment.

---

Outside diff comments:
In `@Workers/Resgrid.Workers.Framework/Logic/CalendarNotifierLogic.cs`:
- Around line 98-136: The entity notification branch in Process(), including
SendCalendarReminderAsync and GetAllGroupsForDepartmentAsync calls, must be
enclosed in the worker’s try-catch. On any exception, log it through
Logging.LogException() and return the established failure tuple instead of
allowing Process() to escape; preserve the existing notification and
MarkAsNotifiedAsync flow on success.

---

Minor comments:
In `@Core/Resgrid.Chatbot/Handlers/CallDispatchedActionHandler.cs`:
- Around line 92-114: Update the group, role, and unit sections in
CallDispatchedActionHandler so each calculates omitted entries beyond
MaxLinesPerSection and appends ChatbotResources.Get("Msg_AndMore", culture) with
that count, matching the existing personnel truncation behavior. Preserve the
current entries and section output when no results are omitted.
- Around line 43-60: Normalize filler-only values such as “call” and “the call”
to an unspecified reference before the reference check in the call-selection
flow. Reuse the responder handler’s existing normalization behavior or helper,
then let empty results follow the sole-active-call fallback; keep valid
references routed through CallReferenceResolver.ResolveAsync.

In `@Core/Resgrid.Chatbot/Handlers/DispatchCallHandler.cs`:
- Line 47: Move the hard-coded English chatbot responses into the existing
localized resource system: replace the disabled-dispatch text in
DispatchCallHandler, the complete negative call-response format in
RespondToCallHandler, and the “your status” confirmation label in
StatusActionHandler with resource lookups, adding the corresponding resource
entries and preserving each response’s current formatting and behavior.

In `@Core/Resgrid.Chatbot/Handlers/HelpActionHandler.cs`:
- Line 139: Update the help text in HelpActionHandler to mention that a bare YES
or NO also answers a pending calendar RSVP, alongside the existing latest-poll
behavior.

In `@Core/Resgrid.Chatbot/Services/ConversationEngine.cs`:
- Around line 292-305: Localize the headings in the SetStatus and SetStaffing
branches of the conversation flow by adding corresponding ChatbotResources keys
for each prompt and passing the culture-specific values to BuildOptionsPrompt.
Preserve the existing custom-state lookup and option handling, and use the
session/request culture already established by the surrounding code.

---

Nitpick comments:
In `@Core/Resgrid.Chatbot/Handlers/AvailabilityActionHandler.cs`:
- Around line 61-77: Update the availability flow before the user iteration to
build dictionaries keyed by UserId from lastActionLogs and userStates, then
replace the per-user FirstOrDefault lookups in the foreach over allUsers with
dictionary lookups while preserving null and missing-entry behavior.

In `@Core/Resgrid.Chatbot/Services/ChatbotIngressService.cs`:
- Around line 52-69: Replace constructor injection for ITextResponseResolver in
ChatbotIngressService with
Bootstrapper.GetKernel().Resolve<ITextResponseResolver>(), removing the
corresponding constructor parameter and assignment. Apply the same change in
Core/Resgrid.Chatbot/Services/ConversationEngine.cs at lines 16-20 for
ICustomStateService: resolve it through
Bootstrapper.GetKernel().Resolve<ICustomStateService>() instead of injecting it.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6b30cace-b05b-4015-9fbb-4661d4b3c1db

📥 Commits

Reviewing files that changed from the base of the PR and between 04cc0fb and 939bd97.

⛔ Files ignored due to path filters (4)
  • Tests/Resgrid.Tests/Chatbot/ChatbotAvailabilityIntentClassifierTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Chatbot/ChatbotHandlerTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Chatbot/ChatbotTextResponseResolverTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Services/CommunicationServiceTests.cs is excluded by !**/Tests/**
📒 Files selected for processing (39)
  • Core/Resgrid.Chatbot.NLU/Providers/KeywordIntentClassifier.cs
  • Core/Resgrid.Chatbot.NLU/Providers/OpenAiCompatibleNluProvider.cs
  • Core/Resgrid.Chatbot/ChatbotModule.cs
  • Core/Resgrid.Chatbot/Handlers/AvailabilityActionHandler.cs
  • Core/Resgrid.Chatbot/Handlers/CallDispatchedActionHandler.cs
  • Core/Resgrid.Chatbot/Handlers/CallRespondersActionHandler.cs
  • Core/Resgrid.Chatbot/Handlers/DispatchCallHandler.cs
  • Core/Resgrid.Chatbot/Handlers/HelpActionHandler.cs
  • Core/Resgrid.Chatbot/Handlers/MyCallsActionHandler.cs
  • Core/Resgrid.Chatbot/Handlers/MyScheduleActionHandler.cs
  • Core/Resgrid.Chatbot/Handlers/MyStatusActionHandler.cs
  • Core/Resgrid.Chatbot/Handlers/PollCreateHandler.cs
  • Core/Resgrid.Chatbot/Handlers/RespondToCallHandler.cs
  • Core/Resgrid.Chatbot/Handlers/SetUnitStatusHandler.cs
  • Core/Resgrid.Chatbot/Handlers/StaffingActionHandler.cs
  • Core/Resgrid.Chatbot/Handlers/StatusActionHandler.cs
  • Core/Resgrid.Chatbot/Handlers/UnitsAvailableActionHandler.cs
  • Core/Resgrid.Chatbot/Interfaces/IConversationEngine.cs
  • Core/Resgrid.Chatbot/Interfaces/ITextResponseResolver.cs
  • Core/Resgrid.Chatbot/Localization/ChatbotResources.cs
  • Core/Resgrid.Chatbot/Models/ChatbotIntent.cs
  • Core/Resgrid.Chatbot/Models/ChatbotSession.cs
  • Core/Resgrid.Chatbot/Models/PendingTextResponse.cs
  • Core/Resgrid.Chatbot/Services/ChatbotIngressService.cs
  • Core/Resgrid.Chatbot/Services/ConversationEngine.cs
  • Core/Resgrid.Chatbot/Services/CustomStateMatcher.cs
  • Core/Resgrid.Chatbot/Services/IntentMapper.cs
  • Core/Resgrid.Chatbot/Services/RedisSessionStore.cs
  • Core/Resgrid.Chatbot/Services/TextResponseResolver.cs
  • Core/Resgrid.Model/MessageTypes.cs
  • Core/Resgrid.Model/Messages/TextResponsePromptMetadata.cs
  • Core/Resgrid.Model/Services/ITextResponsePromptService.cs
  • Core/Resgrid.Services/CalendarService.cs
  • Core/Resgrid.Services/CommunicationService.cs
  • Core/Resgrid.Services/CustomStateService.cs
  • Core/Resgrid.Services/ServicesModule.cs
  • Core/Resgrid.Services/TextResponsePromptService.cs
  • Workers/Resgrid.Workers.Framework/Logic/CalendarNotifierLogic.cs
  • Workers/Resgrid.Workers.Framework/Logic/NotificationBroadcastLogic.cs

Comment thread Core/Resgrid.Chatbot.NLU/Providers/KeywordIntentClassifier.cs Outdated
Comment on lines +24 to +45
private readonly IUsersService _usersService;
private readonly IActionLogsService _actionLogsService;
private readonly IUserStateService _userStateService;
private readonly ICustomStateService _customStateService;
private readonly IPlatformReportingService _platformReportingService;
private readonly IAuthorizationService _authorizationService;

public AvailabilityActionHandler(
IUsersService usersService,
IActionLogsService actionLogsService,
IUserStateService userStateService,
ICustomStateService customStateService,
IPlatformReportingService platformReportingService,
IAuthorizationService authorizationService)
{
_usersService = usersService;
_actionLogsService = actionLogsService;
_userStateService = userStateService;
_customStateService = customStateService;
_platformReportingService = platformReportingService;
_authorizationService = authorizationService;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Use the repository’s required dependency-resolution pattern across the new classes.

  • Core/Resgrid.Chatbot/Handlers/AvailabilityActionHandler.cs#L24-L45: resolve all six services through Bootstrapper.GetKernel().Resolve<T>().
  • Core/Resgrid.Chatbot/Handlers/UnitsAvailableActionHandler.cs#L22-L34: resolve the three handler services explicitly.
  • Core/Resgrid.Chatbot/Handlers/MyCallsActionHandler.cs#L26-L33: resolve calls and units services explicitly.
  • Core/Resgrid.Chatbot/Handlers/PollCreateHandler.cs#L28-L40: resolve message, department, and profile services explicitly.
  • Core/Resgrid.Chatbot/Services/TextResponseResolver.cs#L19-L29: resolve message, calendar, and authorization services explicitly.

As per coding guidelines, use Bootstrapper.GetKernel().Resolve<T>() in constructors rather than constructor injection.

📍 Affects 5 files
  • Core/Resgrid.Chatbot/Handlers/AvailabilityActionHandler.cs#L24-L45 (this comment)
  • Core/Resgrid.Chatbot/Handlers/UnitsAvailableActionHandler.cs#L22-L34
  • Core/Resgrid.Chatbot/Handlers/MyCallsActionHandler.cs#L26-L33
  • Core/Resgrid.Chatbot/Handlers/PollCreateHandler.cs#L28-L40
  • Core/Resgrid.Chatbot/Services/TextResponseResolver.cs#L19-L29
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Core/Resgrid.Chatbot/Handlers/AvailabilityActionHandler.cs` around lines 24 -
45, Replace constructor injection with Bootstrapper.GetKernel().Resolve<T>()
assignments for all dependencies in
Core/Resgrid.Chatbot/Handlers/AvailabilityActionHandler.cs lines 24-45,
including all six services; resolve the three handler services explicitly in
Core/Resgrid.Chatbot/Handlers/UnitsAvailableActionHandler.cs lines 22-34;
resolve calls and units services explicitly in
Core/Resgrid.Chatbot/Handlers/MyCallsActionHandler.cs lines 26-33; resolve
message, department, and profile services explicitly in
Core/Resgrid.Chatbot/Handlers/PollCreateHandler.cs lines 28-40; and resolve
message, calendar, and authorization services explicitly in
Core/Resgrid.Chatbot/Services/TextResponseResolver.cs lines 19-29. Remove the
corresponding constructor parameters while preserving the existing private
dependency fields.

Source: Coding guidelines

Comment on lines +29 to +50
private readonly ICallsService _callsService;
private readonly IActionLogsService _actionLogsService;
private readonly IUnitsService _unitsService;
private readonly ICustomStateService _customStateService;
private readonly IUserProfileService _userProfileService;
private readonly IAuthorizationService _authorizationService;

public CallRespondersActionHandler(
ICallsService callsService,
IActionLogsService actionLogsService,
IUnitsService unitsService,
ICustomStateService customStateService,
IUserProfileService userProfileService,
IAuthorizationService authorizationService)
{
_callsService = callsService;
_actionLogsService = actionLogsService;
_unitsService = unitsService;
_customStateService = customStateService;
_userProfileService = userProfileService;
_authorizationService = authorizationService;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Use the required Service Locator pattern for the newly added dependencies.

  • Core/Resgrid.Chatbot/Handlers/CallRespondersActionHandler.cs#L29-L50: resolve the six handler services through Bootstrapper.GetKernel().
  • Core/Resgrid.Chatbot/Handlers/CallDispatchedActionHandler.cs#L22-L34: resolve the three handler services through the kernel.
  • Core/Resgrid.Services/CalendarService.cs#L28-L46: resolve ITextResponsePromptService explicitly rather than adding an optional parameter.
  • Core/Resgrid.Services/TextResponsePromptService.cs#L14-L19: resolve IMessageService through the kernel.
  • Core/Resgrid.Chatbot/Handlers/MyScheduleActionHandler.cs#L24-L36: resolve the three handler services through the kernel.

As per coding guidelines, “Use Service Locator pattern via Bootstrapper.GetKernel().Resolve<T>() to resolve dependencies explicitly in constructors, rather than constructor injection.”

📍 Affects 5 files
  • Core/Resgrid.Chatbot/Handlers/CallRespondersActionHandler.cs#L29-L50 (this comment)
  • Core/Resgrid.Chatbot/Handlers/CallDispatchedActionHandler.cs#L22-L34
  • Core/Resgrid.Services/CalendarService.cs#L28-L46
  • Core/Resgrid.Services/TextResponsePromptService.cs#L14-L19
  • Core/Resgrid.Chatbot/Handlers/MyScheduleActionHandler.cs#L24-L36
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Core/Resgrid.Chatbot/Handlers/CallRespondersActionHandler.cs` around lines 29
- 50, Replace constructor injection with explicit
Bootstrapper.GetKernel().Resolve<T>() resolution for all six services in
Core/Resgrid.Chatbot/Handlers/CallRespondersActionHandler.cs#L29-L50, all three
services in
Core/Resgrid.Chatbot/Handlers/CallDispatchedActionHandler.cs#L22-L34, and all
three services in
Core/Resgrid.Chatbot/Handlers/MyScheduleActionHandler.cs#L24-L36. In
Core/Resgrid.Services/CalendarService.cs#L28-L46, resolve
ITextResponsePromptService explicitly instead of using an optional constructor
parameter; in Core/Resgrid.Services/TextResponsePromptService.cs#L14-L19,
resolve IMessageService through the kernel. Preserve each constructor’s existing
initialization behavior while applying the required Service Locator pattern.

Source: Coding guidelines

Comment thread Core/Resgrid.Chatbot/Handlers/CallRespondersActionHandler.cs Outdated
Comment on lines +26 to +31
public DispatchCallHandler(ICallsService callsService, IAuthorizationService authorizationService,
IChatbotDepartmentConfigService departmentConfigService = null)
{
_callsService = callsService;
_authorizationService = authorizationService;
_departmentConfigService = departmentConfigService;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Resolve the new services explicitly instead of making them optional. The optional defaults silently disable policy enforcement or response resolution when older construction paths omit them.

  • Core/Resgrid.Chatbot/Handlers/DispatchCallHandler.cs#L26-L31: always resolve the department configuration service so dispatch restrictions cannot fail open.
  • Core/Resgrid.Chatbot/Handlers/RespondToCallHandler.cs#L29-L37: always resolve custom-state, group, and role services so eligible dispatches are not omitted.
  • Core/Resgrid.Chatbot/Handlers/StatusActionHandler.cs#L18-L23: always resolve department configuration so required confirmation cannot be bypassed.

As per coding guidelines, “Use Service Locator pattern via Bootstrapper.GetKernel().Resolve<T>() to resolve dependencies explicitly in constructors, rather than constructor injection.”

📍 Affects 3 files
  • Core/Resgrid.Chatbot/Handlers/DispatchCallHandler.cs#L26-L31 (this comment)
  • Core/Resgrid.Chatbot/Handlers/RespondToCallHandler.cs#L29-L37
  • Core/Resgrid.Chatbot/Handlers/StatusActionHandler.cs#L18-L23
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Core/Resgrid.Chatbot/Handlers/DispatchCallHandler.cs` around lines 26 - 31,
Replace optional constructor service parameters with explicit
Bootstrapper.GetKernel().Resolve<T>() resolution: in
Core/Resgrid.Chatbot/Handlers/DispatchCallHandler.cs lines 26-31, always resolve
IChatbotDepartmentConfigService; in
Core/Resgrid.Chatbot/Handlers/RespondToCallHandler.cs lines 29-37, always
resolve the custom-state, group, and role services; and in
Core/Resgrid.Chatbot/Handlers/StatusActionHandler.cs lines 18-23, always resolve
the department configuration service. Update each constructor’s field assignment
while preserving the existing handler behavior.

Source: Coding guidelines

Comment thread Core/Resgrid.Chatbot/Services/RedisSessionStore.cs
Comment on lines +585 to +598
private async System.Threading.Tasks.Task SendCalendarPromptAsync(CalendarItem calendarItem, string userId, string message,
string departmentNumber, string title, UserProfile profile, Department department)
{
await _communicationService.SendCalendarAsync(userId, calendarItem.DepartmentId, message,
departmentNumber, title, profile, department);

if (_textResponsePromptService == null
|| calendarItem.SignupType != (int)CalendarItemSignupTypes.RSVP)
return;

try
{
await _textResponsePromptService.RecordCalendarRsvpPromptAsync(calendarItem, userId);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Persist RSVP prompts only after communication accepts delivery.

All three paths ignore the communication method’s boolean result, creating pending responses for filtered recipients.

  • Core/Resgrid.Services/CalendarService.cs#L585-L598: check SendCalendarAsync before recording.
  • Workers/Resgrid.Workers.Framework/Logic/CalendarNotifierLogic.cs#L142-L153: check SendNotificationAsync before recording.
  • Workers/Resgrid.Workers.Framework/Logic/NotificationBroadcastLogic.cs#L87-L92: retain the send result and skip recording when false.
📍 Affects 3 files
  • Core/Resgrid.Services/CalendarService.cs#L585-L598 (this comment)
  • Workers/Resgrid.Workers.Framework/Logic/CalendarNotifierLogic.cs#L142-L153
  • Workers/Resgrid.Workers.Framework/Logic/NotificationBroadcastLogic.cs#L87-L92
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Core/Resgrid.Services/CalendarService.cs` around lines 585 - 598, Use the
boolean result from SendCalendarAsync in CalendarService.SendCalendarPromptAsync
and record the RSVP prompt only when delivery is accepted. Apply the same guard
to SendNotificationAsync in CalendarNotifierLogic, and retain its send result in
NotificationBroadcastLogic before skipping prompt recording when it is false;
update all three specified sites accordingly.

Comment thread Core/Resgrid.Services/CommunicationService.cs Outdated
Comment thread Core/Resgrid.Services/TextResponsePromptService.cs
if (state == null)
return AvailabilityClass.Unknown;

if (state.State <= 25)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kody code-review Kody Rules low

Magic number literal 25 acts as a threshold distinguishing built-in UserStateTypes values from custom CustomStateDetailId values across multiple call sites (CallRespondersActionHandler.cs:198,216; StatusActionHandler.cs:121; CustomStateMatcher.cs:23-36) without a named constant. Define a shared constant (e.g., MaxBuiltInStateDetailId = 25) or reference an existing one from the Model layer.

Kody rule violation: Replace magic numbers with named constants

private const int MaxBuiltInStateDetailId = 25;
// ...
if (state.State <= MaxBuiltInStateDetailId)
Prompt for LLM

File Core/Resgrid.Chatbot/Handlers/AvailabilityActionHandler.cs:

Line 123:

Violates rule 'Replace magic numbers with named constants': the literal `25` is used as a threshold to distinguish built-in UserStateTypes values from custom CustomStateDetailId values. This number has domain meaning but is not a named constant. The same unguarded literal appears in CallRespondersActionHandler.cs:198,216 and StatusActionHandler.cs:121. Define a shared constant (e.g., `private const int MaxBuiltInStateDetailId = 25;`) or reference an existing one from the Model layer.

Suggested Code:

private const int MaxBuiltInStateDetailId = 25;
// ...
if (state.State <= MaxBuiltInStateDetailId)

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

Comment thread Core/Resgrid.Chatbot/Handlers/CallRespondersActionHandler.cs
Comment on lines +99 to +111
foreach (var activeCall in (activeCalls ?? new List<Call>())
.OrderByDescending(x => x.LastDispatchedOn ?? x.DispatchOn ?? x.LoggedOn))
{
var call = await _callsService.PopulateCallData(activeCall,
getDispatches: true,
getAttachments: false,
getNotes: false,
getGroupDispatches: true,
getUnitDispatches: false,
getRoleDispatches: true,
getProtocols: false,
getReferences: false,
getContacts: false);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kody code-review Kody Rules high

N+1 query in ResolveMostRecentDispatchAsync: the method iterates all active calls invoking _callsService.PopulateCallData() per call plus nested _departmentGroupsService.GetAllMembersForGroupAsync() calls with no scan limit, unlike the sibling MyCallsActionHandler.GetActiveCallsWithDispatchesAsync which bounds with .Take(MaxActiveCallsToScan) (25). Add a .Take(N) limit to bound the number of PopulateCallData calls.

Kody rule violation: Detect N+1 style queries and suggest batching

private const int MaxActiveCallsToScan = 25;
// ...
foreach (var activeCall in (activeCalls ?? new List<Call>())
	.OrderByDescending(x => x.LastDispatchedOn ?? x.DispatchOn ?? x.LoggedOn)
	.Take(MaxActiveCallsToScan))
{
	var call = await _callsService.PopulateCallData(activeCall, ...);
Prompt for LLM

File Core/Resgrid.Chatbot/Handlers/RespondToCallHandler.cs:

Line 99 to 111:

Violates rule 'Detect N+1 style queries and suggest batching': `ResolveMostRecentDispatchAsync` iterates through ALL active calls calling `_callsService.PopulateCallData()` (a multi-query DB operation) per call, plus `_departmentGroupsService.GetAllMembersForGroupAsync()` inside nested loops, with no scan limit. The sibling `MyCallsActionHandler.GetActiveCallsWithDispatchesAsync` in the same PR correctly limits with `.Take(MaxActiveCallsToScan)` (25), but this method does not. Add a `.Take(N)` limit to bound the number of PopulateCallData calls.

Suggested Code:

private const int MaxActiveCallsToScan = 25;
// ...
foreach (var activeCall in (activeCalls ?? new List<Call>())
	.OrderByDescending(x => x.LastDispatchedOn ?? x.DispatchOn ?? x.LoggedOn)
	.Take(MaxActiveCallsToScan))
{
	var call = await _callsService.PopulateCallData(activeCall, ...);

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

Comment on lines +105 to +111
if (assignedState == null || assignedState.IsDeleted
|| assignedState.DepartmentId != departmentId
|| assignedState.Type != (int)CustomStateTypes.Unit)
return new List<CustomStateDetail>();

return assignedState.GetActiveDetails();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kody code-review Bug high

Logic gap in GetAllowedStatesAsync: returning an empty list for null or deleted custom state sets causes every status name lookup to fail with a misleading 'Unknown status' response, locking the unit out of chatbot status changes until an admin fixes the configuration. Fall back to GetDefaultUnitStatuses() for the IsDeleted/null cases, matching the CustomStatesId <= 0 branch.

if (assignedState == null || assignedState.IsDeleted)
    return _customStateService.GetDefaultUnitStatuses() ?? new List<CustomStateDetail>();

if (assignedState.DepartmentId != departmentId
    || assignedState.Type != (int)CustomStateTypes.Unit)
    return new List<CustomStateDetail>();

return assignedState.GetActiveDetails();
Prompt for LLM

File Core/Resgrid.Chatbot/Handlers/SetUnitStatusHandler.cs:

Line 105 to 111:

WHAT: GetAllowedStatesAsync returns an empty list when the unit type's assigned custom state set is deleted or null, instead of falling back to default unit statuses. WHY: Every status name the user sends will fail to match against the empty list, producing a misleading 'Unknown status' response and locking the unit out of chatbot status changes until an admin fixes the configuration. HOW: Fall back to GetDefaultUnitStatuses() for the IsDeleted/null cases, matching the CustomStatesId <= 0 branch.

Suggested Code:

if (assignedState == null || assignedState.IsDeleted)
    return _customStateService.GetDefaultUnitStatuses() ?? new List<CustomStateDetail>();

if (assignedState.DepartmentId != departmentId
    || assignedState.Type != (int)CustomStateTypes.Unit)
    return new List<CustomStateDetail>();

return assignedState.GetActiveDetails();

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

: (ChatbotConfig.DefaultSessionTimeoutMinutes > 0 ? ChatbotConfig.DefaultSessionTimeoutMinutes : 30));

private bool UseRedis()
{

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kody code-review Kody Rules high

Silent exception swallowing: the bare catch { return false; } skips logging that every other catch block in RedisSessionStore.cs performs via Logging.LogException(ex) (lines 78-82, 102-105, 131-134, 155-158). Log the exception with Logging.LogException(ex) before returning false.

Kody rule violation: Avoid empty catch blocks

try
{
    return _cacheProvider.IsConnected();
}
catch (Exception ex)
{
    Logging.LogException(ex);
    return false;
}
Prompt for LLM

File Core/Resgrid.Chatbot/Services/RedisSessionStore.cs:

Line 41:

Bare `catch { return false; }` swallows the exception from `IsConnected()` without logging. Every other catch block in this same file logs with `Logging.LogException(ex)` (lines 78-82, 102-105, 131-134, 155-158). Rule 33 requires logging exceptions with context rather than silently swallowing them.

Suggested Code:

try
{
    return _cacheProvider.IsConnected();
}
catch (Exception ex)
{
    Logging.LogException(ex);
    return false;
}

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

@Resgrid-Bot

Copy link
Copy Markdown

PR Summary (Comment created by Kody 🤖)

Code Review Started! 🚀

✋ Hi, team! I'm already looking at the changed files and starting the review to ensure everything is in order. If you need more details, I'm here! Kody

📂 Changed Files
File Status ➕ Additions ➖ Deletions 🔄 Changes
.gitignore modified 2 0 2
Core/Resgrid.Chatbot.NLU/Providers/KeywordIntentClassifier.cs modified 2 2 4
Core/Resgrid.Chatbot/Handlers/CallRespondersActionHandler.cs modified 30 13 43
Core/Resgrid.Chatbot/Handlers/MyCallsActionHandler.cs modified 6 4 10
Core/Resgrid.Chatbot/Handlers/MyScheduleActionHandler.cs modified 21 12 33
Core/Resgrid.Chatbot/Handlers/PollCreateHandler.cs modified 4 0 4
Core/Resgrid.Chatbot/Handlers/RespondToCallHandler.cs modified 2 2 4
Core/Resgrid.Chatbot/Handlers/StaffingActionHandler.cs modified 0 5 5
Core/Resgrid.Chatbot/Interfaces/IChatbotSessionManager.cs modified 2 1 3
Core/Resgrid.Chatbot/Interfaces/IChatbotSessionStore.cs modified 2 1 3
Core/Resgrid.Chatbot/Services/ChatbotIngressService.cs modified 5 4 9
Core/Resgrid.Chatbot/Services/ChatbotSessionManager.cs modified 3 2 5
Core/Resgrid.Chatbot/Services/RedisSessionStore.cs modified 19 11 30
Core/Resgrid.Chatbot/Services/TextResponseResolver.cs modified 8 0 8
Core/Resgrid.Framework/FileHelper.cs modified 20 0 20
Core/Resgrid.Model/Messages/TextResponsePromptMetadata.cs modified 15 0 15
Core/Resgrid.Model/Queue/CallQueueItem.cs modified 52 0 52
Core/Resgrid.Services/CalendarService.cs modified 4 3 7
Core/Resgrid.Services/CommunicationService.cs modified 2 2 4
Core/Resgrid.Services/TextResponsePromptService.cs modified 32 15 47
Tests/Resgrid.Tests/Chatbot/CallRespondersActionHandlerTests.cs added 195 0 195
Tests/Resgrid.Tests/Chatbot/ChatbotAvailabilityIntentClassifierTests.cs modified 11 0 11
Tests/Resgrid.Tests/Chatbot/ChatbotDeptConfigAndSessionTests.cs modified 95 0 95
Tests/Resgrid.Tests/Chatbot/ChatbotHandlerTests.cs modified 112 3 115
Tests/Resgrid.Tests/Chatbot/ChatbotTextResponseResolverTests.cs modified 214 4 218
Tests/Resgrid.Tests/Chatbot/MyCallsActionHandlerTests.cs added 105 0 105
Tests/Resgrid.Tests/Chatbot/MyScheduleActionHandlerTests.cs added 113 0 113
Tests/Resgrid.Tests/Framework/FileHelperTests.cs modified 80 0 80
Tests/Resgrid.Tests/Models/CallQueueItemTests.cs added 110 0 110
Tests/Resgrid.Tests/Models/DocumentTests.cs added 47 0 47
Tests/Resgrid.Tests/Services/CalendarServiceTests.cs modified 109 1 110
Tests/Resgrid.Tests/Services/CommunicationServiceTests.cs modified 34 4 38
Tests/Resgrid.Tests/Web/DocumentPageTests.cs added 75 0 75
Tests/Resgrid.Tests/Web/UploadFormEncodingTests.cs added 47 0 47
Web/Resgrid.Web.Services/Controllers/v4/CallsController.cs modified 1 0 1
Web/Resgrid.Web/Areas/User/Controllers/DispatchController.cs modified 1 0 1
Web/Resgrid.Web/Areas/User/Controllers/DocumentsController.cs modified 65 22 87
Web/Resgrid.Web/Areas/User/Controllers/ProfileController.cs modified 18 14 32
Web/Resgrid.Web/Areas/User/Models/Documents/ViewDocumentView.cs added 14 0 14
Web/Resgrid.Web/Areas/User/Views/Documents/EditDocument.cshtml modified 2 6 8
Web/Resgrid.Web/Areas/User/Views/Documents/Index.cshtml modified 48 38 86
Web/Resgrid.Web/Areas/User/Views/Documents/ViewDocument.cshtml added 110 0 110
Web/Resgrid.Web/Areas/User/Views/Profile/AddCertification.cshtml modified 1 1 2
Web/Resgrid.Web/Areas/User/Views/Profile/EditCertification.cshtml modified 1 1 2
Web/Resgrid.Web/Helpers/ClaimsAuthorizationHelper.cs modified 5 0 5
Web/Resgrid.Web/wwwroot/js/app/internal/documents/resgrid.documents.newdocument.js modified 1 1 2
Workers/Resgrid.Workers.Framework/Logic/CalendarNotifierLogic.cs modified 2 2 4
Workers/Resgrid.Workers.Framework/Logic/CallBroadcast.cs modified 1 0 1
Workers/Resgrid.Workers.Framework/Logic/NotificationBroadcastLogic.cs modified 13 6 19
📊 Summary of Changes
  • Total Files: 49
  • Total Lines Added: 1861
  • Total Lines Removed: 180
  • Total Changes: 2041

@ucswift

ucswift commented Jul 18, 2026

Copy link
Copy Markdown
Member Author

Approve

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR is approved.

@ucswift
ucswift merged commit e21da36 into master Jul 18, 2026
18 of 19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants