using Microsoft.EntityFrameworkCore; public class ObservationQueryService : IObservationQueryService { private readonly GatewayDbContext _db; public ObservationQueryService(GatewayDbContext db) => _db = db; public async Task> GetHistoryAsync( Guid encounterId, string? code, DateTimeOffset? from, DateTimeOffset? to, int limit, string? cursorToken) { limit = Math.Clamp(limit, 1, 100); var cursor = ObservationCursor.Decode(cursorToken); var query = _db.Observations .AsNoTracking() .Where(o => o.EncounterId == encounterId); if (!string.IsNullOrEmpty(code)) query = query.Where(o => o.ObservationCode == code); if (from.HasValue) query = query.Where(o => o.RecordedAt >= from.Value); if (to.HasValue) query = query.Where(o => o.RecordedAt <= to.Value); if (cursor is not null) { var cursorTime = cursor.RecordedAt; var cursorId = cursor.Id; query = query.Where(o => o.RecordedAt < cursorTime || (o.RecordedAt == cursorTime && o.Id.CompareTo(cursorId) < 0)); } var items = await query .OrderByDescending(o => o.RecordedAt) .ThenByDescending(o => o.Id) .Take(limit + 1) .ToListAsync(); var hasMore = items.Count > limit; if (hasMore) items.RemoveAt(limit); var nextCursor = hasMore ? new ObservationCursor(items[^1].RecordedAt, items[^1].Id).Encode() : null; return new CursorPage(items, nextCursor, hasMore); } }