Skip to content
This repository was archived by the owner on Sep 8, 2025. It is now read-only.

Commit 4828cbd

Browse files
committed
Get duplicate detection working
Fixes #5 Signed-off-by: Dave Thaler <dthaler1968@gmail.com>
1 parent e620c17 commit 4828cbd

File tree

1 file changed

+133
-13
lines changed

1 file changed

+133
-13
lines changed

OrcaHelloProxy/PeriodicTasks.cs

Lines changed: 133 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,16 @@ public class PeriodicTasks : BackgroundService
1414
private readonly IServiceScopeFactory _scopeFactory;
1515
private readonly ILogger<PeriodicTasks> _logger;
1616
private static HttpClient _httpClient = new HttpClient();
17-
private static string _orcaHelloDetectionsUri = "https://aifororcasdetections.azurewebsites.net/api/detections?Page=1&SortBy=timestamp&SortOrder=desc&Timeframe=24h&Location=all&RecordsPerPage=1000";
18-
private static string _orcasiteDetectionsUri = "https://live.orcasound.net/api/json/detections?sort=-timestamp&filter[category]=whale&filter[source]=machine&page[limit]=1";
17+
18+
// We really only need to get all that haven't been submitted already,
19+
// but for now we will just get up to 1000 detections from the last week.
20+
// TODO: make URI configurable.
21+
//private static string _orcaHelloGetDetectionsUri = "https://aifororcasdetections.azurewebsites.net/api/detections?Page=1&SortBy=timestamp&SortOrder=desc&Timeframe=24h&Location=all&RecordsPerPage=1000";
22+
private static string _orcaHelloGetDetectionsUri = "https://aifororcasdetections.azurewebsites.net/api/detections?Page=1&SortBy=timestamp&SortOrder=desc&Timeframe=1w&Location=all&RecordsPerPage=1000";
23+
24+
private static string _orcasiteGetDetectionsUri = "https://beta.orcasound.net/api/json/detections?sort=-timestamp&filter[category]=whale&filter[source]=machine&page[limit]=10&fields[detection]=id,source_ip,playlist_timestamp,player_offset,listener_count,timestamp,description,visible,source,category,candidate_id,feed_id";
1925
private static string _orcasitePostDetectionUri = "https://beta.orcasound.net/api/json/detections?fields%5Bdetection%5D=id%2Csource_ip%2Cplaylist_timestamp%2Cplayer_offset%2Clistener_count%2Ctimestamp%2Cdescription%2Cvisible%2Csource%2Ccategory%2Ccandidate_id%2Cfeed_id";
20-
private static string _orcasiteFeedsUri = "https://beta.orcasound.net/api/json/feeds?fields%5Bfeed%5D=id%2Cname%2Cnode_name%2Cslug%2Clocation_point%2Cintro_html%2Cimage_url%2Cvisible%2Cbucket%2Cbucket_region%2Ccloudfront_url%2Cdataplicity_id%2Corcahello_id";
26+
private static string _orcasiteGetFeedsUri = "https://beta.orcasound.net/api/json/feeds?fields%5Bfeed%5D=id%2Cname%2Cnode_name%2Cslug%2Clocation_point%2Cintro_html%2Cimage_url%2Cvisible%2Cbucket%2Cbucket_region%2Ccloudfront_url%2Cdataplicity_id%2Corcahello_id";
2127
private static string? _orcasiteApiKey;
2228

2329
public PeriodicTasks(IServiceScopeFactory scopeFactory, ILogger<PeriodicTasks> logger)
@@ -80,6 +86,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
8086
return arrayElement;
8187
} catch (Exception e)
8288
{
89+
_logger.LogError($"Error: {e.Message}");
8390
return null;
8491
}
8592
}
@@ -99,6 +106,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
99106
}
100107
catch (Exception e)
101108
{
109+
_logger.LogError($"Error: {e.Message}");
102110
return null;
103111
}
104112
}
@@ -111,25 +119,23 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
111119

112120
try
113121
{
114-
JsonElement? orcasiteFeedsArray = await GetDataArrayAsync(_orcasiteFeedsUri);
122+
JsonElement? orcasiteFeedsArray = await GetDataArrayAsync(_orcasiteGetFeedsUri);
115123
if (orcasiteFeedsArray == null)
116124
{
117125
_logger.LogError("Failed to retrieve orcasite feeds.");
118126
return;
119127
}
120128

121-
// Query detections from OrcaHello over the past 24 hours.
122-
// We really only need to get all that haven't been submitted already,
123-
// but for now we will just get up to 1000 detections from the last 24 hours.
124-
JsonElement? orcaHelloDetectionsArray = await GetRawArrayAsync(_orcaHelloDetectionsUri);
129+
// Query the most recent detections from OrcaHello.
130+
JsonElement? orcaHelloDetectionsArray = await GetRawArrayAsync(_orcaHelloGetDetectionsUri);
125131
if (orcaHelloDetectionsArray == null)
126132
{
127133
_logger.LogError("Failed to retrieve orcahello detections.");
128134
return;
129135
}
130136

131-
// Query the last 100 detections already on Orcasite.
132-
JsonElement? orcasiteDetectionsArray = await GetDataArrayAsync(_orcasiteDetectionsUri);
137+
// Query the most recent detection(s) already on Orcasite.
138+
JsonElement? orcasiteDetectionsArray = await GetDataArrayAsync(_orcasiteGetDetectionsUri);
133139
if (orcasiteDetectionsArray == null)
134140
{
135141
_logger.LogError("Failed to retrieve orcasite detections.");
@@ -192,6 +198,11 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
192198
_logger.LogError($"Invalid timestamp kind in ExecuteTask: {timestamp.ValueKind}");
193199
continue;
194200
}
201+
if (!DateTime.TryParse(timestamp.GetString(), out DateTime dateTime))
202+
{
203+
_logger.LogError($"Invalid timestamp ExecuteTask: {timestamp}");
204+
continue;
205+
}
195206

196207
// Get comments from OrcaHello.
197208
if (!orcaHelloDetection.TryGetProperty("comments", out var comments))
@@ -201,6 +212,12 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
201212
}
202213
string? commentsString = comments.ValueKind == JsonValueKind.String ? comments.GetString() : null;
203214

215+
// See if the detection is already on orcasite.
216+
if (OrcasiteAlreadyHasDetection(orcasiteDetectionsArray, dateTime, feedId, commentsString))
217+
{
218+
continue;
219+
}
220+
204221
// Compose a detections post.
205222
JsonObject newDetection = new JsonObject
206223
{
@@ -241,18 +258,18 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
241258
// Optionally handle response
242259
if (response.IsSuccessStatusCode)
243260
{
244-
Console.WriteLine("Detection posted successfully!");
261+
_logger.LogInformation($"Detection for {timestamp} posted successfully!");
245262
}
246263
else
247264
{
248265
string message = await response.Content.ReadAsStringAsync();
249-
Console.WriteLine($"Error: {response.StatusCode} - {message}");
266+
_logger.LogError($"Error: {response.StatusCode} - {message}");
250267
}
251268
}
252269
}
253270
catch (Exception e)
254271
{
255-
272+
_logger.LogError($"Error: {e.Message}");
256273
}
257274
#if false
258275
OrcanodeMonitorContext context = scope.ServiceProvider.GetRequiredService<OrcanodeMonitorContext>();
@@ -269,8 +286,111 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
269286
#endif
270287
}
271288

289+
/// <summary>
290+
/// Check whether Orcasite already has a detection with the same timestamp, feed_id, and comments.
291+
/// </summary>
292+
/// <param name="orcasiteDetectionsArray">Most recent detections already on Orcasite</param>
293+
/// <param name="dateTime">Timestamp of OrcaHello detection</param>
294+
/// <param name="feedId">Feed ID of OrcaHello detection</param>
295+
/// <param name="comments">Comments from OrcaHello detection</param>
296+
/// <returns></returns>
297+
private bool OrcasiteAlreadyHasDetection(JsonElement? orcasiteDetectionsArray, DateTime dateTime, string feedId, string? comments)
298+
{
299+
if (orcasiteDetectionsArray == null)
300+
{
301+
return false;
302+
}
303+
304+
// Compare against each detection known to orcasite, with newest first.
305+
foreach (JsonElement detection in orcasiteDetectionsArray.Value.EnumerateArray())
306+
{
307+
if (!detection.TryGetProperty("attributes", out var attributes))
308+
{
309+
_logger.LogError($"Missing attributes in Orcasite result");
310+
continue;
311+
}
312+
if (attributes.ValueKind != JsonValueKind.Object)
313+
{
314+
_logger.LogError($"Invalid attributes kind in ExecuteTask: {attributes.ValueKind}");
315+
continue;
316+
}
317+
318+
// Get timestamp according to Orcasite.
319+
if (!attributes.TryGetProperty("timestamp", out var orcasiteTimestamp))
320+
{
321+
_logger.LogError($"Missing timestamp in Orcasite result");
322+
continue;
323+
}
324+
if (orcasiteTimestamp.ValueKind != JsonValueKind.String)
325+
{
326+
_logger.LogError($"Invalid timestamp kind in ExecuteTask: {orcasiteTimestamp.ValueKind}");
327+
continue;
328+
}
329+
string orcasiteTimestampString = orcasiteTimestamp.GetString() ?? "";
330+
DateTime orcasiteDateTime = DateTime.Parse(orcasiteTimestampString);
331+
if (orcasiteDateTime != dateTime)
332+
{
333+
if (dateTime < orcasiteDateTime)
334+
{
335+
// The new detection is older than the latest on orcasite,
336+
// so just assume that orcasite already has it.
337+
return true;
338+
}
339+
continue;
340+
}
341+
342+
// Get description according to Orcasite.
343+
if (!attributes.TryGetProperty("description", out var description))
344+
{
345+
_logger.LogError($"Missing description in Orcasite result");
346+
continue;
347+
}
348+
if (description.ValueKind != JsonValueKind.String)
349+
{
350+
_logger.LogError($"Invalid description kind in ExecuteTask: {description.ValueKind}");
351+
continue;
352+
}
353+
string? orcasiteDescriptionString = description.GetString();
354+
if (orcasiteDescriptionString != comments)
355+
{
356+
continue;
357+
}
358+
359+
// Get feed_id according to Orcasite.
360+
if (!attributes.TryGetProperty("feed_id", out var orcasiteFeedId))
361+
{
362+
_logger.LogError($"Missing feed_id in Orcasite result");
363+
continue;
364+
}
365+
if (orcasiteFeedId.ValueKind != JsonValueKind.String)
366+
{
367+
_logger.LogError($"Invalid feed_id kind in ExecuteTask: {orcasiteFeedId.ValueKind}");
368+
continue;
369+
}
370+
string orcasiteFeedIdString = orcasiteFeedId.GetString() ?? "";
371+
if (orcasiteFeedIdString != feedId)
372+
{
373+
continue;
374+
}
375+
376+
return true;
377+
}
378+
return false;
379+
}
380+
272381
private string? GetFeedId(string nameToFind, JsonElement feedsArray)
273382
{
383+
// Implement some aliases for backwards compatibility since OrcaHello uses
384+
// its own strings.
385+
if (nameToFind == "North SJC")
386+
{
387+
nameToFind = "North San Juan Channel";
388+
}
389+
else if (nameToFind == "Haro Strait")
390+
{
391+
nameToFind = "Orcasound Lab";
392+
}
393+
274394
foreach (JsonElement feed in feedsArray.EnumerateArray())
275395
{
276396
if (!feed.TryGetProperty("attributes", out var attributes))

0 commit comments

Comments
 (0)