Initial commit: BOM Local Service

This commit is contained in:
2025-12-07 10:47:20 +10:00
parent 6d95920864
commit 86ff2c6f66
32 changed files with 3110 additions and 0 deletions
+40
View File
@@ -0,0 +1,40 @@
namespace BomLocalService.Utilities;
public static class FilePathHelper
{
/// <summary>
/// Gets the cache directory path from configuration or returns default
/// </summary>
public static string GetCacheDirectory(IConfiguration configuration)
{
return configuration.GetValue<string>("CacheDirectory")
?? Path.Combine(AppContext.BaseDirectory, "cache");
}
/// <summary>
/// Gets the metadata file path for a given screenshot path
/// </summary>
public static string GetMetadataFilePath(string screenshotPath)
{
return Path.ChangeExtension(screenshotPath, ".json");
}
/// <summary>
/// Gets the cache file pattern for a location key (e.g., "Pomona_QLD_*.png")
/// </summary>
public static string GetCacheFilePattern(string locationKey)
{
var safeLocationKey = LocationHelper.SanitizeFileName(locationKey);
return $"{safeLocationKey}_*.png";
}
/// <summary>
/// Gets the cache file pattern for suburb and state
/// </summary>
public static string GetCacheFilePattern(string suburb, string state)
{
var locationKey = LocationHelper.GetLocationKey(suburb, state);
return GetCacheFilePattern(locationKey);
}
}
+63
View File
@@ -0,0 +1,63 @@
namespace BomLocalService.Utilities;
public static class LocationHelper
{
/// <summary>
/// Creates a location key from suburb and state (e.g., "Pomona_QLD")
/// </summary>
public static string GetLocationKey(string suburb, string state)
{
return $"{suburb}_{state}";
}
/// <summary>
/// Sanitizes a filename by removing invalid characters and replacing spaces/commas
/// </summary>
public static string SanitizeFileName(string fileName)
{
var invalidChars = Path.GetInvalidFileNameChars();
return string.Join("_", fileName.Split(invalidChars, StringSplitOptions.RemoveEmptyEntries))
.Replace(" ", "_")
.Replace(",", "");
}
/// <summary>
/// Parses suburb and state from a cache filename (format: Suburb_State_YYYYMMDD_HHMMSS.png)
/// Returns null if parsing fails
/// </summary>
public static (string suburb, string state)? ParseLocationFromFilename(string fileName)
{
var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileName);
var parts = fileNameWithoutExtension.Split('_');
if (parts.Length >= 2)
{
return (parts[0], parts[1]);
}
return null;
}
/// <summary>
/// Parses timestamp from a cache filename (format: Suburb_State_YYYYMMDD_HHMMSS.png)
/// Returns null if parsing fails
/// </summary>
public static DateTime? ParseTimestampFromFilename(string fileName)
{
var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileName);
var parts = fileNameWithoutExtension.Split('_');
if (parts.Length >= 3)
{
// Last two parts should be date and time
var dateTimeStr = $"{parts[^2]}_{parts[^1]}";
if (DateTime.TryParseExact(dateTimeStr, "yyyyMMdd_HHmmss", null, System.Globalization.DateTimeStyles.None, out var fileTime))
{
return fileTime;
}
}
return null;
}
}
+40
View File
@@ -0,0 +1,40 @@
using BomLocalService.Models;
namespace BomLocalService.Utilities;
public static class ResponseBuilder
{
/// <summary>
/// Creates a RadarScreenshotResponse from a file path and metadata
/// </summary>
public static RadarScreenshotResponse CreateRadarScreenshotResponse(
string imagePath,
LastUpdatedInfo? metadata = null)
{
var lastWriteTime = File.Exists(imagePath)
? File.GetLastWriteTime(imagePath)
: DateTime.UtcNow;
if (metadata == null)
{
return new RadarScreenshotResponse
{
ImagePath = imagePath,
LastUpdated = lastWriteTime,
ObservationTime = DateTime.UtcNow,
ForecastTime = DateTime.UtcNow
};
}
return new RadarScreenshotResponse
{
ImagePath = imagePath,
LastUpdated = lastWriteTime,
ObservationTime = metadata.ObservationTime,
ForecastTime = metadata.ForecastTime,
WeatherStation = metadata.WeatherStation,
Distance = metadata.Distance
};
}
}
+37
View File
@@ -0,0 +1,37 @@
using Microsoft.Playwright;
namespace BomLocalService.Utilities;
public static class SelectorHelper
{
/// <summary>
/// Tries to find a locator by trying multiple selectors in order
/// Returns the first visible locator found, or null if none found
/// </summary>
public static async Task<ILocator?> FindLocatorBySelectorsAsync(
IPage page,
string[] selectors,
ILogger? logger = null)
{
foreach (var selector in selectors)
{
try
{
var locator = page.Locator(selector).First;
if (await locator.IsVisibleAsync())
{
logger?.LogInformation("Found element with selector: {Selector}", selector);
return locator;
}
}
catch
{
// Continue to next selector
continue;
}
}
return null;
}
}
+45
View File
@@ -0,0 +1,45 @@
namespace BomLocalService.Utilities;
public static class StateAbbreviationHelper
{
private static readonly Dictionary<string, string[]> StateMappings = new(StringComparer.OrdinalIgnoreCase)
{
{ "qld", new[] { "queensland" } },
{ "nsw", new[] { "new south wales" } },
{ "vic", new[] { "victoria" } },
{ "sa", new[] { "south australia" } },
{ "wa", new[] { "western australia" } },
{ "tas", new[] { "tasmania" } },
{ "nt", new[] { "northern territory" } },
{ "act", new[] { "australian capital territory" } }
};
/// <summary>
/// Checks if the text matches the state abbreviation or full state name
/// </summary>
public static bool MatchesState(string text, string stateAbbreviation)
{
if (string.IsNullOrWhiteSpace(stateAbbreviation))
{
return true; // No state specified, match any
}
var textLower = text.ToLower();
var stateLower = stateAbbreviation.ToLower().Trim();
// Direct match
if (textLower.Contains(stateLower))
{
return true;
}
// Check abbreviation mappings
if (StateMappings.TryGetValue(stateLower, out var fullNames))
{
return fullNames.Any(fullName => textLower.Contains(fullName));
}
return false;
}
}
+24
View File
@@ -0,0 +1,24 @@
namespace BomLocalService.Utilities;
public static class ValidationHelper
{
/// <summary>
/// Validates suburb and state parameters
/// Returns error message if validation fails, null if valid
/// </summary>
public static string? ValidateLocation(string suburb, string state)
{
if (string.IsNullOrWhiteSpace(suburb))
{
return "Suburb is required";
}
if (string.IsNullOrWhiteSpace(state))
{
return "State is required";
}
return null;
}
}