================================================================================
BrightLocationsController - Summary & Response Structures
File: SonWinCommonAPI/Controllers/BrightLocationsController.cs
================================================================================
--------------------------------------------------------------------------------
WHAT THE CONTROLLER DOES
--------------------------------------------------------------------------------
BrightLocationsController exposes a customer's locations (installations /
"installationer") and the supply-type services attached to them from the
SonWin/Sonlinc system, shaped into the "Bright" location contract. It requires
authorization ([Authorize]) and, on any thrown exception, goes through
CustomBase.HandleError which logs the error and returns HTTP 500 with the
exception message in the body.
--------------------------------------------------------------------------------
METHODS
--------------------------------------------------------------------------------
1) GetLocations
Route: GET /bright/locations
Params:
- customerId (required)
- limit (optional)
- (CancellationToken)
Returns:
- 200 OK -> IEnumerable<BrightLocation>
- 400 BadRequest (declared; see note below)
- 401 Unauthorized
- 404 NotFound (declared; see note below)
What it does:
Returns the list of active locations for a customer, each with its electricity service(s). It first validates the customer exists and validates input (customerId required; limit, if supplied, must be 1-1000, default 100).
It then loads location headers, loads the electricity services for those installations, attaches each service's current supply start/end dates, and maps everything to the BrightLocation response.
Validation that affects the result (all surface as HTTP 500 with a message):
- Missing customerId -> "Missing parameter 'CustomerId'."
- limit out of range (1-1000) -> "Parameter outside of allowed range..."
- No locations found -> "No locations found for customer {id}"
- A service with no start date -> "Location with missing supply start date found for meteringpointid: {id}..."
(Every returned service MUST have a supply start date or the whole call fails. The start/end dates come from the SWMarket supply-range query, which only returns the CURRENTLY active supply: FromDate <= today and ToDate is null or in the future.)
NOTE on status codes: Every error (bad input, not found, missing dates,
DB failure) currently comes back as HTTP 500 with the message in the body.
--------------------------------------------------------------------------------
RESPONSE STRUCTURE (BrightLocation)
--------------------------------------------------------------------------------
Returned as a JSON ARRAY (IEnumerable<BrightLocation>), one element per active
installation for the customer.
[
{
"Id": <value> // Data: SonWin installation number (AINSD.INSTNR), as string
"Address": <value> // Data: street + house number (AINSD.ADRESSE + AINSD.HUSNUMMER)
"ZipCode": <value> // Data: postal code (AINSD.POSTNUM)
"City": <value> // Data: city name (IPOST.NAVN, falling back to AINSD.STED; empty string if both null)
"Properties": null // Hardcoded null: BrightLocationProperties is never populated by the mapper
// (PropertyType, NumberOfResidents, Size, HouseAge, Cars[], Heating,
// HeatingSecondary, Gadgets[] are all unused today)
"PriceZone": <value> // Derived: from ZipCode -> "DK1" if zip >= 6000, "DK2" if zip < 6000,
// null if the zip code cannot be parsed
"Services": [ // Data: the electricity service(s) for this installation (empty array if none)
{
"Id": <value> // Data: metering point id / EAN (IndividualMPoints.MeteringPointId, aka AftageNr)
"Type": <value> // Derived: mapped from SonWin forsyningsart. For electricity this is
// always "consumption_trade". (Other types map to water/heating/
// cooling/tv/broadband/waste/gas_trade, but only El is queried today.)
"Status": "Active" // Hardcoded: BrightServiceStatus.Active.ToString().
// NOTE: this emits the enum NAME "Active" (capital A), NOT the
// display value "active". TODO in code: derive real status from SonWin.
"MeasurementResolution": <value> // Derived from forsyningsart: electricity => "hour"
// (broadband => "none"; other types default to "none" - TODO).
"MeasurementId": <value> // Data: metering point id (same value as Service.Id / AftageNr)
"DeliveryPointId": null // Hardcoded null: never set by the mapper
"CostResolution": null // Hardcoded null: never set by the mapper
"CostId": null // Hardcoded null: never set by the mapper
"Properties": null // Hardcoded null: BrightServiceProperties (ShowCost,
// RealtimeMeasurements) is never populated
"StartDate": <value> // Data: current supply start date (SWMarket MeteringPointRoleOccurrence.FromDate),
// as DateTimeOffset (UTC/zero offset). Required - null fails validation.
"EndDate": <value> // Data: current supply end date (SWMarket ...ToDate), as DateTimeOffset.
// OMITTED from the JSON entirely when null (JsonIgnore WhenWritingNull);
// null/absent means the supply has no scheduled end (still active).
}
],
"Longitude": null // Hardcoded null: never set by the mapper
"Latitude": null // Hardcoded null: never set by the mapper
}
]
Notes:
- Properties, Longitude and Latitude on the location are always null/absent.
- On a service, DeliveryPointId, CostResolution, CostId and Properties are
always null; Id and MeasurementId always carry the same metering point id.
- Service.Status is the only hardcoded non-null literal and is currently the
enum NAME "Active" rather than the lowercase display string "active".
- EndDate is the only field deliberately omitted (not null) when it has no
value, due to [JsonIgnore(WhenWritingNull)].
- A location can have an empty Services array, but if a service IS present it
must have a StartDate or the whole request fails validation.