Skip to content
Merged

Dev #2534

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
0ae0f3d
AB#32531: Fix Styling of Email Attachments Table and Button
aurelio-aot May 30, 2026
1c8f9f0
Merge pull request #2515 from bcgov/bugfix/AB#32531-Email-Attachments…
JamesPasta Jun 2, 2026
2f3e12b
bugfix/AB#33298-FixDBContext
JamesPasta Jun 2, 2026
07c0032
Merge pull request #2517 from bcgov/bugfix/AB#33298-FixDBContextUOW
JamesPasta Jun 2, 2026
7d9db29
[AB#29848] Refactor form synchronization to handle descriptions
plavoie-BC Jun 2, 2026
5a80f8d
Merge branch 'dev' into bugfix/AB#29848-form-descriptions
plavoie-BC Jun 2, 2026
611779c
[AB#29848] Support form description whitespace overwrite on sync
plavoie-BC Jun 2, 2026
ce04675
AB#33270 - update docs
AndreGAot Jun 2, 2026
31a667d
AB#33267 structure empty AI attachment context
jacobwillsmith May 8, 2026
7934187
Merge pull request #2521 from bcgov/bugfix/AB#33270-docs-updates
AndreGAot Jun 2, 2026
f2ca462
AB#33313 remove large AI model output logging
jacobwillsmith Jun 3, 2026
600f41f
AB#33315 align AI analysis tab height
jacobwillsmith Jun 3, 2026
9ce32e6
AB#33223: Bugfix - Selective Attachment Download
aurelio-aot Jun 3, 2026
3b3ec25
Merge pull request #2529 from bcgov/hotfix/AB#FixTestConflicts
JamesPasta Jun 3, 2026
d29603a
AB#33319 unify active AI generation status
jacobwillsmith Jun 3, 2026
f22b2f3
Merge pull request #2530 from bcgov/feature/AB#33319-unify-active-ai-…
JamesPasta Jun 3, 2026
1b36f29
Merge pull request #2528 from bcgov/bugfix/AB#33223-Download-All-Button
JamesPasta Jun 3, 2026
7bfb8d3
Merge pull request #2526 from bcgov/feature/AB#33315-align-ai-analysi…
JamesPasta Jun 3, 2026
28cdecd
Merge pull request #2524 from bcgov/feature/AB#33313-remove-large-ai-…
JamesPasta Jun 3, 2026
b19d992
Merge pull request #2523 from bcgov/feature/AB#33267-structured-empty…
JamesPasta Jun 3, 2026
cff0c80
Merge pull request #2518 from bcgov/bugfix/AB#29848-form-descriptions
JamesPasta Jun 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ You are a careful grant review assistant for human reviewers. Do not fill gaps,

TASK
Using SCHEMA, DATA, ATTACHMENTS, RUBRIC, SCORE, OUTPUT, and RULES:
1. Review the application and attachments for the strongest reviewer-relevant evidence.
1. Review the application and any provided attachments for the strongest reviewer-relevant evidence.
2. Determine which conclusions are directly supported by that evidence.
3. Exclude weak, repetitive, or loosely supported conclusions.
4. Return only the strongest evidence-backed reviewer conclusions.
4. Return only the strongest evidence-backed reviewer conclusions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ TASK
Using DATA, ATTACHMENTS, SECTION, RESPONSE, OUTPUT, and RULES:
1. Review each question in SECTION one at a time.
2. Identify the exact condition the question asks about.
3. Consider only the most relevant evidence in DATA and ATTACHMENTS for that condition.
3. Consider only the most relevant evidence in DATA and any provided ATTACHMENTS for that condition.
4. Choose the most conservative valid answer supported by that evidence.
5. If evidence is incomplete or indirect, explain the uncertainty in the rationale.
6. Repeat for every question in SECTION.
6. Repeat for every question in SECTION.
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
- Any narrative text response must be at least 12 words.
- If ATTACHMENTS is empty, use DATA only and do not mention missing attachments unless their absence is material to the specific conclusion or question.
- Return values exactly as specified in OUTPUT.
- Do not return keys outside OUTPUT.
- Return valid JSON only.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ public async Task<ApplicationScoringResponse> GenerateApplicationScoringAsync(Ap
{
var attachments = attachmentSummaries.Count > 0
? string.Join("\n- ", attachmentSummaries.Select((summary, index) => $"Attachment {index + 1}: {summary}"))
: "No attachments provided.";
: "[]";

var section = OpenAIPromptRenderer.BuildAliasedApplicationScoringSection(request.SectionName, sectionJson, out var questionIdAliasMap);
var response = OpenAIPromptRenderer.BuildApplicationScoringResponseTemplate(section);
Expand Down Expand Up @@ -318,14 +318,12 @@ private async Task<AIOperationResult> GenerateWithRetryAsync(
private async Task LogPromptInputAsync(string promptType, string promptVersion, string? systemPrompt, string userPrompt, CancellationToken cancellationToken = default)
{
var formattedInput = FormatPromptInputForLog(systemPrompt, userPrompt);
_logger.LogInformation("AI {PromptType} ({PromptVersion}) input payload: {PromptInput}", promptType, promptVersion, formattedInput);
await WritePromptLogFileAsync(promptType, promptVersion, "INPUT", formattedInput, cancellationToken);
}

private async Task LogPromptOutputAsync(string promptType, string promptVersion, string output, CancellationToken cancellationToken = default)
{
var formattedOutput = FormatPromptOutputForLog(output);
_logger.LogInformation("AI {PromptType} ({PromptVersion}) model output payload: {ModelOutput}", promptType, promptVersion, formattedOutput);
await WritePromptLogFileAsync(promptType, promptVersion, "OUTPUT", formattedOutput, cancellationToken);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ OPTIONAL FIELDS (may be left blank):

TASK
Using SCHEMA, DATA, ATTACHMENTS, RUBRIC, SCORE, OUTPUT, and RULES:
1. Review the application and attachments for the strongest reviewer-relevant evidence.
1. Review the application and any provided attachments for the strongest reviewer-relevant evidence.
2. Determine which conclusions are directly supported by that evidence.
3. Exclude weak, repetitive, or loosely supported conclusions.
4. Return only the strongest evidence-backed reviewer conclusions.
Expand Down Expand Up @@ -520,7 +520,7 @@ 2. A brief rationale (1-2 complete sentences) citing concrete supporting evidenc
Using DATA, ATTACHMENTS, SECTION, RESPONSE, OUTPUT, and RULES:
1. Review each question in SECTION one at a time.
2. Identify the exact condition the question asks about.
3. Consider only the most relevant evidence in DATA and ATTACHMENTS for that condition.
3. Consider only the most relevant evidence in DATA and any provided ATTACHMENTS for that condition.
4. Choose the most conservative valid answer supported by that evidence.
5. If evidence is incomplete or indirect, explain the uncertainty in the rationale.
6. Repeat for every question in SECTION.
Expand Down Expand Up @@ -583,6 +583,7 @@ 4. Choose the most conservative valid answer supported by that evidence.
// ── v1/common.rules.txt ──────────────────────────────────────────────────
private const string CommonRules = """
- Any narrative text response must be at least 12 words.
- If ATTACHMENTS is empty, use DATA only and do not mention missing attachments unless their absence is material to the specific conclusion or question.
- Return values exactly as specified in OUTPUT.
- Do not return keys outside OUTPUT.
- Return valid JSON only.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ public class AIGenerationRequestDto : EntityDto<Guid>
public DateTime? CompletedAt { get; set; }
public string? FailureReason { get; set; }
public bool IsActive { get; set; }
public bool IsGenerating { get; set; }
public int RetryAfterSeconds { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@
using System.Text.Json;
using System.Threading.Tasks;
using Unity.Flex.WorksheetInstances;
using Unity.Flex.Worksheets;
using Unity.AI.Models;
using Unity.AI.Responses;
using Unity.Flex.Worksheets;
using Unity.AI.Models;
using Unity.AI.RateLimit;
using Unity.AI.Responses;
using Unity.GrantManager.Applicants;
using Unity.GrantManager.ApplicationForms;
using Unity.GrantManager.Applications;
Expand Down Expand Up @@ -55,10 +56,11 @@ public class GrantApplicationAppService(
IApplicantAgentRepository applicantAgentRepository,
IApplicantAddressRepository applicantAddressRepository,
IApplicantSupplierAppService applicantSupplierService,
IPaymentRequestAppService paymentRequestService,
IApplicationAIGenerationQueue aiGenerationQueue,
IAIGenerationStatusAppService aiGenerationStatusAppService,
IFeatureChecker featureChecker)
IPaymentRequestAppService paymentRequestService,
IApplicationAIGenerationQueue aiGenerationQueue,
IAIGenerationStatusAppService aiGenerationStatusAppService,
IAIRateLimiter aiRateLimiter,
IFeatureChecker featureChecker)
: GrantManagerAppService, IGrantApplicationAppService
#pragma warning restore S107 // Methods should not have too many parameters
{
Expand Down Expand Up @@ -1180,7 +1182,8 @@ public async Task<AIGenerationRequestDto> QueueApplicationAnalysisAsync(Guid app
AIGenerationRequestKeyHelper.ApplicationAnalysisOperationType,
CurrentTenant.Id);

return request ?? throw new UserFriendlyException("Unable to queue AI analysis request.");
return await ApplyRateLimitStateAsync(
request ?? throw new UserFriendlyException("Unable to queue AI analysis request."));
}

[Authorize(AIPermissions.Analysis.GenerateAttachmentSummaries)]
Expand All @@ -1195,7 +1198,8 @@ public async Task<AIGenerationRequestDto> QueueAttachmentSummaryAsync(QueueAttac
AIGenerationRequestKeyHelper.AttachmentSummaryOperationType,
CurrentTenant.Id);

return request ?? throw new UserFriendlyException("Unable to queue AI attachment summary request.");
return await ApplyRateLimitStateAsync(
request ?? throw new UserFriendlyException("Unable to queue AI attachment summary request."));
}

[Authorize(AIPermissions.Analysis.GenerateScoring)]
Expand All @@ -1209,13 +1213,15 @@ public async Task<AIGenerationRequestDto> QueueApplicationScoringAsync(Guid appl
AIGenerationRequestKeyHelper.ApplicationScoringOperationType,
CurrentTenant.Id);

return request ?? throw new UserFriendlyException("Unable to queue AI scoring request.");
return await ApplyRateLimitStateAsync(
request ?? throw new UserFriendlyException("Unable to queue AI scoring request."));
}

public async Task<AIGenerationRequestDto?> GetAIGenerationStatusAsync(Guid applicationId, string operationType, string? promptVersion = null)
{
await EnsureAIGenerationStatusAccessAsync(operationType);
return await aiGenerationStatusAppService.GetLatestAsync(applicationId, operationType, CurrentTenant.Id);
await EnsureAIGenerationStatusAccessAsync(operationType);
return await ApplyRateLimitStateOrDefaultAsync(
await aiGenerationStatusAppService.GetLatestAsync(applicationId, operationType, CurrentTenant.Id));
}

[Authorize(AIPermissions.Analysis.GenerateApplicationAnalysis)]
Expand All @@ -1233,8 +1239,27 @@ public async Task<AIGenerationRequestDto> QueueAllAIStagesAsync(Guid application
AIGenerationRequestKeyHelper.PipelineOperationType,
CurrentTenant.Id);

return request ?? throw new UserFriendlyException("Unable to queue AI generation request.");
}
return await ApplyRateLimitStateAsync(
request ?? throw new UserFriendlyException("Unable to queue AI generation request."));
}

private async Task<AIGenerationRequestDto?> ApplyRateLimitStateOrDefaultAsync(AIGenerationRequestDto? request)
{
if (request is null)
{
return null;
}

return await ApplyRateLimitStateAsync(request);
}

private async Task<AIGenerationRequestDto> ApplyRateLimitStateAsync(AIGenerationRequestDto request)
{
var state = await aiRateLimiter.GetStateAsync();
request.IsGenerating = state.IsGenerating;
request.RetryAfterSeconds = state.RetryAfterSeconds;
return request;
}

private async Task EnsureAIGenerationStatusAccessAsync(string operationType)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -536,7 +536,17 @@ public partial class ApplicationActionResultItemToDtoMapper : MapperBase<Applica
[Mapper] public partial class CommunityToDtoMapper : MapperBase<Community, CommunityDto> { public override partial CommunityDto Map(Community source); public override partial void Map(Community source, CommunityDto destination); }
[Mapper] public partial class RegionalDistrictToDtoMapper : MapperBase<RegionalDistrict, RegionalDistrictDto> { public override partial RegionalDistrictDto Map(RegionalDistrict source); public override partial void Map(RegionalDistrict source, RegionalDistrictDto destination); }
[Mapper] public partial class ApplicationTagsToDtoMapper : MapperBase<ApplicationTags, ApplicationTagsDto> { public override partial ApplicationTagsDto Map(ApplicationTags source); public override partial void Map(ApplicationTags source, ApplicationTagsDto destination); }
[Mapper] public partial class AIGenerationRequestToDtoMapper : MapperBase<AIGenerationRequest, AIGenerationRequestDto> { public override partial AIGenerationRequestDto Map(AIGenerationRequest source); public override partial void Map(AIGenerationRequest source, AIGenerationRequestDto destination); }
[Mapper]
public partial class AIGenerationRequestToDtoMapper : MapperBase<AIGenerationRequest, AIGenerationRequestDto>
{
[MapperIgnoreTarget(nameof(AIGenerationRequestDto.IsGenerating))]
[MapperIgnoreTarget(nameof(AIGenerationRequestDto.RetryAfterSeconds))]
public override partial AIGenerationRequestDto Map(AIGenerationRequest source);

[MapperIgnoreTarget(nameof(AIGenerationRequestDto.IsGenerating))]
[MapperIgnoreTarget(nameof(AIGenerationRequestDto.RetryAfterSeconds))]
public override partial void Map(AIGenerationRequest source, AIGenerationRequestDto destination);
}
[Mapper(AllowNullPropertyAssignment = true)]
public partial class ApplicantToGrantApplicationApplicantDtoMapper : MapperBase<Applicant, GrantApplicationApplicantDto>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Volo.Abp;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Uow;

namespace Unity.GrantManager.Notifications
{
Expand All @@ -24,6 +25,7 @@ public NotificationsAppService(IDynamicUrlRepository dynamicUrlRepository)
_teamsNotificationService = new TeamsNotificationService();
}

[UnitOfWork]
public async Task<string> InitializeTeamsChannelAsync(string keyName)
{
DynamicUrl? teamsChannel = await _dynamicUrlRepository.FirstOrDefaultAsync(q => q.KeyName == keyName);
Expand All @@ -50,6 +52,7 @@ public async Task NotifyChefsEventToTeamsAsync(string factName, string factValue
await _teamsNotificationService.PostFactsToTeamsAsync(teamsChannel, activityTitle, activitySubtitle);
}

[UnitOfWork]
public async Task PostToTeamsAsync(string activityTitle, string activitySubtitle, List<Fact> facts)
{
string teamsChannel = await InitializeTeamsChannelAsync(TeamsNotificationService.TEAMS_NOTIFICATION);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
using System.Threading.Tasks;
using Volo.Abp.Domain.Services;
using Unity.GrantManager.Intakes;
using Newtonsoft.Json.Linq;
using System.Threading.Tasks;
using Unity.GrantManager.Events;
using Unity.GrantManager.Intakes;
using Volo.Abp.Domain.Repositories;
using Newtonsoft.Json.Linq;
using Volo.Abp.Domain.Services;

namespace Unity.GrantManager.Applications
{
Expand Down Expand Up @@ -58,9 +58,14 @@ public ApplicationForm SynchronizePublishedForm(ApplicationForm applicationForm,
JObject formObject = JObject.Parse(form.ToString());
var formName = formObject.SelectToken("name");
var formDescription = formObject.SelectToken("description");
if(formName != null && formDescription != null)

if (formName != null)
{
applicationForm.ApplicationFormName = formName.ToString();
}

if (formDescription != null && string.IsNullOrWhiteSpace(applicationForm.ApplicationFormDescription))
{
applicationForm.ApplicationFormDescription = formDescription.ToString();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,9 +272,18 @@ select.selected {
margin-top: 0.8rem;
}

.details-nav .nav-link {
display: flex;
}
.details-nav .nav-link {
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
height: 42px;
line-height: 1.5;
}

.details-nav .nav-link i {
line-height: 1;
}

.details-nav .nav-link .fl {
margin-right: 4px;
Expand Down Expand Up @@ -338,15 +347,15 @@ select.selected {
z-index: 999;
}

#detailsTab .nav-item .nav-link {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.action-bar {
display: flex !important;
}
#detailsTab .nav-item .nav-link {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

.action-bar {
display: flex !important;
}

#DetailsActionBarStart {
margin-right: 0.5rem;
Expand Down Expand Up @@ -677,36 +686,7 @@ form label.error {
border-color: #f5c2c7;
}

#ai-analysis-tab {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
}

.ai-analysis-tab-icon-wrap {
display: inline-flex;
align-items: center;
justify-content: center;
}

.ai-analysis-tab-indicator {
width: 8px;
height: 8px;
border-radius: 999px;
background-color: #adb5bd;
flex-shrink: 0;
}

.ai-analysis-tab-indicator.proceed {
background-color: #198754;
}

.ai-analysis-tab-indicator.hold {
background-color: #dc3545;
}

.ai-analysis-action-btn {
.ai-analysis-action-btn {
background: #ffffff;
border: 1px solid #d1d5db;
padding: 4px 10px;
Expand All @@ -728,4 +708,4 @@ form label.error {

.worksheet_AssessmentInfo {
padding-left: 0.5rem;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -456,8 +456,8 @@ globalThis.queueApplicationAnalysis = function(triggerButton = null) {

if (status === 'Completed') {
globalThis.AIGenerationButtonState?.restoreForCooldownCheck($button, existingHtml);
globalThis.AIGenerationButtonState?.applyStatusState(request);
loadAIAnalysis();
globalThis.syncAIRateLimitButtons?.();
return;
}

Expand Down
Loading
Loading