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
|
namespace Services.Interaces
|
||||||
{
|
{
|
||||||
public interface IAiAnalysisService
|
public interface IAiAnalysisService
|
||||||
{
|
{
|
||||||
Task<DocumentSentiment> AnalyzeSentimentAsync(string text);
|
#region Azure Language Service Methods
|
||||||
Task<string> GetRiskAssessmentAsync(string text);
|
|
||||||
Task<List<string>> ExtractKeyPhrasesAsync(string text);
|
/// <summary>
|
||||||
|
/// Analyzes sentiment of response text using Azure Language Service
|
||||||
|
/// </summary>
|
||||||
|
Task<SentimentAnalysisResult> AnalyzeSentimentAsync(string text);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extracts key phrases and workplace factors from response text
|
||||||
|
/// </summary>
|
||||||
|
Task<KeyPhrasesResult> ExtractKeyPhrasesAsync(string text);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes PII (Personally Identifiable Information) from response text
|
||||||
|
/// </summary>
|
||||||
|
Task<string> AnonymizeTextAsync(string text);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Detects entities in text (workplace factors, departments, roles, etc.)
|
||||||
|
/// </summary>
|
||||||
|
Task<List<string>> DetectEntitiesAsync(string text);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Azure OpenAI Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Assesses mental health risk level using GPT-3.5 Turbo
|
||||||
|
/// </summary>
|
||||||
|
Task<MentalHealthRiskAssessment> AssessMentalHealthRiskAsync(string anonymizedText, string questionContext);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates workplace insights and intervention recommendations
|
||||||
|
/// </summary>
|
||||||
|
Task<List<WorkplaceInsight>> GenerateWorkplaceInsightsAsync(string anonymizedText, string questionContext);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates executive summary for questionnaire analysis
|
||||||
|
/// </summary>
|
||||||
|
Task<string> GenerateExecutiveSummaryAsync(List<ResponseAnalysisResult> analysisResults);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Categorizes responses into mental health themes
|
||||||
|
/// </summary>
|
||||||
|
Task<List<string>> CategorizeResponseAsync(string anonymizedText);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Combined Analysis Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Performs complete AI analysis on a single response (both Azure services)
|
||||||
|
/// </summary>
|
||||||
|
Task<ResponseAnalysisResult> AnalyzeCompleteResponseAsync(AnalysisRequest request);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Analyzes multiple responses for a specific question
|
||||||
|
/// </summary>
|
||||||
|
Task<List<ResponseAnalysisResult>> AnalyzeQuestionResponsesAsync(int questionId, List<AnalysisRequest> requests);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates comprehensive analysis overview for entire questionnaire
|
||||||
|
/// </summary>
|
||||||
|
Task<QuestionnaireAnalysisOverview> GenerateQuestionnaireOverviewAsync(int questionnaireId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Batch processes multiple responses efficiently
|
||||||
|
/// </summary>
|
||||||
|
Task<List<ResponseAnalysisResult>> BatchAnalyzeResponsesAsync(List<AnalysisRequest> requests);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Mental Health Specific Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Identifies responses requiring immediate attention (high risk)
|
||||||
|
/// </summary>
|
||||||
|
Task<List<ResponseAnalysisResult>> IdentifyHighRiskResponsesAsync(int questionnaireId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates mental health trends across time periods
|
||||||
|
/// </summary>
|
||||||
|
Task<List<WorkplaceInsight>> AnalyzeMentalHealthTrendsAsync(int questionnaireId, DateTime fromDate, DateTime toDate);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compares mental health metrics between departments/teams
|
||||||
|
/// </summary>
|
||||||
|
Task<Dictionary<string, QuestionnaireAnalysisOverview>> CompareTeamMentalHealthAsync(int questionnaireId, List<string> teamIdentifiers);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates intervention recommendations based on overall analysis
|
||||||
|
/// </summary>
|
||||||
|
Task<List<WorkplaceInsight>> GenerateInterventionRecommendationsAsync(int questionnaireId);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Reporting Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates detailed analysis report for specific questionnaire
|
||||||
|
/// </summary>
|
||||||
|
Task<string> GenerateDetailedAnalysisReportAsync(int questionnaireId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates anonymized data export for further analysis
|
||||||
|
/// </summary>
|
||||||
|
Task<List<ResponseAnalysisResult>> ExportAnonymizedAnalysisAsync(int questionnaireId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates management dashboard summary
|
||||||
|
/// </summary>
|
||||||
|
Task<QuestionnaireAnalysisOverview> GenerateManagementDashboardAsync(int questionnaireId);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Utility Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests connection to Azure Language Service
|
||||||
|
/// </summary>
|
||||||
|
Task<bool> TestAzureLanguageServiceConnectionAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests connection to Azure OpenAI Service
|
||||||
|
/// </summary>
|
||||||
|
Task<bool> TestAzureOpenAIConnectionAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Validates analysis request before processing
|
||||||
|
/// </summary>
|
||||||
|
Task<bool> ValidateAnalysisRequestAsync(AnalysisRequest request);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets analysis service health status
|
||||||
|
/// </summary>
|
||||||
|
Task<Dictionary<string, bool>> GetServiceHealthStatusAsync();
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Azure.AI.OpenAI" Version="2.1.0" />
|
||||||
<PackageReference Include="Azure.AI.TextAnalytics" Version="5.3.0" />
|
<PackageReference Include="Azure.AI.TextAnalytics" Version="5.3.0" />
|
||||||
<PackageReference Include="MailJet.Api" Version="3.0.0" />
|
<PackageReference Include="MailJet.Api" Version="3.0.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.0.4" />
|
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.0.4" />
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ using Microsoft.VisualStudio.TextTemplating;
|
||||||
using Microsoft.VisualStudio.Web.CodeGenerators.Mvc.Templates.BlazorIdentity.Pages.Manage;
|
using Microsoft.VisualStudio.Web.CodeGenerators.Mvc.Templates.BlazorIdentity.Pages.Manage;
|
||||||
using Model;
|
using Model;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using OpenAI_API;
|
|
||||||
using Services.EmailSend;
|
using Services.EmailSend;
|
||||||
using Services.Implemnetation;
|
using Services.Implemnetation;
|
||||||
using Services.Interaces;
|
using Services.Interaces;
|
||||||
|
|
|
||||||
|
|
@ -1,173 +1,623 @@
|
||||||
using Azure;
|
// Web/Areas/Admin/Controllers/SurveyAnalysisController.cs
|
||||||
using Data;
|
using Data;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using Model;
|
using Model;
|
||||||
using Web.ViewModel.QuestionnaireVM;
|
using Services.AIViewModel;
|
||||||
|
|
||||||
using System.IO;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Services.Implemnetation;
|
|
||||||
using Services.Interaces;
|
using Services.Interaces;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace Web.Areas.Admin.Controllers
|
namespace Web.Areas.Admin.Controllers
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
public class SurveyAnalysisController : Controller
|
public class SurveyAnalysisController : Controller
|
||||||
{
|
{
|
||||||
private readonly SurveyContext _context;
|
|
||||||
private readonly IAiAnalysisService _aiAnalysisService;
|
private readonly IAiAnalysisService _aiAnalysisService;
|
||||||
|
private readonly SurveyContext _context;
|
||||||
|
private readonly ILogger<SurveyAnalysisController> _logger;
|
||||||
|
|
||||||
public SurveyAnalysisController(SurveyContext context, IAiAnalysisService aiAnalysisService)
|
public SurveyAnalysisController(
|
||||||
|
IAiAnalysisService aiAnalysisService,
|
||||||
|
SurveyContext context,
|
||||||
|
ILogger<SurveyAnalysisController> logger)
|
||||||
{
|
{
|
||||||
_context = context;
|
|
||||||
_aiAnalysisService = aiAnalysisService;
|
_aiAnalysisService = aiAnalysisService;
|
||||||
}
|
_context = context;
|
||||||
public IActionResult Index()
|
_logger = logger;
|
||||||
{
|
|
||||||
var questionnaires = _context.Responses
|
|
||||||
.Include(r => r.Questionnaire) // Ensure the navigation property is correctly set up in the Response model
|
|
||||||
.Select(r => r.Questionnaire)
|
|
||||||
.Distinct() // Ensure each questionnaire is listed once
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var viewModel = questionnaires.Select(q => new ResponseQuestionnaireWithUsersViewModel
|
|
||||||
{
|
|
||||||
Id = q.Id,
|
|
||||||
Title = q.Title
|
|
||||||
|
|
||||||
}).ToList();
|
|
||||||
|
|
||||||
return View(viewModel);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region Dashboard and Overview
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Main dashboard showing all questionnaires available for analysis
|
||||||
[HttpGet]
|
/// </summary>
|
||||||
public IActionResult Analysis(int id)
|
public async Task<IActionResult> Index()
|
||||||
{
|
{
|
||||||
var viewModel = _context.Responses
|
try
|
||||||
.Where(r => r.QuestionnaireId == id)
|
|
||||||
.Include(r => r.Questionnaire)
|
|
||||||
.ThenInclude(q => q.Questions)
|
|
||||||
.ThenInclude(q => q.Answers)
|
|
||||||
.Select(r => new ResponseQuestionnaireWithUsersViewModel
|
|
||||||
{
|
{
|
||||||
Id = r.Questionnaire.Id,
|
var questionnaires = await _context.Questionnaires
|
||||||
Title = r.Questionnaire.Title,
|
.Include(q => q.Questions)
|
||||||
Description = r.Questionnaire.Description,
|
.Select(q => new
|
||||||
UserName = r.UserName, // Assuming you want the user who answered the questionnaire
|
|
||||||
Email = r.UserEmail,
|
|
||||||
ParticipantCount = _context.Responses.Count(rs => rs.QuestionnaireId == id),
|
|
||||||
QuestionsAnsweredPercentage = _context.Questionnaires
|
|
||||||
.Where(q => q.Id == id)
|
|
||||||
.SelectMany(q => q.Questions)
|
|
||||||
.Count() > 0
|
|
||||||
? (double)_context.ResponseDetails
|
|
||||||
.Where(rd => rd.Response.QuestionnaireId == id && rd.TextResponse != null)
|
|
||||||
.Select(rd => rd.QuestionId)
|
|
||||||
.Distinct()
|
|
||||||
.Count() / _context.Questionnaires
|
|
||||||
.Where(q => q.Id == id)
|
|
||||||
.SelectMany(q => q.Questions)
|
|
||||||
.Count() * 100.0
|
|
||||||
: 0.0, // Avoid division by zero
|
|
||||||
Questions = r.Questionnaire.Questions.Select(q => new ResponseQuestionViewModel
|
|
||||||
{
|
{
|
||||||
Id = q.Id,
|
q.Id,
|
||||||
Text = q.Text,
|
q.Title,
|
||||||
Type = q.Type,
|
q.Description,
|
||||||
Answers = q.Answers.Select(a => new ResponseAnswerViewModel
|
QuestionCount = q.Questions.Count,
|
||||||
{
|
ResponseCount = _context.Responses.Count(r => r.QuestionnaireId == q.Id),
|
||||||
Id = a.Id,
|
TextResponseCount = _context.Responses
|
||||||
Text = a.Text ?? _context.ResponseDetails
|
.Where(r => r.QuestionnaireId == q.Id)
|
||||||
.Where(rd => rd.QuestionId == q.Id && rd.ResponseId == r.Id)
|
.SelectMany(r => r.ResponseDetails)
|
||||||
.Select(rd => rd.TextResponse)
|
.Count(rd => !string.IsNullOrEmpty(rd.TextResponse)),
|
||||||
.FirstOrDefault(),
|
LastResponse = _context.Responses
|
||||||
Count = _context.ResponseAnswers.Count(ra => ra.AnswerId == a.Id) // Count how many times each answer was selected
|
.Where(r => r.QuestionnaireId == q.Id)
|
||||||
}).ToList(),
|
.OrderByDescending(r => r.SubmissionDate)
|
||||||
SelectedAnswerIds = _context.ResponseDetails
|
.Select(r => r.SubmissionDate)
|
||||||
.Where(rd => rd.QuestionId == q.Id)
|
.FirstOrDefault()
|
||||||
.SelectMany(rd => rd.ResponseAnswers)
|
|
||||||
.Select(ra => ra.AnswerId)
|
|
||||||
.Distinct()
|
|
||||||
.ToList(),
|
|
||||||
SelectedText = _context.ResponseDetails
|
|
||||||
.Where(rd => rd.QuestionId == q.Id)
|
|
||||||
.Select(rd => rd.TextResponse)
|
|
||||||
.Where(t => !string.IsNullOrEmpty(t))
|
|
||||||
.ToList()
|
|
||||||
}).ToList(),
|
|
||||||
Users = _context.Responses
|
|
||||||
.Where(rs => rs.QuestionnaireId == id)
|
|
||||||
.Select(rs => new ResponseUserViewModel
|
|
||||||
{
|
|
||||||
UserName = rs.UserName,
|
|
||||||
Email = rs.UserEmail
|
|
||||||
}).Distinct().ToList()
|
|
||||||
})
|
})
|
||||||
.FirstOrDefault();
|
|
||||||
|
|
||||||
if (viewModel == null)
|
|
||||||
{
|
|
||||||
return NotFound("No questionnaire found for the given ID.");
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
.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 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
|
ResponseId = response.Id,
|
||||||
var sentiment = await _aiAnalysisService.AnalyzeSentimentAsync(detail.TextResponse);
|
QuestionId = detail.QuestionId,
|
||||||
var riskAssessment = await _aiAnalysisService.GetRiskAssessmentAsync(detail.TextResponse);
|
ResponseText = detail.TextResponse,
|
||||||
var keyPhrases = await _aiAnalysisService.ExtractKeyPhrasesAsync(detail.TextResponse);
|
QuestionText = detail.Question?.Text ?? ""
|
||||||
|
|
||||||
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
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!analysisRequests.Any())
|
||||||
|
{
|
||||||
|
TempData["WarningMessage"] = "No text responses found to analyze.";
|
||||||
|
return RedirectToAction(nameof(AnalyzeQuestionnaire), new { id });
|
||||||
}
|
}
|
||||||
|
|
||||||
ViewBag.QuestionnaireName = responses.First().Questionnaire.Title;
|
// Process batch analysis (this might take a while)
|
||||||
return View(analysisResults);
|
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>
|
||||||
|
|
||||||
|
<!-- Service Health Status -->
|
||||||
<div class="card bg-default mb-3 ">
|
@if (ViewBag.ServiceHealth != null)
|
||||||
<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)
|
|
||||||
{
|
{
|
||||||
<tr>
|
var serviceHealth = ViewBag.ServiceHealth as Dictionary<string, bool>;
|
||||||
<td><input type="checkbox" name="ids" value="@item.Id" /></td>
|
<div class="row mb-4">
|
||||||
<td>@item.Id</td>
|
<div class="col-12">
|
||||||
<td>@item.Title</td>
|
<div class="card border-0 shadow-sm">
|
||||||
|
<div class="card-body py-3">
|
||||||
<td class="text-end">
|
<div class="row align-items-center">
|
||||||
<a asp-action="Analysis" asp-route-id="@item.Id" class="btn btn-info btn-sm"><i class="bi bi-graph-up-arrow"></i> Analyzer</a>
|
<div class="col-md-6">
|
||||||
<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>
|
<h6 class="mb-0">
|
||||||
</td>
|
<i class="fas fa-heartbeat me-2"></i>AI Services Status
|
||||||
</tr>
|
</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>
|
</form>
|
||||||
|
|
||||||
|
<div id="testResults" class="d-none">
|
||||||
|
<hr>
|
||||||
|
<h6><i class="fas fa-chart-bar me-2"></i>Analysis Results</h6>
|
||||||
|
<div id="testResultsContent"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||||
|
<button type="button" class="btn btn-primary" onclick="performTestAnalysis()">
|
||||||
|
<i class="fas fa-play me-2"></i>Run Analysis
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@section Scripts {
|
@section Scripts {
|
||||||
<script>
|
<script>
|
||||||
function selectAll(source) {
|
// Test AI Services
|
||||||
checkboxes = document.getElementsByName('ids');
|
function testAIServices() {
|
||||||
for (var i = 0, n = checkboxes.length; i < n; i++) {
|
$.get('@Url.Action("ServiceHealth")')
|
||||||
checkboxes[i].checked = source.checked;
|
.done(function(data) {
|
||||||
|
if (data.success && data.services) {
|
||||||
|
let status = '';
|
||||||
|
for (let service in data.services) {
|
||||||
|
let icon = data.services[service] ? 'fa-check text-success' : 'fa-times text-danger';
|
||||||
|
let statusText = data.services[service] ? 'Online ✓' : 'Offline ✗';
|
||||||
|
status += `<div class="mb-2"><i class="fas ${icon} me-2"></i><strong>${service.replace('Azure', '')}:</strong> ${statusText}</div>`;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
</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.AspNetCore.Identity;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Model;
|
using Model;
|
||||||
using OpenAI_API;
|
|
||||||
using Services.Implemnetation;
|
using Services.Implemnetation;
|
||||||
using Services.Interaces;
|
using Services.Interaces;
|
||||||
using Web.AIConfiguration;
|
using Web.AIConfiguration;
|
||||||
|
|
@ -128,7 +127,7 @@ namespace Web.Extesions
|
||||||
{
|
{
|
||||||
services.Configure<OpenAIOptions>(configuration.GetSection("OpenAI"));
|
services.Configure<OpenAIOptions>(configuration.GetSection("OpenAI"));
|
||||||
|
|
||||||
services.AddSingleton<IOpenAIAPI, OpenAIAPI>();
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="8.0.2" />
|
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="8.0.2" />
|
||||||
<PackageReference Include="OpenAI" Version="1.11.0" />
|
<PackageReference Include="OpenAI" Version="2.1.0" />
|
||||||
<PackageReference Include="System.Drawing.Common" Version="8.0.4" />
|
<PackageReference Include="System.Drawing.Common" Version="8.0.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue