Skip to content

Commit 2f53062

Browse files
committed
Drop confidence threshold for CLOE timeseries upload
The sara-constant-level-oiler analysis already gates on its own configurable confidence threshold, so re-gating in SARA is redundant. Upload whenever oilLevel and confidence are both non-null.
1 parent bc34e55 commit 2f53062

6 files changed

Lines changed: 141 additions & 3 deletions

File tree

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using System.Collections.Concurrent;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Threading.Tasks;
5+
using api.Services;
6+
7+
namespace Api.Test.Mocks;
8+
9+
/// <summary>
10+
/// Test fake for <see cref="ITimeseriesService"/> that records every upload
11+
/// request for later inspection by tests. CO2 fetches return null.
12+
/// </summary>
13+
public class RecordingTimeseriesService : ITimeseriesService
14+
{
15+
private readonly ConcurrentQueue<TriggerTimeseriesUploadRequest> _uploads = new();
16+
17+
public IReadOnlyCollection<TriggerTimeseriesUploadRequest> Uploads => _uploads.ToArray();
18+
19+
public Task TriggerTimeseriesUpload(TriggerTimeseriesUploadRequest uploadRequest)
20+
{
21+
_uploads.Enqueue(uploadRequest);
22+
return Task.CompletedTask;
23+
}
24+
25+
public Task<double?> FetchCO2ConcentrationFromTimeseries(FetchCO2MeasurementRequest fetchRequest)
26+
{
27+
return Task.FromResult<double?>(null);
28+
}
29+
30+
public void Reset()
31+
{
32+
_uploads.Clear();
33+
}
34+
}

api.Tests/Services/ResultHandlers/WorkflowResultHandlers/CLOEResultHandlerTests.cs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ public async Task OnWorkflowCompleted_ValidResultForSingleRecord_PublishesAnalys
7575
var published = Assert.Single(_factory.MqttPublisher.AnalysisResultMessages);
7676
Assert.Equal(oilLevel.ToString("F2"), published.Value);
7777
Assert.Equal(confidence * 100, published.Confidence);
78+
79+
var upload = Assert.Single(_factory.TimeseriesService.Uploads);
80+
Assert.Equal(oilLevel, upload.Value);
7881
}
7982

8083
[Fact]
@@ -185,4 +188,44 @@ public async Task OnWorkflowCompleted_ResultWithWarning_PublishesWarning()
185188
Assert.Equal(confidence * 100, published.Confidence);
186189
Assert.Equal(warning, published.Warning);
187190
}
191+
192+
[Fact]
193+
public async Task OnWorkflowCompleted_NullOilLevel_DoesNotUploadTimeseries()
194+
{
195+
var record = await _db.NewInspectionRecord(inspectionId: "insp-123");
196+
var analysis = await _db.NewAnalysis(inspectionRecords: [record]);
197+
var run = await _db.NewAnalysisRun(analysis);
198+
var workflow = await _db.NewWorkflow(run, workflowType: "cloe");
199+
workflow.ResultJson = JsonSerializer.Serialize(
200+
new { oilLevel = (float?)null, confidence = 0.9f }
201+
);
202+
await _context.SaveChangesAsync(TestContext.Current.CancellationToken);
203+
204+
using var scope = _factory.Services.CreateScope();
205+
var handler = ResolveHandler(scope);
206+
207+
await handler.OnWorkflowCompleted(workflow);
208+
209+
Assert.Empty(_factory.TimeseriesService.Uploads);
210+
}
211+
212+
[Fact]
213+
public async Task OnWorkflowCompleted_NullConfidence_DoesNotUploadTimeseries()
214+
{
215+
var record = await _db.NewInspectionRecord(inspectionId: "insp-123");
216+
var analysis = await _db.NewAnalysis(inspectionRecords: [record]);
217+
var run = await _db.NewAnalysisRun(analysis);
218+
var workflow = await _db.NewWorkflow(run, workflowType: "cloe");
219+
workflow.ResultJson = JsonSerializer.Serialize(
220+
new { oilLevel = 0.42f, confidence = (float?)null }
221+
);
222+
await _context.SaveChangesAsync(TestContext.Current.CancellationToken);
223+
224+
using var scope = _factory.Services.CreateScope();
225+
var handler = ResolveHandler(scope);
226+
227+
await handler.OnWorkflowCompleted(workflow);
228+
229+
Assert.Empty(_factory.TimeseriesService.Uploads);
230+
}
188231
}

api.Tests/TestWebApplicationFactory.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ public class TestWebApplicationFactory<TProgram>(string postgresConnectionString
3434
public RecordingMqttPublisher MqttPublisher { get; } = new();
3535
public RecordingHttpMessageHandler ArgoHttpHandler { get; } = new();
3636
public RecordingEmailService EmailService { get; } = new();
37+
public RecordingTimeseriesService TimeseriesService { get; } = new();
3738

3839
/// <summary>
3940
/// Returns the configured Argo trigger URL for the given workflow type.
@@ -71,6 +72,7 @@ protected override void ConfigureWebHost(IWebHostBuilder builder)
7172
ReplaceMqttPublisher(services);
7273
ReplaceArgoHttpClient(services);
7374
ReplaceEmailService(services);
75+
ReplaceTimeseriesService(services);
7476
ReplaceAuthentication(services);
7577
RegisterMqttEventHandler(services);
7678
RemoveHostedServices(services);
@@ -127,6 +129,16 @@ private void ReplaceEmailService(IServiceCollection services)
127129
services.AddSingleton<IEmailService>(EmailService);
128130
}
129131

132+
private void ReplaceTimeseriesService(IServiceCollection services)
133+
{
134+
var existing = services.Where(d => d.ServiceType == typeof(ITimeseriesService)).ToList();
135+
foreach (var descriptor in existing)
136+
{
137+
services.Remove(descriptor);
138+
}
139+
services.AddSingleton<ITimeseriesService>(TimeseriesService);
140+
}
141+
130142
private static void RemoveHostedServices(IServiceCollection services)
131143
{
132144
var hostedDescriptors = services

api.Tests/appsettings.Test.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"Frontend": {
2929
"Enabled": false
3030
},
31+
"SARATimeseriesBaseUrl": "",
3132
"OpenTelemetry": {
3233
"Enabled": false
3334
},

api/Services/ResultHandlers/WorkflowResultHandlers/CLOEResultHandler.cs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Globalization;
12
using api.Database.Context;
23
using api.Database.Models;
34
using api.MQTT;
@@ -15,6 +16,7 @@ internal sealed class CLOEResult
1516
public class CLOEResultHandler(
1617
SaraDbContext context,
1718
IMqttPublisherService mqttPublisherService,
19+
ITimeseriesService timeseriesService,
1820
ILogger<CLOEResultHandler> logger
1921
) : IWorkflowResultHandler
2022
{
@@ -72,5 +74,54 @@ public async Task OnWorkflowCompleted(Workflow workflow)
7274
}
7375

7476
await mqttPublisherService.PublishSaraAnalysisResultAvailable(message);
77+
78+
await TryUploadTimeseries(workflow, inspectionRecord, result);
79+
}
80+
81+
private async Task TryUploadTimeseries(
82+
Workflow workflow,
83+
InspectionRecord inspectionRecord,
84+
CLOEResult? result
85+
)
86+
{
87+
if (result?.OilLevel is not { } oilLevel || result.Confidence is not { } confidence)
88+
{
89+
logger.LogWarning(
90+
"Skipping CLOE timeseries upload for workflow {WorkflowId}: oilLevel or confidence is null",
91+
workflow.Id
92+
);
93+
return;
94+
}
95+
96+
var uploadRequest = new TriggerTimeseriesUploadRequest
97+
{
98+
Name =
99+
$"{inspectionRecord.InstallationCode}_{inspectionRecord.Tag}_{inspectionRecord.InspectionDescription}",
100+
Facility = inspectionRecord.InstallationCode,
101+
ExternalId = "",
102+
Description = "CLOE-oil-level",
103+
Unit = "percentage",
104+
AssetId = inspectionRecord.InstallationCode,
105+
Value = oilLevel,
106+
Timestamp = inspectionRecord.Timestamp ?? DateTime.UtcNow,
107+
Step = true,
108+
Metadata = new Dictionary<string, string>
109+
{
110+
{ "Confidence", confidence.ToString(CultureInfo.InvariantCulture) },
111+
},
112+
};
113+
114+
try
115+
{
116+
await timeseriesService.TriggerTimeseriesUpload(uploadRequest);
117+
}
118+
catch (Exception ex)
119+
{
120+
logger.LogError(
121+
ex,
122+
"Failed to upload CLOE oil-level datapoint to Timeseries for workflow {WorkflowId}",
123+
workflow.Id
124+
);
125+
}
75126
}
76127
}

api/appsettings.json

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,5 @@
8282
"Fencilla": {
8383
"Installations": {}
8484
}
85-
},
86-
"Thresholds": {
87-
"CLOETimeseriesUploadConfidenceThreshold": 0.6
8885
}
8986
}

0 commit comments

Comments
 (0)