feature: Live Capture (Track B)

This commit is contained in:
voltsrage
2026-06-27 04:23:51 +08:00
parent 01000a2489
commit 88e70b3dbe
30 changed files with 4547 additions and 23 deletions
@@ -0,0 +1,88 @@
using System.Security.Claims;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
/// <summary>
/// Track B live capture endpoints for credentialed clinicians.
/// Observations entered via these endpoints skip the verification queue
/// and are promoted synchronously to VigilCareClinical with full
/// critical value alerting.
/// </summary>
[ApiController]
[Route("api/v1/live-capture")]
[Produces("application/json")]
[Authorize(Roles = "CLINICIAN")]
public class LiveCaptureController : ControllerBase
{
private readonly ILiveCaptureService _liveCapture;
public LiveCaptureController(ILiveCaptureService liveCapture) =>
_liveCapture = liveCapture;
/// <summary>
/// Records observations against an existing VigilCareClinical encounter.
/// Requires clinician attestation and password re-confirmation.
/// Returns promoted observation IDs and any synchronous critical alerts.
/// </summary>
/// <remarks>
/// Track B workflow: no verification queue. Clinician attestation replaces
/// the dual-human gate used in Track A (backfill). Each observation is
/// promoted immediately and evaluated against critical alert thresholds
/// before this response returns.
///
/// Critical alerts are generated synchronously — a critical potassium
/// value will have an open ClinicalAlert row in the database before
/// the 201 response reaches the clinician's tablet.
/// </remarks>
[HttpPost("encounters/{encounterId:guid}/observations")]
[ProducesResponseType(typeof(ApiResponse<LiveCaptureResponse>), StatusCodes.Status201Created)]
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status403Forbidden)]
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status409Conflict)]
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status422UnprocessableEntity)]
public async Task<IActionResult> RecordObservations(
Guid encounterId,
[FromBody] RecordObservationsRequest request)
{
var clinicianUserId = Guid.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!);
var result = await _liveCapture.RecordObservationsAsync(
encounterId, request, clinicianUserId);
return StatusCode(201, ApiResponse<LiveCaptureResponse>.Created(result));
}
/// <summary>
/// Opens a new encounter and records initial vitals in a single request.
/// Designed for outpatient workflows where the encounter does not yet
/// exist in VigilCareClinical.
/// </summary>
/// <remarks>
/// Creates the encounter with status "active", records all observations,
/// promotes immediately, and evaluates critical thresholds — all within
/// a single database transaction.
///
/// If the patient already has an active encounter, returns 409 with
/// ACTIVE_ENCOUNTER_EXISTS. Use the observation-only endpoint instead.
/// </remarks>
[HttpPost("encounters")]
[ProducesResponseType(typeof(ApiResponse<LiveCaptureResponse>), StatusCodes.Status201Created)]
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status403Forbidden)]
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status409Conflict)]
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status422UnprocessableEntity)]
public async Task<IActionResult> OpenEncounterWithVitals(
[FromBody] OpenEncounterWithVitalsRequest request)
{
var clinicianUserId = Guid.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!);
var result = await _liveCapture.OpenEncounterWithVitalsAsync(
request, clinicianUserId);
return StatusCode(201, ApiResponse<LiveCaptureResponse>.Created(result));
}
}