Implement Azure OpenAI integration to analyze user-submitted questions and answers

This commit is contained in:
Qaisyousuf 2025-08-30 18:07:28 +02:00
parent 481b7797a1
commit a9c0e6a0c1
15 changed files with 4053 additions and 740 deletions

View file

@ -0,0 +1,109 @@
// Services/AIViewModel/AIAnalysisViewModels.cs
namespace Services.AIViewModel
{
public enum RiskLevel
{
Low = 1,
Moderate = 2,
High = 3,
Critical = 4
}
public class SentimentAnalysisResult
{
public string Sentiment { get; set; } = string.Empty; // Positive, Negative, Neutral
public double ConfidenceScore { get; set; }
public double PositiveScore { get; set; }
public double NegativeScore { get; set; }
public double NeutralScore { get; set; }
public DateTime AnalyzedAt { get; set; } = DateTime.UtcNow;
}
public class KeyPhrasesResult
{
public List<string> KeyPhrases { get; set; } = new List<string>();
public List<string> WorkplaceFactors { get; set; } = new List<string>();
public List<string> EmotionalIndicators { get; set; } = new List<string>();
public DateTime ExtractedAt { get; set; } = DateTime.UtcNow;
}
public class MentalHealthRiskAssessment
{
public RiskLevel RiskLevel { get; set; }
public double RiskScore { get; set; } // 0-1 scale
public List<string> RiskIndicators { get; set; } = new List<string>();
public List<string> ProtectiveFactors { get; set; } = new List<string>();
public bool RequiresImmediateAttention { get; set; }
public string RecommendedAction { get; set; } = string.Empty;
public DateTime AssessedAt { get; set; } = DateTime.UtcNow;
}
public class WorkplaceInsight
{
public string Category { get; set; } = string.Empty; // e.g., "Work-Life Balance", "Management", "Workload"
public string Issue { get; set; } = string.Empty;
public string RecommendedIntervention { get; set; } = string.Empty;
public int Priority { get; set; } // 1-5 scale
public List<string> AffectedAreas { get; set; } = new List<string>();
public DateTime IdentifiedAt { get; set; } = DateTime.UtcNow;
}
public class ResponseAnalysisResult
{
public int ResponseId { get; set; }
public int QuestionId { get; set; }
public string QuestionText { get; set; } = string.Empty;
public string ResponseText { get; set; } = string.Empty;
public string AnonymizedResponseText { get; set; } = string.Empty; // PII removed
// Azure Language Service Results
public SentimentAnalysisResult? SentimentAnalysis { get; set; }
public KeyPhrasesResult? KeyPhrases { get; set; }
// Azure OpenAI Results
public MentalHealthRiskAssessment? RiskAssessment { get; set; }
public List<WorkplaceInsight> Insights { get; set; } = new List<WorkplaceInsight>();
public DateTime AnalyzedAt { get; set; } = DateTime.UtcNow;
public bool IsAnalysisComplete { get; set; } = false;
}
public class QuestionnaireAnalysisOverview
{
public int QuestionnaireId { get; set; }
public string QuestionnaireTitle { get; set; } = string.Empty;
public int TotalResponses { get; set; }
public int AnalyzedResponses { get; set; }
// Overall Statistics
public double OverallPositiveSentiment { get; set; }
public double OverallNegativeSentiment { get; set; }
public double OverallNeutralSentiment { get; set; }
// Risk Distribution
public int LowRiskResponses { get; set; }
public int ModerateRiskResponses { get; set; }
public int HighRiskResponses { get; set; }
public int CriticalRiskResponses { get; set; }
// Top Issues
public List<WorkplaceInsight> TopWorkplaceIssues { get; set; } = new List<WorkplaceInsight>();
public List<string> MostCommonKeyPhrases { get; set; } = new List<string>();
public DateTime LastAnalyzedAt { get; set; }
public string ExecutiveSummary { get; set; } = string.Empty;
}
public class AnalysisRequest
{
public int ResponseId { get; set; }
public int QuestionId { get; set; }
public string ResponseText { get; set; } = string.Empty;
public string QuestionText { get; set; } = string.Empty;
public string QuestionType { get; set; } = string.Empty;
public bool IncludeSentimentAnalysis { get; set; } = true;
public bool IncludeKeyPhraseExtraction { get; set; } = true;
public bool IncludeRiskAssessment { get; set; } = true;
public bool IncludeWorkplaceInsights { get; set; } = true;
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,11 +1,147 @@
using Azure.AI.TextAnalytics; // Services/Interfaces/IAiAnalysisService.cs
using Services.AIViewModel;
namespace Services.Interaces namespace Services.Interaces
{ {
public interface IAiAnalysisService public interface IAiAnalysisService
{ {
Task<DocumentSentiment> AnalyzeSentimentAsync(string text); #region Azure Language Service Methods
Task<string> GetRiskAssessmentAsync(string text);
Task<List<string>> ExtractKeyPhrasesAsync(string text); /// <summary>
/// Analyzes sentiment of response text using Azure Language Service
/// </summary>
Task<SentimentAnalysisResult> AnalyzeSentimentAsync(string text);
/// <summary>
/// Extracts key phrases and workplace factors from response text
/// </summary>
Task<KeyPhrasesResult> ExtractKeyPhrasesAsync(string text);
/// <summary>
/// Removes PII (Personally Identifiable Information) from response text
/// </summary>
Task<string> AnonymizeTextAsync(string text);
/// <summary>
/// Detects entities in text (workplace factors, departments, roles, etc.)
/// </summary>
Task<List<string>> DetectEntitiesAsync(string text);
#endregion
#region Azure OpenAI Methods
/// <summary>
/// Assesses mental health risk level using GPT-3.5 Turbo
/// </summary>
Task<MentalHealthRiskAssessment> AssessMentalHealthRiskAsync(string anonymizedText, string questionContext);
/// <summary>
/// Generates workplace insights and intervention recommendations
/// </summary>
Task<List<WorkplaceInsight>> GenerateWorkplaceInsightsAsync(string anonymizedText, string questionContext);
/// <summary>
/// Creates executive summary for questionnaire analysis
/// </summary>
Task<string> GenerateExecutiveSummaryAsync(List<ResponseAnalysisResult> analysisResults);
/// <summary>
/// Categorizes responses into mental health themes
/// </summary>
Task<List<string>> CategorizeResponseAsync(string anonymizedText);
#endregion
#region Combined Analysis Methods
/// <summary>
/// Performs complete AI analysis on a single response (both Azure services)
/// </summary>
Task<ResponseAnalysisResult> AnalyzeCompleteResponseAsync(AnalysisRequest request);
/// <summary>
/// Analyzes multiple responses for a specific question
/// </summary>
Task<List<ResponseAnalysisResult>> AnalyzeQuestionResponsesAsync(int questionId, List<AnalysisRequest> requests);
/// <summary>
/// Generates comprehensive analysis overview for entire questionnaire
/// </summary>
Task<QuestionnaireAnalysisOverview> GenerateQuestionnaireOverviewAsync(int questionnaireId);
/// <summary>
/// Batch processes multiple responses efficiently
/// </summary>
Task<List<ResponseAnalysisResult>> BatchAnalyzeResponsesAsync(List<AnalysisRequest> requests);
#endregion
#region Mental Health Specific Methods
/// <summary>
/// Identifies responses requiring immediate attention (high risk)
/// </summary>
Task<List<ResponseAnalysisResult>> IdentifyHighRiskResponsesAsync(int questionnaireId);
/// <summary>
/// Generates mental health trends across time periods
/// </summary>
Task<List<WorkplaceInsight>> AnalyzeMentalHealthTrendsAsync(int questionnaireId, DateTime fromDate, DateTime toDate);
/// <summary>
/// Compares mental health metrics between departments/teams
/// </summary>
Task<Dictionary<string, QuestionnaireAnalysisOverview>> CompareTeamMentalHealthAsync(int questionnaireId, List<string> teamIdentifiers);
/// <summary>
/// Generates intervention recommendations based on overall analysis
/// </summary>
Task<List<WorkplaceInsight>> GenerateInterventionRecommendationsAsync(int questionnaireId);
#endregion
#region Reporting Methods
/// <summary>
/// Creates detailed analysis report for specific questionnaire
/// </summary>
Task<string> GenerateDetailedAnalysisReportAsync(int questionnaireId);
/// <summary>
/// Generates anonymized data export for further analysis
/// </summary>
Task<List<ResponseAnalysisResult>> ExportAnonymizedAnalysisAsync(int questionnaireId);
/// <summary>
/// Creates management dashboard summary
/// </summary>
Task<QuestionnaireAnalysisOverview> GenerateManagementDashboardAsync(int questionnaireId);
#endregion
#region Utility Methods
/// <summary>
/// Tests connection to Azure Language Service
/// </summary>
Task<bool> TestAzureLanguageServiceConnectionAsync();
/// <summary>
/// Tests connection to Azure OpenAI Service
/// </summary>
Task<bool> TestAzureOpenAIConnectionAsync();
/// <summary>
/// Validates analysis request before processing
/// </summary>
Task<bool> ValidateAnalysisRequestAsync(AnalysisRequest request);
/// <summary>
/// Gets analysis service health status
/// </summary>
Task<Dictionary<string, bool>> GetServiceHealthStatusAsync();
#endregion
} }
} }

View file

@ -0,0 +1,17 @@
// Services/Options/AzureOptions.cs
namespace Services.Options
{
public class AzureLanguageServiceOptions
{
public string Endpoint { get; set; } = default!;
public string Key { get; set; } = default!;
public string Region { get; set; } = default!;
}
public class AzureOpenAIOptions
{
public string Endpoint { get; set; } = default!;
public string Key { get; set; } = default!;
public string DeploymentName { get; set; } = default!;
}
}

View file

@ -7,6 +7,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Azure.AI.OpenAI" Version="2.1.0" />
<PackageReference Include="Azure.AI.TextAnalytics" Version="5.3.0" /> <PackageReference Include="Azure.AI.TextAnalytics" Version="5.3.0" />
<PackageReference Include="MailJet.Api" Version="3.0.0" /> <PackageReference Include="MailJet.Api" Version="3.0.0" />
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.0.4" /> <PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.0.4" />

View file

@ -8,7 +8,7 @@ using Microsoft.VisualStudio.TextTemplating;
using Microsoft.VisualStudio.Web.CodeGenerators.Mvc.Templates.BlazorIdentity.Pages.Manage; using Microsoft.VisualStudio.Web.CodeGenerators.Mvc.Templates.BlazorIdentity.Pages.Manage;
using Model; using Model;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using OpenAI_API;
using Services.EmailSend; using Services.EmailSend;
using Services.Implemnetation; using Services.Implemnetation;
using Services.Interaces; using Services.Interaces;

View file

@ -1,173 +1,623 @@
using Azure; // Web/Areas/Admin/Controllers/SurveyAnalysisController.cs
using Data; using Data;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Model; using Model;
using Web.ViewModel.QuestionnaireVM; using Services.AIViewModel;
using System.IO;
using Microsoft.AspNetCore.Authorization;
using Services.Implemnetation;
using Services.Interaces; using Services.Interaces;
using System.Text;
using System.Text.Json;
namespace Web.Areas.Admin.Controllers namespace Web.Areas.Admin.Controllers
{ {
public class SurveyAnalysisController : Controller public class SurveyAnalysisController : Controller
{ {
private readonly SurveyContext _context;
private readonly IAiAnalysisService _aiAnalysisService; private readonly IAiAnalysisService _aiAnalysisService;
private readonly SurveyContext _context;
private readonly ILogger<SurveyAnalysisController> _logger;
public SurveyAnalysisController(SurveyContext context, IAiAnalysisService aiAnalysisService) public SurveyAnalysisController(
IAiAnalysisService aiAnalysisService,
SurveyContext context,
ILogger<SurveyAnalysisController> logger)
{ {
_aiAnalysisService = aiAnalysisService;
_context = context; _context = context;
_aiAnalysisService = aiAnalysisService; _logger = logger;
}
public IActionResult Index()
{
var questionnaires = _context.Responses
.Include(r => r.Questionnaire) // Ensure the navigation property is correctly set up in the Response model
.Select(r => r.Questionnaire)
.Distinct() // Ensure each questionnaire is listed once
.ToList();
var viewModel = questionnaires.Select(q => new ResponseQuestionnaireWithUsersViewModel
{
Id = q.Id,
Title = q.Title
}).ToList();
return View(viewModel);
} }
#region Dashboard and Overview
/// <summary>
/// Main dashboard showing all questionnaires available for analysis
[HttpGet] /// </summary>
public IActionResult Analysis(int id) public async Task<IActionResult> Index()
{ {
var viewModel = _context.Responses try
.Where(r => r.QuestionnaireId == id)
.Include(r => r.Questionnaire)
.ThenInclude(q => q.Questions)
.ThenInclude(q => q.Answers)
.Select(r => new ResponseQuestionnaireWithUsersViewModel
{
Id = r.Questionnaire.Id,
Title = r.Questionnaire.Title,
Description = r.Questionnaire.Description,
UserName = r.UserName, // Assuming you want the user who answered the questionnaire
Email = r.UserEmail,
ParticipantCount = _context.Responses.Count(rs => rs.QuestionnaireId == id),
QuestionsAnsweredPercentage = _context.Questionnaires
.Where(q => q.Id == id)
.SelectMany(q => q.Questions)
.Count() > 0
? (double)_context.ResponseDetails
.Where(rd => rd.Response.QuestionnaireId == id && rd.TextResponse != null)
.Select(rd => rd.QuestionId)
.Distinct()
.Count() / _context.Questionnaires
.Where(q => q.Id == id)
.SelectMany(q => q.Questions)
.Count() * 100.0
: 0.0, // Avoid division by zero
Questions = r.Questionnaire.Questions.Select(q => new ResponseQuestionViewModel
{
Id = q.Id,
Text = q.Text,
Type = q.Type,
Answers = q.Answers.Select(a => new ResponseAnswerViewModel
{
Id = a.Id,
Text = a.Text ?? _context.ResponseDetails
.Where(rd => rd.QuestionId == q.Id && rd.ResponseId == r.Id)
.Select(rd => rd.TextResponse)
.FirstOrDefault(),
Count = _context.ResponseAnswers.Count(ra => ra.AnswerId == a.Id) // Count how many times each answer was selected
}).ToList(),
SelectedAnswerIds = _context.ResponseDetails
.Where(rd => rd.QuestionId == q.Id)
.SelectMany(rd => rd.ResponseAnswers)
.Select(ra => ra.AnswerId)
.Distinct()
.ToList(),
SelectedText = _context.ResponseDetails
.Where(rd => rd.QuestionId == q.Id)
.Select(rd => rd.TextResponse)
.Where(t => !string.IsNullOrEmpty(t))
.ToList()
}).ToList(),
Users = _context.Responses
.Where(rs => rs.QuestionnaireId == id)
.Select(rs => new ResponseUserViewModel
{
UserName = rs.UserName,
Email = rs.UserEmail
}).Distinct().ToList()
})
.FirstOrDefault();
if (viewModel == null)
{ {
return NotFound("No questionnaire found for the given ID."); var questionnaires = await _context.Questionnaires
} .Include(q => q.Questions)
.Select(q => new
return View(viewModel);
}
[HttpGet]
public async Task<IActionResult> AiAnalysis(int id)
{
// Get survey responses for the questionnaire
var responses = await _context.Responses
.Where(r => r.QuestionnaireId == id)
.Include(r => r.ResponseDetails)
.ThenInclude(rd => rd.Question)
.Include(r => r.Questionnaire)
.ToListAsync();
if (!responses.Any())
{
return NotFound("No responses found for this questionnaire.");
}
var analysisResults = new List<dynamic>();
foreach (var response in responses)
{
foreach (var detail in response.ResponseDetails)
{
if (!string.IsNullOrWhiteSpace(detail.TextResponse))
{ {
// Analyze the text response with AI q.Id,
var sentiment = await _aiAnalysisService.AnalyzeSentimentAsync(detail.TextResponse); q.Title,
var riskAssessment = await _aiAnalysisService.GetRiskAssessmentAsync(detail.TextResponse); q.Description,
var keyPhrases = await _aiAnalysisService.ExtractKeyPhrasesAsync(detail.TextResponse); 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()
})
.ToListAsync();
analysisResults.Add(new ViewBag.ServiceHealth = await _aiAnalysisService.GetServiceHealthStatusAsync();
return View(questionnaires);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error loading survey analysis dashboard");
TempData["ErrorMessage"] = "Error loading dashboard. Please try again.";
return View(new List<object>());
}
}
/// <summary>
/// Generate comprehensive analysis overview for a questionnaire
/// </summary>
public async Task<IActionResult> AnalyzeQuestionnaire(int id)
{
try
{
var questionnaire = await _context.Questionnaires
.Include(q => q.Questions)
.FirstOrDefaultAsync(q => q.Id == id);
if (questionnaire == null)
{
TempData["ErrorMessage"] = "Questionnaire not found.";
return RedirectToAction(nameof(Index));
}
// Check if there are responses to analyze
var hasResponses = await _context.Responses
.AnyAsync(r => r.QuestionnaireId == id);
if (!hasResponses)
{
TempData["WarningMessage"] = "No responses found for this questionnaire.";
return RedirectToAction(nameof(Index));
}
_logger.LogInformation("Starting analysis for questionnaire {QuestionnaireId}", id);
// Generate comprehensive analysis
var analysisOverview = await _aiAnalysisService.GenerateQuestionnaireOverviewAsync(id);
_logger.LogInformation("Analysis completed successfully for questionnaire {QuestionnaireId}", id);
return View(analysisOverview);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error analyzing questionnaire {QuestionnaireId}: {ErrorMessage}", id, ex.Message);
TempData["ErrorMessage"] = $"Error analyzing questionnaire: {ex.Message}. Please check the logs for more details.";
return RedirectToAction(nameof(Index));
}
}
#endregion
#region High-Risk Response Management
/// <summary>
/// Identify and display high-risk responses requiring immediate attention
/// </summary>
public async Task<IActionResult> HighRiskResponses(int id)
{
try
{
var questionnaire = await _context.Questionnaires
.FirstOrDefaultAsync(q => q.Id == id);
if (questionnaire == null)
{
TempData["ErrorMessage"] = "Questionnaire not found.";
return RedirectToAction(nameof(Index));
}
var highRiskResponses = await _aiAnalysisService.IdentifyHighRiskResponsesAsync(id);
ViewBag.QuestionnaireName = questionnaire.Title;
ViewBag.QuestionnaireId = id;
return View(highRiskResponses);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error identifying high-risk responses for questionnaire {QuestionnaireId}", id);
TempData["ErrorMessage"] = "Error identifying high-risk responses. Please try again.";
return RedirectToAction(nameof(Index));
}
}
/// <summary>
/// View detailed analysis of a specific high-risk response
/// </summary>
public async Task<IActionResult> ViewHighRiskResponse(int questionnaireId, int responseId)
{
try
{
var response = await _context.Responses
.Include(r => r.Questionnaire)
.Include(r => r.ResponseDetails)
.ThenInclude(rd => rd.Question)
.FirstOrDefaultAsync(r => r.Id == responseId && r.QuestionnaireId == questionnaireId);
if (response == null)
{
TempData["ErrorMessage"] = "Response not found.";
return RedirectToAction(nameof(HighRiskResponses), new { id = questionnaireId });
}
// Get AI analysis for each text response
var analysisResults = new List<ResponseAnalysisResult>();
foreach (var detail in response.ResponseDetails.Where(rd => !string.IsNullOrWhiteSpace(rd.TextResponse)))
{
var analysisRequest = new AnalysisRequest
{
ResponseId = response.Id,
QuestionId = detail.QuestionId,
ResponseText = detail.TextResponse,
QuestionText = detail.Question?.Text ?? ""
};
var analysis = await _aiAnalysisService.AnalyzeCompleteResponseAsync(analysisRequest);
analysisResults.Add(analysis);
}
ViewBag.Response = response;
return View(analysisResults);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error viewing high-risk response {ResponseId}", responseId);
TempData["ErrorMessage"] = "Error loading response details. Please try again.";
return RedirectToAction(nameof(HighRiskResponses), new { id = questionnaireId });
}
}
#endregion
#region Individual Response Analysis
/// <summary>
/// Analyze a single response in detail
/// </summary>
[HttpPost]
public async Task<IActionResult> AnalyzeResponse(int responseId, int questionId, string responseText, string questionText)
{
try
{
var analysisRequest = new AnalysisRequest
{
ResponseId = responseId,
QuestionId = questionId,
ResponseText = responseText,
QuestionText = questionText
};
var isValid = await _aiAnalysisService.ValidateAnalysisRequestAsync(analysisRequest);
if (!isValid)
{
return Json(new { success = false, message = "Invalid analysis request." });
}
var analysis = await _aiAnalysisService.AnalyzeCompleteResponseAsync(analysisRequest);
return Json(new
{
success = true,
analysis = new
{
sentiment = analysis.SentimentAnalysis,
keyPhrases = analysis.KeyPhrases?.KeyPhrases ?? new List<string>(),
riskLevel = analysis.RiskAssessment?.RiskLevel.ToString(),
riskScore = analysis.RiskAssessment?.RiskScore ?? 0,
requiresAttention = analysis.RiskAssessment?.RequiresImmediateAttention ?? false,
recommendedAction = analysis.RiskAssessment?.RecommendedAction ?? "",
insights = analysis.Insights.Select(i => new {
category = i.Category,
issue = i.Issue,
intervention = i.RecommendedIntervention,
priority = i.Priority
})
}
});
}
catch (Exception ex)
{
_logger.LogError(ex, "Error analyzing individual response {ResponseId}", responseId);
return Json(new { success = false, message = "Error analyzing response. Please try again." });
}
}
#endregion
#region Batch Analysis
/// <summary>
/// Process batch analysis for all responses in a questionnaire
/// </summary>
public async Task<IActionResult> BatchAnalyze(int id)
{
try
{
var questionnaire = await _context.Questionnaires
.FirstOrDefaultAsync(q => q.Id == id);
if (questionnaire == null)
{
TempData["ErrorMessage"] = "Questionnaire not found.";
return RedirectToAction(nameof(Index));
}
// Get all text responses for the questionnaire
var responses = await _context.Responses
.Include(r => r.ResponseDetails)
.ThenInclude(rd => rd.Question)
.Where(r => r.QuestionnaireId == id)
.ToListAsync();
var analysisRequests = new List<AnalysisRequest>();
foreach (var response in responses)
{
foreach (var detail in response.ResponseDetails.Where(rd => !string.IsNullOrWhiteSpace(rd.TextResponse)))
{
analysisRequests.Add(new AnalysisRequest
{ {
UserName = response.UserName, ResponseId = response.Id,
UserEmail = response.UserEmail, QuestionId = detail.QuestionId,
Question = detail.Question.Text, ResponseText = detail.TextResponse,
Response = detail.TextResponse, QuestionText = detail.Question?.Text ?? ""
Sentiment = sentiment.Sentiment.ToString(),
PositiveScore = sentiment.ConfidenceScores.Positive,
NegativeScore = sentiment.ConfidenceScores.Negative,
NeutralScore = sentiment.ConfidenceScores.Neutral,
RiskAssessment = riskAssessment,
KeyPhrases = keyPhrases
}); });
} }
} }
if (!analysisRequests.Any())
{
TempData["WarningMessage"] = "No text responses found to analyze.";
return RedirectToAction(nameof(AnalyzeQuestionnaire), new { id });
}
// Process batch analysis (this might take a while)
ViewBag.QuestionnaireName = questionnaire.Title;
ViewBag.QuestionnaireId = id;
ViewBag.TotalRequests = analysisRequests.Count;
return View("BatchAnalysisProgress");
}
catch (Exception ex)
{
_logger.LogError(ex, "Error starting batch analysis for questionnaire {QuestionnaireId}", id);
TempData["ErrorMessage"] = "Error starting batch analysis. Please try again.";
return RedirectToAction(nameof(Index));
} }
ViewBag.QuestionnaireName = responses.First().Questionnaire.Title;
return View(analysisResults);
} }
}
}
/// <summary>
/// AJAX endpoint for batch analysis progress
/// </summary>
[HttpPost]
public async Task<IActionResult> ProcessBatchAnalysis(int questionnaireId)
{
try
{
var responses = await _context.Responses
.Include(r => r.ResponseDetails)
.ThenInclude(rd => rd.Question)
.Where(r => r.QuestionnaireId == questionnaireId)
.ToListAsync();
var analysisRequests = new List<AnalysisRequest>();
foreach (var response in responses)
{
foreach (var detail in response.ResponseDetails.Where(rd => !string.IsNullOrWhiteSpace(rd.TextResponse)))
{
analysisRequests.Add(new AnalysisRequest
{
ResponseId = response.Id,
QuestionId = detail.QuestionId,
ResponseText = detail.TextResponse,
QuestionText = detail.Question?.Text ?? ""
});
}
}
var results = await _aiAnalysisService.BatchAnalyzeResponsesAsync(analysisRequests);
return Json(new
{
success = true,
processedCount = results.Count,
highRiskCount = results.Count(r => r.RiskAssessment?.RiskLevel >= RiskLevel.High),
message = $"Successfully analyzed {results.Count} responses."
});
}
catch (Exception ex)
{
_logger.LogError(ex, "Error processing batch analysis for questionnaire {QuestionnaireId}", questionnaireId);
return Json(new
{
success = false,
message = "Error processing batch analysis. Please try again."
});
}
}
#endregion
#region Reporting
/// <summary>
/// Generate detailed analysis report for management
/// </summary>
public async Task<IActionResult> GenerateReport(int id)
{
try
{
var questionnaire = await _context.Questionnaires
.FirstOrDefaultAsync(q => q.Id == id);
if (questionnaire == null)
{
TempData["ErrorMessage"] = "Questionnaire not found.";
return RedirectToAction(nameof(Index));
}
var report = await _aiAnalysisService.GenerateDetailedAnalysisReportAsync(id);
ViewBag.QuestionnaireName = questionnaire.Title;
ViewBag.QuestionnaireId = id;
ViewBag.Report = report;
ViewBag.GeneratedDate = DateTime.Now;
return View();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error generating report for questionnaire {QuestionnaireId}", id);
TempData["ErrorMessage"] = "Error generating report. Please try again.";
return RedirectToAction(nameof(Index));
}
}
/// <summary>
/// Download report as text file
/// </summary>
public async Task<IActionResult> DownloadReport(int id)
{
try
{
var questionnaire = await _context.Questionnaires
.FirstOrDefaultAsync(q => q.Id == id);
if (questionnaire == null)
{
TempData["ErrorMessage"] = "Questionnaire not found.";
return RedirectToAction(nameof(Index));
}
var report = await _aiAnalysisService.GenerateDetailedAnalysisReportAsync(id);
var bytes = Encoding.UTF8.GetBytes(report);
var fileName = $"Mental_Health_Analysis_{questionnaire.Title}_{DateTime.Now:yyyy-MM-dd}.txt";
return File(bytes, "text/plain", fileName);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error downloading report for questionnaire {QuestionnaireId}", id);
TempData["ErrorMessage"] = "Error downloading report. Please try again.";
return RedirectToAction(nameof(Index));
}
}
/// <summary>
/// Export anonymized analysis data
/// </summary>
public async Task<IActionResult> ExportAnalysis(int id)
{
try
{
var questionnaire = await _context.Questionnaires
.FirstOrDefaultAsync(q => q.Id == id);
if (questionnaire == null)
{
TempData["ErrorMessage"] = "Questionnaire not found.";
return RedirectToAction(nameof(Index));
}
var analysisData = await _aiAnalysisService.ExportAnonymizedAnalysisAsync(id);
var json = System.Text.Json.JsonSerializer.Serialize(analysisData, new JsonSerializerOptions
{
WriteIndented = true
});
var bytes = Encoding.UTF8.GetBytes(json);
var fileName = $"Anonymized_Analysis_{questionnaire.Title}_{DateTime.Now:yyyy-MM-dd}.json";
return File(bytes, "application/json", fileName);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error exporting analysis for questionnaire {QuestionnaireId}", id);
TempData["ErrorMessage"] = "Error exporting analysis. Please try again.";
return RedirectToAction(nameof(Index));
}
}
#endregion
#region Mental Health Trends
/// <summary>
/// Analyze mental health trends over time periods
/// </summary>
public async Task<IActionResult> AnalyzeTrends(int id, DateTime? fromDate = null, DateTime? toDate = null)
{
try
{
var questionnaire = await _context.Questionnaires
.FirstOrDefaultAsync(q => q.Id == id);
if (questionnaire == null)
{
TempData["ErrorMessage"] = "Questionnaire not found.";
return RedirectToAction(nameof(Index));
}
// Default to last 6 months if no dates provided
var from = fromDate ?? DateTime.Now.AddMonths(-6);
var to = toDate ?? DateTime.Now;
var trends = await _aiAnalysisService.AnalyzeMentalHealthTrendsAsync(id, from, to);
ViewBag.QuestionnaireName = questionnaire.Title;
ViewBag.QuestionnaireId = id;
ViewBag.FromDate = from;
ViewBag.ToDate = to;
return View(trends);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error analyzing trends for questionnaire {QuestionnaireId}", id);
TempData["ErrorMessage"] = "Error analyzing trends. Please try again.";
return RedirectToAction(nameof(Index));
}
}
#endregion
#region Service Health and Testing
/// <summary>
/// Check AI service health status
/// </summary>
public async Task<IActionResult> ServiceHealth()
{
try
{
var healthStatus = await _aiAnalysisService.GetServiceHealthStatusAsync();
return Json(new
{
success = true,
services = healthStatus,
message = "Service health check completed successfully"
});
}
catch (Exception ex)
{
_logger.LogError(ex, "Error checking service health");
return Json(new
{
success = false,
error = "Unable to check service health",
message = ex.Message
});
}
}
/// <summary>
/// Test AI analysis with sample text
/// </summary>
[HttpPost]
public async Task<IActionResult> TestAnalysis(string sampleText)
{
try
{
if (string.IsNullOrWhiteSpace(sampleText))
{
return Json(new { success = false, message = "Please provide sample text." });
}
var analysisRequest = new AnalysisRequest
{
ResponseId = 0, // Test request
QuestionId = 0, // Test request
ResponseText = sampleText,
QuestionText = "Test question: How are you feeling about your work environment?"
};
var analysis = await _aiAnalysisService.AnalyzeCompleteResponseAsync(analysisRequest);
return Json(new
{
success = true,
sentiment = analysis.SentimentAnalysis?.Sentiment,
riskLevel = analysis.RiskAssessment?.RiskLevel.ToString(),
keyPhrases = analysis.KeyPhrases?.KeyPhrases,
insights = analysis.Insights.Select(i => i.Category).ToList()
});
}
catch (Exception ex)
{
_logger.LogError(ex, "Error in test analysis");
return Json(new
{
success = false,
message = "Error performing test analysis. Please try again."
});
}
}
#endregion
#region Management Dashboard
/// <summary>
/// Executive dashboard for mental health overview
/// </summary>
public async Task<IActionResult> Dashboard(int id)
{
try
{
var questionnaire = await _context.Questionnaires
.FirstOrDefaultAsync(q => q.Id == id);
if (questionnaire == null)
{
TempData["ErrorMessage"] = "Questionnaire not found.";
return RedirectToAction(nameof(Index));
}
var dashboard = await _aiAnalysisService.GenerateManagementDashboardAsync(id);
return View(dashboard);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error loading dashboard for questionnaire {QuestionnaireId}", id);
TempData["ErrorMessage"] = "Error loading dashboard. Please try again.";
return RedirectToAction(nameof(Index));
}
}
#endregion
}
}

View file

@ -1,71 +0,0 @@
@model IEnumerable<dynamic>
@{
ViewData["Title"] = "AI Analysis Results";
}
<div class="container mt-5">
<div class="card" id="Errocard">
<div class="card-header">
<h3>AI Analysis: @ViewBag.QuestionnaireName</h3>
</div>
<div class="card-body">
@if (Model.Any())
{
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>User</th>
<th>Question</th>
<th>Response</th>
<th>Sentiment</th>
<th>Risk Level</th>
<th>Key Phrases</th>
<th>Confidence Scores</th>
</tr>
</thead>
<tbody>
@foreach (var item in Model)
{
<tr class="@(item.RiskAssessment.Contains("High") ? "table-danger" :
item.RiskAssessment.Contains("Medium") ? "table-warning" : "table-success")">
<td>@item.UserName</td>
<td>@item.Question</td>
<td>@item.Response</td>
<td>
<span class="badge @(item.Sentiment == "Positive" ? "badge-success" :
item.Sentiment == "Negative" ? "badge-danger" : "badge-secondary")">
@item.Sentiment
</span>
</td>
<td>
<span class="badge @(item.RiskAssessment.Contains("High") ? "badge-danger" :
item.RiskAssessment.Contains("Medium") ? "badge-warning" : "badge-success")">
@item.RiskAssessment
</span>
</td>
<td>
@string.Join(", ", item.KeyPhrases)
</td>
<td>
<small>
Pos: @item.PositiveScore.ToString("F2")<br />
Neg: @item.NegativeScore.ToString("F2")<br />
Neu: @item.NeutralScore.ToString("F2")
</small>
</td>
</tr>
}
</tbody>
</table>
</div>
}
else
{
<p>No text responses found to analyze.</p>
}
</div>
</div>
</div>

View file

@ -1,446 +0,0 @@
@using Newtonsoft.Json
@model ResponseQuestionnaireWithUsersViewModel
@{
ViewData["Title"] = "Detailed Survey Analysis";
}
<style>
.progress {
position: relative;
height: 30px; /* Increased height for better visibility */
}
.progress-text {
position: absolute;
width: 100%;
text-align: center;
color: black; /* Ensures text is visible on light backgrounds */
font-weight: bold;
line-height: 30px; /* Align text vertically */
}
#Errocard{
box-shadow: 0px 0px 13px 0px rgb(20 101 230 / 35%);
-webkit-box-shadow: 0px 0px 13px 0px rgb(20 101 230 / 35%);
border-radius: 5px;
background-color: 0px 0px 8px -1px rgba(20,101,230,1);
height: auto;
}
.Errocard {
box-shadow: 0px 0px 13px 0px rgb(20 101 230 / 35%);
-webkit-box-shadow: 0px 0px 13px 0px rgb(20 101 230 / 35%);
border-radius: 5px;
}
/* rect{
border-radius:5px !important;
background-color:transparent !important;
} */
body {
font-family: "Poppins", Arial, sans-serif;
font-size: 14px;
line-height: 1.8;
font-weight: normal;
background: #ffffff;
color: gray;
}
img{
box-shadow: 0px 0px 13px 0px rgb(20 101 230 / 35%);
-webkit-box-shadow: 0px 0px 13px 0px rgb(20 101 230 / 35%);
border-radius: 5px;
background-color: 0px 0px 8px -1px rgba(20,101,230,1);
height: auto;
}
</style>
<div class="container mt-5 p-5">
<div class="card" id="Errocard">
<div class="card-header">
Survey Analyzer
</div>
<div class="card-body">
<h5 class="card-title font-weight-bolder ">@Model.Title</h5>
<p class="card-text">@Html.Raw(Model.Description)</p>
</div>
<div class="card-footer">
<div class="font-weight-bolder">
Total user respond <span class="badge text-bg-primary">@Model.ParticipantCount</span><br />
Participated users
@foreach (var item in Model.Users)
{
<span class="badge text-bg-primary">@item.UserName</span>
}
</div>
</div>
</div>
<div class="container mt-5 p-5">
@foreach (var question in Model.Questions)
{
<div class="text-center">
<!-- Chart container for all questions -->
<div id="chart_div_@question.Id" class="Errocard m-5 p-3" style="width:auto; height: 300px;"></div>
<!-- Image container only for image questions -->
@if (question.Type == QuestionType.Image)
{
<div id="img_div_@question.Id" class="Errocard m-5 p-3" style="width:auto; height: auto;"></div>
}
</div>
}
</div>
</div>
@* @section Scripts {
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<script type="text/javascript">
google.charts.load('current', { 'packages': ['corechart', 'bar','line','table'] }); // Ensure 'bar' package is loaded if using BarChart
google.charts.setOnLoadCallback(drawCharts);
function drawCharts() {
@foreach (var question in Model.Questions.Select((value, index) => new { Value = value, Index = index }))
{
<text>
var type = '@question.Value.Type.ToString()';
console.log('Type:', type); // Debug output to check type
var chartContainer = document.getElementById('chart_div_@question.Value.Id');
var chart; // Declare chart variable here for scope
var data = new google.visualization.DataTable();
data.addColumn('string', 'Option');
data.addColumn('number', 'Count');
</text>
if (question.Value.Type == QuestionType.Text || question.Value.Type == QuestionType.Slider || question.Value.Type == QuestionType.Open_ended)
{
<text>
console.log('Initializing Bar Chart'); // Debug output
</text>
@foreach (var item in question.Value.SelectedText)
{
<text>
data.addRow(['@Html.Raw(item)', @question.Value.Answers]);
</text>
}
<text>
chart = new google.visualization.Table(chartContainer);
</text>
} else {
<text>
console.log('Initializing Pie Chart'); // Debug output
</text>
@foreach (var answer in question.Value.Answers)
{
<text>
data.addRow(['@Html.Raw(answer.Text)', @answer.Count]);
</text>
}
<text>
chart = new google.visualization.PieChart(chartContainer);
</text>
}
<text>
var options = {
'title': 'Question @(question.Index + 1): @Html.Raw(question.Value.Text)',
is3D: type !== "Text" && type !== "Slider" && type !== "Open_ended", // Only use 3D for Pie Charts
'titleTextStyle': { color: '#17a2b8', fontSize: 12, bold: true },
'colors': ['#cc0000', '#00cc00', '#0000cc', '#cccc00', '#00cccc', '#cc00cc', '#008080', '#808000', '#800080', '#800000', '#808080', '#404040']
};
chart.draw(data, options); // Draw chart outside conditional blocks
</text>
}
}
</script>
}
*@
@section Scripts {
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<script type="text/javascript">
google.charts.load('current', { 'packages': ['corechart'] });
google.charts.setOnLoadCallback(drawCharts);
function drawCharts() {
@foreach (var question in Model.Questions.Select((value, index) => new { Value = value, Index = index }))
{
<text>
var type = '@question.Value.Type.ToString()';
var chartContainer = document.getElementById('chart_div_@question.Value.Id');
var imgContainer = (type === "Image") ? document.getElementById('img_div_@question.Value.Id') : null;
var colors = [
'#cc0000', // dark red
'#00cc00', // dark green
'#0000cc', // dark blue
'#cccc00', // dark yellow
'#00cccc', // dark cyan
'#cc00cc', // dark magenta
'#008080', // dark teal
'#808000', // olive
'#800080', // purple
'#800000', // maroon
'#808080', // gray
'#404040' // darker gray
];
var data = new google.visualization.DataTable();
data.addColumn('string', 'Option');
data.addColumn('number', 'Count');
var colorIndex = 0;
</text>
if (question.Value.Type ==QuestionType.Text || question.Value.Type ==QuestionType.Slider || question.Value.Type ==QuestionType.Open_ended)
{
@foreach (var item in question.Value.SelectedText)
{
<text>
data.addRow(['@Html.Raw(item)', 1]);
</text>
}
<text>
// Initialize Bar Chart for Text, Slider, or Open-ended questions
var chart = new google.visualization.BarChart(chartContainer);
</text>
}
else
{
@foreach (var answer in question.Value.Answers)
{
<text>
data.addRow(['@Html.Raw(answer.Text)', @answer.Count]);
</text>
}
}
<text>
var options = {
'title': 'Question @(question.Index + 1): @Html.Raw(question.Value.Text)',
is3D: true,
'titleTextStyle': { color: '#17a2b8', fontSize: 12, bold: true },
'colors': colors,
'pieSliceText': 'percentage', // Display percentages on the chart slices
'tooltip': { text: 'percentage' } // Configure tooltip to show percentage
};
if (chartContainer) {
var chart = new google.visualization.PieChart(chartContainer);
chart.draw(data, options);
}
if (imgContainer && type === "Image") {
var imagesHtml = '';
@foreach (var answer in question.Value.Answers.Where(a => !string.IsNullOrEmpty(a.Text)))
{
<text>
var color = colors[colorIndex % colors.length];
imagesHtml += '<div style="display: inline-block; text-align: center; margin: 10px;">' +
'<img src="@answer.Text" style="max-width:50%; height:auto; display: block; margin: auto;">' +
'<div style="display: flex; align-items: center; gap: 10px;">' +
'<span style="width: 12px; height: 12px; border-radius: 50%; background-color: ' + color + ';"></span>' +
'<span style="color: ' + color + ';">@Html.Raw(answer.Text)</span>' +
'</div></div>';
colorIndex++;
</text>
}
imgContainer.innerHTML = imagesHtml;
}
</text>
}
}
</script>
}
@* @section Scripts {
// <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
// <script type="text/javascript">
// google.charts.load('current', { 'packages': ['corechart'] });
// google.charts.setOnLoadCallback(drawCharts);
// function drawCharts() {
// @foreach (var question in Model.Questions.Select((value, index) => new { Value = value, Index = index }))
// {
// <text>
// var type = '@question.Value.Type.ToString()';
// var chartContainer = document.getElementById('chart_div_@question.Value.Id');
// var imgContainer = (type === "Image") ? document.getElementById('img_div_@question.Value.Id') : null;
// var colors = [
// '#cc0000', // dark red
// '#00cc00', // dark green
// '#0000cc', // dark blue
// '#cccc00', // dark yellow
// '#00cccc', // dark cyan
// '#cc00cc', // dark magenta
// '#008080', // dark teal
// '#808000', // olive
// '#800080', // purple
// '#800000', // maroon
// '#808080', // gray
// '#404040' // darker gray
// ];
// var data = new google.visualization.DataTable();
// data.addColumn('string', 'Option');
// data.addColumn('number', 'Count');
// data.addRows([]);
// var colorIndex = 0; // Initialize colorIndex here
// </text>
// @foreach (var answer in question.Value.Answers)
// {
// <text>
// data.addRow(['@Html.Raw(answer.Text)', @answer.Count]);
// </text>
// }
// <text>
// var options = {
// 'title': 'Question @(question.Index + 1): @Html.Raw(question.Value.Text)',
// is3D: true,
// 'titleTextStyle': { color: '#17a2b8', fontSize: 12, bold: true },
// 'colors': colors
// };
// if (chartContainer) {
// var chart = new google.visualization.PieChart(chartContainer);
// chart.draw(data, options);
// }
// if (imgContainer && type === "Image") {
// var imagesHtml = '';
// @foreach (var answer in question.Value.Answers.Where(a => !string.IsNullOrEmpty(a.Text)))
// {
// <text>
// var color = colors[colorIndex % colors.length]; // Correct usage of colorIndex
// imagesHtml += '<div style="display: inline-block; text-align: center; margin: 10px;">' +
// '<img src="@answer.Text" style="max-width:50%; height:auto; display: block; margin: auto;">' +
// '<div style="display: flex; align-items: center; gap: 10px;">' +
// '<span style="width: 12px; height: 12px; border-radius: 50%; background-color: ' + color + ';"></span>' +
// '<span style="color: ' + color + ';">@Html.Raw(answer.Text)</span>' +
// '</div></div>';
// colorIndex++; // Increment colorIndex within the loop
// </text>
// }
// imgContainer.innerHTML = imagesHtml;
// }
// </text>
// }
// }
// </script>
// }
@* @section Scripts {
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<script type="text/javascript">
google.charts.load('current', { 'packages': ['corechart'] });
google.charts.setOnLoadCallback(drawCharts);
function drawCharts() {
@foreach (var question in Model.Questions.Select((value, index) => new { Value = value, Index = index }))
{
<text>
var type = '@question.Value.Type.ToString()';
var chartContainer = document.getElementById('chart_div_@question.Value.Id');
var imgContainer = type === "Image" ? document.getElementById('img_div_@question.Value.Id') : null;
var data = new google.visualization.DataTable();
data.addColumn('string', 'Option');
data.addColumn('number', 'Count');
data.addRows([
</text>
@foreach (var answer in question.Value.Answers)
{
<text>['@Html.Raw(answer.Text)', @answer.Count], </text>
}
<text>
]);
var options = {
'title': 'Question @(question.Index + 1): @Html.Raw(question.Value.Text)',
is3D: true,
'titleTextStyle': { color: 'blue', fontSize: 15, bold: true }
};
if (chartContainer) {
var chart = new google.visualization.PieChart(chartContainer);
chart.draw(data, options);
}
if (imgContainer) {
var imagesHtml = '';
@foreach (var answer in question.Value.Answers.Where(a => !string.IsNullOrEmpty(a.Text)))
{
<text>imagesHtml += '<img src="@answer.Text" style="max-width:20%; height:auto; display: block; margin: 20px auto;">'; </text>
}
imgContainer.innerHTML = imagesHtml;
}
</text>
}
}
</script>
}
*@
@* @section Scripts {
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<script type="text/javascript">
google.charts.load('current', { 'packages': ['corechart'] });
google.charts.setOnLoadCallback(drawCharts);
function drawCharts() {
@foreach (var question in Model.Questions)
{
<text>
var data = new google.visualization.DataTable();
data.addColumn('string', 'Option');
data.addColumn('number', 'Count');
data.addRows([
@foreach (var answer in question.Answers)
{
<text>['@Html.Raw(answer.Text)', @answer.Count], </text>
}
]);
// Set chart options
var options = {
'title': 'Question: @Html.Raw(question.Text)',
is3D: true,
};
// Select the right container for the chart
var container = document.getElementById('chart_div_@question.Id');
if (container) {
var chart = new google.visualization.PieChart(container);
chart.draw(data, options);
}
</text>
}
}
</script>
} *@

View file

@ -0,0 +1,504 @@

@model Services.AIViewModel.QuestionnaireAnalysisOverview
@{
ViewData["Title"] = $"AI Analysis - {Model.QuestionnaireTitle}";
}
<div class="container-fluid">
<!-- Header Section -->
<div class="row mb-4">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center">
<div>
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item">
<a href="@Url.Action("Index")">
<i class="fas fa-brain"></i> Analysis Dashboard
</a>
</li>
<li class="breadcrumb-item active">@Model.QuestionnaireTitle</li>
</ol>
</nav>
<h1 class="h3 mb-1">
<i class="fas fa-chart-line text-primary me-2"></i>
AI Analysis Results
</h1>
<p class="text-muted mb-0">Comprehensive mental health analysis powered by Azure AI</p>
</div>
<div class="text-end">
<div class="btn-group" role="group">
<a href="@Url.Action("GenerateReport", new { id = Model.QuestionnaireId })"
class="btn btn-outline-primary btn-sm">
<i class="fas fa-file-alt"></i> Generate Report
</a>
<a href="@Url.Action("ExportAnalysis", new { id = Model.QuestionnaireId })"
class="btn btn-outline-info btn-sm">
<i class="fas fa-download"></i> Export Data
</a>
<a href="@Url.Action("HighRiskResponses", new { id = Model.QuestionnaireId })"
class="btn btn-outline-danger btn-sm">
<i class="fas fa-exclamation-triangle"></i> High Risk
</a>
</div>
</div>
</div>
</div>
</div>
<!-- Key Metrics Overview -->
<div class="row mb-4">
<div class="col-lg-3 col-md-6 mb-3">
<div class="card border-0 shadow-sm h-100">
<div class="card-body text-center">
<div class="text-primary mb-2">
<i class="fas fa-users fa-2x"></i>
</div>
<h4 class="mb-0">@Model.TotalResponses</h4>
<small class="text-muted">Total Responses</small>
</div>
</div>
</div>
<div class="col-lg-3 col-md-6 mb-3">
<div class="card border-0 shadow-sm h-100">
<div class="card-body text-center">
<div class="text-info mb-2">
<i class="fas fa-brain fa-2x"></i>
</div>
<h4 class="mb-0">@Model.AnalyzedResponses</h4>
<small class="text-muted">AI Analyzed</small>
</div>
</div>
</div>
<div class="col-lg-3 col-md-6 mb-3">
<div class="card border-0 shadow-sm h-100">
<div class="card-body text-center">
<div class="text-success mb-2">
<i class="fas fa-smile fa-2x"></i>
</div>
<h4 class="mb-0">@Math.Round(Model.OverallPositiveSentiment * 100, 1)%</h4>
<small class="text-muted">Positive Sentiment</small>
</div>
</div>
</div>
<div class="col-lg-3 col-md-6 mb-3">
<div class="card border-0 shadow-sm h-100">
<div class="card-body text-center">
<div class="@(Model.CriticalRiskResponses > 0 ? "text-danger" : Model.HighRiskResponses > 0 ? "text-warning" : "text-success") mb-2">
<i class="fas fa-shield-alt fa-2x"></i>
</div>
<h4 class="mb-0">@(Model.HighRiskResponses + Model.CriticalRiskResponses)</h4>
<small class="text-muted">High/Critical Risk</small>
</div>
</div>
</div>
</div>
<!-- Risk Distribution & Sentiment Analysis -->
<div class="row mb-4">
<div class="col-lg-6 mb-4">
<div class="card border-0 shadow-sm h-100">
<div class="card-header bg-white border-bottom">
<h5 class="mb-0">
<i class="fas fa-shield-alt me-2 text-primary"></i>
Mental Health Risk Distribution
</h5>
</div>
<div class="card-body">
@if (Model.AnalyzedResponses > 0)
{
<div class="mb-3">
<div class="d-flex justify-content-between align-items-center mb-2">
<span class="text-success">
<i class="fas fa-circle me-1"></i>Low Risk
</span>
<strong>@Model.LowRiskResponses</strong>
</div>
<div class="progress mb-3" style="height: 10px;">
<div class="progress-bar bg-success"
style="width: @(Model.AnalyzedResponses > 0 ? (Model.LowRiskResponses * 100.0 / Model.AnalyzedResponses) : 0)%">
</div>
</div>
</div>
<div class="mb-3">
<div class="d-flex justify-content-between align-items-center mb-2">
<span class="text-warning">
<i class="fas fa-circle me-1"></i>Moderate Risk
</span>
<strong>@Model.ModerateRiskResponses</strong>
</div>
<div class="progress mb-3" style="height: 10px;">
<div class="progress-bar bg-warning"
style="width: @(Model.AnalyzedResponses > 0 ? (Model.ModerateRiskResponses * 100.0 / Model.AnalyzedResponses) : 0)%">
</div>
</div>
</div>
<div class="mb-3">
<div class="d-flex justify-content-between align-items-center mb-2">
<span class="text-danger">
<i class="fas fa-circle me-1"></i>High Risk
</span>
<strong>@Model.HighRiskResponses</strong>
</div>
<div class="progress mb-3" style="height: 10px;">
<div class="progress-bar bg-danger"
style="width: @(Model.AnalyzedResponses > 0 ? (Model.HighRiskResponses * 100.0 / Model.AnalyzedResponses) : 0)%">
</div>
</div>
</div>
<div class="mb-3">
<div class="d-flex justify-content-between align-items-center mb-2">
<span class="text-dark">
<i class="fas fa-circle me-1"></i>Critical Risk
</span>
<strong>@Model.CriticalRiskResponses</strong>
</div>
<div class="progress mb-3" style="height: 10px;">
<div class="progress-bar bg-dark"
style="width: @(Model.AnalyzedResponses > 0 ? (Model.CriticalRiskResponses * 100.0 / Model.AnalyzedResponses) : 0)%">
</div>
</div>
</div>
@if (Model.HighRiskResponses > 0 || Model.CriticalRiskResponses > 0)
{
<div class="alert alert-warning mb-0">
<i class="fas fa-exclamation-triangle me-2"></i>
<strong>@(Model.HighRiskResponses + Model.CriticalRiskResponses) responses</strong> require immediate attention.
<a href="@Url.Action("HighRiskResponses", new { id = Model.QuestionnaireId })"
class="alert-link">View details</a>
</div>
}
}
else
{
<div class="text-center text-muted">
<i class="fas fa-info-circle mb-2"></i>
<p>No risk assessment data available</p>
</div>
}
</div>
</div>
</div>
<div class="col-lg-6 mb-4">
<div class="card border-0 shadow-sm h-100">
<div class="card-header bg-white border-bottom">
<h5 class="mb-0">
<i class="fas fa-heart me-2 text-primary"></i>
Overall Sentiment Analysis
</h5>
</div>
<div class="card-body">
@if (Model.AnalyzedResponses > 0)
{
<div class="mb-4">
<div class="d-flex justify-content-between align-items-center mb-2">
<span class="text-success">
<i class="fas fa-smile me-1"></i>Positive
</span>
<strong>@Math.Round(Model.OverallPositiveSentiment * 100, 1)%</strong>
</div>
<div class="progress mb-3" style="height: 15px;">
<div class="progress-bar bg-success"
style="width: @(Model.OverallPositiveSentiment * 100)%">
</div>
</div>
</div>
<div class="mb-4">
<div class="d-flex justify-content-between align-items-center mb-2">
<span class="text-secondary">
<i class="fas fa-meh me-1"></i>Neutral
</span>
<strong>@Math.Round(Model.OverallNeutralSentiment * 100, 1)%</strong>
</div>
<div class="progress mb-3" style="height: 15px;">
<div class="progress-bar bg-secondary"
style="width: @(Model.OverallNeutralSentiment * 100)%">
</div>
</div>
</div>
<div class="mb-4">
<div class="d-flex justify-content-between align-items-center mb-2">
<span class="text-danger">
<i class="fas fa-frown me-1"></i>Negative
</span>
<strong>@Math.Round(Model.OverallNegativeSentiment * 100, 1)%</strong>
</div>
<div class="progress mb-3" style="height: 15px;">
<div class="progress-bar bg-danger"
style="width: @(Model.OverallNegativeSentiment * 100)%">
</div>
</div>
</div>
<!-- Sentiment Interpretation -->
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";
}
<div class="alert alert-light border">
<i class="fas @sentimentIcon @sentimentColor me-2"></i>
<strong class="@sentimentColor">@sentimentStatus</strong>
</div>
}
else
{
<div class="text-center text-muted">
<i class="fas fa-info-circle mb-2"></i>
<p>No sentiment analysis data available</p>
</div>
}
</div>
</div>
</div>
</div>
<!-- Executive Summary -->
@if (!string.IsNullOrEmpty(Model.ExecutiveSummary))
{
<div class="row mb-4">
<div class="col-12">
<div class="card border-0 shadow-sm">
<div class="card-header bg-primary text-white">
<h5 class="mb-0">
<i class="fas fa-clipboard-list me-2"></i>
Executive Summary
</h5>
</div>
<div class="card-body">
<div class="executive-summary">
@Html.Raw(Model.ExecutiveSummary.Replace("\n", "<br />"))
</div>
</div>
</div>
</div>
</div>
}
<!-- Top Workplace Issues -->
<div class="row mb-4">
<div class="col-lg-8 mb-4">
<div class="card border-0 shadow-sm h-100">
<div class="card-header bg-white border-bottom">
<h5 class="mb-0">
<i class="fas fa-exclamation-circle me-2 text-primary"></i>
Top Workplace Issues & Interventions
</h5>
</div>
<div class="card-body">
@if (Model.TopWorkplaceIssues != null && Model.TopWorkplaceIssues.Any())
{
@foreach (var issue in Model.TopWorkplaceIssues.Take(5))
{
<div class="border-start border-4 @GetPriorityBorderColor(issue.Priority) ps-3 mb-4">
<div class="d-flex justify-content-between align-items-start mb-2">
<h6 class="mb-0">@issue.Category</h6>
<span class="badge @GetPriorityBadgeColor(issue.Priority)">
Priority @issue.Priority
</span>
</div>
<p class="text-muted mb-2 small">@issue.Issue</p>
<div class="bg-light rounded p-2">
<strong class="small">Recommended Intervention:</strong>
<p class="mb-0 small">@issue.RecommendedIntervention</p>
</div>
@if (issue.AffectedAreas.Any())
{
<div class="mt-2">
@foreach (var area in issue.AffectedAreas)
{
<span class="badge bg-light text-dark me-1">@area</span>
}
</div>
}
</div>
}
}
else
{
<div class="text-center text-muted py-4">
<i class="fas fa-info-circle mb-2"></i>
<p>No workplace issues identified in the analysis</p>
</div>
}
</div>
</div>
</div>
<div class="col-lg-4 mb-4">
<div class="card border-0 shadow-sm h-100">
<div class="card-header bg-white border-bottom">
<h5 class="mb-0">
<i class="fas fa-tags me-2 text-primary"></i>
Common Themes
</h5>
</div>
<div class="card-body">
@if (Model.MostCommonKeyPhrases != null && Model.MostCommonKeyPhrases.Any())
{
<div class="mb-3">
<h6 class="small text-muted mb-2">MOST MENTIONED PHRASES</h6>
@foreach (var phrase in Model.MostCommonKeyPhrases.Take(8))
{
<span class="badge bg-primary me-1 mb-1">@phrase</span>
}
</div>
@if (Model.TopWorkplaceIssues.Any())
{
<div class="mb-3">
<h6 class="small text-muted mb-2">ISSUE CATEGORIES</h6>
@foreach (var category in Model.TopWorkplaceIssues.Select(i => i.Category).Distinct().Take(5))
{
<span class="badge bg-warning text-dark me-1 mb-1">@category</span>
}
</div>
}
}
else
{
<div class="text-center text-muted">
<i class="fas fa-info-circle mb-2"></i>
<p>No common themes identified</p>
</div>
}
</div>
</div>
</div>
</div>
<!-- Analysis Metadata -->
<div class="row">
<div class="col-12">
<div class="card border-0 shadow-sm">
<div class="card-body">
<div class="row align-items-center">
<div class="col-md-8">
<small class="text-muted">
<i class="fas fa-info-circle me-1"></i>
Analysis completed on @Model.LastAnalyzedAt.ToString("MMMM dd, yyyy 'at' HH:mm")
| @Model.AnalyzedResponses of @Model.TotalResponses responses analyzed
| Powered by Azure AI Services
</small>
</div>
<div class="col-md-4 text-end">
<div class="btn-group" role="group">
<a href="@Url.Action("BatchAnalyze", new { id = Model.QuestionnaireId })"
class="btn btn-outline-primary btn-sm">
<i class="fas fa-sync"></i> Refresh Analysis
</a>
<a href="@Url.Action("AnalyzeTrends", new { id = Model.QuestionnaireId })"
class="btn btn-outline-info btn-sm">
<i class="fas fa-chart-area"></i> View Trends
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@functions {
private string GetPriorityBorderColor(int priority)
{
return priority switch
{
5 => "border-danger",
4 => "border-warning",
3 => "border-primary",
2 => "border-info",
_ => "border-secondary"
};
}
private string GetPriorityBadgeColor(int priority)
{
return priority switch
{
5 => "bg-danger",
4 => "bg-warning text-dark",
3 => "bg-primary",
2 => "bg-info",
_ => "bg-secondary"
};
}
}
@section Styles {
<style>
.progress {
border-radius: 10px;
overflow: hidden;
}
.progress-bar {
transition: width 0.6s ease;
}
.executive-summary {
line-height: 1.6;
font-size: 1rem;
}
.card {
transition: transform 0.2s ease-in-out;
}
.card:hover {
transform: translateY(-1px);
}
.border-start {
border-left-width: 4px !important;
}
.badge {
font-size: 0.75em;
}
.btn-group .btn {
font-size: 0.875rem;
}
@@media (max-width: 768px) {
.btn-group {
width: 100%;
}
.btn-group .btn {
flex: 1;
}
}
</style>
}

View file

@ -0,0 +1,492 @@
@* Views/Admin/SurveyAnalysis/HighRiskResponses.cshtml *@
@model List<Services.AIViewModel.ResponseAnalysisResult>
@{
ViewData["Title"] = $"High Risk Responses - {ViewBag.QuestionnaireName}";
}
<div class="container-fluid">
<!-- Header Section -->
<div class="row mb-4">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center">
<div>
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item">
<a href="@Url.Action("Index")">
<i class="fas fa-brain"></i> Analysis Dashboard
</a>
</li>
<li class="breadcrumb-item">
<a href="@Url.Action("AnalyzeQuestionnaire", new { id = ViewBag.QuestionnaireId })">
@ViewBag.QuestionnaireName
</a>
</li>
<li class="breadcrumb-item active">High Risk Responses</li>
</ol>
</nav>
<h1 class="h3 mb-1">
<i class="fas fa-shield-alt text-danger me-2"></i>
High Risk Mental Health Cases
</h1>
<p class="text-muted mb-0">Employees requiring immediate attention and intervention</p>
</div>
<div class="text-end">
<div class="btn-group" role="group">
<a href="@Url.Action("AnalyzeQuestionnaire", new { id = ViewBag.QuestionnaireId })"
class="btn btn-outline-primary btn-sm">
<i class="fas fa-chart-line"></i> Full Analysis
</a>
<a href="@Url.Action("GenerateReport", new { id = ViewBag.QuestionnaireId })"
class="btn btn-outline-info btn-sm">
<i class="fas fa-file-medical"></i> Generate Report
</a>
</div>
</div>
</div>
</div>
</div>
<!-- Alert Banner -->
@if (Model != null && Model.Any())
{
var criticalCount = Model.Count(r => r.RiskAssessment?.RiskLevel == Services.AIViewModel.RiskLevel.Critical);
var highCount = Model.Count(r => r.RiskAssessment?.RiskLevel == Services.AIViewModel.RiskLevel.High);
var immediateAttentionCount = Model.Count(r => r.RiskAssessment?.RequiresImmediateAttention == true);
<div class="row mb-4">
<div class="col-12">
<div class="alert alert-danger border-0 shadow-sm">
<div class="row align-items-center">
<div class="col-md-8">
<h5 class="alert-heading mb-2">
<i class="fas fa-exclamation-triangle me-2"></i>
Mental Health Alert: @Model.Count Cases Requiring Attention
</h5>
<p class="mb-0">
@if (criticalCount > 0)
{
<span class="badge bg-dark me-2">@criticalCount Critical</span>
}
@if (highCount > 0)
{
<span class="badge bg-danger me-2">@highCount High Risk</span>
}
@if (immediateAttentionCount > 0)
{
<span class="badge bg-warning text-dark me-2">@immediateAttentionCount Immediate Attention</span>
}
<small class="text-muted">| Professional intervention recommended</small>
</p>
</div>
<div class="col-md-4 text-end">
<i class="fas fa-user-md fa-3x text-white-50"></i>
</div>
</div>
</div>
</div>
</div>
}
<!-- High Risk Cases -->
@if (Model != null && Model.Any())
{
<div class="row">
@foreach (var response in Model.OrderByDescending(r => r.RiskAssessment?.RiskScore ?? 0))
{
var riskLevel = response.RiskAssessment?.RiskLevel ?? Services.AIViewModel.RiskLevel.Low;
var riskScore = response.RiskAssessment?.RiskScore ?? 0;
var requiresAttention = response.RiskAssessment?.RequiresImmediateAttention ?? false;
<div class="col-lg-6 mb-4">
<div class="card border-0 shadow-sm h-100 @(requiresAttention ? "border-warning border-2" : "")">
<!-- Card Header -->
<div class="card-header @GetRiskHeaderClass(riskLevel) text-white">
<div class="d-flex justify-content-between align-items-center">
<div>
<h6 class="mb-0">
<i class="fas @GetRiskIcon(riskLevel) me-2"></i>
@riskLevel Risk Level
</h6>
<small class="opacity-75">Response ID: #@response.ResponseId</small>
</div>
<div class="text-end">
<div class="risk-score-badge">
<span class="badge bg-white text-dark">
Risk Score: @Math.Round(riskScore * 100, 0)%
</span>
</div>
@if (requiresAttention)
{
<div class="mt-1">
<span class="badge bg-warning text-dark">
<i class="fas fa-bell fa-xs"></i> Immediate
</span>
</div>
}
</div>
</div>
</div>
<!-- Card Body -->
<div class="card-body">
<!-- Question Context -->
<div class="mb-3">
<h6 class="text-muted mb-2">
<i class="fas fa-question-circle me-1"></i>Question Context
</h6>
<p class="small bg-light rounded p-2 mb-0">@response.QuestionText</p>
</div>
<!-- Response Preview (Anonymized) -->
<div class="mb-3">
<h6 class="text-muted mb-2">
<i class="fas fa-comment me-1"></i>Response (Privacy Protected)
</h6>
<div class="response-preview bg-light rounded p-3">
<p class="mb-0">@(response.AnonymizedResponseText?.Length > 150 ? response.AnonymizedResponseText.Substring(0, 150) + "..." : response.AnonymizedResponseText)</p>
</div>
</div>
<!-- Risk Indicators -->
@if (response.RiskAssessment?.RiskIndicators?.Any() == true)
{
<div class="mb-3">
<h6 class="text-muted mb-2">
<i class="fas fa-warning me-1 text-danger"></i>Risk Indicators
</h6>
<div>
@foreach (var indicator in response.RiskAssessment.RiskIndicators.Take(3))
{
<span class="badge bg-danger me-1 mb-1">@indicator</span>
}
</div>
</div>
}
<!-- Recommended Action -->
@if (!string.IsNullOrEmpty(response.RiskAssessment?.RecommendedAction))
{
<div class="mb-3">
<h6 class="text-muted mb-2">
<i class="fas fa-clipboard-check me-1 text-success"></i>Recommended Action
</h6>
<div class="bg-success bg-opacity-10 border border-success border-opacity-25 rounded p-2">
<p class="mb-0 small text-success">@response.RiskAssessment.RecommendedAction</p>
</div>
</div>
}
<!-- Workplace Insights -->
@if (response.Insights?.Any() == true)
{
<div class="mb-3">
<h6 class="text-muted mb-2">
<i class="fas fa-lightbulb me-1 text-warning"></i>Key Insights
</h6>
@foreach (var insight in response.Insights.Take(2))
{
<div class="border-start border-3 border-primary ps-2 mb-2">
<small class="fw-bold text-primary">@insight.Category</small>
<p class="mb-0 small">@insight.RecommendedIntervention</p>
</div>
}
</div>
}
<!-- Protective Factors (if any) -->
@if (response.RiskAssessment?.ProtectiveFactors?.Any() == true)
{
<div class="mb-3">
<h6 class="text-muted mb-2">
<i class="fas fa-shield me-1 text-success"></i>Protective Factors
</h6>
<div>
@foreach (var factor in response.RiskAssessment.ProtectiveFactors.Take(3))
{
<span class="badge bg-success me-1 mb-1">@factor</span>
}
</div>
</div>
}
</div>
<!-- Card Footer -->
<div class="card-footer bg-white border-top">
<div class="d-flex justify-content-between align-items-center">
<div>
<small class="text-muted">
<i class="fas fa-clock me-1"></i>
Analyzed: @response.AnalyzedAt.ToString("MMM dd, HH:mm")
</small>
</div>
<div class="btn-group" role="group">
<a href="@Url.Action("ViewHighRiskResponse", new { questionnaireId = ViewBag.QuestionnaireId, responseId = response.ResponseId })"
class="btn btn-outline-primary btn-sm">
<i class="fas fa-eye"></i> View Details
</a>
@if (requiresAttention)
{
<button type="button" class="btn btn-warning btn-sm" onclick="markAsReviewed(@response.ResponseId)">
<i class="fas fa-check"></i> Mark Reviewed
</button>
}
</div>
</div>
</div>
</div>
</div>
}
</div>
<!-- Action Panel -->
<div class="row mt-4">
<div class="col-12">
<div class="card border-0 shadow-sm">
<div class="card-header bg-primary text-white">
<h5 class="mb-0">
<i class="fas fa-user-md me-2"></i>
Mental Health Professional Actions
</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-4 mb-3">
<h6><i class="fas fa-phone text-success me-2"></i>Immediate Actions</h6>
<ul class="small mb-0">
<li>Contact employees with Critical/High risk levels</li>
<li>Schedule follow-up conversations</li>
<li>Refer to mental health professionals if needed</li>
</ul>
</div>
<div class="col-md-4 mb-3">
<h6><i class="fas fa-clipboard-list text-info me-2"></i>Documentation</h6>
<ul class="small mb-0">
<li>Document interventions taken</li>
<li>Track response to interventions</li>
<li>Update risk assessments as needed</li>
</ul>
</div>
<div class="col-md-4 mb-3">
<h6><i class="fas fa-users text-warning me-2"></i>Organizational</h6>
<ul class="small mb-0">
<li>Alert management to workplace issues</li>
<li>Implement preventive measures</li>
<li>Schedule team interventions</li>
</ul>
</div>
</div>
<hr>
<div class="text-center">
<button type="button" class="btn btn-success me-2" onclick="exportHighRiskReport()">
<i class="fas fa-file-medical"></i> Export Professional Report
</button>
<button type="button" class="btn btn-info me-2" onclick="scheduleFollowUps()">
<i class="fas fa-calendar-plus"></i> Schedule Follow-ups
</button>
<a href="@Url.Action("AnalyzeTrends", new { id = ViewBag.QuestionnaireId })" class="btn btn-outline-primary">
<i class="fas fa-chart-area"></i> View Trends
</a>
</div>
</div>
</div>
</div>
</div>
}
else
{
<!-- No High Risk Cases -->
<div class="row">
<div class="col-12">
<div class="card border-0 shadow-sm">
<div class="card-body text-center py-5">
<div class="text-success mb-4">
<i class="fas fa-shield-alt" style="font-size: 4rem;"></i>
</div>
<h4 class="text-success mb-3">Excellent Mental Health Status</h4>
<p class="text-muted mb-4">
No high-risk or critical mental health cases were identified in this survey analysis.
This indicates a generally positive workplace mental health environment.
</p>
<div class="btn-group" role="group">
<a href="@Url.Action("AnalyzeQuestionnaire", new { id = ViewBag.QuestionnaireId })"
class="btn btn-primary">
<i class="fas fa-chart-line"></i> View Full Analysis
</a>
<a href="@Url.Action("AnalyzeTrends", new { id = ViewBag.QuestionnaireId })"
class="btn btn-outline-info">
<i class="fas fa-chart-area"></i> View Trends
</a>
<a href="@Url.Action("GenerateReport", new { id = ViewBag.QuestionnaireId })"
class="btn btn-outline-success">
<i class="fas fa-file-alt"></i> Generate Report
</a>
</div>
</div>
</div>
</div>
</div>
}
</div>
@functions {
private string GetRiskHeaderClass(Services.AIViewModel.RiskLevel riskLevel)
{
switch (riskLevel)
{
case Services.AIViewModel.RiskLevel.Critical:
return "bg-dark";
case Services.AIViewModel.RiskLevel.High:
return "bg-danger";
case Services.AIViewModel.RiskLevel.Moderate:
return "bg-warning";
default:
return "bg-secondary";
}
}
private string GetRiskIcon(Services.AIViewModel.RiskLevel riskLevel)
{
switch (riskLevel)
{
case Services.AIViewModel.RiskLevel.Critical:
return "fa-exclamation-triangle";
case Services.AIViewModel.RiskLevel.High:
return "fa-shield-alt";
case Services.AIViewModel.RiskLevel.Moderate:
return "fa-info-circle";
default:
return "fa-check-circle";
}
}
}
@section Scripts {
<script>
// Mark response as reviewed
function markAsReviewed(responseId) {
Swal.fire({
title: 'Mark as Reviewed',
text: 'Confirm that this high-risk case has been professionally reviewed and appropriate action taken.',
icon: 'question',
showCancelButton: true,
confirmButtonColor: '#28a745',
cancelButtonColor: '#6c757d',
confirmButtonText: 'Yes, Mark as Reviewed',
cancelButtonText: 'Cancel'
}).then((result) => {
if (result.isConfirmed) {
// In a real implementation, this would make an API call
// to update the response status in the database
Swal.fire({
title: 'Marked as Reviewed',
text: 'The case has been marked as reviewed. Please ensure proper documentation.',
icon: 'success',
timer: 2000,
showConfirmButton: false
});
// Optionally remove the "Immediate" badge or update the card
// In a real implementation, you might refresh the page or update the UI
}
});
}
// Export high risk report
function exportHighRiskReport() {
window.location.href = '@Url.Action("DownloadReport", new { id = ViewBag.QuestionnaireId })';
}
// Schedule follow-ups
function scheduleFollowUps() {
Swal.fire({
title: 'Schedule Follow-ups',
text: 'This feature would integrate with your calendar system to schedule follow-up meetings with high-risk individuals.',
icon: 'info',
confirmButtonText: 'OK'
});
}
// Auto-refresh every 10 minutes to check for new high-risk cases
setInterval(function() {
// In a real implementation, you might check for new cases via AJAX
// and show a notification if new high-risk responses have been submitted
}, 600000);
</script>
}
@section Styles {
<style>
.response-preview {
font-style: italic;
line-height: 1.5;
}
.risk-score-badge {
font-size: 0.875rem;
}
.border-2 {
border-width: 2px !important;
}
.card {
transition: all 0.2s ease-in-out;
}
.card:hover {
transform: translateY(-2px);
}
.badge {
font-size: 0.75em;
}
.border-start {
border-left-width: 3px !important;
}
.opacity-75 {
opacity: 0.75;
}
.bg-opacity-10 {
--bs-bg-opacity: 0.1;
}
.border-opacity-25 {
--bs-border-opacity: 0.25;
}
@@media (max-width: 768px) {
.btn-group {
width: 100%;
}
.btn-group .btn {
flex: 1;
font-size: 0.8rem;
}
}
/* Pulse animation for immediate attention cases */
.border-warning.border-2 {
animation: pulse-border 2s infinite;
}
@@keyframes pulse-border {
0% {
box-shadow: 0 0 0 0 rgba(255, 193, 7, 0.7);
}
70% {
box-shadow: 0 0 0 10px rgba(255, 193, 7, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(255, 193, 7, 0);
}
}
</style>
}

View file

@ -1,64 +1,446 @@
@model IEnumerable<ResponseQuestionnaireWithUsersViewModel> @* Views/Admin/SurveyAnalysis/Index.cshtml *@
@model IEnumerable<dynamic>
@{ @{
ViewData["Title"] = "Survey Analysis"; ViewData["Title"] = "Mental Health Survey Analysis - Dashboard";
} }
<div class="container-fluid mt-5">
<partial name="_Notification" /> <div class="container-fluid">
<!-- Header Section -->
<div class="row mb-4">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center">
<div>
<h1 class="h3 mb-1">
<i class="fas fa-brain text-primary me-2"></i>
Mental Health Survey Analysis
</h1>
<p class="text-muted">AI-powered analysis of workplace mental health surveys</p>
</div>
<div class="text-end">
<small class="text-muted">
<i class="fas fa-user-md"></i> NVKN Nærværskonsulenterne
</small>
</div>
</div>
</div>
</div>
<!-- Service Health Status -->
@if (ViewBag.ServiceHealth != null)
{
var serviceHealth = ViewBag.ServiceHealth as Dictionary<string, bool>;
<div class="row mb-4">
<div class="col-12">
<div class="card border-0 shadow-sm">
<div class="card-body py-3">
<div class="row align-items-center">
<div class="col-md-6">
<h6 class="mb-0">
<i class="fas fa-heartbeat me-2"></i>AI Services Status
</h6>
</div>
<div class="col-md-6">
<div class="d-flex justify-content-end gap-3">
@foreach (var service in serviceHealth)
{
<div class="d-flex align-items-center">
<span class="badge @(service.Value ? "bg-success" : "bg-danger") me-2">
<i class="fas @(service.Value ? "fa-check" : "fa-times")"></i>
</span>
<small class="text-muted">@service.Key.Replace("Azure", "")</small>
</div>
}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
}
<div class="card bg-default mb-3 "> <!-- Alert Messages -->
<div class="card-header">Survey analysis</div> @if (TempData["ErrorMessage"] != null)
<div class="card-body"> {
<h4 class="card-title">Survey analysis list</h4> <div class="alert alert-danger alert-dismissible fade show" role="alert">
<i class="fas fa-exclamation-triangle me-2"></i>
@TempData["ErrorMessage"]
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
}
@if (TempData["SuccessMessage"] != null)
{
<div class="alert alert-success alert-dismissible fade show" role="alert">
<i class="fas fa-check-circle me-2"></i>
@TempData["SuccessMessage"]
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
}
<form asp-action="DeleteUnusedQuestionnaires" method="post"> @if (TempData["WarningMessage"] != null)
{
<div class="alert alert-warning alert-dismissible fade show" role="alert">
<i class="fas fa-exclamation-circle me-2"></i>
@TempData["WarningMessage"]
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
}
<table class="table table-responsive w-100 d-block d-md-table table-hover "> <!-- Quick Actions -->
<thead class="w-100"> <div class="row mb-4">
<tr> <div class="col-12">
<th><input type="checkbox" onclick="selectAll(this)" /></th> <!-- Master checkbox --> <div class="card border-0 shadow-sm">
<th>Id</th> <div class="card-body">
<th>Questionnaire</th> <div class="row">
<th class="text-end">Action</th> <div class="col-md-6">
</tr> <h6 class="mb-3">
</thead> <i class="fas fa-tools me-2"></i>Quick Actions
<tbody class="w-100"> </h6>
@foreach (var item in Model) <button type="button" class="btn btn-outline-primary btn-sm me-2" onclick="testAIServices()">
{ <i class="fas fa-vial"></i> Test AI Services
<tr> </button>
<td><input type="checkbox" name="ids" value="@item.Id" /></td> <button type="button" class="btn btn-outline-info btn-sm" data-bs-toggle="modal" data-bs-target="#testAnalysisModal">
<td>@item.Id</td> <i class="fas fa-microscope"></i> Test Analysis
<td>@item.Title</td> </button>
</div>
<div class="col-md-6 text-end">
<small class="text-muted">
Last updated: @DateTime.Now.ToString("yyyy-MM-dd HH:mm")
</small>
</div>
</div>
</div>
</div>
</div>
</div>
<td class="text-end"> <!-- Questionnaires Grid -->
<a asp-action="Analysis" asp-route-id="@item.Id" class="btn btn-info btn-sm"><i class="bi bi-graph-up-arrow"></i> Analyzer</a> <div class="row">
<a asp-action="AiAnalysis" asp-route-id="@item.Id" class="btn btn-primary btn-sm ms-1"><i class="bi bi-brain"></i> AI Analysis</a> @if (Model != null && Model.Any())
</td> {
</tr> @foreach (var questionnaire in Model)
} {
</tbody> <div class="col-lg-6 col-xl-4 mb-4">
</table> <div class="card border-0 shadow-sm h-100">
<div class="card-header bg-white border-bottom">
<div class="d-flex justify-content-between align-items-start">
<div>
<h6 class="mb-1 fw-bold">@questionnaire.Title</h6>
<small class="text-muted">@questionnaire.Description</small>
</div>
<div class="dropdown">
<button class="btn btn-link btn-sm text-muted" type="button" data-bs-toggle="dropdown">
<i class="fas fa-ellipsis-v"></i>
</button>
<ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item" href="@Url.Action("AnalyzeQuestionnaire", new { id = questionnaire.Id })">
<i class="fas fa-chart-line me-2"></i>Full Analysis
</a></li>
<li><a class="dropdown-item" href="@Url.Action("HighRiskResponses", new { id = questionnaire.Id })">
<i class="fas fa-exclamation-triangle me-2 text-danger"></i>High Risk
</a></li>
<li><a class="dropdown-item" href="@Url.Action("Dashboard", new { id = questionnaire.Id })">
<i class="fas fa-tachometer-alt me-2"></i>Dashboard
</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="@Url.Action("GenerateReport", new { id = questionnaire.Id })">
<i class="fas fa-file-alt me-2"></i>Generate Report
</a></li>
</ul>
</div>
</div>
</div>
<button type="submit" class="btn btn-danger">Delete Selected</button> <div class="card-body">
</form> <!-- Statistics -->
<div class="row text-center mb-3">
<div class="col-4">
<div class="border-end">
<h5 class="mb-0 text-primary">@questionnaire.QuestionCount</h5>
<small class="text-muted">Questions</small>
</div>
</div>
<div class="col-4">
<div class="border-end">
<h5 class="mb-0 text-success">@questionnaire.ResponseCount</h5>
<small class="text-muted">Responses</small>
</div>
</div>
<div class="col-4">
<h5 class="mb-0 text-info">@questionnaire.TextResponseCount</h5>
<small class="text-muted">Text Answers</small>
</div>
</div>
<!-- Last Response -->
@if (questionnaire.LastResponse != null && questionnaire.LastResponse != DateTime.MinValue)
{
<div class="text-center mb-3">
<small class="text-muted">
<i class="fas fa-clock me-1"></i>
Last response: @((DateTime)questionnaire.LastResponse).ToString("MMM dd, yyyy")
</small>
</div>
}
<!-- Analysis Status -->
@if (questionnaire.TextResponseCount > 0)
{
<div class="text-center">
<span class="badge bg-success mb-2">
<i class="fas fa-check me-1"></i>Ready for Analysis
</span>
</div>
}
else
{
<div class="text-center">
<span class="badge bg-secondary mb-2">
<i class="fas fa-info me-1"></i>No Text Responses
</span>
</div>
}
</div>
<div class="card-footer bg-white border-top-0 pt-0">
<div class="d-grid gap-2">
@if (questionnaire.TextResponseCount > 0)
{
<a href="@Url.Action("AnalyzeQuestionnaire", new { id = questionnaire.Id })"
class="btn btn-primary btn-sm">
<i class="fas fa-brain me-2"></i>Start AI Analysis
</a>
<div class="btn-group" role="group">
<a href="@Url.Action("HighRiskResponses", new { id = questionnaire.Id })"
class="btn btn-outline-danger btn-sm">
<i class="fas fa-shield-alt"></i> High Risk
</a>
<a href="@Url.Action("BatchAnalyze", new { id = questionnaire.Id })"
class="btn btn-outline-info btn-sm">
<i class="fas fa-layer-group"></i> Batch Process
</a>
<a href="@Url.Action("AnalyzeTrends", new { id = questionnaire.Id })"
class="btn btn-outline-success btn-sm">
<i class="fas fa-chart-area"></i> Trends
</a>
</div>
}
else
{
<button class="btn btn-secondary btn-sm" disabled>
<i class="fas fa-ban me-2"></i>No Text Data Available
</button>
}
</div>
</div>
</div>
</div>
}
}
else
{
<div class="col-12">
<div class="card border-0 shadow-sm">
<div class="card-body text-center py-5">
<i class="fas fa-clipboard-list text-muted mb-3" style="font-size: 3rem;"></i>
<h5 class="text-muted">No Questionnaires Found</h5>
<p class="text-muted mb-4">There are no questionnaires available for analysis at the moment.</p>
<a href="@Url.Action("Index", "Questionnaire")" class="btn btn-primary">
<i class="fas fa-plus me-2"></i>Create New Questionnaire
</a>
</div>
</div>
</div>
}
</div>
</div>
<!-- Test Analysis Modal -->
<div class="modal fade" id="testAnalysisModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
<i class="fas fa-microscope me-2"></i>Test AI Analysis
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="testAnalysisForm">
<div class="mb-3">
<label for="sampleText" class="form-label">Sample Response Text</label>
<textarea class="form-control" id="sampleText" rows="4"
placeholder="Enter sample employee response for testing AI analysis..."></textarea>
<div class="form-text">
Example: "I feel overwhelmed with my workload and have trouble sleeping due to work stress."
</div>
</div>
</form>
<div id="testResults" class="d-none">
<hr>
<h6><i class="fas fa-chart-bar me-2"></i>Analysis Results</h6>
<div id="testResultsContent"></div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" onclick="performTestAnalysis()">
<i class="fas fa-play me-2"></i>Run Analysis
</button>
</div>
</div> </div>
</div> </div>
</div> </div>
@section Scripts { @section Scripts {
<script> <script>
function selectAll(source) { // Test AI Services
checkboxes = document.getElementsByName('ids'); function testAIServices() {
for (var i = 0, n = checkboxes.length; i < n; i++) { $.get('@Url.Action("ServiceHealth")')
checkboxes[i].checked = source.checked; .done(function(data) {
if (data.success && data.services) {
let status = '';
for (let service in data.services) {
let icon = data.services[service] ? 'fa-check text-success' : 'fa-times text-danger';
let statusText = data.services[service] ? 'Online ✓' : 'Offline ✗';
status += `<div class="mb-2"><i class="fas ${icon} me-2"></i><strong>${service.replace('Azure', '')}:</strong> ${statusText}</div>`;
}
Swal.fire({
title: 'AI Services Health Check',
html: `<div class="text-start">${status}</div><hr><small class="text-muted">${data.message}</small>`,
icon: 'info',
confirmButtonText: 'OK',
width: '400px'
});
} else {
Swal.fire({
title: 'Service Health Error',
text: data.error || 'Unable to check service status',
icon: 'error',
confirmButtonText: 'OK'
});
}
})
.fail(function() {
Swal.fire({
title: 'Connection Error',
text: 'Unable to connect to service health endpoint',
icon: 'error',
confirmButtonText: 'OK'
});
});
}
// Perform Test Analysis
function performTestAnalysis() {
const sampleText = document.getElementById('sampleText').value.trim();
if (!sampleText) {
Swal.fire('Warning', 'Please enter sample text for analysis', 'warning');
return;
}
// Show loading
const resultsDiv = document.getElementById('testResults');
const contentDiv = document.getElementById('testResultsContent');
contentDiv.innerHTML = '<div class="text-center"><i class="fas fa-spinner fa-spin"></i> Analyzing...</div>';
resultsDiv.classList.remove('d-none');
$.post('@Url.Action("TestAnalysis")', { sampleText: sampleText })
.done(function(data) {
if (data.success) {
let html = `
<div class="row">
<div class="col-md-6">
<strong>Sentiment:</strong>
<span class="badge bg-${getSentimentColor(data.sentiment)}">${data.sentiment || 'N/A'}</span>
</div>
<div class="col-md-6">
<strong>Risk Level:</strong>
<span class="badge bg-${getRiskColor(data.riskLevel)}">${data.riskLevel || 'N/A'}</span>
</div>
</div>
<div class="mt-3">
<strong>Key Phrases:</strong><br>
${data.keyPhrases ? data.keyPhrases.map(phrase => `<span class="badge bg-light text-dark me-1">${phrase}</span>`).join('') : 'None'}
</div>
<div class="mt-3">
<strong>Categories:</strong><br>
${data.insights ? data.insights.map(cat => `<span class="badge bg-info me-1">${cat}</span>`).join('') : 'None'}
</div>
`;
contentDiv.innerHTML = html;
} else {
contentDiv.innerHTML = `<div class="alert alert-danger">${data.message}</div>`;
}
})
.fail(function() {
contentDiv.innerHTML = '<div class="alert alert-danger">Error performing analysis</div>';
});
}
function getSentimentColor(sentiment) {
switch(sentiment?.toLowerCase()) {
case 'positive': return 'success';
case 'negative': return 'danger';
case 'neutral': return 'secondary';
default: return 'secondary';
} }
} }
</script>
function getRiskColor(riskLevel) {
switch(riskLevel?.toLowerCase()) {
case 'low': return 'success';
case 'moderate': return 'warning';
case 'high': return 'danger';
case 'critical': return 'dark';
default: return 'secondary';
}
}
// Auto-refresh service health every 5 minutes
setInterval(function() {
location.reload();
}, 300000);
</script>
} }
@section Styles {
<style>
.card {
transition: transform 0.2s ease-in-out;
}
.card:hover {
transform: translateY(-2px);
}
.border-end {
border-right: 1px solid #dee2e6 !important;
}
@@media (max-width: 768px) {
.border-end {
border-right: none !important;
border-bottom: 1px solid #dee2e6 !important;
padding-bottom: 1rem;
margin-bottom: 1rem;
}
}
.badge {
font-size: 0.75em;
}
.btn-sm {
font-size: 0.8rem;
}
</style>
}

View file

@ -0,0 +1,699 @@

@model List<Services.AIViewModel.ResponseAnalysisResult>
@{
var response = ViewBag.Response as Model.Response;
ViewData["Title"] = $"High Risk Case Details - Response #{response?.Id}";
}
<div class="container-fluid">
<!-- Header Section -->
<div class="row mb-4">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center">
<div>
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item">
<a href="@Url.Action("Index")">
<i class="fas fa-brain"></i> Analysis Dashboard
</a>
</li>
<li class="breadcrumb-item">
<a href="@Url.Action("AnalyzeQuestionnaire", new { id = response?.QuestionnaireId })">
@response?.Questionnaire?.Title
</a>
</li>
<li class="breadcrumb-item">
<a href="@Url.Action("HighRiskResponses", new { id = response?.QuestionnaireId })">
High Risk Cases
</a>
</li>
<li class="breadcrumb-item active">Response #@response?.Id</li>
</ol>
</nav>
<h1 class="h3 mb-1">
<i class="fas fa-user-injured text-danger me-2"></i>
High Risk Case Analysis
</h1>
<p class="text-muted mb-0">Detailed mental health assessment requiring professional intervention</p>
</div>
<div class="text-end">
<div class="btn-group" role="group">
<button type="button" class="btn btn-success btn-sm" onclick="markForIntervention()">
<i class="fas fa-user-md"></i> Schedule Intervention
</button>
<button type="button" class="btn btn-warning btn-sm" onclick="flagForReview()">
<i class="fas fa-flag"></i> Flag for Review
</button>
<button type="button" class="btn btn-info btn-sm" onclick="exportCaseReport()">
<i class="fas fa-file-medical"></i> Export Case
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Critical Alert Banner -->
@if (Model.Any(r => r.RiskAssessment?.RequiresImmediateAttention == true))
{
<div class="row mb-4">
<div class="col-12">
<div class="alert alert-danger border-0 shadow-lg">
<div class="row align-items-center">
<div class="col-md-10">
<h4 class="alert-heading mb-2">
<i class="fas fa-exclamation-triangle me-2"></i>
<strong>IMMEDIATE ATTENTION REQUIRED</strong>
</h4>
<p class="mb-2">This employee's responses indicate significant mental health concerns that require urgent professional intervention.</p>
<div class="d-flex gap-2 flex-wrap">
<span class="badge bg-white text-danger fw-bold">HIGH PRIORITY</span>
<span class="badge bg-white text-danger">PROFESSIONAL INTERVENTION</span>
<span class="badge bg-white text-danger">CONFIDENTIAL</span>
</div>
</div>
<div class="col-md-2 text-end">
<i class="fas fa-heartbeat fa-3x text-white opacity-50"></i>
</div>
</div>
</div>
</div>
</div>
}
<!-- Employee Response Overview -->
@if (response != null)
{
<div class="row mb-4">
<div class="col-lg-8">
<div class="card border-0 shadow-sm">
<div class="card-header bg-primary text-white">
<h5 class="mb-0">
<i class="fas fa-user me-2"></i>
Employee Response Overview
</h5>
</div>
<div class="card-body">
<div class="row mb-3">
<div class="col-md-4">
<strong>Response ID:</strong><br>
<span class="text-muted">#@response.Id</span>
</div>
<div class="col-md-4">
<strong>Submission Date:</strong><br>
<span class="text-muted">@response.SubmissionDate.ToString("MMMM dd, yyyy")</span>
</div>
<div class="col-md-4">
<strong>Survey:</strong><br>
<span class="text-muted">@response.Questionnaire?.Title</span>
</div>
</div>
<div class="alert alert-info border-0">
<i class="fas fa-shield-alt me-2"></i>
<strong>Privacy Notice:</strong> All personal information has been anonymized to protect employee privacy while enabling professional mental health assessment.
</div>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="card border-0 shadow-sm">
<div class="card-header bg-warning text-dark">
<h6 class="mb-0">
<i class="fas fa-clipboard-check me-2"></i>
Action Checklist
</h6>
</div>
<div class="card-body">
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" id="reviewed">
<label class="form-check-label small" for="reviewed">
Case reviewed by mental health professional
</label>
</div>
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" id="contacted">
<label class="form-check-label small" for="contacted">
Employee contacted for follow-up
</label>
</div>
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" id="intervention">
<label class="form-check-label small" for="intervention">
Intervention plan created
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="documented">
<label class="form-check-label small" for="documented">
Actions documented in system
</label>
</div>
</div>
</div>
</div>
</div>
}
<!-- Detailed Analysis by Question -->
@if (Model != null && Model.Any())
{
<div class="row">
@foreach (var analysis in Model.OrderByDescending(a => a.RiskAssessment?.RiskScore ?? 0))
{
var riskLevel = analysis.RiskAssessment?.RiskLevel ?? Services.AIViewModel.RiskLevel.Low;
var riskScore = analysis.RiskAssessment?.RiskScore ?? 0;
<div class="col-12 mb-4">
<div class="card border-0 shadow-sm">
<!-- Question Header -->
<div class="card-header @GetRiskHeaderClass(riskLevel) text-white">
<div class="row align-items-center">
<div class="col-md-8">
<h6 class="mb-1">
<i class="fas fa-question-circle me-2"></i>
Question Analysis
</h6>
<p class="mb-0 opacity-75">@analysis.QuestionText</p>
</div>
<div class="col-md-4 text-end">
<div class="d-flex align-items-center justify-content-end gap-2">
<span class="badge bg-white text-dark">
<i class="fas @GetRiskIcon(riskLevel) me-1"></i>
@riskLevel Risk
</span>
<span class="badge bg-white text-dark">
@Math.Round(riskScore * 100, 0)% Risk Score
</span>
</div>
</div>
</div>
</div>
<div class="card-body">
<div class="row">
<!-- Response Text -->
<div class="col-lg-6 mb-4">
<h6 class="text-muted mb-3">
<i class="fas fa-comment-alt me-2"></i>Employee Response (Anonymized)
</h6>
<div class="bg-light rounded p-3 border-start border-4 border-primary">
<p class="mb-0 font-italic">@analysis.AnonymizedResponseText</p>
</div>
<!-- Sentiment Analysis -->
@if (analysis.SentimentAnalysis != null)
{
<div class="mt-3">
<h6 class="text-muted mb-2">
<i class="fas fa-heart me-2"></i>Emotional Sentiment
</h6>
<div class="sentiment-bars">
<div class="d-flex justify-content-between align-items-center mb-1">
<small class="text-success">Positive</small>
<small class="fw-bold">@Math.Round(analysis.SentimentAnalysis.PositiveScore * 100, 1)%</small>
</div>
<div class="progress mb-2" style="height: 8px;">
<div class="progress-bar bg-success" style="width: @(analysis.SentimentAnalysis.PositiveScore * 100)%"></div>
</div>
<div class="d-flex justify-content-between align-items-center mb-1">
<small class="text-warning">Neutral</small>
<small class="fw-bold">@Math.Round(analysis.SentimentAnalysis.NeutralScore * 100, 1)%</small>
</div>
<div class="progress mb-2" style="height: 8px;">
<div class="progress-bar bg-warning" style="width: @(analysis.SentimentAnalysis.NeutralScore * 100)%"></div>
</div>
<div class="d-flex justify-content-between align-items-center mb-1">
<small class="text-danger">Negative</small>
<small class="fw-bold">@Math.Round(analysis.SentimentAnalysis.NegativeScore * 100, 1)%</small>
</div>
<div class="progress" style="height: 8px;">
<div class="progress-bar bg-danger" style="width: @(analysis.SentimentAnalysis.NegativeScore * 100)%"></div>
</div>
</div>
</div>
}
</div>
<!-- AI Analysis Results -->
<div class="col-lg-6">
<!-- Risk Assessment -->
@if (analysis.RiskAssessment != null)
{
<div class="mb-4">
<h6 class="text-muted mb-3">
<i class="fas fa-shield-alt me-2"></i>Mental Health Risk Assessment
</h6>
<!-- Risk Indicators -->
@if (analysis.RiskAssessment.RiskIndicators?.Any() == true)
{
<div class="mb-3">
<strong class="text-danger small">RISK INDICATORS:</strong>
<div class="mt-2">
@foreach (var indicator in analysis.RiskAssessment.RiskIndicators)
{
<span class="badge bg-danger me-1 mb-1">
<i class="fas fa-exclamation-triangle me-1"></i>@indicator
</span>
}
</div>
</div>
}
<!-- Recommended Action -->
@if (!string.IsNullOrEmpty(analysis.RiskAssessment.RecommendedAction))
{
<div class="mb-3">
<strong class="text-success small">RECOMMENDED ACTION:</strong>
<div class="bg-success bg-opacity-10 border border-success border-opacity-25 rounded p-2 mt-2">
<p class="mb-0 small">@analysis.RiskAssessment.RecommendedAction</p>
</div>
</div>
}
<!-- Protective Factors -->
@if (analysis.RiskAssessment.ProtectiveFactors?.Any() == true)
{
<div class="mb-3">
<strong class="text-success small">PROTECTIVE FACTORS:</strong>
<div class="mt-2">
@foreach (var factor in analysis.RiskAssessment.ProtectiveFactors)
{
<span class="badge bg-success me-1 mb-1">
<i class="fas fa-shield me-1"></i>@factor
</span>
}
</div>
</div>
}
</div>
}
<!-- Key Phrases -->
@if (analysis.KeyPhrases?.KeyPhrases?.Any() == true)
{
<div class="mb-3">
<h6 class="text-muted mb-2">
<i class="fas fa-tags me-2"></i>Key Phrases Identified
</h6>
<div>
@foreach (var phrase in analysis.KeyPhrases.KeyPhrases.Take(6))
{
<span class="badge bg-primary me-1 mb-1">@phrase</span>
}
</div>
</div>
}
</div>
</div>
</div>
<!-- Workplace Insights -->
@if (analysis.Insights?.Any() == true)
{
<div class="card-footer bg-light">
<h6 class="text-muted mb-3">
<i class="fas fa-lightbulb me-2"></i>Workplace Intervention Recommendations
</h6>
<div class="row">
@foreach (var insight in analysis.Insights)
{
<div class="col-md-6 mb-3">
<div class="border-start border-4 @GetPriorityBorderClass(insight.Priority) ps-3">
<div class="d-flex justify-content-between align-items-start mb-1">
<strong class="small text-primary">@insight.Category</strong>
<span class="badge @GetPriorityBadgeClass(insight.Priority)">
P@insight.Priority
</span>
</div>
<p class="small text-muted mb-1">@insight.Issue</p>
<p class="small mb-0">
<i class="fas fa-arrow-right me-1 text-success"></i>
@insight.RecommendedIntervention
</p>
</div>
</div>
}
</div>
</div>
}
</div>
</div>
}
</div>
}
<!-- Professional Actions Panel -->
<div class="row mt-4">
<div class="col-12">
<div class="card border-0 shadow-sm">
<div class="card-header bg-dark text-white">
<h5 class="mb-0">
<i class="fas fa-user-md me-2"></i>
Mental Health Professional Action Plan
</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<h6 class="text-primary mb-3">
<i class="fas fa-phone me-2"></i>Immediate Actions (Next 24 Hours)
</h6>
<ul class="list-unstyled">
<li class="mb-2">
<i class="fas fa-check-circle text-success me-2"></i>
Contact employee for confidential check-in
</li>
<li class="mb-2">
<i class="fas fa-check-circle text-success me-2"></i>
Assess immediate safety and support needs
</li>
<li class="mb-2">
<i class="fas fa-check-circle text-success me-2"></i>
Provide mental health resources and contacts
</li>
<li class="mb-2">
<i class="fas fa-check-circle text-success me-2"></i>
Document initial intervention in confidential records
</li>
</ul>
</div>
<div class="col-md-6">
<h6 class="text-info mb-3">
<i class="fas fa-calendar-alt me-2"></i>Follow-up Actions (Next 7 Days)
</h6>
<ul class="list-unstyled">
<li class="mb-2">
<i class="fas fa-calendar-check text-info me-2"></i>
Schedule follow-up conversation
</li>
<li class="mb-2">
<i class="fas fa-calendar-check text-info me-2"></i>
Review workplace factors with management
</li>
<li class="mb-2">
<i class="fas fa-calendar-check text-info me-2"></i>
Implement recommended workplace interventions
</li>
<li class="mb-2">
<i class="fas fa-calendar-check text-info me-2"></i>
Assess progress and adjust support plan
</li>
</ul>
</div>
</div>
<hr>
<div class="text-center">
<div class="btn-group" role="group">
<button type="button" class="btn btn-primary" onclick="createInterventionPlan()">
<i class="fas fa-clipboard-list"></i> Create Intervention Plan
</button>
<button type="button" class="btn btn-success" onclick="scheduleFollowUp()">
<i class="fas fa-calendar-plus"></i> Schedule Follow-up
</button>
<button type="button" class="btn btn-info" onclick="contactEmployee()">
<i class="fas fa-phone"></i> Initiate Contact
</button>
<button type="button" class="btn btn-warning" onclick="alertManagement()">
<i class="fas fa-exclamation-triangle"></i> Alert Management
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@functions {
private string GetRiskHeaderClass(Services.AIViewModel.RiskLevel riskLevel)
{
switch (riskLevel)
{
case Services.AIViewModel.RiskLevel.Critical:
return "bg-dark";
case Services.AIViewModel.RiskLevel.High:
return "bg-danger";
case Services.AIViewModel.RiskLevel.Moderate:
return "bg-warning";
default:
return "bg-secondary";
}
}
private string GetRiskIcon(Services.AIViewModel.RiskLevel riskLevel)
{
switch (riskLevel)
{
case Services.AIViewModel.RiskLevel.Critical:
return "fa-exclamation-triangle";
case Services.AIViewModel.RiskLevel.High:
return "fa-shield-alt";
case Services.AIViewModel.RiskLevel.Moderate:
return "fa-info-circle";
default:
return "fa-check-circle";
}
}
private string GetPriorityBorderClass(int priority)
{
switch (priority)
{
case 5:
return "border-danger";
case 4:
return "border-warning";
case 3:
return "border-primary";
case 2:
return "border-info";
default:
return "border-secondary";
}
}
private string GetPriorityBadgeClass(int priority)
{
switch (priority)
{
case 5:
return "bg-danger";
case 4:
return "bg-warning text-dark";
case 3:
return "bg-primary";
case 2:
return "bg-info";
default:
return "bg-secondary";
}
}
}
@section Scripts {
<script>
// Mark for intervention
function markForIntervention() {
Swal.fire({
title: 'Schedule Mental Health Intervention',
html: `
<div class="text-start">
<div class="mb-3">
<label class="form-label">Intervention Type:</label>
<select class="form-select" id="interventionType">
<option value="immediate">Immediate Support Call</option>
<option value="counseling">Professional Counseling Referral</option>
<option value="workplace">Workplace Accommodation Meeting</option>
<option value="followup">Follow-up Assessment</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Priority Level:</label>
<select class="form-select" id="priorityLevel">
<option value="urgent">Urgent (Same Day)</option>
<option value="high">High (Within 2 Days)</option>
<option value="normal">Normal (Within 1 Week)</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Notes:</label>
<textarea class="form-control" id="interventionNotes" rows="3" placeholder="Additional intervention notes..."></textarea>
</div>
</div>
`,
showCancelButton: true,
confirmButtonText: 'Schedule Intervention',
confirmButtonColor: '#28a745',
cancelButtonText: 'Cancel',
width: '500px'
}).then((result) => {
if (result.isConfirmed) {
Swal.fire('Intervention Scheduled', 'Mental health intervention has been scheduled and documented.', 'success');
}
});
}
// Flag for review
function flagForReview() {
Swal.fire({
title: 'Flag Case for Additional Review',
text: 'This will alert senior mental health professionals for additional assessment.',
icon: 'warning',
showCancelButton: true,
confirmButtonText: 'Flag for Review',
confirmButtonColor: '#ffc107'
}).then((result) => {
if (result.isConfirmed) {
Swal.fire('Case Flagged', 'Case has been flagged for additional professional review.', 'success');
}
});
}
// Export case report
function exportCaseReport() {
// In a real implementation, this would generate a detailed case report
Swal.fire('Exporting Case Report', 'Generating comprehensive case report for professional documentation...', 'info');
}
// Create intervention plan
function createInterventionPlan() {
Swal.fire({
title: 'Create Intervention Plan',
text: 'This would open a detailed intervention planning interface with specific actions, timelines, and resources.',
icon: 'info',
confirmButtonText: 'Continue to Planner'
});
}
// Schedule follow-up
function scheduleFollowUp() {
Swal.fire({
title: 'Schedule Follow-up',
text: 'Integration with calendar system to schedule follow-up mental health assessment.',
icon: 'info',
confirmButtonText: 'Open Calendar'
});
}
// Contact employee
function contactEmployee() {
Swal.fire({
title: 'Initiate Employee Contact',
html: `
<div class="text-start">
<div class="alert alert-warning">
<i class="fas fa-shield-alt me-2"></i>
<strong>Privacy Notice:</strong> All contact must maintain confidentiality and follow mental health protocols.
</div>
<p>Recommended contact approach:</p>
<ul class="small">
<li>Confidential, non-judgmental approach</li>
<li>Express concern and support</li>
<li>Provide mental health resources</li>
<li>Schedule follow-up if appropriate</li>
</ul>
</div>
`,
icon: 'info',
confirmButtonText: 'Proceed with Contact',
showCancelButton: true
});
}
// Alert management
function alertManagement() {
Swal.fire({
title: 'Alert Management Team',
text: 'This will notify management about workplace factors affecting employee mental health (without personal details).',
icon: 'warning',
showCancelButton: true,
confirmButtonText: 'Send Alert',
confirmButtonColor: '#ffc107'
}).then((result) => {
if (result.isConfirmed) {
Swal.fire('Management Notified', 'Workplace mental health concerns have been escalated to management.', 'success');
}
});
}
// Auto-save checklist progress
document.querySelectorAll('.form-check-input').forEach(checkbox => {
checkbox.addEventListener('change', function() {
// In a real implementation, this would save progress to the database
console.log('Checklist updated:', this.id, this.checked);
});
});
</script>
}
@section Styles {
<style>
.sentiment-bars .progress {
height: 8px;
border-radius: 4px;
}
.border-start {
border-left-width: 4px !important;
}
.bg-opacity-10 {
--bs-bg-opacity: 0.1;
}
.border-opacity-25 {
--bs-border-opacity: 0.25;
}
.font-italic {
font-style: italic;
}
.card {
transition: all 0.2s ease-in-out;
}
.list-unstyled li {
transition: all 0.2s ease-in-out;
}
.list-unstyled li:hover {
background-color: rgba(0,0,0,0.05);
padding: 0.25rem;
border-radius: 0.25rem;
}
.badge {
font-size: 0.75em;
}
.opacity-75 {
opacity: 0.75;
}
@@media (max-width: 768px) {
.btn-group
{
flex-direction: column;
width: 100%;
}
.btn-group .btn {
border-radius: 0.375rem !important;
margin-bottom: 0.5rem;
}
}
</style>
}

View file

@ -2,7 +2,6 @@
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Model; using Model;
using OpenAI_API;
using Services.Implemnetation; using Services.Implemnetation;
using Services.Interaces; using Services.Interaces;
using Web.AIConfiguration; using Web.AIConfiguration;
@ -128,7 +127,7 @@ namespace Web.Extesions
{ {
services.Configure<OpenAIOptions>(configuration.GetSection("OpenAI")); services.Configure<OpenAIOptions>(configuration.GetSection("OpenAI"));
services.AddSingleton<IOpenAIAPI, OpenAIAPI>();

View file

@ -24,7 +24,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="8.0.2" /> <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="8.0.2" />
<PackageReference Include="OpenAI" Version="1.11.0" /> <PackageReference Include="OpenAI" Version="2.1.0" />
<PackageReference Include="System.Drawing.Common" Version="8.0.4" /> <PackageReference Include="System.Drawing.Common" Version="8.0.4" />
</ItemGroup> </ItemGroup>