=== Summary: 675 files | 7 generated | 484 fresh | 4605 untracked | 4268 adopted | 355 marked | 466 validated-ok | 2+0 stale (sig+body) | 0 skipped | 0 failed | elapsed 11:08:11.442 (40091.44s) ===

This commit is contained in:
julian
2026-06-28 02:50:05 -07:00
parent a19fb90902
commit 586f02a2ca
654 changed files with 5260 additions and 0 deletions
@@ -14,6 +14,7 @@ namespace adas_core.Application.Subscriptions;
/// <remarks>
/// This service depends on an optional cache, a grouped observation service, a logger, and a lazily-initialized client message service to support its subscriber-related operations.
/// </remarks>
/// <!-- aidoc:v1 sig=01f4003 -->
public class SubscriberGroupedService(
ICacheService? cacheService,
IGroupedObservationService groupedObservationService,
@@ -48,6 +49,7 @@ public class SubscriberGroupedService(
/// Returns a thread-safe snapshot of the grouped subscribers as a new list. The internal collection is locked during the copy to ensure consistency and prevent concurrent modification.
/// </summary>
/// <returns>A new <see cref="List{WsSubscriberGrouped}"/> containing a copy of the grouped subscribers.</returns>
/// <!-- aidoc:v1 sig=6e3438d body=29d89cc -->
public List<WsSubscriberGrouped> GetGrouped()
{
lock (_subscriberGrouped)
@@ -60,6 +62,7 @@ public class SubscriberGroupedService(
/// Removes all grouped observations associated with the specified patient identifier from the subscriber collection in a thread-safe manner.
/// </summary>
/// <param name="patientId">The string representation of the patient identifier used to locate and remove the matching grouped observations.</param>
/// <!-- aidoc:v1 sig=60f9191 body=6bbfa2e -->
public void RemoveGroupedObsByPatientId(string patientId)
{
lock (_subscriberGrouped)
@@ -76,6 +79,10 @@ public class SubscriberGroupedService(
/// </summary>
/// <param name="patientId">objectId on db for Patient</param>
/// <param name="newLocation"></param>
/// <!-- aidoc-review:v1 severity=high kind=wrong_summary
/// "Summary describes conditional removal logic distinguishing section vs box subscribers, but the code removes ALL wsSubscribers for the given patientId without any section/box differentiation." -->
/// <!-- aidoc-review:v1 severity=high kind=mentions_removed_behavior
/// "Summary mentions BoxSubscribers and SectionSubscribers with conditional behavior that does not exist in the code; CheckEmptySubscriberGroup is called but no selective filtering is performed." -->
public void RemoveWsSubscriberByLocation(string patientId, PatientLocation? newLocation)
{
lock (_subscriberGrouped)
@@ -100,6 +107,7 @@ public class SubscriberGroupedService(
/// </summary>
/// <param name="patientId">The patient identifier used to locate the subscriber groups to update.</param>
/// <param name="wsIdToRemove">The WebSocket subscriber ID to remove from the matching subscriber and group collections.</param>
/// <!-- aidoc:v1 sig=66711e2 body=1eb38c9 -->
public void RemoveWsSubscriberPatientIdAndWsId(string patientId, string wsIdToRemove)
{
lock (_subscriberGrouped)
@@ -139,6 +147,8 @@ public class SubscriberGroupedService(
/// Object with properties to generate new grouped observations, clients using those
/// grouped obsercations and the generated list of grouped observations
/// </param>
/// <!-- aidoc-review:v1 severity=medium kind=missing_returns
/// "Method returns a List<string> describing what was removed (e.g. 'ws:{PatientId}-{Names}-{Regularity}-{Group.Count}' when the whole object is removed, or 'group:{key}' per removed group), but no <returns> tag exists." -->
public List<string> CheckEmptySubscriberGroup(WsSubscriberGrouped? wsSubscriberGrouped)
{
lock (_subscriberGrouped)
@@ -179,6 +189,7 @@ public class SubscriberGroupedService(
/// Uses locking to ensure that concurrent calls do not corrupt the subscriber list.
/// </summary>
/// <param name="wsSubscriberGrouped">The grouped WebSocket subscriber instance to be added to the collection.</param>
/// <!-- aidoc:v1 sig=38538c1 body=b4c236b -->
public void AddSubscriberGrouped(WsSubscriberGrouped wsSubscriberGrouped)
{
lock (_subscriberGrouped)
@@ -195,6 +206,7 @@ public class SubscriberGroupedService(
/// <param name="timeZoneId">The optional time zone identifier applied to the new subscription when one is created.</param>
/// <param name="connectionId">The unique identifier of the client connection being registered.</param>
/// <param name="lastObsInGroup">The most recent observation in the group, used to initialize a new subscription.</param>
/// <!-- aidoc:v1 sig=fbd3149 body=ea8c43a -->
public void CheckOnSubscriptionGroup(GroupedField groupedField, ObjectId patientId, string? timeZoneId,
string connectionId, GroupedObservation lastObsInGroup)
{
@@ -235,6 +247,7 @@ public class SubscriberGroupedService(
/// </summary>
/// <param name="wsgHashCode">The hash code used to identify the target subscriber group whose last grouped observation should be updated.</param>
/// <param name="newGroupedObservation">The new <see cref="GroupedObservation"/> to set as the last grouped observation in the matched group.</param>
/// <!-- aidoc:v1 sig=da02c06 body=39de597 -->
public void UpdateLastGroupedObsInGroup(string wsgHashCode, GroupedObservation newGroupedObservation)
{
lock (_subscriberGrouped)
@@ -247,6 +260,7 @@ public class SubscriberGroupedService(
/// Creates the next empty grouped observation for the supplied WebSocket subscriber group and distributes it asynchronously to every subscriber in the group, applying the subscriber-specific group value resolved from the group mapping.
/// </summary>
/// <param name="ws">The grouped WebSocket subscriber context whose next empty observation is generated and whose subscribers will receive the notification.</param>
/// <!-- aidoc:v1 sig=cabbfc3 body=1ebe417 -->
private async Task AddEmptyObs(WsSubscriberGrouped ws)
{
var gobs = await groupedObservationService.CreateNextEmptyObs(ws);
@@ -16,6 +16,7 @@ namespace adas_core.Application.Subscriptions;
/// <summary>
/// Represents a grouped WebSocket subscriber that aggregates and manages multiple related subscription registrations.
/// </summary>
/// <!-- aidoc:v1 sig=fe6f386 -->
public class WsSubscriberGrouped
{
private readonly EventHandler<string> _sendEvent;
@@ -30,6 +31,8 @@ public class WsSubscriberGrouped
/// <param name="lastGroupedObservationObs">The most recent <see cref="GroupedObservation"/> passed to <see cref="UpdateLastGo"/> to initialize the last-observation state.</param>
/// <param name="sendEvent">The <see cref="EventHandler{String}"/> invoked by the timer to forward outgoing messages to the subscriber.</param>
/// <!-- aidoc:v1 sig=16f9dfc body=cc103b1 -->
/// <!-- aidoc-review:v1 severity=low kind=wrong_summary
/// "Summary states the constructor 'starts the internal Timer', but Timer.Start() is only invoked when Regularity is not GroupedObservationEnum.Regularity.Times; in the 'Times' regularity case the timer is configured but not started." -->
public WsSubscriberGrouped(ObjectId patientId, string wsId, string? timeZoneId,
GroupedField gf, GroupedObservation lastGroupedObservationObs, EventHandler<string> sendEvent)
{
@@ -74,6 +77,7 @@ public class WsSubscriberGrouped
/// Refreshes the cached last observations by matching each known name against the supplied grouped observation, falling back to the most recent observation when no match is found.
/// </summary>
/// <param name="lastGroupedObservationObs">The source grouped observation whose observations are searched to populate the last-observation cache.</param>
/// <!-- aidoc:v1 sig=9a3db7f body=189f23b -->
public void UpdateLastGo(GroupedObservation lastGroupedObservationObs)
{
LastGroupedObservationObs.Clear();
@@ -150,6 +154,7 @@ public class WsSubscriberGrouped
/// <summary>
/// Restarts the timer by recalculating its interval through <c>SetUpTimerInterval</c> and then starting it.
/// </summary>
/// <!-- aidoc:v1 sig=a307365 body=43d12e3 -->
private void TimerReestart()
{
Timer.Interval = SetUpTimerInterval();
@@ -160,6 +165,7 @@ public class WsSubscriberGrouped
/// Calculates the timer interval in milliseconds until the next scheduled occurrence based on the configured <see cref="Regularity"/>, supporting Second, Minute, Day, and a default (Hour) case, and adds a per-regularity grace time.
/// </summary>
/// <returns>The number of milliseconds to wait until the next interval, including the grace time.</returns>
/// <!-- aidoc:v1 sig=e8a752a body=602d58e -->
private int SetUpTimerInterval()
{
var currentDateTime = DateTime.Now;
@@ -230,6 +236,7 @@ public class WsSubscriberGrouped
/// <summary>
/// Provides static extension methods to enhance WebSocket subscriber functionality.
/// </summary>
/// <!-- aidoc:v1 sig=1808a25 -->
public static class WsSubscriberExtension
{
/// <summary>
@@ -239,6 +246,7 @@ public static class WsSubscriberExtension
/// <param name="r">The <see cref="GroupedField"/> to compare against the source.</param>
/// <param name="patientId">The patient identifier to match against the source patient identifier.</param>
/// <returns><c>true</c> if all compared properties are equal; otherwise, <c>false</c>.</returns>
/// <!-- aidoc:v1 sig=d3fad4a body=13fa9d8 -->
public static bool Compare(this WsSubscriberGrouped source, GroupedField r, ObjectId patientId)
{
return r.Regularity == source.Regularity &&
@@ -256,6 +264,7 @@ public static class WsSubscriberExtension
/// <param name="shift">The target shift list to compare.</param>
/// <param name="sourceShift">The source shift list to compare against.</param>
/// <returns><c>true</c> if both lists are null or if their sequence of strings is equal; otherwise, <c>false</c>.</returns>
/// <!-- aidoc:v1 sig=71a6e44 body=3a82a4f -->
private static bool CompareStringShiftList(List<string>? shift, List<string>? sourceShift)
{
if (shift != null && sourceShift != null) return sourceShift.SequenceEqual(shift);
@@ -271,6 +280,7 @@ public static class WsSubscriberExtension
/// <param name="name">The fallback candidate list of names to compare against <paramref name="sourceNames"/> when <paramref name="names"/> cannot be used.</param>
/// <param name="sourceNames">The source list of names being compared.</param>
/// <returns><c>true</c> if <paramref name="sourceNames"/> is a sequence match for either <paramref name="names"/> or <paramref name="name"/>; otherwise, <c>false</c>.</returns>
/// <!-- aidoc:v1 sig=96b8c77 body=49d77d4 -->
private static bool CompareNamesStringList(List<string> names, List<string> name, List<string> sourceNames)
{
if (!CollectionsUtils.IsEmptyOrNull(names) && sourceNames.SequenceEqual(names)) return true;
@@ -281,6 +291,8 @@ public static class WsSubscriberExtension
/// Check if the incoming observation affect to the group and is new info or is irrelevant
/// </summary>
/// <returns></returns>
/// <!-- aidoc-review:v1 severity=medium kind=missing_returns
/// "The <returns> tag is present but contains no description; the reader cannot tell what true vs false means without reading the implementation" -->
public static bool IsNewObservationRelevantForGroup(this WsSubscriberGrouped group, PatientObservation obs)
{
double.TryParse(obs.Value.ToString(), out var pobsValue);
@@ -9,6 +9,7 @@ namespace adas_core.Application.Subscriptions;
/// <summary>
/// Represents a WebSocket subscriber identified by a unique string identifier.
/// </summary>
/// <!-- aidoc:v1 sig=e5ef774 -->
public class WsSubscriber(string id)
{
public string? UserName;