mirror of
https://github.com/alexhopeoconnor/bom-local-service.git
synced 2026-10-04 03:28:11 +10:00
Fix frame deduplication and chronological ordering in time series endpoint
- Fix scraping to wait for display label to change after step forward, preventing duplicate minutesAgo values within same folder - Add hashset-based deduplication by AbsoluteObservationTime across cache folders - Filter frames that are too close together (< 4 minutes apart) - Preserve original folder structure while pruning filtered frames - Sort folders by earliest frame time to maintain chronological order - Add validation to detect folder overlaps that could cause out-of-order frames
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
namespace BomLocalService.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Information about a cache folder containing cached data for a location.
|
||||
/// Each cache folder represents one capture session and can contain multiple data types.
|
||||
/// </summary>
|
||||
public class CacheFolder
|
||||
{
|
||||
/// <summary>
|
||||
/// The folder name (e.g., "Pomona_QLD_20251207_000906").
|
||||
/// </summary>
|
||||
public string FolderName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The UTC timestamp when this cache folder was created (extracted from folder name).
|
||||
/// </summary>
|
||||
public DateTime CacheTimestamp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The observation time from the metadata (when the weather data was captured by BOM).
|
||||
/// </summary>
|
||||
public DateTime ObservationTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// List of data types available in this cache folder.
|
||||
/// </summary>
|
||||
public List<CachedDataType> AvailableDataTypes { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Whether this cache folder is complete and ready to use.
|
||||
/// </summary>
|
||||
public bool IsComplete { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
namespace BomLocalService.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Information about the available cache range for a location.
|
||||
/// Provides oldest and newest cache folder information to help clients understand available historical data.
|
||||
/// </summary>
|
||||
public class CacheRange
|
||||
{
|
||||
/// <summary>
|
||||
/// Information about the oldest available cache folder.
|
||||
/// </summary>
|
||||
public CacheFolder? OldestCache { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Information about the newest available cache folder.
|
||||
/// </summary>
|
||||
public CacheFolder? NewestCache { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Total number of complete cache folders available.
|
||||
/// </summary>
|
||||
public int TotalCacheFolders { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The time span between oldest and newest cache (in minutes).
|
||||
/// Null if there are fewer than 2 cache folders.
|
||||
/// </summary>
|
||||
public int? TimeSpanMinutes { get; set; }
|
||||
}
|
||||
@@ -50,6 +50,7 @@ public class CacheUpdateStatus
|
||||
/// - "Cache is valid, no update needed" - Cache exists and is still fresh
|
||||
/// - "Cache is stale, update triggered" - Cache exists but expired, update initiated
|
||||
/// - "No cache exists, update triggered" - No cache found, update initiated
|
||||
/// - "Cache update already in progress" - An update is currently running
|
||||
/// </summary>
|
||||
public string? Message { get; set; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace BomLocalService.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Types of cached data available from BOM weather pages.
|
||||
/// </summary>
|
||||
public enum CachedDataType
|
||||
{
|
||||
/// <summary>
|
||||
/// Rain radar images showing historical precipitation (40 min ago → 10 min ago).
|
||||
/// Frames represent a time series and can be joined across cache folders for extended historical slideshows.
|
||||
/// </summary>
|
||||
Radar
|
||||
// Future data types (not yet implemented):
|
||||
// Temperature - forecast images showing current conditions and projected temperatures for the week ahead
|
||||
// Wind - forecast images showing current conditions and projected wind patterns for the week ahead
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
namespace BomLocalService.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Base model representing a single cached frame image.
|
||||
/// Extended by type-specific frame models (RadarFrame, etc.).
|
||||
/// </summary>
|
||||
public class CachedFrame
|
||||
{
|
||||
/// <summary>
|
||||
/// Frame index within the frameset (0-based).
|
||||
/// </summary>
|
||||
public int FrameIndex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Full file system path to the frame image file (server-side only).
|
||||
/// </summary>
|
||||
public string ImagePath { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// URL endpoint to retrieve this frame image.
|
||||
/// </summary>
|
||||
public string ImageUrl { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The data type this frame belongs to.
|
||||
/// </summary>
|
||||
public CachedDataType DataType { get; set; }
|
||||
}
|
||||
+17
-21
@@ -1,33 +1,29 @@
|
||||
namespace BomLocalService.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a single frame from the radar slideshow (0-6).
|
||||
/// Represents a single radar frame (historical precipitation data).
|
||||
/// Frame 0 is oldest (40 minutes ago), Frame 6 is newest (10 minutes ago).
|
||||
/// Radar frames can be joined across cache folders to create extended historical slideshows.
|
||||
/// </summary>
|
||||
public class RadarFrame
|
||||
public class RadarFrame : CachedFrame
|
||||
{
|
||||
/// <summary>
|
||||
/// Frame index (0-6), where 0 is the oldest frame (40 minutes ago) and 6 is the newest (10 minutes ago).
|
||||
/// </summary>
|
||||
public int FrameIndex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Full file system path to the frame image file (server-side only).
|
||||
/// Format: "{CacheDirectory}/{LocationKey}_{Timestamp}/frame_{FrameIndex}.png"
|
||||
/// Example: "/app/cache/Pomona_QLD_20251207_000906/frame_0.png"
|
||||
/// </summary>
|
||||
public string ImagePath { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// URL endpoint to retrieve this frame image.
|
||||
/// Format: "/api/radar/{Suburb}/{State}/frame/{FrameIndex}"
|
||||
/// Example: "/api/radar/Pomona/QLD/frame/0"
|
||||
/// </summary>
|
||||
public string ImageUrl { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Number of minutes ago this frame represents (40, 35, 30, 25, 20, 15, 10).
|
||||
/// Frame 0 = 40 minutes ago, Frame 6 = 10 minutes ago.
|
||||
/// This is relative to the cache folder's observation time.
|
||||
/// </summary>
|
||||
public int MinutesAgo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The absolute UTC observation time for this frame.
|
||||
/// Calculated as: ObservationTime - MinutesAgo.
|
||||
/// This is set when frames are joined across cache folders in timeseries responses.
|
||||
/// </summary>
|
||||
public DateTime? AbsoluteObservationTime { get; set; }
|
||||
|
||||
public RadarFrame()
|
||||
{
|
||||
DataType = CachedDataType.Radar;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
namespace BomLocalService.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Response containing radar frames from multiple cache folders (historical data).
|
||||
/// Used when requesting radar data across a time range rather than just the most recent frames.
|
||||
/// Radar frames can be joined across cache folders because they represent a historical time series.
|
||||
/// </summary>
|
||||
public class RadarTimeSeriesResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// List of cache folders, each containing radar frames from a single capture session.
|
||||
/// Ordered chronologically (oldest first).
|
||||
/// </summary>
|
||||
public List<RadarCacheFolderFrames> CacheFolders { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// The start time of the requested range (UTC).
|
||||
/// </summary>
|
||||
public DateTime? StartTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The end time of the requested range (UTC).
|
||||
/// </summary>
|
||||
public DateTime? EndTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Total number of frames across all cache folders.
|
||||
/// </summary>
|
||||
public int TotalFrames { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A cache folder with its associated radar frames.
|
||||
/// Represents all frames from a single radar capture session (from the radar subfolder).
|
||||
/// </summary>
|
||||
public class RadarCacheFolderFrames
|
||||
{
|
||||
/// <summary>
|
||||
/// The cache folder name this frameset came from.
|
||||
/// </summary>
|
||||
public string CacheFolderName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The timestamp when this cache folder was created (UTC).
|
||||
/// </summary>
|
||||
public DateTime CacheTimestamp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The observation time from metadata (UTC).
|
||||
/// </summary>
|
||||
public DateTime ObservationTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// All radar frames from this cache folder's radar subfolder (typically 7 frames, 0-6).
|
||||
/// Frame URLs are already generated and ready to use.
|
||||
/// </summary>
|
||||
public List<RadarFrame> Frames { get; set; } = new();
|
||||
}
|
||||
Reference in New Issue
Block a user