fix: Missing input validators + No patient update endpoint + Pagination inconsistencies + Missing list/get endpoints
This commit is contained in:
@@ -100,7 +100,7 @@ public class AnalyticsController : ControllerBase
|
||||
[FromQuery] string? q,
|
||||
[FromQuery] string? department,
|
||||
[FromQuery] string? status,
|
||||
[FromQuery] int page = 0,
|
||||
[FromQuery] int page = 1,
|
||||
[FromQuery] int pageSize = 20)
|
||||
{
|
||||
var result = await _analytics.SearchPatientsAsync(q, department, status, page, pageSize);
|
||||
|
||||
@@ -77,7 +77,7 @@ public class ObservationsController : ControllerBase
|
||||
[FromQuery] string? code,
|
||||
[FromQuery] DateTimeOffset? from,
|
||||
[FromQuery] DateTimeOffset? to,
|
||||
[FromQuery] int limit = 50,
|
||||
[FromQuery] int limit = 20,
|
||||
[FromQuery] string? cursor = null)
|
||||
{
|
||||
var page = await _query.GetHistoryAsync(encounterId, code, from, to, limit, cursor);
|
||||
|
||||
@@ -67,6 +67,22 @@ public class PatientsController : ControllerBase
|
||||
return Ok(ApiResponse<Patient>.Ok(patient));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates patient demographics. Only non-null fields are applied.
|
||||
/// </summary>
|
||||
/// <param name="id">Patient id.</param>
|
||||
/// <param name="req">Fields to update.</param>
|
||||
/// <returns>The updated patient record.</returns>
|
||||
[HttpPatch("{id:guid}")]
|
||||
[AuthorizePermission(ClinicalPermissions.PatientsWrite)]
|
||||
[ProducesResponseType(typeof(ApiResponse<Patient>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> Update(Guid id, [FromBody] UpdatePatientRequest req)
|
||||
{
|
||||
var patient = await _patients.UpdateAsync(id, req);
|
||||
return Ok(ApiResponse<Patient>.Ok(patient));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens a new active encounter for the patient.
|
||||
/// </summary>
|
||||
|
||||
@@ -25,4 +25,23 @@ public class QsofaController : ControllerBase
|
||||
var score = await _qsofa.GetCurrentAsync(encounterId);
|
||||
return Ok(ApiResponse<QsofaCurrentResponse>.Ok(score));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns cursor-paginated qSOFA alert history for an encounter.
|
||||
/// </summary>
|
||||
[HttpGet("history")]
|
||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> History(
|
||||
Guid encounterId,
|
||||
[FromQuery] int limit = 20,
|
||||
[FromQuery] string? cursor = null)
|
||||
{
|
||||
var page = await _qsofa.GetHistoryAsync(encounterId, limit, cursor);
|
||||
return Ok(ApiResponse<object>.Ok(new
|
||||
{
|
||||
items = page.Items,
|
||||
nextCursor = page.NextCursor,
|
||||
hasMore = page.HasMore
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
/// <summary>
|
||||
/// Data quality reconciliation alerts: unacknowledged critical alerts, pending orders without results,
|
||||
/// and active inpatients without recent observations.
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/v1/reconciliation-alerts")]
|
||||
[Produces("application/json")]
|
||||
[AuthorizePermission(ClinicalPermissions.AlertsRead)]
|
||||
public class ReconciliationAlertsController : ControllerBase
|
||||
{
|
||||
private readonly AppDbContext _db;
|
||||
|
||||
public ReconciliationAlertsController(AppDbContext db) => _db = db;
|
||||
|
||||
/// <summary>
|
||||
/// Lists reconciliation alerts with optional filters.
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> List(
|
||||
[FromQuery] string? checkType,
|
||||
[FromQuery] bool? resolved,
|
||||
[FromQuery] int page = 1,
|
||||
[FromQuery] int pageSize = 20)
|
||||
{
|
||||
pageSize = Math.Clamp(pageSize, 1, 100);
|
||||
|
||||
var query = _db.ReconciliationAlerts
|
||||
.AsNoTracking()
|
||||
.AsQueryable();
|
||||
|
||||
if (checkType is not null)
|
||||
{
|
||||
var parsed = ReconciliationCheckTypeExtensions.FromDbString(checkType);
|
||||
query = query.Where(a => a.CheckType == parsed);
|
||||
}
|
||||
|
||||
if (resolved == true)
|
||||
query = query.Where(a => a.ResolvedAt != null);
|
||||
else if (resolved == false)
|
||||
query = query.Where(a => a.ResolvedAt == null);
|
||||
|
||||
var total = await query.CountAsync();
|
||||
var items = await query
|
||||
.OrderByDescending(a => a.CreatedAt)
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToListAsync();
|
||||
|
||||
return Ok(ApiResponse<object>.Ok(new
|
||||
{
|
||||
items,
|
||||
page,
|
||||
pageSize,
|
||||
totalCount = total,
|
||||
totalPages = (int)Math.Ceiling((double)total / pageSize)
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,31 @@ public class SepsisBundlesController : ControllerBase
|
||||
|
||||
public SepsisBundlesController(ISepsisBundleService bundles) => _bundles = bundles;
|
||||
|
||||
/// <summary>
|
||||
/// Lists sepsis bundles across all encounters with optional status filter.
|
||||
/// </summary>
|
||||
[HttpGet("api/v1/sepsis-bundles")]
|
||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> List(
|
||||
[FromQuery] string? status,
|
||||
[FromQuery] int page = 1,
|
||||
[FromQuery] int pageSize = 20)
|
||||
{
|
||||
SepsisBundleComplianceStatus? parsed = status is not null
|
||||
? SepsisBundleComplianceStatusExtensions.FromDbString(status)
|
||||
: null;
|
||||
|
||||
var result = await _bundles.ListAsync(parsed, page, pageSize);
|
||||
return Ok(ApiResponse<object>.Ok(new
|
||||
{
|
||||
items = result.Items,
|
||||
page = result.Page,
|
||||
pageSize = result.PageSize,
|
||||
totalCount = result.TotalCount,
|
||||
totalPages = result.TotalPages
|
||||
}));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current (most recent) sepsis bundle for an encounter with elements and linked orders.
|
||||
/// </summary>
|
||||
|
||||
Reference in New Issue
Block a user