diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/AI/AIAttachmentPromptItem.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/AI/AIAttachmentPromptItem.cs new file mode 100644 index 000000000..2918f03e8 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/AI/AIAttachmentPromptItem.cs @@ -0,0 +1,8 @@ +namespace Unity.GrantManager.AI +{ + public class AIAttachmentPromptItem + { + public string Name { get; set; } = string.Empty; + public string Summary { get; set; } = string.Empty; + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/AI/AICompletionRequest.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/AI/AICompletionRequest.cs new file mode 100644 index 000000000..8598d3300 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/AI/AICompletionRequest.cs @@ -0,0 +1,10 @@ +namespace Unity.GrantManager.AI +{ + public class AICompletionRequest + { + public string UserPrompt { get; set; } = string.Empty; + public string? SystemPrompt { get; set; } + public int MaxTokens { get; set; } = 150; + public double? Temperature { get; set; } + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/AI/AIJsonKeys.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/AI/AIJsonKeys.cs new file mode 100644 index 000000000..5ebbf4df9 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/AI/AIJsonKeys.cs @@ -0,0 +1,20 @@ +namespace Unity.GrantManager.AI +{ + public static class AIJsonKeys + { + public const string Rating = "rating"; + public const string Errors = "errors"; + public const string Warnings = "warnings"; + public const string Summaries = "summaries"; + public const string Dismissed = "dismissed"; + + public const string Id = "id"; + public const string Title = "title"; + public const string Detail = "detail"; + public const string Summary = "summary"; + + public const string Answer = "answer"; + public const string Rationale = "rationale"; + public const string Confidence = "confidence"; + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/AI/ApplicationAnalysisRequest.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/AI/ApplicationAnalysisRequest.cs new file mode 100644 index 000000000..bf676f06a --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/AI/ApplicationAnalysisRequest.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Text.Json; + +namespace Unity.GrantManager.AI +{ + public class ApplicationAnalysisRequest + { + public JsonElement Schema { get; set; } + public JsonElement Data { get; set; } + public List Attachments { get; set; } = new(); + public string? Rubric { get; set; } + } + + public class ApplicationAnalysisAttachment + { + public string Name { get; set; } = string.Empty; + public string Summary { get; set; } = string.Empty; + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/AI/ApplicationAnalysisResponse.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/AI/ApplicationAnalysisResponse.cs new file mode 100644 index 000000000..f766a1f4d --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/AI/ApplicationAnalysisResponse.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Unity.GrantManager.AI +{ + public class ApplicationAnalysisResponse + { + [JsonPropertyName(AIJsonKeys.Rating)] + public string? Rating { get; set; } + + [JsonPropertyName(AIJsonKeys.Errors)] + public List Errors { get; set; } = new(); + + [JsonPropertyName(AIJsonKeys.Warnings)] + public List Warnings { get; set; } = new(); + + [JsonPropertyName(AIJsonKeys.Summaries)] + public List Summaries { get; set; } = new(); + + [JsonPropertyName(AIJsonKeys.Dismissed)] + public List Dismissed { get; set; } = new(); + } + + public class ApplicationAnalysisFinding + { + [JsonPropertyName(AIJsonKeys.Id)] + public string? Id { get; set; } + + [JsonPropertyName(AIJsonKeys.Title)] + public string? Title { get; set; } + + [JsonPropertyName(AIJsonKeys.Detail)] + public string? Detail { get; set; } + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/AI/AttachmentSummaryRequest.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/AI/AttachmentSummaryRequest.cs new file mode 100644 index 000000000..2cce56ae7 --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/AI/AttachmentSummaryRequest.cs @@ -0,0 +1,9 @@ +namespace Unity.GrantManager.AI +{ + public class AttachmentSummaryRequest + { + public string FileName { get; set; } = string.Empty; + public byte[] FileContent { get; set; } = System.Array.Empty(); + public string ContentType { get; set; } = "application/octet-stream"; + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/AI/IAIService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/AI/IAIService.cs index e4c3d26ac..ffddbbe6c 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/AI/IAIService.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/AI/IAIService.cs @@ -6,10 +6,17 @@ namespace Unity.GrantManager.AI public interface IAIService { Task IsAvailableAsync(); - Task GenerateSummaryAsync(string content, string? prompt = null, int maxTokens = 150); + + Task GenerateCompletionAsync(AICompletionRequest request); + Task GenerateAttachmentSummaryAsync(AttachmentSummaryRequest request); Task GenerateAttachmentSummaryAsync(string fileName, byte[] fileContent, string contentType); + Task GenerateApplicationAnalysisAsync(ApplicationAnalysisRequest request); + Task GenerateScoresheetSectionAnswersAsync(ScoresheetSectionRequest request); + Task GenerateScoresheetSectionAnswersAsync(string applicationContent, List attachmentSummaries, string sectionJson, string sectionName); + + // Legacy compatibility methods retained until flow orchestration refactor. + Task GenerateSummaryAsync(string content, string? prompt = null, int maxTokens = 150); Task AnalyzeApplicationAsync(string applicationContent, List attachmentSummaries, string rubric, string? formFieldConfiguration = null); Task GenerateScoresheetAnswersAsync(string applicationContent, List attachmentSummaries, string scoresheetQuestions); - Task GenerateScoresheetSectionAnswersAsync(string applicationContent, List attachmentSummaries, string sectionJson, string sectionName); } } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/AI/ScoresheetSectionRequest.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/AI/ScoresheetSectionRequest.cs new file mode 100644 index 000000000..f20d4935e --- /dev/null +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/AI/ScoresheetSectionRequest.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using System.Text.Json; + +namespace Unity.GrantManager.AI +{ + public class ScoresheetSectionRequest + { + public JsonElement Data { get; set; } + public List Attachments { get; set; } = new(); + public string SectionName { get; set; } = string.Empty; + public JsonElement SectionSchema { get; set; } + } +} diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/GrantApplicationDto.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/GrantApplicationDto.cs index 31a35a873..f2906d116 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/GrantApplicationDto.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/GrantApplications/GrantApplicationDto.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Unity.GrantManager.AI; using Unity.GrantManager.ApplicationForms; using Volo.Abp.Application.Dtos; @@ -83,4 +84,5 @@ public class GrantApplicationDto : AuditedEntityDto public string? UnityApplicationId { get; set; } public string? ApplicantElectoralDistrict { get; set; } public string? AIAnalysis { get; set; } + public ApplicationAnalysisResponse? AIAnalysisData { get; set; } } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/AI/OpenAIService.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/AI/OpenAIService.cs index ad7786c4b..3830cd75e 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/AI/OpenAIService.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/AI/OpenAIService.cs @@ -54,6 +54,36 @@ public Task IsAvailableAsync() return Task.FromResult(true); } + public Task GenerateCompletionAsync(AICompletionRequest request) + { + return GenerateSummaryAsync( + request?.UserPrompt ?? string.Empty, + request?.SystemPrompt, + request?.MaxTokens ?? 150); + } + + public Task GenerateApplicationAnalysisAsync(ApplicationAnalysisRequest request) + { + var dataJson = JsonSerializer.Serialize(request.Data, JsonLogOptions); + var schemaJson = JsonSerializer.Serialize(request.Schema, JsonLogOptions); + + var attachmentSummaries = request.Attachments + .Select(a => $"{a.Name}: {a.Summary}") + .ToList(); + + var applicationContent = $@"DATA +{dataJson}"; + + var formFieldConfiguration = $@"SCHEMA +{schemaJson}"; + + return AnalyzeApplicationAsync( + applicationContent, + attachmentSummaries, + request.Rubric ?? string.Empty, + formFieldConfiguration); + } + public async Task GenerateSummaryAsync(string content, string? prompt = null, int maxTokens = 150) { if (string.IsNullOrEmpty(ApiKey)) @@ -177,6 +207,14 @@ Produce a concise reviewer-facing summary of the provided attachment context. } } + public Task GenerateAttachmentSummaryAsync(AttachmentSummaryRequest request) + { + return GenerateAttachmentSummaryAsync( + request?.FileName ?? string.Empty, + request?.FileContent ?? Array.Empty(), + request?.ContentType ?? "application/octet-stream"); + } + public async Task AnalyzeApplicationAsync(string applicationContent, List attachmentSummaries, string rubric, string? formFieldConfiguration = null) { if (string.IsNullOrEmpty(ApiKey)) @@ -512,6 +550,22 @@ public async Task GenerateScoresheetSectionAnswersAsync(string applicati } } + public Task GenerateScoresheetSectionAnswersAsync(ScoresheetSectionRequest request) + { + var dataJson = JsonSerializer.Serialize(request.Data, JsonLogOptions); + var sectionJson = JsonSerializer.Serialize(request.SectionSchema, JsonLogOptions); + + var attachmentSummaries = request.Attachments + .Select(a => $"{a.Name}: {a.Summary}") + .ToList(); + + return GenerateScoresheetSectionAnswersAsync( + dataJson, + attachmentSummaries, + sectionJson, + request.SectionName); + } + private async Task LogPromptInputAsync(string promptType, string? systemPrompt, string userPrompt) { var formattedInput = FormatPromptInputForLog(systemPrompt, userPrompt);