Files
vigilcare-clinical/VigilCareClinicalAPI/Controllers/AlertsController.cs
T

169 lines
6.5 KiB
C#

using Microsoft.AspNetCore.Mvc;
/// <summary>
/// Clinical alert listing, acknowledgment, and resolution.
/// </summary>
[ApiController]
[Produces("application/json")]
public class AlertsController : ControllerBase
{
private readonly IAlertService _alerts;
public AlertsController(IAlertService alerts) => _alerts = alerts;
/// <summary>
/// Lists alerts for a single encounter with optional status filter.
/// </summary>
/// <param name="encounterId">Encounter id.</param>
/// <param name="status">Optional status filter (DB literal, e.g. OPEN).</param>
/// <param name="page">Page number (1-based).</param>
/// <param name="pageSize">Results per page.</param>
/// <returns>A paginated list of alerts for the encounter.</returns>
[HttpGet("api/v1/encounters/{encounterId:guid}/alerts")]
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status400BadRequest)]
public async Task<IActionResult> ListByEncounter(
Guid encounterId,
[FromQuery] string? status,
[FromQuery] int page = 1,
[FromQuery] int pageSize = 20)
{
AlertStatus? parsedStatus = null;
if (!string.IsNullOrEmpty(status))
{
try
{
parsedStatus = AlertStatusExtensions.FromDbString(status);
}
catch (ArgumentOutOfRangeException)
{
return BadRequest(ApiResponse<object>.Fail(400, "Invalid status filter.", "INVALID_STATUS"));
}
}
var result = await _alerts.ListByEncounterAsync(encounterId, parsedStatus, page, pageSize);
return Ok(ApiResponse<object>.Ok(new
{
items = result.Items,
page = result.Page,
pageSize = result.PageSize,
totalCount = result.TotalCount,
totalPages = result.TotalPages
}));
}
/// <summary>
/// Lists alerts across all encounters with optional status, severity, and department filters.
/// </summary>
/// <param name="status">Optional status filter (DB literal, e.g. OPEN).</param>
/// <param name="severity">Optional severity filter (DB literal, e.g. CRITICAL).</param>
/// <param name="department">Optional department filter.</param>
/// <param name="page">Page number (1-based).</param>
/// <param name="pageSize">Results per page.</param>
/// <returns>A paginated list of alerts.</returns>
[HttpGet("api/v1/alerts")]
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status400BadRequest)]
public async Task<IActionResult> ListGlobal(
[FromQuery] string? status,
[FromQuery] string? severity,
[FromQuery] string? department,
[FromQuery] int page = 1,
[FromQuery] int pageSize = 20)
{
AlertStatus? parsedStatus = null;
if (!string.IsNullOrEmpty(status))
{
try
{
parsedStatus = AlertStatusExtensions.FromDbString(status);
}
catch (ArgumentOutOfRangeException)
{
return BadRequest(ApiResponse<object>.Fail(400, "Invalid status filter.", "INVALID_STATUS"));
}
}
AlertSeverity? parsedSeverity = null;
if (!string.IsNullOrEmpty(severity))
{
try
{
parsedSeverity = AlertSeverityExtensions.FromDbString(severity);
}
catch (ArgumentOutOfRangeException)
{
return BadRequest(ApiResponse<object>.Fail(400, "Invalid severity filter.", "INVALID_SEVERITY"));
}
}
Department? parsedDepartment = null;
if (!string.IsNullOrEmpty(department))
{
try
{
parsedDepartment = DepartmentExtensions.FromDbString(department);
}
catch (ArgumentOutOfRangeException)
{
return BadRequest(ApiResponse<object>.Fail(400, "Invalid department filter.", "INVALID_DEPARTMENT"));
}
}
var result = await _alerts.ListGlobalAsync(parsedStatus, parsedSeverity, parsedDepartment, page, pageSize);
return Ok(ApiResponse<object>.Ok(new
{
items = result.Items,
page = result.Page,
pageSize = result.PageSize,
totalCount = result.TotalCount,
totalPages = result.TotalPages
}));
}
/// <summary>
/// Gets a single alert by id, including its encounter.
/// </summary>
/// <param name="id">Alert id.</param>
/// <returns>The alert record.</returns>
[HttpGet("api/v1/alerts/{id:guid}")]
[ProducesResponseType(typeof(ApiResponse<ClinicalAlert>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
public async Task<IActionResult> Get(Guid id)
{
var alert = await _alerts.GetByIdAsync(id);
return Ok(ApiResponse<ClinicalAlert>.Ok(alert));
}
/// <summary>
/// Acknowledges an open or escalated alert and emits an outbox event for downstream consumers.
/// </summary>
/// <param name="id">Alert id.</param>
/// <param name="req">Clinician id and optional note.</param>
/// <returns>The updated alert.</returns>
[HttpPost("api/v1/alerts/{id:guid}/acknowledge")]
[ProducesResponseType(typeof(ApiResponse<ClinicalAlert>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status409Conflict)]
public async Task<IActionResult> Acknowledge(Guid id, [FromBody] AcknowledgeAlertRequest req)
{
var alert = await _alerts.AcknowledgeAsync(id, req);
return Ok(ApiResponse<ClinicalAlert>.Ok(alert));
}
/// <summary>
/// Resolves an acknowledged alert.
/// </summary>
/// <param name="id">Alert id.</param>
/// <returns>The updated alert.</returns>
[HttpPost("api/v1/alerts/{id:guid}/resolve")]
[ProducesResponseType(typeof(ApiResponse<ClinicalAlert>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status409Conflict)]
public async Task<IActionResult> Resolve(Guid id)
{
var alert = await _alerts.ResolveAsync(id);
return Ok(ApiResponse<ClinicalAlert>.Ok(alert));
}
}