diff --git a/Services/Implemnetation/AiAnalysisService.cs b/Services/Implemnetation/AiAnalysisService.cs index fe02c33..5f88058 100644 --- a/Services/Implemnetation/AiAnalysisService.cs +++ b/Services/Implemnetation/AiAnalysisService.cs @@ -1,19 +1,20 @@ // Services/Implementation/AiAnalysisService.cs using Azure; -using Azure.AI.TextAnalytics; using Azure.AI.OpenAI; -using OpenAI.Chat; +using Azure.AI.TextAnalytics; +using Data; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; +using Model; +using OpenAI.Chat; using Services.AIViewModel; using Services.Interaces; +using System.ClientModel; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; using System.Text.RegularExpressions; -using Data; -using Microsoft.EntityFrameworkCore; -using System.ClientModel; namespace Services.Implemnetation { @@ -605,13 +606,32 @@ Respond with a JSON array of applicable categories: [""category1"", ""category2" { foreach (var detail in response.ResponseDetails) { + string responseText = ""; + + // Handle text-based questions (existing) if (!string.IsNullOrWhiteSpace(detail.TextResponse)) + { + responseText = detail.TextResponse; + } + // Handle CheckBox questions (NEW) + else if (detail.QuestionType == QuestionType.CheckBox && detail.ResponseAnswers.Any()) + { + var selectedAnswers = detail.ResponseAnswers + .Select(ra => detail.Question.Answers.FirstOrDefault(a => a.Id == ra.AnswerId)?.Text) + .Where(text => !string.IsNullOrEmpty(text)) + .ToList(); + + responseText = $"Multiple Selection Question: {detail.Question.Text}\nSelected Options: {string.Join(", ", selectedAnswers)}\nAnalyze these selected workplace factors for mental health implications and patterns."; + } + + // Create analysis request for ALL supported responses + if (!string.IsNullOrEmpty(responseText)) { var request = new AnalysisRequest { ResponseId = response.Id, QuestionId = detail.QuestionId, - ResponseText = detail.TextResponse, + ResponseText = responseText, QuestionText = detail.Question?.Text ?? "" }; @@ -868,13 +888,32 @@ Respond with a JSON array of applicable categories: [""category1"", ""category2" { foreach (var detail in response.ResponseDetails) { + string responseText = ""; + + // Handle text-based questions if (!string.IsNullOrWhiteSpace(detail.TextResponse)) + { + responseText = detail.TextResponse; + } + // Handle CheckBox questions + else if (detail.QuestionType == QuestionType.CheckBox && detail.ResponseAnswers.Any()) + { + var selectedAnswers = detail.ResponseAnswers + .Select(ra => detail.Question.Answers.FirstOrDefault(a => a.Id == ra.AnswerId)?.Text) + .Where(text => !string.IsNullOrEmpty(text)) + .ToList(); + + responseText = $"Multiple Selection Question: {detail.Question.Text}\nSelected Options: {string.Join(", ", selectedAnswers)}\nAnalyze these selected workplace factors for mental health implications and patterns."; + } + + // Create analysis request if we have text to analyze + if (!string.IsNullOrEmpty(responseText)) { var request = new AnalysisRequest { ResponseId = response.Id, QuestionId = detail.QuestionId, - ResponseText = detail.TextResponse, + ResponseText = responseText, QuestionText = detail.Question?.Text ?? "" }; @@ -1087,6 +1126,8 @@ Respond with a JSON array of applicable categories: [""category1"", ""category2" return false; } + + #endregion } } diff --git a/Web/Areas/Admin/Controllers/SurveyAnalysisController.cs b/Web/Areas/Admin/Controllers/SurveyAnalysisController.cs index eafe186..ed59ca4 100644 --- a/Web/Areas/Admin/Controllers/SurveyAnalysisController.cs +++ b/Web/Areas/Admin/Controllers/SurveyAnalysisController.cs @@ -40,40 +40,42 @@ namespace Web.Areas.Admin.Controllers { var questionnaires = await _context.Questionnaires .Include(q => q.Questions) - .Select(q => new - { - q.Id, - q.Title, - q.Description, - QuestionCount = q.Questions.Count, - ResponseCount = _context.Responses.Count(r => r.QuestionnaireId == q.Id), - TextResponseCount = _context.Responses - .Where(r => r.QuestionnaireId == q.Id) - .SelectMany(r => r.ResponseDetails) - .Count(rd => !string.IsNullOrEmpty(rd.TextResponse)), - LastResponse = _context.Responses - .Where(r => r.QuestionnaireId == q.Id) - .OrderByDescending(r => r.SubmissionDate) - .Select(r => r.SubmissionDate) - .FirstOrDefault(), - // Add Users information for displaying participant details - Users = _context.Responses - .Where(r => r.QuestionnaireId == q.Id && !string.IsNullOrEmpty(r.UserName)) - .OrderByDescending(r => r.SubmissionDate) - .Select(r => new - { - UserName = r.UserName, - Email = r.UserEmail - }) - .Distinct() - .Take(5) // Show up to 5 recent participants - .ToList() - }) .ToListAsync(); - ViewBag.ServiceHealth = await _aiAnalysisService.GetServiceHealthStatusAsync(); + var result = questionnaires.Select(q => new + { + q.Id, + q.Title, + q.Description, + QuestionCount = q.Questions.Count, + ResponseCount = _context.Responses.Count(r => r.QuestionnaireId == q.Id), + AnalyzableResponseCount = _context.Responses + .Include(r => r.ResponseDetails) + .ThenInclude(rd => rd.ResponseAnswers) + .Where(r => r.QuestionnaireId == q.Id) + .SelectMany(r => r.ResponseDetails) + .Count(rd => !string.IsNullOrEmpty(rd.TextResponse) || + (rd.QuestionType == QuestionType.CheckBox && rd.ResponseAnswers.Any())), + LastResponse = _context.Responses + .Where(r => r.QuestionnaireId == q.Id) + .OrderByDescending(r => r.SubmissionDate) + .Select(r => r.SubmissionDate) + .FirstOrDefault(), + Users = _context.Responses + .Where(r => r.QuestionnaireId == q.Id && !string.IsNullOrEmpty(r.UserName)) + .OrderByDescending(r => r.SubmissionDate) + .Select(r => new + { + UserName = r.UserName, + Email = r.UserEmail + }) + .Distinct() + .Take(5) + .ToList() + }).ToList(); - return View(questionnaires); + ViewBag.ServiceHealth = await _aiAnalysisService.GetServiceHealthStatusAsync(); + return View(result); } catch (Exception ex) { @@ -101,21 +103,52 @@ namespace Web.Areas.Admin.Controllers } // Check if there are responses to analyze - var hasResponses = await _context.Responses - .AnyAsync(r => r.QuestionnaireId == id); + var totalResponses = await _context.Responses + .CountAsync(r => r.QuestionnaireId == id); - if (!hasResponses) + if (totalResponses == 0) { TempData["WarningMessage"] = "No responses found for this questionnaire."; return RedirectToAction(nameof(Index)); } - _logger.LogInformation("Starting analysis for questionnaire {QuestionnaireId}", id); + // Calculate analyzable responses (same logic as dashboard) + var analyzableCount = await _context.Responses + .Where(r => r.QuestionnaireId == id) + .SelectMany(r => r.ResponseDetails) + .CountAsync(rd => !string.IsNullOrEmpty(rd.TextResponse) || + (rd.QuestionType == QuestionType.CheckBox && + _context.ResponseAnswers.Any(ra => ra.ResponseDetailId == rd.Id))); + + if (analyzableCount == 0) + { + TempData["WarningMessage"] = "No analyzable responses found. Responses must contain text or checkbox selections."; + return RedirectToAction(nameof(Index)); + } + + _logger.LogInformation("Starting analysis for questionnaire {QuestionnaireId}. Total responses: {TotalResponses}, Analyzable: {AnalyzableCount}", + id, totalResponses, analyzableCount); // Generate comprehensive analysis var analysisOverview = await _aiAnalysisService.GenerateQuestionnaireOverviewAsync(id); - _logger.LogInformation("Analysis completed successfully for questionnaire {QuestionnaireId}", id); + var actuallyAnalyzed = analysisOverview.AnalyzedResponses; + + _logger.LogInformation("Analysis completed for questionnaire {QuestionnaireId}. " + + "Analyzable: {AnalyzableCount}, Successfully Analyzed: {ActuallyAnalyzed}", + id, analyzableCount, actuallyAnalyzed); + + // Provide user feedback about the difference if significant + if (analyzableCount > actuallyAnalyzed && (analyzableCount - actuallyAnalyzed) > 0) + { + var difference = analyzableCount - actuallyAnalyzed; + TempData["InfoMessage"] = $"Analysis completed successfully. {actuallyAnalyzed} of {analyzableCount} analyzable responses were processed. " + + $"{difference} response(s) could not be analyzed due to processing limitations or API constraints."; + } + else + { + TempData["SuccessMessage"] = $"Analysis completed successfully. All {actuallyAnalyzed} analyzable responses were processed."; + } return View(analysisOverview); } @@ -295,15 +328,37 @@ namespace Web.Areas.Admin.Controllers foreach (var response in responses) { - foreach (var detail in response.ResponseDetails.Where(rd => !string.IsNullOrWhiteSpace(rd.TextResponse))) + foreach (var detail in response.ResponseDetails) { - analysisRequests.Add(new AnalysisRequest + string responseText = ""; + + // Handle text-based questions + if (!string.IsNullOrWhiteSpace(detail.TextResponse)) { - ResponseId = response.Id, - QuestionId = detail.QuestionId, - ResponseText = detail.TextResponse, - QuestionText = detail.Question?.Text ?? "" - }); + responseText = detail.TextResponse; + } + // Handle CheckBox questions + else if (detail.QuestionType == QuestionType.CheckBox && detail.ResponseAnswers.Any()) + { + var selectedAnswers = detail.ResponseAnswers + .Select(ra => detail.Question.Answers.FirstOrDefault(a => a.Id == ra.AnswerId)?.Text) + .Where(text => !string.IsNullOrEmpty(text)) + .ToList(); + + responseText = $"Multiple Selection Question: {detail.Question.Text}\nSelected Options: {string.Join(", ", selectedAnswers)}\nAnalyze these selected workplace factors for mental health implications and patterns."; + } + + // Add to analysis requests if we have text to analyze + if (!string.IsNullOrEmpty(responseText)) + { + analysisRequests.Add(new AnalysisRequest + { + ResponseId = response.Id, + QuestionId = detail.QuestionId, + ResponseText = responseText, + QuestionText = detail.Question?.Text ?? "" + }); + } } } @@ -346,15 +401,37 @@ namespace Web.Areas.Admin.Controllers foreach (var response in responses) { - foreach (var detail in response.ResponseDetails.Where(rd => !string.IsNullOrWhiteSpace(rd.TextResponse))) + foreach (var detail in response.ResponseDetails) { - analysisRequests.Add(new AnalysisRequest + string responseText = ""; + + // Handle text-based questions + if (!string.IsNullOrWhiteSpace(detail.TextResponse)) { - ResponseId = response.Id, - QuestionId = detail.QuestionId, - ResponseText = detail.TextResponse, - QuestionText = detail.Question?.Text ?? "" - }); + responseText = detail.TextResponse; + } + // Handle CheckBox questions + else if (detail.QuestionType == QuestionType.CheckBox && detail.ResponseAnswers.Any()) + { + var selectedAnswers = detail.ResponseAnswers + .Select(ra => detail.Question.Answers.FirstOrDefault(a => a.Id == ra.AnswerId)?.Text) + .Where(text => !string.IsNullOrEmpty(text)) + .ToList(); + + responseText = $"Multiple Selection Question: {detail.Question.Text}\nSelected Options: {string.Join(", ", selectedAnswers)}\nAnalyze these selected workplace factors for mental health implications and patterns."; + } + + // Add to analysis requests if we have text to analyze + if (!string.IsNullOrEmpty(responseText)) + { + analysisRequests.Add(new AnalysisRequest + { + ResponseId = response.Id, + QuestionId = detail.QuestionId, + ResponseText = responseText, + QuestionText = detail.Question?.Text ?? "" + }); + } } } diff --git a/Web/Areas/Admin/Views/SurveyAnalysis/AnalyzeQuestionnaire.cshtml b/Web/Areas/Admin/Views/SurveyAnalysis/AnalyzeQuestionnaire.cshtml index 9a19d4a..b24d2d9 100644 --- a/Web/Areas/Admin/Views/SurveyAnalysis/AnalyzeQuestionnaire.cshtml +++ b/Web/Areas/Admin/Views/SurveyAnalysis/AnalyzeQuestionnaire.cshtml @@ -3,434 +3,493 @@ @{ ViewData["Title"] = $"AI Analysis - {Model.QuestionnaireTitle}"; - } -
+
+ +
+
+
+
+ -
-
-
-
- -

- - AI Analysis Results -

-

Comprehensive mental health analysis powered by Azure AI

+
+
-
-
-
-
-
- +
+
+
+
+
+
+
-

@Model.TotalResponses

- Total Responses -
-
-
-
-
-
-
- +
+
@Model.TotalResponses
+
Total Responses
-

@Model.AnalyzedResponses

- AI Analyzed +
-
-
-
-
-
-
- -
-

@Math.Round(Model.OverallPositiveSentiment * 100, 1)%

- Positive Sentiment -
-
-
-
-
-
-
0 ? "text-warning" : "text-success") mb-2"> - -
-

@(Model.HighRiskResponses + Model.CriticalRiskResponses)

- High/Critical Risk -
-
-
-
- -
-
-
-
-
- - Mental Health Risk Distribution -
+
+
+
+ +
+
+
@Model.AnalyzedResponses
+
Responses Analyzed
+
+
-
- @if (Model.AnalyzedResponses > 0) + +
+
+
+ +
+
+
@Math.Round(Model.OverallPositiveSentiment * 100, 1)%
+
Positive Sentiment
+
+
+
+ +
+
+
+ +
+
+
@(Model.HighRiskResponses + Model.CriticalRiskResponses)
+
High/Critical Risk
+
+
+ @if (Model.HighRiskResponses + Model.CriticalRiskResponses > 0) { -
-
- - Low Risk - - @Model.LowRiskResponses -
-
-
-
-
-
+
+ } +
+
+
+
-
-
- - Moderate Risk - - @Model.ModerateRiskResponses -
-
-
-
-
+ +
+
+
+ +
+
+
+
+
- -
-
- - High Risk - - @Model.HighRiskResponses -
-
-
-
-
+
+

Mental Health Risk Distribution

+

Comprehensive risk assessment across all response types

- -
-
- - Critical Risk - - @Model.CriticalRiskResponses -
-
-
-
-
-
- - @if (Model.HighRiskResponses > 0 || Model.CriticalRiskResponses > 0) +
+
+ @if (Model.AnalyzedResponses > 0) { -
- - @(Model.HighRiskResponses + Model.CriticalRiskResponses) responses require immediate attention. - View details +
+
+
+
+ Low Risk + @Model.LowRiskResponses +
+
+
+
+
@Math.Round(Model.AnalyzedResponses > 0 ? (Model.LowRiskResponses * 100.0 / Model.AnalyzedResponses) : 0, 1)%
+
+ +
+
+
+ Moderate Risk + @Model.ModerateRiskResponses +
+
+
+
+
@Math.Round(Model.AnalyzedResponses > 0 ? (Model.ModerateRiskResponses * 100.0 / Model.AnalyzedResponses) : 0, 1)%
+
+ +
+
+
+ High Risk + @Model.HighRiskResponses +
+
+
+
+
@Math.Round(Model.AnalyzedResponses > 0 ? (Model.HighRiskResponses * 100.0 / Model.AnalyzedResponses) : 0, 1)%
+
+ +
+
+
+ Critical Risk + @Model.CriticalRiskResponses +
+
+
+
+
@Math.Round(Model.AnalyzedResponses > 0 ? (Model.CriticalRiskResponses * 100.0 / Model.AnalyzedResponses) : 0, 1)%
+
+
+ + @if (Model.HighRiskResponses > 0 || Model.CriticalRiskResponses > 0) + { +
+
+
+
+
ATTENTION REQUIRED
+
+ @(Model.HighRiskResponses + Model.CriticalRiskResponses) responses require immediate attention. + View details +
+
+
+ } + } + else + { +
+ +

No risk assessment data available

} - } - else - { -
- -

No risk assessment data available

+
+
+ + +
+
+
+
+
- } +
+

Overall Sentiment Analysis

+

Emotional tone assessment across responses

+
+
+
+ @if (Model.AnalyzedResponses > 0) + { +
+
+
+ + Positive + @Math.Round(Model.OverallPositiveSentiment * 100, 1)% +
+
+
+
+
+ +
+
+ + Neutral + @Math.Round(Model.OverallNeutralSentiment * 100, 1)% +
+
+
+
+
+ +
+
+ + Negative + @Math.Round(Model.OverallNegativeSentiment * 100, 1)% +
+
+
+
+
+
+ + + string sentimentStatus = ""; + string sentimentColor = ""; + string sentimentIcon = ""; + + if (Model.OverallPositiveSentiment >= 0.6) + { + sentimentStatus = "Excellent mental health climate"; + sentimentColor = "success"; + sentimentIcon = "fa-thumbs-up"; + } + else if (Model.OverallPositiveSentiment >= 0.4) + { + sentimentStatus = "Moderate mental health climate"; + sentimentColor = "warning"; + sentimentIcon = "fa-balance-scale"; + } + else + { + sentimentStatus = "Concerning mental health climate"; + sentimentColor = "danger"; + sentimentIcon = "fa-exclamation-triangle"; + } + + +
+
+ +
+
@sentimentStatus
+
+ } + else + { +
+ +

No sentiment analysis data available

+
+ } +
- -
-
-
-
- - Overall Sentiment Analysis -
-
-
- @if (Model.AnalyzedResponses > 0) - { -
-
- - Positive - - @Math.Round(Model.OverallPositiveSentiment * 100, 1)% -
-
-
-
-
-
- -
-
- - Neutral - - @Math.Round(Model.OverallNeutralSentiment * 100, 1)% -
-
-
-
-
-
- -
-
- - Negative - - @Math.Round(Model.OverallNegativeSentiment * 100, 1)% -
-
-
-
-
-
- - - - string sentimentStatus = ""; - string sentimentColor = ""; - string sentimentIcon = ""; - - if (Model.OverallPositiveSentiment >= 0.6) - { - sentimentStatus = "Excellent mental health climate"; - sentimentColor = "text-success"; - sentimentIcon = "fa-thumbs-up"; - } - else if (Model.OverallPositiveSentiment >= 0.4) - { - sentimentStatus = "Moderate mental health climate"; - sentimentColor = "text-warning"; - sentimentIcon = "fa-balance-scale"; - } - else - { - sentimentStatus = "Concerning mental health climate"; - sentimentColor = "text-danger"; - sentimentIcon = "fa-exclamation-triangle"; - } - - -
- - @sentimentStatus -
- } - else - { -
- -

No sentiment analysis data available

-
- } -
-
-
-
+
@if (!string.IsNullOrEmpty(Model.ExecutiveSummary)) { -
-
-
-
-
- - Executive Summary -
+
+
+
+
+
+
+
+ +

Executive Summary

+

Comprehensive analysis overview across all question types

+
-
-
+
+
@Html.Raw(Model.ExecutiveSummary.Replace("\n", "
"))
-
+
} - -
-
-
-
-
- - Top Workplace Issues & Interventions -
-
-
- @if (Model.TopWorkplaceIssues != null && Model.TopWorkplaceIssues.Any()) - { - @foreach (var issue in Model.TopWorkplaceIssues.Take(5)) + +
+
+
+ +
+
+
+
+ +
+
+

Top Workplace Issues & Interventions

+

Priority-ranked issues with recommended actions

+
+
+
+ @if (Model.TopWorkplaceIssues != null && Model.TopWorkplaceIssues.Any()) { -
-
-
@issue.Category
- - Priority @issue.Priority - -
-

@issue.Issue

-
- Recommended Intervention: -

@issue.RecommendedIntervention

-
- @if (issue.AffectedAreas.Any()) +
+ @foreach (var issue in Model.TopWorkplaceIssues.Take(5)) { -
- @foreach (var area in issue.AffectedAreas) +
+
+
+ P@issue.Priority +
+
+
@issue.Category
+
+ Priority @issue.Priority +
+
+
+
@issue.Issue
+
+
+ + Recommended Intervention +
+
@issue.RecommendedIntervention
+
+ @if (issue.AffectedAreas.Any()) { - @area +
+ @foreach (var area in issue.AffectedAreas) + { + @area + } +
}
}
} - } - else - { -
- -

No workplace issues identified in the analysis

-
- } -
-
-
- -
-
-
-
- - Common Themes -
-
-
- @if (Model.MostCommonKeyPhrases != null && Model.MostCommonKeyPhrases.Any()) - { -
-
MOST MENTIONED PHRASES
- @foreach (var phrase in Model.MostCommonKeyPhrases.Take(8)) - { - @phrase - } -
- - @if (Model.TopWorkplaceIssues.Any()) + else { -
-
ISSUE CATEGORIES
- @foreach (var category in Model.TopWorkplaceIssues.Select(i => i.Category).Distinct().Take(5)) +
+ +

No workplace issues identified in the analysis

+
+ } +
+
+ + +
+
+
+
+ +
+
+

Common Themes

+

Key patterns and categories identified

+
+
+
+ @if (Model.MostCommonKeyPhrases != null && Model.MostCommonKeyPhrases.Any()) + { +
+
+
+ + MOST MENTIONED PHRASES +
+
+ @foreach (var phrase in Model.MostCommonKeyPhrases.Take(8)) + { + @phrase + } +
+
+ + @if (Model.TopWorkplaceIssues.Any()) { - @category +
+
+ + ISSUE CATEGORIES +
+
+ @foreach (var category in Model.TopWorkplaceIssues.Select(i => i.Category).Distinct().Take(5)) + { + @category + } +
+
}
} - } - else - { -
- -

No common themes identified

-
- } -
-
-
-
- - -
-
-
-
-
-
- - - Analysis completed on @Model.LastAnalyzedAt.ToString("MMMM dd, yyyy 'at' HH:mm") - | @Model.AnalyzedResponses of @Model.TotalResponses responses analyzed - | Powered by Azure AI Services - -
-
-
- - Refresh Analysis - - - View Trends - + else + { +
+ +

No common themes identified

-
+ }
-
+
+ + +
@functions { @@ -471,47 +530,1309 @@ @section Styles { diff --git a/Web/Areas/Admin/Views/SurveyAnalysis/AnalyzeTrends.cshtml b/Web/Areas/Admin/Views/SurveyAnalysis/AnalyzeTrends.cshtml index 1877205..aa7e643 100644 --- a/Web/Areas/Admin/Views/SurveyAnalysis/AnalyzeTrends.cshtml +++ b/Web/Areas/Admin/Views/SurveyAnalysis/AnalyzeTrends.cshtml @@ -3,609 +3,2044 @@ @{ ViewData["Title"] = $"Mental Health Trends - {ViewBag.QuestionnaireName}"; - } -
- -
-
-
-
- -

- - Mental Health Trends Analysis -

-

Track workplace mental health patterns and intervention effectiveness over time

-
-
-
- - -
-
-
-
+@section Styles { + +} + +