Implement Azure OpenAI integration to analyze user-submitted questions and answers
This commit is contained in:
parent
481b7797a1
commit
a9c0e6a0c1
15 changed files with 4053 additions and 740 deletions
109
Services/AIViewModel/AIViewModels.cs
Normal file
109
Services/AIViewModel/AIViewModels.cs
Normal 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
|
|
@ -1,11 +1,147 @@
|
|||
using Azure.AI.TextAnalytics;
|
||||
// Services/Interfaces/IAiAnalysisService.cs
|
||||
using Services.AIViewModel;
|
||||
|
||||
namespace Services.Interaces
|
||||
{
|
||||
public interface IAiAnalysisService
|
||||
{
|
||||
Task<DocumentSentiment> AnalyzeSentimentAsync(string text);
|
||||
Task<string> GetRiskAssessmentAsync(string text);
|
||||
Task<List<string>> ExtractKeyPhrasesAsync(string text);
|
||||
#region Azure Language Service Methods
|
||||
|
||||
/// <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
|
||||
}
|
||||
}
|
||||
17
Services/Options/AzureOptions.cs
Normal file
17
Services/Options/AzureOptions.cs
Normal 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!;
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" Version="2.1.0" />
|
||||
<PackageReference Include="Azure.AI.TextAnalytics" Version="5.3.0" />
|
||||
<PackageReference Include="MailJet.Api" Version="3.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.0.4" />
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ using Microsoft.VisualStudio.TextTemplating;
|
|||
using Microsoft.VisualStudio.Web.CodeGenerators.Mvc.Templates.BlazorIdentity.Pages.Manage;
|
||||
using Model;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OpenAI_API;
|
||||
|
||||
using Services.EmailSend;
|
||||
using Services.Implemnetation;
|
||||
using Services.Interaces;
|
||||
|
|
|
|||
|
|
@ -1,173 +1,623 @@
|
|||
using Azure;
|
||||
// Web/Areas/Admin/Controllers/SurveyAnalysisController.cs
|
||||
using Data;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Model;
|
||||
using Web.ViewModel.QuestionnaireVM;
|
||||
|
||||
using System.IO;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Services.Implemnetation;
|
||||
using Services.AIViewModel;
|
||||
using Services.Interaces;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Web.Areas.Admin.Controllers
|
||||
{
|
||||
|
||||
|
||||
public class SurveyAnalysisController : Controller
|
||||
{
|
||||
private readonly SurveyContext _context;
|
||||
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)
|
||||
{
|
||||
_context = context;
|
||||
_aiAnalysisService = aiAnalysisService;
|
||||
}
|
||||
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);
|
||||
_context = context;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
#region Dashboard and Overview
|
||||
|
||||
|
||||
|
||||
[HttpGet]
|
||||
public IActionResult Analysis(int id)
|
||||
/// <summary>
|
||||
/// Main dashboard showing all questionnaires available for analysis
|
||||
/// </summary>
|
||||
public async Task<IActionResult> Index()
|
||||
{
|
||||
var viewModel = _context.Responses
|
||||
.Where(r => r.QuestionnaireId == id)
|
||||
.Include(r => r.Questionnaire)
|
||||
.ThenInclude(q => q.Questions)
|
||||
.ThenInclude(q => q.Answers)
|
||||
.Select(r => new ResponseQuestionnaireWithUsersViewModel
|
||||
try
|
||||
{
|
||||
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
|
||||
var questionnaires = await _context.Questionnaires
|
||||
.Include(q => q.Questions)
|
||||
.Select(q => new
|
||||
{
|
||||
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()
|
||||
q.Id,
|
||||
q.Title,
|
||||
q.Description,
|
||||
QuestionCount = q.Questions.Count,
|
||||
ResponseCount = _context.Responses.Count(r => r.QuestionnaireId == q.Id),
|
||||
TextResponseCount = _context.Responses
|
||||
.Where(r => r.QuestionnaireId == q.Id)
|
||||
.SelectMany(r => r.ResponseDetails)
|
||||
.Count(rd => !string.IsNullOrEmpty(rd.TextResponse)),
|
||||
LastResponse = _context.Responses
|
||||
.Where(r => r.QuestionnaireId == q.Id)
|
||||
.OrderByDescending(r => r.SubmissionDate)
|
||||
.Select(r => r.SubmissionDate)
|
||||
.FirstOrDefault()
|
||||
})
|
||||
.FirstOrDefault();
|
||||
|
||||
if (viewModel == null)
|
||||
{
|
||||
return NotFound("No questionnaire found for the given ID.");
|
||||
}
|
||||
|
||||
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())
|
||||
ViewBag.ServiceHealth = await _aiAnalysisService.GetServiceHealthStatusAsync();
|
||||
|
||||
return View(questionnaires);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return NotFound("No responses found for this questionnaire.");
|
||||
_logger.LogError(ex, "Error loading survey analysis dashboard");
|
||||
TempData["ErrorMessage"] = "Error loading dashboard. Please try again.";
|
||||
return View(new List<object>());
|
||||
}
|
||||
}
|
||||
|
||||
var analysisResults = new List<dynamic>();
|
||||
/// <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)
|
||||
foreach (var detail in response.ResponseDetails.Where(rd => !string.IsNullOrWhiteSpace(rd.TextResponse)))
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(detail.TextResponse))
|
||||
analysisRequests.Add(new AnalysisRequest
|
||||
{
|
||||
// Analyze the text response with AI
|
||||
var sentiment = await _aiAnalysisService.AnalyzeSentimentAsync(detail.TextResponse);
|
||||
var riskAssessment = await _aiAnalysisService.GetRiskAssessmentAsync(detail.TextResponse);
|
||||
var keyPhrases = await _aiAnalysisService.ExtractKeyPhrasesAsync(detail.TextResponse);
|
||||
|
||||
analysisResults.Add(new
|
||||
{
|
||||
UserName = response.UserName,
|
||||
UserEmail = response.UserEmail,
|
||||
Question = detail.Question.Text,
|
||||
Response = detail.TextResponse,
|
||||
Sentiment = sentiment.Sentiment.ToString(),
|
||||
PositiveScore = sentiment.ConfidenceScores.Positive,
|
||||
NegativeScore = sentiment.ConfidenceScores.Negative,
|
||||
NeutralScore = sentiment.ConfidenceScores.Neutral,
|
||||
RiskAssessment = riskAssessment,
|
||||
KeyPhrases = keyPhrases
|
||||
ResponseId = response.Id,
|
||||
QuestionId = detail.QuestionId,
|
||||
ResponseText = detail.TextResponse,
|
||||
QuestionText = detail.Question?.Text ?? ""
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!analysisRequests.Any())
|
||||
{
|
||||
TempData["WarningMessage"] = "No text responses found to analyze.";
|
||||
return RedirectToAction(nameof(AnalyzeQuestionnaire), new { id });
|
||||
}
|
||||
|
||||
ViewBag.QuestionnaireName = responses.First().Questionnaire.Title;
|
||||
return View(analysisResults);
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
|
||||
/// <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
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
} *@
|
||||
504
Web/Areas/Admin/Views/SurveyAnalysis/AnalyzeQuestionnaire.cshtml
Normal file
504
Web/Areas/Admin/Views/SurveyAnalysis/AnalyzeQuestionnaire.cshtml
Normal 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>
|
||||
}
|
||||
492
Web/Areas/Admin/Views/SurveyAnalysis/HighRiskResponses.cshtml
Normal file
492
Web/Areas/Admin/Views/SurveyAnalysis/HighRiskResponses.cshtml
Normal 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>
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
||||
|
||||
<div class="card bg-default mb-3 ">
|
||||
<div class="card-header">Survey analysis</div>
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">Survey analysis list</h4>
|
||||
|
||||
|
||||
<form asp-action="DeleteUnusedQuestionnaires" method="post">
|
||||
|
||||
<table class="table table-responsive w-100 d-block d-md-table table-hover ">
|
||||
<thead class="w-100">
|
||||
<tr>
|
||||
<th><input type="checkbox" onclick="selectAll(this)" /></th> <!-- Master checkbox -->
|
||||
<th>Id</th>
|
||||
<th>Questionnaire</th>
|
||||
<th class="text-end">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="w-100">
|
||||
@foreach (var item in Model)
|
||||
<!-- Service Health Status -->
|
||||
@if (ViewBag.ServiceHealth != null)
|
||||
{
|
||||
<tr>
|
||||
<td><input type="checkbox" name="ids" value="@item.Id" /></td>
|
||||
<td>@item.Id</td>
|
||||
<td>@item.Title</td>
|
||||
|
||||
<td class="text-end">
|
||||
<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>
|
||||
<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>
|
||||
</td>
|
||||
</tr>
|
||||
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>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<button type="submit" class="btn btn-danger">Delete Selected</button>
|
||||
<!-- Alert Messages -->
|
||||
@if (TempData["ErrorMessage"] != null)
|
||||
{
|
||||
<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>
|
||||
}
|
||||
|
||||
@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>
|
||||
}
|
||||
|
||||
<!-- Quick Actions -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h6 class="mb-3">
|
||||
<i class="fas fa-tools me-2"></i>Quick Actions
|
||||
</h6>
|
||||
<button type="button" class="btn btn-outline-primary btn-sm me-2" onclick="testAIServices()">
|
||||
<i class="fas fa-vial"></i> Test AI Services
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-info btn-sm" data-bs-toggle="modal" data-bs-target="#testAnalysisModal">
|
||||
<i class="fas fa-microscope"></i> Test Analysis
|
||||
</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>
|
||||
|
||||
<!-- Questionnaires Grid -->
|
||||
<div class="row">
|
||||
@if (Model != null && Model.Any())
|
||||
{
|
||||
@foreach (var questionnaire in Model)
|
||||
{
|
||||
<div class="col-lg-6 col-xl-4 mb-4">
|
||||
<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>
|
||||
|
||||
<div class="card-body">
|
||||
<!-- 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>
|
||||
|
||||
|
||||
|
||||
@section Scripts {
|
||||
<script>
|
||||
function selectAll(source) {
|
||||
checkboxes = document.getElementsByName('ids');
|
||||
for (var i = 0, n = checkboxes.length; i < n; i++) {
|
||||
checkboxes[i].checked = source.checked;
|
||||
// Test AI Services
|
||||
function testAIServices() {
|
||||
$.get('@Url.Action("ServiceHealth")')
|
||||
.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>`;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
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';
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
}
|
||||
699
Web/Areas/Admin/Views/SurveyAnalysis/ViewHighRiskResponse.cshtml
Normal file
699
Web/Areas/Admin/Views/SurveyAnalysis/ViewHighRiskResponse.cshtml
Normal 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>
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@
|
|||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Model;
|
||||
using OpenAI_API;
|
||||
using Services.Implemnetation;
|
||||
using Services.Interaces;
|
||||
using Web.AIConfiguration;
|
||||
|
|
@ -128,7 +127,7 @@ namespace Web.Extesions
|
|||
{
|
||||
services.Configure<OpenAIOptions>(configuration.GetSection("OpenAI"));
|
||||
|
||||
services.AddSingleton<IOpenAIAPI, OpenAIAPI>();
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<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" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue