feat(datasync): add custom interval support to DataUpdateRepository

Add optional customIntervals parameter to GetSyncStatusAsync to allow
per-pipeline interval overrides instead of hardcoded defaults. This
enables tables like MisData to use longer sync intervals (e.g., 70 days)
while other tables use standard intervals.

Key changes:
- IDataUpdateRepository.GetSyncStatusAsync now accepts an optional
  Dictionary<string, int> for custom intervals keyed by "TableName_UpdateType"
- GetExpectedInterval and IsOverdue made public static for testing and reuse
- Added GetDefaultInterval method for accessing default values
- Updated DataSyncHealthCheck to use new signature
- Added comprehensive unit tests for custom interval behavior
This commit is contained in:
Joseph Doherty
2026-01-07 01:33:15 -05:00
parent e234c9f29a
commit da02784feb
5 changed files with 299 additions and 22 deletions
@@ -64,9 +64,16 @@ public interface IDataUpdateRepository
/// <summary>
/// Gets sync status for health check purposes.
/// </summary>
/// <param name="customIntervals">
/// Optional dictionary of custom intervals per table/updateType.
/// Key format: "{TableName}_{UpdateType}" where UpdateType is the numeric enum value (e.g., "MisData_3" for Mass).
/// Value: interval in minutes.
/// </param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>List of table sync status records.</returns>
Task<List<TableSyncStatus>> GetSyncStatusAsync(CancellationToken cancellationToken = default);
Task<List<TableSyncStatus>> GetSyncStatusAsync(
Dictionary<string, int>? customIntervals = null,
CancellationToken cancellationToken = default);
}
/// <summary>
@@ -25,7 +25,7 @@ public class DataSyncHealthCheck : IHealthCheck
{
try
{
var statuses = await _repository.GetSyncStatusAsync(cancellationToken);
var statuses = await _repository.GetSyncStatusAsync(customIntervals: null, cancellationToken);
var data = new Dictionary<string, object>();
foreach (var status in statuses)
@@ -158,7 +158,9 @@ WHERE StartDT < DATEADD(DAY, -@retentionDays, GETUTCDATE())";
}
/// <inheritdoc/>
public async Task<List<TableSyncStatus>> GetSyncStatusAsync(CancellationToken cancellationToken = default)
public async Task<List<TableSyncStatus>> GetSyncStatusAsync(
Dictionary<string, int>? customIntervals = null,
CancellationToken cancellationToken = default)
{
const string sql = @"
WITH LastSuccessful AS (
@@ -182,16 +184,18 @@ FROM LastSuccessful";
(UpdateTypes)r.UpdateType,
r.LastSuccessfulSync,
r.LastSuccessfulSync.HasValue,
GetExpectedInterval((UpdateTypes)r.UpdateType),
IsOverdue(r.LastSuccessfulSync, (UpdateTypes)r.UpdateType),
GetExpectedInterval(r.TableName, (UpdateTypes)r.UpdateType, customIntervals),
IsOverdue(r.LastSuccessfulSync, r.TableName, (UpdateTypes)r.UpdateType, customIntervals),
r.RecentFailures))
.ToList();
}
/// <summary>
/// Gets the expected interval in minutes for an update type.
/// Gets the default interval in minutes for an update type.
/// </summary>
private static int GetExpectedInterval(UpdateTypes updateType)
/// <param name="updateType">The update type.</param>
/// <returns>The default interval in minutes.</returns>
public static int GetDefaultInterval(UpdateTypes updateType)
{
return updateType switch
{
@@ -202,20 +206,54 @@ FROM LastSuccessful";
};
}
/// <summary>
/// Gets the expected interval in minutes for a table and update type.
/// Uses custom interval if provided, otherwise falls back to default.
/// </summary>
/// <param name="tableName">The table name.</param>
/// <param name="updateType">The update type.</param>
/// <param name="customIntervals">Optional dictionary of custom intervals per table/updateType.</param>
/// <returns>The expected interval in minutes.</returns>
public static int GetExpectedInterval(
string tableName,
UpdateTypes updateType,
Dictionary<string, int>? customIntervals)
{
if (customIntervals is not null)
{
var key = $"{tableName}_{(int)updateType}";
if (customIntervals.TryGetValue(key, out var customInterval))
{
return customInterval;
}
}
return GetDefaultInterval(updateType);
}
/// <summary>
/// Checks if a sync is overdue based on last successful sync time.
/// </summary>
private static bool IsOverdue(DateTime? lastSync, UpdateTypes updateType)
/// <param name="lastSync">The last successful sync time.</param>
/// <param name="tableName">The table name.</param>
/// <param name="updateType">The update type.</param>
/// <param name="customIntervals">Optional dictionary of custom intervals per table/updateType.</param>
/// <returns>True if the sync is overdue; otherwise, false.</returns>
public static bool IsOverdue(
DateTime? lastSync,
string tableName,
UpdateTypes updateType,
Dictionary<string, int>? customIntervals)
{
if (!lastSync.HasValue)
{
return true;
}
var expectedInterval = GetExpectedInterval(updateType);
var expectedInterval = GetExpectedInterval(tableName, updateType, customIntervals);
var grace = expectedInterval * 0.5; // 50% grace period
var overdueTreshold = DateTime.UtcNow.AddMinutes(-(expectedInterval + grace));
var overdueThreshold = DateTime.UtcNow.AddMinutes(-(expectedInterval + grace));
return lastSync.Value < overdueTreshold;
return lastSync.Value < overdueThreshold;
}
}