Skip to content
Merged

Dev #2128

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
5c2cb76
AB#31677 mq integration portal
AndreGAot Mar 7, 2026
a191c79
Merge branch 'bugfix/AB#32238missing-snapshot-migration' into feature…
AndreGAot Mar 7, 2026
5ad0966
AB#31677 portal mq integration wip
AndreGAot Mar 9, 2026
9c153d9
AB#31677 update documents
AndreGAot Mar 10, 2026
7dd0b81
AB#31677 fix for null conditional preview
AndreGAot Mar 10, 2026
43ee63e
AB#31677 abstract base for inbox/outbox
AndreGAot Mar 11, 2026
6ccd73f
AB#32297 add dev prompt version selection and prompt I/O capture
jacobwillsmith Mar 12, 2026
971245b
AB#32297 tighten prompt capture refresh and attachment selection flow
jacobwillsmith Mar 12, 2026
27ad48d
AB#32297 tighten prompt capture compile and environment checks
jacobwillsmith Mar 12, 2026
9e97e2e
AB#32297 align scoresheet prompt schema with regenerate flow
jacobwillsmith Mar 12, 2026
a81681d
AB#32297 polish prompt capture panels and prompt control layout
jacobwillsmith Mar 12, 2026
21e03f3
AB#32297 align prompt capture labels with UI casing conventions
jacobwillsmith Mar 12, 2026
b9e37bc
Merge remote-tracking branch 'origin/dev' into feature/AB#31677-porta…
AndreGAot Mar 12, 2026
a88604c
AB#32297 simplify prompt capture type naming
jacobwillsmith Mar 12, 2026
ed48260
AB#32297 move dev prompt view state out of components
jacobwillsmith Mar 12, 2026
17a0a2b
AB#31677 sonarQube cleanup
AndreGAot Mar 12, 2026
bd9c3f0
AB#31677 codeQL fixes
AndreGAot Mar 12, 2026
9b3ca50
AB#32297 move prompt tooling into dedicated dev tab
jacobwillsmith Mar 12, 2026
20af7f2
AB#31677 more codeQL
AndreGAot Mar 12, 2026
4cc34a7
AB#32297 polish dev tab prompt actions and section order
jacobwillsmith Mar 12, 2026
30a2482
AB#32297 share prompt data payload shaping across AI flows
jacobwillsmith Mar 13, 2026
9441a67
AB#32297 tighten and consolidate v1 prompt rules
jacobwillsmith Mar 13, 2026
91d7442
AB#32297 polish dev prompt capture UI and actions
jacobwillsmith Mar 13, 2026
e28d148
AB#32297 Filter AI prompt data by form schema
jacobwillsmith Mar 13, 2026
290e4d3
AB#32297 Refine v1 AI prompt instructions
jacobwillsmith Mar 13, 2026
f6f6205
AB#32297 Fix AI button loading spacing
jacobwillsmith Mar 13, 2026
c68fa53
AB#32297 Update AI action labels and indicator color
jacobwillsmith Mar 13, 2026
72b8f49
AB#32297 Remove trailing blank lines
jacobwillsmith Mar 13, 2026
eb9fc9e
AB#32297 Reduce formatting-only diff noise
jacobwillsmith Mar 13, 2026
d3d56d9
AB#32297 Address Sonar cleanup findings
jacobwillsmith Mar 13, 2026
e160ba9
AB#28800 - portal payment info, and cleanup docs
AndreGAot Mar 13, 2026
fc1fef8
Merge pull request #2124 from bcgov/feature/AB#32297-DevPromptVersion…
JamesPasta Mar 13, 2026
1b0c5c4
Merge pull request #2123 from bcgov/feature/AB#31677-portal-mq-integr…
JamesPasta Mar 13, 2026
cfb58c5
Merge remote-tracking branch 'origin/dev' into feature/AB#28800-porta…
AndreGAot Mar 13, 2026
5fa79e1
AB#32297 Remove unused Sonar leftovers
jacobwillsmith Mar 13, 2026
4ae3b93
Merge pull request #2125 from bcgov/feature/AB#32297-DevPromptVersion…
JamesPasta Mar 13, 2026
541259b
AB#28800 reduce logging and inbox/outbox cleanup time
AndreGAot Mar 13, 2026
6102a54
AB#28800 codeQL suggestions
AndreGAot Mar 13, 2026
e7b9258
Merge pull request #2126 from bcgov/feature/AB#28800-portal-payments
AndreGAot Mar 13, 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 @@ -14,5 +14,14 @@ public class ApplicationAnalysisRequest

[JsonPropertyName("attachments")]
public List<AIAttachmentItem> Attachments { get; set; } = new();

[JsonPropertyName("promptVersion")]
public string? PromptVersion { get; set; }

[JsonPropertyName("capturePromptIo")]
public bool CapturePromptIo { get; set; }

[JsonPropertyName("captureContextId")]
public string? CaptureContextId { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,14 @@ public class AttachmentSummaryRequest

[JsonPropertyName("contentType")]
public string ContentType { get; set; } = "application/octet-stream";

[JsonPropertyName("promptVersion")]
public string? PromptVersion { get; set; }

[JsonPropertyName("capturePromptIo")]
public bool CapturePromptIo { get; set; }

[JsonPropertyName("captureContextId")]
public string? CaptureContextId { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,14 @@ public class ScoresheetSectionRequest

[JsonPropertyName("sectionSchema")]
public JsonElement SectionSchema { get; set; }

[JsonPropertyName("promptVersion")]
public string? PromptVersion { get; set; }

[JsonPropertyName("capturePromptIo")]
public bool CapturePromptIo { get; set; }

[JsonPropertyName("captureContextId")]
public string? CaptureContextId { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System;
using System.Text.Json.Serialization;

namespace Unity.GrantManager.AI
{
public class AIPromptCaptureResponse
{
[JsonPropertyName("contextId")]
public string ContextId { get; set; } = string.Empty;

[JsonPropertyName("promptType")]
public string PromptType { get; set; } = string.Empty;

[JsonPropertyName("promptVersion")]
public string PromptVersion { get; set; } = string.Empty;

[JsonPropertyName("captureLabel")]
public string CaptureLabel { get; set; } = string.Empty;

[JsonPropertyName("systemPrompt")]
public string SystemPrompt { get; set; } = string.Empty;

[JsonPropertyName("userPrompt")]
public string UserPrompt { get; set; } = string.Empty;

[JsonPropertyName("rawOutput")]
public string RawOutput { get; set; } = string.Empty;

[JsonPropertyName("formattedOutput")]
public string FormattedOutput { get; set; } = string.Empty;

[JsonPropertyName("capturedAt")]
public DateTime CapturedAt { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
using System.Collections.Generic;

namespace Unity.GrantManager.ApplicantProfile.ProfileData
{
public class ApplicantOrgInfoDto : ApplicantProfileDataDto
{
public override string DataType => "ORGINFO";

public List<OrgInfoItemDto> Organizations { get; set; } = [];
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
using System.Collections.Generic;

namespace Unity.GrantManager.ApplicantProfile.ProfileData
{
public class ApplicantPaymentInfoDto : ApplicantProfileDataDto
{
public override string DataType => "PAYMENTINFO";

public List<PaymentInfoItemDto> Payments { get; set; } = [];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System;

namespace Unity.GrantManager.ApplicantProfile.ProfileData
{
public class OrgInfoItemDto
{
public Guid Id { get; set; }
public string? OrgName { get; set; }
public string? OrganizationType { get; set; }
public string? OrgNumber { get; set; }
public string? OrgStatus { get; set; }
public string? NonRegOrgName { get; set; }
public string? FiscalMonth { get; set; }
public int? FiscalDay { get; set; }
public string? OrganizationSize { get; set; }
public string? Sector { get; set; }
public string? SubSector { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;

namespace Unity.GrantManager.ApplicantProfile.ProfileData
{
public class PaymentInfoItemDto
{
public Guid Id { get; set; }
public string PaymentNumber { get; set; } = string.Empty;
public string ReferenceNo { get; set; } = string.Empty;
public decimal Amount { get; set; }
public string? PaymentDate { get; set; }
public string PaymentStatus { get; set; } = string.Empty;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ public interface IAttachmentAppService : IApplicationService
Task<IList<UnityAttachmentDto>> GetAttachmentsAsync(AttachmentParametersDto attachmentParametersDto);
Task<AttachmentMetadataDto> GetAttachmentMetadataAsync(AttachmentType attachmentType, Guid attachmentId);
Task<AttachmentMetadataDto> UpdateAttachmentMetadataAsync(UpdateAttachmentMetadataDto updateAttachment);
Task<string> GenerateAISummaryAttachmentAsync(Guid attachmentId);
Task<List<string>> GenerateAISummariesAttachmentsAsync(List<Guid> attachmentIds);
Task<string> GenerateAISummaryAttachmentAsync(Guid attachmentId, string? promptVersion = null, bool capturePromptIo = false);
Task<List<string>> GenerateAISummariesAttachmentsAsync(List<Guid> attachmentIds, string? promptVersion = null, bool capturePromptIo = false);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ namespace Unity.GrantManager.GrantApplications
{
public interface IApplicationAIAnalysisAppService : IApplicationService
{
Task<string> GenerateAIAnalysisAsync(Guid applicationId);
Task<string> GenerateAIAnalysisAsync(Guid applicationId, string? promptVersion = null, bool capturePromptIo = false);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.Collections.Generic;
using System;
using System.Threading.Tasks;
using Unity.GrantManager.AI;
using Volo.Abp.Application.Services;

namespace Unity.GrantManager.GrantApplications
{
public interface IApplicationAIPromptCaptureAppService : IApplicationService
{
Task<List<AIPromptCaptureResponse>> GetRecentAsync(Guid applicationId, string promptType, string? promptVersion = null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ namespace Unity.GrantManager.GrantApplications
{
public interface IApplicationAIScoringAppService : IApplicationService
{
Task<string> GenerateAIScoresheetAnswersAsync(Guid applicationId);
Task<string> GenerateAIScoresheetAnswersAsync(Guid applicationId, string? promptVersion = null, bool capturePromptIo = false);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using Volo.Abp.DependencyInjection;

namespace Unity.GrantManager.AI
{
public class AIPromptCaptureStore : IAIPromptCaptureStore, ISingletonDependency
{
private const int MaxCapturesPerKey = 50;
private readonly ConcurrentDictionary<string, ConcurrentQueue<AIPromptCaptureResponse>> _captures = new(StringComparer.OrdinalIgnoreCase);

public void Save(AIPromptCaptureResponse capture)
{
var key = BuildKey(capture.ContextId, capture.PromptType, capture.PromptVersion);
var queue = _captures.GetOrAdd(key, _ => new ConcurrentQueue<AIPromptCaptureResponse>());
queue.Enqueue(capture);

while (queue.Count > MaxCapturesPerKey)
{
queue.TryDequeue(out _);
}
}

public IReadOnlyList<AIPromptCaptureResponse> GetRecent(string contextId, string promptType, string? promptVersion = null, int maxResults = 20)
{
if (!string.IsNullOrWhiteSpace(promptVersion))
{
var key = BuildKey(contextId, promptType, promptVersion);
return _captures.TryGetValue(key, out var captures)
? captures.OrderByDescending(item => item.CapturedAt).Take(maxResults).ToList()
: Array.Empty<AIPromptCaptureResponse>();
}

return _captures.Values
.SelectMany(queue => queue)
.Where(item => string.Equals(item.ContextId, contextId, StringComparison.OrdinalIgnoreCase)
&& string.Equals(item.PromptType, promptType, StringComparison.OrdinalIgnoreCase))
.OrderByDescending(item => item.CapturedAt)
.Take(maxResults)
.ToList();
}

private static string BuildKey(string contextId, string promptType, string promptVersion)
{
return $"{contextId.Trim()}::{promptType.Trim()}::{promptVersion.Trim()}";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,17 @@ public class ApplicationAnalysisService(
};

private const string ComponentsKey = "components";
private static readonly HashSet<string> ExcludedSchemaKeys = new(StringComparer.OrdinalIgnoreCase)
{
"applicantAgent"
};

public async Task<string> RegenerateAndSaveAsync(Guid applicationId)
public async Task<string> RegenerateAndSaveAsync(Guid applicationId, string? promptVersion = null, bool capturePromptIo = false)
{
var application = await applicationRepository.GetAsync(applicationId);
var formSubmission = await applicationFormSubmissionRepository.GetByApplicationAsync(applicationId);
var attachments = await applicationChefsFileAttachmentRepository.GetListAsync(a => a.ApplicationId == applicationId);
var formSchema = await GetFormSchemaAsync(formSubmission?.ApplicationFormVersionId);

var attachmentSummaries = attachments
.Where(a => !string.IsNullOrWhiteSpace(a.AISummary))
Expand All @@ -40,24 +45,6 @@ public async Task<string> RegenerateAndSaveAsync(Guid applicationId)
})
.ToList();

var notSpecified = "Not specified";
var applicationContent = $@"
Project Name: {application.ProjectName}
Reference Number: {application.ReferenceNo}
Requested Amount: ${application.RequestedAmount:N2}
Total Project Budget: ${application.TotalProjectBudget:N2}
Project Summary: {application.ProjectSummary ?? "Not provided"}
City: {application.City ?? notSpecified}
Economic Region: {application.EconomicRegion ?? notSpecified}
Community: {application.Community ?? notSpecified}
Project Start Date: {application.ProjectStartDate?.ToShortDateString() ?? notSpecified}
Project End Date: {application.ProjectEndDate?.ToShortDateString() ?? notSpecified}
Submission Date: {application.SubmissionDate.ToShortDateString()}

FULL APPLICATION FORM SUBMISSION:
{formSubmission?.RenderedHTML ?? "Form submission content not available"}
";

object formFieldConfiguration = new { message = "Form configuration not available." };
if (formSubmission?.ApplicationFormVersionId != null)
{
Expand All @@ -67,8 +54,11 @@ public async Task<string> RegenerateAndSaveAsync(Guid applicationId)
var analysis = await aiService.GenerateApplicationAnalysisAsync(new ApplicationAnalysisRequest
{
Schema = JsonSerializer.SerializeToElement(formFieldConfiguration),
Data = JsonSerializer.SerializeToElement(new { submission_content = applicationContent }),
Attachments = attachmentSummaries
Data = PromptDataPayloadBuilder.BuildPromptDataPayload(application, formSubmission, formSchema, logger),
Attachments = attachmentSummaries,
PromptVersion = promptVersion,
CapturePromptIo = capturePromptIo,
CaptureContextId = applicationId.ToString()
});

var analysisJson = JsonSerializer.Serialize(analysis, _jsonOptionsIndented);
Expand All @@ -77,6 +67,25 @@ public async Task<string> RegenerateAndSaveAsync(Guid applicationId)
return analysisJson;
}

private async Task<string?> GetFormSchemaAsync(Guid? formVersionId)
{
if (formVersionId == null)
{
return null;
}

try
{
var formVersion = await applicationFormVersionRepository.GetAsync(formVersionId.Value);
return string.IsNullOrWhiteSpace(formVersion?.FormSchema) ? null : formVersion.FormSchema;
}
catch (Exception ex)
{
logger.LogWarning(ex, "Unable to load form schema for prompt data generation for form version {FormVersionId}.", formVersionId);
return null;
}
}

private async Task<object> ExtractFormFieldConfigurationAsync(Guid formVersionId)
{
try
Expand Down Expand Up @@ -120,7 +129,7 @@ private static void ExtractFieldRequirements(JArray components, List<string> req
var type = component["type"]?.ToString();
var skipTypes = new HashSet<string> { "button", "simplebuttonadvanced", "html", "htmlelement", "content", "simpleseparator" };

if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(type) || skipTypes.Contains(type))
if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(type) || skipTypes.Contains(type) || ExcludedSchemaKeys.Contains(key))
{
ProcessNestedFieldRequirements(component, type, requiredFields, optionalFields, currentPath);
continue;
Expand Down
Loading
Loading