Skip to content
Merged

Dev #2649

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
b67ae36
AB#33749 simplify AI text normalization
jacobwillsmith Jul 6, 2026
ba70edb
AB#33756 clean up AI route surface
jacobwillsmith Jul 7, 2026
c83893f
AB#33756 fix AI route surface tests
jacobwillsmith Jul 7, 2026
bf4b00f
AB#33760 separate AI execution from persistence adapters
jacobwillsmith Jul 7, 2026
b8b0211
AB#33760 fix AI adapter tests
jacobwillsmith Jul 7, 2026
405843f
AB#32666 split AI prompt persistence store
jacobwillsmith Jul 7, 2026
da167dd
AB#33371 - Filter notification list by sent-date range
hasanpour Jul 7, 2026
3938a92
AB#33265 standardize AI prompt contracts
jacobwillsmith Jul 8, 2026
06a0199
AB#33265 standardize AI prompt contracts
jacobwillsmith Jul 8, 2026
6a5f346
AB#33749 simplify text normalization signature
jacobwillsmith Jul 8, 2026
79341d2
AB#32666 tighten AI prompt template boundary
jacobwillsmith Jul 8, 2026
fb3b0b0
AB#33265 harden AI batch prompt flow
jacobwillsmith Jul 8, 2026
4b89041
AB#33760 tighten AI persistence boundary fixes
jacobwillsmith Jul 8, 2026
7386101
AB#33371 - Added date range filter controls to notification list
hasanpour Jul 8, 2026
3f9a3de
AB#33371 - Fall back to the creation date when there is no sent date
hasanpour Jul 8, 2026
a04ad64
AB#33371 - Fixed timezone on unit test
hasanpour Jul 9, 2026
f73c182
Merge pull request #2635 from bcgov/feature/AB#33749-text-normalizati…
JamesPasta Jul 9, 2026
2eb3057
AB#33265 simplify AI execution modes
jacobwillsmith Jul 9, 2026
1af623d
AB#33760 fix AI persistence adapter coupling
jacobwillsmith Jul 9, 2026
062298b
Merge pull request #2645 from bcgov/feature/AB#33371-Add-filter-to-No…
JamesPasta Jul 9, 2026
e038269
Merge pull request #2638 from bcgov/bugfix/AB#33760-ai-persistence-ad…
JamesPasta Jul 9, 2026
76be948
Merge pull request #2643 from bcgov/feature/AB#33265-standardize-ai-p…
JamesPasta Jul 9, 2026
89e5dcc
Merge pull request #2640 from bcgov/feature/AB#32666-move-ai-persiste…
JamesPasta Jul 9, 2026
6c082b3
AB#32311 reduce AI module data coupling
jacobwillsmith Jul 7, 2026
42ae8f5
AB#32311 remove validator repository coupling
jacobwillsmith Jul 7, 2026
1198956
AB#32311 move AI input data provider to host app
jacobwillsmith Jul 7, 2026
3d32432
AB#32311 move attachment summary data provider to host app
jacobwillsmith Jul 7, 2026
1c6b0c8
AB#32311 fix scoring prerequisite and schema fallback
jacobwillsmith Jul 8, 2026
531e160
AB#32311 fix AI attachment summary tests
jacobwillsmith Jul 8, 2026
a582aa4
AB#32311 decouple AI input data access
jacobwillsmith Jul 8, 2026
de29407
AB#32311 remove stale AI module dependency
jacobwillsmith Jul 9, 2026
1a5b635
AB#32311 align AI input form lookup
jacobwillsmith Jul 9, 2026
fe7926c
Merge pull request #2641 from bcgov/feature/AB#32311-remove-grant-man…
JamesPasta Jul 9, 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 @@ -10,6 +10,7 @@ public interface IAIService
Task<bool> IsAvailableAsync();

Task<AttachmentSummaryResponse> GenerateAttachmentSummaryAsync(AttachmentSummaryRequest request, CancellationToken cancellationToken = default);
Task<AttachmentSummaryBatchResponse> GenerateAttachmentSummaryBatchAsync(AttachmentSummaryBatchRequest request, CancellationToken cancellationToken = default);
Task<ApplicationAnalysisResponse> GenerateApplicationAnalysisAsync(ApplicationAnalysisRequest request, CancellationToken cancellationToken = default);
Task<ApplicationScoringResponse> GenerateApplicationScoringAsync(ApplicationScoringRequest request, CancellationToken cancellationToken = default);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Unity.AI.Models;

namespace Unity.AI.Operations;

public interface IAIApplicationInputDataProvider
{
Task<ApplicationFormSnapshot?> GetApplicationFormAsync(Guid applicationId);

Task<ApplicationSubmissionSnapshot?> GetApplicationSubmissionAsync(Guid applicationId);

Task<ApplicationFormVersionSnapshot?> GetApplicationFormVersionAsync(Guid? formVersionId);

Task<List<AttachmentSummarySnapshot>> GetAttachmentSummariesAsync(Guid applicationId);

Task<ScoresheetSnapshot?> GetScoresheetAsync(Guid scoresheetId);

Task<bool> HasAttachmentsAsync(Guid applicationId);

Task<bool> HasSubmissionAsync(Guid applicationId);
}

public sealed class ApplicationFormSnapshot
{
public Guid? ScoresheetId { get; set; }
}

public sealed class ApplicationSubmissionSnapshot
{
public Guid? ApplicationFormVersionId { get; set; }

public string? Submission { get; set; }
}

public sealed class ApplicationFormVersionSnapshot
{
public string? FormSchema { get; set; }
}

public sealed record AttachmentSummarySnapshot(
string? FileName,
string? Summary);

public sealed class ScoresheetSnapshot
{
public List<ScoresheetSectionSnapshot> Sections { get; set; } = [];
}

public sealed class ScoresheetSectionSnapshot
{
public string Name { get; set; } = string.Empty;

public int Order { get; set; }

public List<ScoresheetFieldSnapshot> Fields { get; set; } = [];
}

public sealed class ScoresheetFieldSnapshot
{
public Guid Id { get; set; }

public string Label { get; set; } = string.Empty;

public string? Description { get; set; }

public string Type { get; set; } = string.Empty;

public int Order { get; set; }

public string? Definition { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Unity.AI.Operations;

public interface IAttachmentSummaryDataProvider
{
Task<AttachmentSummarySource?> GetAttachmentAsync(Guid attachmentId);

Task UpdateAttachmentSummaryAsync(Guid attachmentId, string summary);

Task<List<Guid>> GetApplicationAttachmentIdsAsync(Guid applicationId);
}

public sealed record AttachmentSummarySource(
Guid Id,
string? FileName,
string? ChefsSubmissionId,
string? ChefsFileId);
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Unity.AI.Operations;

public interface IAttachmentSummaryPersistence
{
Task<AttachmentSummarySource> LoadAsync(Guid attachmentId);

Task SaveSummaryAsync(Guid attachmentId, string summary);

Task<List<Guid>> LoadApplicationAttachmentIdsAsync(Guid applicationId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System.Text.Json.Serialization;

namespace Unity.AI.Prompts;

public sealed record UnityPromptAssetManifest(
[property: JsonPropertyName("operationName")] string OperationName,
[property: JsonPropertyName("promptVersion")] string PromptVersion,
[property: JsonPropertyName("inputContractName")] string InputContractName,
[property: JsonPropertyName("outputContractName")] string OutputContractName,
[property: JsonPropertyName("modelHint")] string? ModelHint = null,
[property: JsonPropertyName("profileHint")] string? ProfileHint = null);
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;

namespace Unity.AI.Requests;

public sealed class AttachmentSummaryBatchRequest
{
[JsonPropertyName("attachments")]
public List<AttachmentSummaryBatchItemRequest> Attachments { get; set; } = [];

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

public sealed class AttachmentSummaryBatchItemRequest
{
[JsonPropertyName("attachmentId")]
public string AttachmentId { get; set; } = string.Empty;

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

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

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

namespace Unity.AI.Responses;

public sealed class AttachmentSummaryBatchResponse
{
[JsonPropertyName("attachments")]
public List<AttachmentSummaryBatchItemResponse> Attachments { get; set; } = [];
}

public sealed class AttachmentSummaryBatchItemResponse
{
[JsonPropertyName("attachmentId")]
public string AttachmentId { get; set; } = string.Empty;

[JsonPropertyName("summary")]
public string Summary { get; set; } = string.Empty;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace Unity.AI.Generation;

public class AIGenerationStatusDto
{
public AIGenerationStatusRequestDto? GenerationRequest { get; set; }

public bool IsGenerating { get; set; }

public int RetryAfterSeconds { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;

namespace Unity.AI.Generation;

public class AIGenerationStatusRequestDto
{
public Guid Id { get; set; }

public Guid? ApplicationId { get; set; }

public Guid? OperationId { get; set; }

public string OperationType { get; set; } = string.Empty;

public string Status { get; set; } = string.Empty;

public DateTime? StartedAt { get; set; }

public DateTime? CompletedAt { get; set; }

public string? FailureReason { get; set; }

public bool IsActive { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,6 @@ public interface IAIGenerationAppService : IApplicationService
Task<ApplicationAnalysisResultDto> GenerateApplicationAnalysisAsync(Guid applicationId, string? promptVersion = null);

Task<ApplicationScoringResultDto> GenerateApplicationScoringAsync(Guid applicationId, string? promptVersion = null);

Task<AIGenerationStatusDto> GetStatusAsync(Guid applicationId, string operationType);
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public Task<string> ExtractTextAsync(string fileName, Stream fileContent, string
_ => ExtractByContentType(fileName, fileContent, normalizedContentType, cancellationToken)
};

return Task.FromResult(NormalizeAndLimitText(rawText, fileName));
return Task.FromResult(NormalizeAndLimitText(rawText));
}
catch (OperationCanceledException)
{
Expand Down Expand Up @@ -656,10 +656,9 @@ private static string GetCellText(NPOI.SS.UserModel.ICell cell)
}) ?? string.Empty;
}

private string NormalizeAndLimitText(string text, string fileName)
private string NormalizeAndLimitText(string text)
{
var normalized = NormalizeExtractedText(text);
normalized = RemoveLeadingFileNameArtifact(normalized, fileName);

if (normalized.Length > MaxExtractedTextLength)
{
Expand All @@ -681,67 +680,13 @@ private static string NormalizeExtractedText(string text)
.Replace("\r\n", "\n")
.Replace('\r', '\n');

normalized = LowerToUpperWordBoundaryRegex().Replace(normalized, " ");
normalized = PunctuationToWordBoundaryRegex().Replace(normalized, " ");
normalized = ColonDashSpacingRegex().Replace(normalized, ": - ");
normalized = HyphenSpacingRegex().Replace(normalized, " - ");
normalized = KeywordBoundaryRegex().Replace(normalized, " ");
normalized = MultipleSpacesRegex().Replace(normalized, " ");
normalized = NewlineWhitespaceRegex().Replace(normalized, "\n");
normalized = MultipleNewlinesRegex().Replace(normalized, "\n");

return normalized.Trim();
}

private static string RemoveLeadingFileNameArtifact(string text, string fileName)
{
if (string.IsNullOrWhiteSpace(text) || string.IsNullOrWhiteSpace(fileName))
{
return text;
}

var rawStem = Path.GetFileNameWithoutExtension(fileName)?.Trim();
if (string.IsNullOrWhiteSpace(rawStem))
{
return text;
}

var decodedStem = Uri.UnescapeDataString(rawStem);
foreach (var candidate in new[] { rawStem, decodedStem })
{
if (string.IsNullOrWhiteSpace(candidate))
{
continue;
}

if (text.StartsWith(candidate, StringComparison.OrdinalIgnoreCase))
{
var stripped = text.Substring(candidate.Length).TrimStart(' ', '-', ':', '.', '\t');
if (!string.IsNullOrWhiteSpace(stripped))
{
return stripped;
}
}
}

return text;
}

[GeneratedRegex(@"(?<=[a-z])(?=[A-Z])")]
private static partial Regex LowerToUpperWordBoundaryRegex();

[GeneratedRegex(@"(?<=[\.\,\:\;\)])(?=[A-Za-z0-9])")]
private static partial Regex PunctuationToWordBoundaryRegex();

[GeneratedRegex(@":-")]
private static partial Regex ColonDashSpacingRegex();

[GeneratedRegex(@"(?<=\S)- (?=[A-Za-z])")]
private static partial Regex HyphenSpacingRegex();

[GeneratedRegex(@"(?<=[a-z])(?=(project|funding|budget|community|summary|notes|details|planning|outcomes|background|services)\b)", RegexOptions.IgnoreCase)]
private static partial Regex KeywordBoundaryRegex();

[GeneratedRegex(@"[ \t]+")]
private static partial Regex MultipleSpacesRegex();

Expand Down
Loading
Loading