degreedays.api.data

For specifying and receiving degree-day data and temperature data from the API.

If you are new to this module, we suggest you start by looking at DataApi.

Class summaries

AverageBreakdown

Defines how a set of average degree days should be broken down, including the period in time they should cover.

AverageDataSet

Contains a set of average degree-day data generated to fulfil an AverageDataSpec for a specific Location.

AverageDataSpec

Defines a specification for a set of average data such as 5-year-average degree days.

Breakdown

Defines how a set of degree days should be broken down (e.g. daily, weekly, or monthly), and the period in time they should cover.

Calculation

Defines how degree days should be calculated e.g. HDD or CDD to a specific base temperature.

DataApi

Provides easy, type-safe access to the API's data-related operations.

DataSet

Contains a set of degree-day data generated to fulfil a DataSpec for a specific Location.

DataSets

Contains all sets of data generated to fulfil the DataSpecs for a specific Location.

DataSpec

Defines a specification of a single set of degree-day data (or temperature data) in all aspects other than the location that the data should be generated for.

DataSpecs

Defines up to 120 sets of data that should be generated to fulfil a LocationDataRequest.

DataValue

Contains a value (e.g. an HDD or CDD value) and an approximate indication of its accuracy.

DatedBreakdown

Defines how a set of dated degree days (in a DatedDataSet) should be broken down, including the period in time they should cover.

DatedDataSet

Contains a set of dated data (e.g. daily/weekly/monthly degree days) generated to fulfil a DatedDataSpec for a specific Location.

DatedDataSpec

Defines a specification for a set of dated data such as daily, weekly, or monthly degree days covering a specific period in time.

DatedDataValue

Contains a degree-day value for a specific dated period (a single day or a range of days like a specific week, month, or year).

GeographicLocation

Defines a location in terms of a longitude/latitude or postal/zip code, leaving it to the API to find the nearest good weather station.

Location

Defines a location for which degree days should be calculated.

LocationDataRequest

Defines a request for one or more sets of data from a particular location.

LocationDataResponse

Contains the data generated to fulfil a LocationDataRequest.

LocationError

Indicates a Failure in the API's processing of a request, caused by problems with the Location that the request specified.

LocationInfoRequest

Defines a request for info about the station(s) that would be used to fulfil an equivalent LocationDataRequest.

LocationInfoResponse

Contains the location/station-related info returned in response to a LocationInfoRequest.

Period

Defines the period in time that a set of degree days should cover.

Source

Contains basic information about a source of data that was used to satisfy a request.

SourceDataError

Indicates a Failure to generate a DataSet caused by problems with the source temperature data for the Location and Period requested.

Station

Contains basic information about a weather station.

Temperature

Defines a temperature value, typically the base temperature of a degree-day calculation.

TemperatureUnit

Defines the units of temperature measurement with class constants TemperatureUnit.CELSIUS and TemperatureUnit.FAHRENHEIT.

TimeSeriesCalculation

Defines how time-series data should be calculated e.g. temperature data, with hourly figures, in Celsius.

TimeSeriesDataSet

Contains a set of time-series data (e.g. hourly temperature data) generated to fulfil a TimeSeriesDataSpec for a specific Location.

TimeSeriesDataSpec

Defines a specification for a set of time-series data such as hourly temperature data covering a specific period in time.

TimeSeriesDataValue

Contains a value (e.g. a temperature value) for a specific point in time, and an approximate indication of its accuracy.

TimeSeriesInterval

Defines the interval (e.g. hourly) that time-series data should be calculated with.

degreedays.api.data

For specifying and receiving degree-day data and temperature data from the API.

If you are new to this module, we suggest you start by looking at DataApi.

class degreedays.api.data.AverageBreakdown

Defines how a set of average degree days should be broken down, including the period in time they should cover.

Inheritance diagram of AverageBreakdown, degreedays.api.data.impl.FullYearsAverageBreakdown

This is the abstract superclass of the types of Breakdown that can be taken by an AverageDataSpec.

To create an AverageBreakdown object use the fullYears static factory method. For example:

fiveYearAverage = AverageBreakdown.fullYears(Period.latestValues(5))

AverageDataSpec has more on how to actually fetch average degree-day data with your specified AverageBreakdown.

static fullYears(period: Period) impl.FullYearsAverageBreakdown

Returns a FullYearsAverageBreakdown object that specifies average degree days derived from data covering full calendar years determined by the specified period.

Parameters:

period (Period) – specifies the full calendar years of data that the average figures should be derived from. Typically you’d want to use Period.latestValues for this, specifying at least 2 values (since an average of 1 year of data is not very meaningful). But you can also use Period.dayRange - in this case the period may be widened for calculation purposes to make it cover full calendar years.

Raises:

TypeError – if period is not a Period object.

class degreedays.api.data.AverageDataSet(firstYear: int, lastYear: int, annualAverage: DataValue, monthlyAverages: Sequence[DataValue])

Contains a set of average degree-day data generated to fulfil an AverageDataSpec for a specific Location.

Inheritance diagram of AverageDataSet

See AverageDataSpec for example code showing how to fetch an AverageDataSet of average degree days from the API.

property annualAverage: DataValue

The average annual value.

property firstYear: int

The first year of the continuous set of data that was used to calculate the average figures.

property fullRange: DayRange

The degreedays.time.DayRange object indicating the period of time that is covered by this DataSet.

property lastYear: int

The last year of the continuous set of data that was used to calculate the average figures.

monthlyAverage(monthIndexWithJanAs1: int) DataValue

The average value for the specified month (e.g. pass 1 for the average value for the month of January).

Parameters:

monthIndexWithJanAs1 (int) – a number between 1 (for January) and 12 (for December).

Raises:
  • TypeError – if monthIndexWithJanAs1 is not an int.

  • IndexError – if monthIndexWithJanAs1 is less than 1 or greater than 12.

property numberOfYears: int

The number of years of data that the average figures were calculated from (e.g. for 5-year-average data this would return 5).

property percentageEstimated: float

A number between 0 and 100 (both inclusive), indicating the overall extent to which this DataSet is based on estimated data.

Generally speaking, data with a lower percentage-estimated figure is likely to be more reliable than data with a higher percentage-estimated figure.

class degreedays.api.data.AverageDataSpec(calculation: Calculation, averageBreakdown: AverageBreakdown)

Defines a specification for a set of average data such as 5-year-average degree days.

Inheritance diagram of AverageDataSpec

To create an AverageDataSpec object use DataSpec.average.

An AverageDataSpec specifies a set of average degree days in terms of:

  • the calculation process used to calculate the dated figures that the averages are derived from (heating or cooling, and the base temperature); and

  • its breakdown (which years of data to include in the averaged figures).

Example AverageDataSpec code:

Here’s how you could specify 10-year average heating degree days with a base temperature of 70°F:

averageDataSpec = DataSpec.average(
        Calculation.heatingDegreeDays(Temperature.fahrenheit(70)),
        AverageBreakdown.fullYears(Period.latestValues(10)))

You could then send that AverageDataSpec to the API as part of a LocationDataRequest, and get a response containing an AverageDataSet back:

api = DegreeDaysApi.fromKeys(AccountKey(yourStringAccountKey),
        SecurityKey(yourStringSecurityKey))
request = new LocationDataRequest(
        Location.postalCode('02630', 'US'),
        new DataSpecs(averageDataSpec))
response = api.dataApi.getLocationData(request)
averageData = response.dataSets[averageDataSpec]
for month in range(1, 12):
print('Average HDD for month %i: %g' %
        (month, averageData.monthlyAverage(month).value))
print('Annual average HDD: %g' % averageData.annualAverage.value)

To request multiple sets of average data with different calculation processes (e.g. multiple different base temperatures) and/or different breakdowns, simply put multiple AverageDataSpec objects into the DataSpecs object that you pass into your LocationDataRequest.

See also: DatedDataSpec, TimeSeriesDataSpec

class degreedays.api.data.Breakdown

Defines how a set of degree days should be broken down (e.g. daily, weekly, or monthly), and the period in time they should cover.

Inheritance diagram of Breakdown, DatedBreakdown, AverageBreakdown, degreedays.api.data.impl.DailyBreakdown, degreedays.api.data.impl.WeeklyBreakdown, degreedays.api.data.impl.MonthlyBreakdown, degreedays.api.data.impl.YearlyBreakdown, degreedays.api.data.impl.CustomBreakdown, degreedays.api.data.impl.FullYearsAverageBreakdown

To create a Breakdown object, use the static factory methods on the DatedBreakdown and AverageBreakdown subclasses.

class degreedays.api.data.Calculation

Defines how degree days should be calculated e.g. HDD or CDD to a specific base temperature.

Inheritance diagram of Calculation, degreedays.api.data.impl.HeatingDegreeDaysCalculation, degreedays.api.data.impl.CoolingDegreeDaysCalculation

To create a Calculation object you can use the static factory methods of this class. For example:

hdd = Calculation.heatingDegreeDays(Temperature.celsius(12.5))
cdd = Calculation.coolingDegreeDays(Temperature.celsius(21))
static coolingDegreeDays(baseTemperature: Temperature) impl.CoolingDegreeDaysCalculation

Returns a CoolingDegreeDaysCalculation object with the specified base temperature.

Parameters:

baseTemperature (Temperature) – the base temperature that the cooling degree days should be calculated to.

Raises:

TypeError – if baseTemperature is not a Temperature object.

static heatingDegreeDays(baseTemperature: Temperature) impl.HeatingDegreeDaysCalculation

Returns a HeatingDegreeDaysCalculation object with the specified base temperature.

Parameters:

baseTemperature (Temperature) – the base temperature that the heating degree days should be calculated to.

Raises:

TypeError – if baseTemperature is not a Temperature object.

class degreedays.api.data.DataApi(requestProcessor)

Provides easy, type-safe access to the API’s data-related operations.

To get a DataApi object and use it, create a degreedays.api.DegreeDaysApi object, get the DataApi object from that, then call the method you want. For example, to send a LocationDataRequest to the API and get a LocationDataResponse back:

api = DegreeDaysApi.fromKeys(
        AccountKey(yourStringAccountKey),
        SecurityKey(yourStringSecurityKey))
response = api.dataApi.getLocationData(yourLocationDataRequest)

See getLocationData(locationDataRequest) and getLocationInfo(locationInfoRequest) for much more info and sample code.

getLocationData(locationDataRequest: LocationDataRequest) LocationDataResponse

Sends your request for data (for a specified location) to the API servers, returning a response containing data you requested, or throwing an appropriate subclass of degreedays.api.DegreeDaysApiError if something goes wrong.

Parameters:

locationDataRequest (LocationDataRequest) – specifies the data you want and the location you want it for.

Returns:

LocationDataResponse containing the data you requested or as much of it as was available for the location you specified (given the rules explained further below).

Raises:

Sample LocationDataRequest/LocationDataResponse code

Here’s a simple example showing how to fetch monthly 65°F-base-temperature heating degree days, covering the 12 months of 2024, for an automatically-selected weather station near US zip code 02633 (which is on Cape Cod so you can use the free test API account). The HDD figures are output to the command line:

api = DegreeDaysApi.fromKeys(
        AccountKey('test-test-test'),
        SecurityKey('test-test-test-test-test-test-test-test-test-test-test-test-test'))
dayRange = degreedays.time.DayRange(
        datetime.date(2024, 1, 1), datetime.date(2024, 12, 31))
hddSpec = DataSpec.dated(
        Calculation.heatingDegreeDays(Temperature.fahrenheit(65)),
        DatedBreakdown.monthly(Period.dayRange(dayRange)))
request = LocationDataRequest(
        Location.postalCode('02532', 'US'),
        DataSpecs(hddSpec))
response = api.dataApi.getLocationData(request)
hddData = response.dataSets[hddSpec]
for v in hddData.values:
    print('%s: %g' % (v.firstDay, v.value))

And here is a more complex example that fetches HDD, CDD, and hourly temperature data, covering just the latest available day, for a longitude/latitude position on Cape Cod, Massachusetts (again chosen so you can try out this code using the free test API account):

api = DegreeDaysApi.fromKeys(
        AccountKey('test-test-test'),
        SecurityKey('test-test-test-test-test-test-test-test-test-test-test-test-test'))
breakdown = DatedBreakdown.daily(Period.latestValues(1))
hddSpec = DataSpec.dated(
        Calculation.heatingDegreeDays(Temperature.celsius(15.5)),
        breakdown)
cddSpec = DataSpec.dated(
        Calculation.coolingDegreeDays(Temperature.celsius(21)),
        breakdown)
hourlyTempsSpec = DataSpec.timeSeries(
        TimeSeriesCalculation.hourlyTemperature(TemperatureUnit.CELSIUS),
        breakdown)
request = LocationDataRequest(
        Location.longLat(degreedays.geo.LongLat(-70.305634, 41.695475)),
        DataSpecs(hddSpec, cddSpec, hourlyTempsSpec))
response = api.dataApi.getLocationData(request)
hddData = response.dataSets[hddSpec]
cddData = response.dataSets[cddSpec]
hourlyTempsData = response.dataSets[hourlyTempsSpec]
print('Station ID: %s' % response.stationId)
for v in hddData.values:
    print('%s HDD: %g' % (v.firstDay, v.value))
for v in cddData.values:
    print('%s CDD: %g' % (v.firstDay, v.value))
for v in hourlyTempsData.values:
    print('%s: %g C' % (v.datetime, v.value))

Both examples above specify a location on Cape Cod, Massachusetts, so you can try them out using the free test API account. To access data for locations worldwide (whether station IDs, postal/zip codes, or longitude/latitude positions), you can sign up for a full API account on our website, swap your own account key and security key into the code samples above, then change the location however you wish.

Expanding on these examples

The examples above are just a starting point…

  • The LocationDataRequest is highly configurable:

    • You can specify the Location you want data for as a weather-station ID or a geographic location (postal/zip code, or longitude/latitude position). For geographic locations the API will automatically select the best weather station to satisfy your request.

    • There are various components that enable you to specify exactly what each set of data should contain. Each DataSpec can be either a DatedDataSpec (for daily/weekly/monthly/yearly/custom degree days), an AverageDataSpec (for average degree days), or a TimeSeriesDataSpec (for hourly temperature data). Each of these is configured with objects that determine the data Calculation (or TimeSeriesCalculation), the Breakdown, and the Period of coverage that you want.

    • You can fetch multiple sets of data from a location in a single request. For example, you might want to fetch HDD and CDD with multiple base temperatures each. To do this just create the DataSpecs in your request with multiple different DataSpec items.

  • The LocationDataResponse also contains information about the weather station(s) used to generate the returned data. If you request data from a geographic location initially, you might want to use the station ID to fetch updates later. If you are specifying geographic locations, but storing data by station ID, you can avoid re-fetching data unnecessarily by using getLocationInfo(locationInfoRequest) to get the station ID that would be returned by an equivalent call to getLocationData(locationDataRequest). We call this two-stage data fetching.

  • Error handling would be important for production code. Catching DegreeDaysApiException will cover everything that you should be prepared for, but it’s often useful to get more detail. Check the list of exceptions for this method further above and the notes on getting DataSet objects out of a DataSets object to see exactly what subclasses of DegreeDaysApiException can be thrown.

Rules that govern what you can get in response

It’s worth understanding the rules that govern what you can get in response to a request for one or more sets of data from a particular location:

Stations that data can come from:

  • If your request specifies a StationIdLocation (created via Location.stationId) then the API will only ever return data from that station. It will never substitute in data from another station.

  • If your request specifies a GeographicLocation the API will choose which station(s) to use automatically. The choice will depend on the data you requested as well as the location you requested it for. Some stations have more data than others, and the quality of a station’s data can vary over time. The API will choose the station(s) that can best satisfy your specific request.

  • The API will never send a LocationDataResponse for an inactive station. An inactive station is one that hasn’t sent any usable weather reports for roughly 10 days or more (10 being an approximate number that is subject to change). See LocationError.isDueToLocationNotSupported for more information on inactive stations. If you request data from an inactive station, or for a GeographicLocation for which no active station can be found, you will get a LocationError to indicate that the location is not supported.

When you request more data than is available:

  • If your request specifies data for a location for which an active station exists, you should, barring other failures, get a LocationDataResponse. But, if any of the DataSpec objects that you specified cannot be satisfied (either fully or partially), you will get a SourceDataError each time you try to get the corresponding DataSet from the DataSets object held by the response.

  • If there’s not enough data to fully satisfy your request, the API will return partial sets of data if it can. For example, you might request 10 years of data, but only get 5. Because data is only returned for active stations, you can rely on recent times being covered if requested (usually including yesterday’s data in the station’s time-zone, but always to within around 10 days or so of that day). But data from further back could be missing.

  • If you’d rather the API gave you a SourceDataError (see above) than any partially-complete DataSet, make sure to specify the minimumNumberOfValues on Period.latestValues or the minimumDayRange on Period.dayRange for the Period objects in your request. Unless you specify minimums, the API will return the best it can from within the specification you give.

Other data guarantees:

  • Excepting the widening rules that apply when a DayRangePeriod (created with Period.dayRange) is specified imprecisely, the API will never return more data than you asked for, or data from outside of the range that you asked for.

  • The API will never return data with gaps in it. No gaps, and no special error values like NaN, 0, -1, 9999 etc. If a station has small gaps in its source temperature data, the API will fill those gaps with estimates before calculating any degree days. But larger gaps are not tolerated, and the API will only ever use data from after such gaps. If a larger gap is ongoing (i.e. there is no good data after it), the station will be declared inactive (see above). This approach ensures that active stations will always have contiguous sets of data that run through to recent days.

getLocationInfo(locationInfoRequest: LocationInfoRequest) LocationInfoResponse

A lightweight alternative to getLocationData(locationDataRequest) that returns info about the station(s) that would be used to satisfy an equivalent LocationDataRequest, but not the data itself. Typically you would specify a GeographicLocation in the LocationInfoRequest, using this getLocationInfo(locationInfoRequest) method to map postal/zip codes or longitude/latitude positions to station IDs.

Parameters:

locationInfoRequest (LocationInfoRequest) – specifies the location you want data for and the data that you want (as this can affect the station-selection process for GeographicLocation types).

Returns:

LocationInfoResponse containing info about the station(s) that would be used to satisfy an equivalent LocationDataRequest.

Raises:

This getLocationInfo(locationInfoRequest) method can be useful if you have a database of data stored by station ID, but are using GeographicLocation types (postal/zip codes or longitude/latitude positions) to determine which station ID to use for each of your real-world locations. A call to this getLocationInfo(locationInfoRequest) method will only ever take one request unit, whilst a call to getLocationData(locationDataRequest) can take many more (depending on how much data it fetches), so it often makes sense to use this method to avoid the overhead of re-fetching data that you already have stored. We call this two-stage data fetching.

Note, however, that this returns nothing that isn’t also returned by a call to getLocationData(locationDataRequest). So, if you know you’ll be fetching data anyway, you might as well use getLocationData(locationDataRequest) from the start.

Sample LocationInfoRequest/LocationInfoResponse code

Here’s a simple example showing how to find out what station ID the API would use to provide 10 recent calendar years’ worth of data for US zip code 02633 (which is on Cape Cod so you can use the free test API account). The station ID is output to the command line:

api = DegreeDaysApi.fromKeys(
        AccountKey(yourStringAccountKey),
        SecurityKey(yourStringSecurityKey))
hddSpec = DataSpec.dated(
        Calculation.heatingDegreeDays(Temperature.fahrenheit(65)),
        DatedBreakdown.yearly(Period.latestValues(10)))
request = LocationInfoRequest(
        Location.postalCode('02532', 'US'),
        DataSpecs(hddSpec))
response = api.dataApi.getLocationInfo(request)
print('Station ID: %s' % response.stationId)

Exceptions and data availability

The exceptions thrown by this getLocationInfo(locationInfoRequest) method are the same as those thrown by getLocationData(locationDataRequest), and they are thrown under exactly the same circumstances.

However, because the LocationInfoResponse returned by this method does not contain any data (i.e. no DataSets), there’s no way to tell from this whether an equivalent LocationDataResponse would actually contain any or all of the data you want. So, although you can call this method to determine what station ID the API would use for an equivalent call to getLocationData(LocationDataRequest), you would have to actually make that call to be sure that you could get all the data you wanted.

class degreedays.api.data.DataSet

Contains a set of degree-day data generated to fulfil a DataSpec for a specific Location.

Inheritance diagram of DataSet, DatedDataSet, AverageDataSet, TimeSeriesDataSet

property fullRange: DayRange

The degreedays.time.DayRange object indicating the period of time that is covered by this DataSet.

property percentageEstimated: float

A number between 0 and 100 (both inclusive), indicating the overall extent to which this DataSet is based on estimated data.

Generally speaking, data with a lower percentage-estimated figure is likely to be more reliable than data with a higher percentage-estimated figure.

class degreedays.api.data.DataSets(dataSpecsOrNone: DataSpecs | None, stringKeyToResultDict: Mapping[str, DataSet | api.Failure])

Contains all sets of data generated to fulfil the DataSpecs for a specific Location.

How to get DataSet objects out of a DataSets

You need to keep hold of the DataSpec objects that you specified in your request. Then you can access the DataSet object that corresponds to each DataSpec like so:

datedDataSet = dataSets[yourDatedDataSpec]
averageDataSet = dataSets[yourAverageDataSpec]
timeSeriesDataSet = dataSets[yourTimeSeriesDataSpec]

In rare cases you might also want to access the DataSet objects using the keys that were set in the DataSpecs sent with the LocationDataRequest, like dataSets[stringKey].

Watch out for SourceDataError

If the API servers were unable to generate a particular DataSet (e.g. because the weather station did not have the weather-data records necessary to generate data to match your DataSpec), you’ll get a SourceDataError when you try to get the DataSet object out of the DataSets. For example:

try:
    dataSet = dataSets[yourDataSpec]
except SourceDataError as e:
    print('Failed to get data for', yourDataSpec, e)
class degreedays.api.data.DataSpec

Defines a specification of a single set of degree-day data (or temperature data) in all aspects other than the location that the data should be generated for.

Inheritance diagram of DataSpec, DatedDataSpec, AverageDataSpec, TimeSeriesDataSpec

A DataSpec defines a single set of data only (e.g. degree days in just one base temperature), but the DataSpecs class will enable you to include multiple DataSpec objects in a single LocationDataRequest.

To create a DataSpec object you can use the static factory methods of this class. For example:

thirtyMostRecentHddValues = DataSpec.dated(
        Calculation.heatingDegreeDays(Temperature.fahrenheit(55)),
        DatedBreakdown.daily(Period.latestValues(30)))

fiveYearAverageCdd = DataSpec.average(
        Calculation.coolingDegreeDays(Temperature.fahrenheit(65)),
        AverageBreakdown.fullYears(Period.latestValues(5)))

hourlyTempsForLastCalendarYear = DataSpec.timeSeries(
        TimeSeriesCalculation.hourlyTemperature(TemperatureUnit.CELSIUS),
        DatedBreakdown.yearly(Period.latestValues(1)))

See DatedDataSpec, AverageDataSpec, and TimeSeriesDataSpec for example code showing how to use each one individually to fetch data from the API. Though do remember that, as mentioned above, you can also fetch multiple sets of data in a single request.

static average(calculation: Calculation, averageBreakdown: AverageBreakdown) AverageDataSpec

Returns an AverageDataSpec object with the specified Calculation and AverageBreakdown.

Parameters:
  • calculation (Calculation) – defines the way in which the degree days should be calculated in terms of their base temperature and whether they should be heating degree days or cooling degree days.

  • averageBreakdown (AverageBreakdown) – defines the way in which the data should be broken down and the period that it should cover.

Raises:

TypeError – if calculation is not a Calculation object, or if averageBreakdown is not an AverageBreakdown object.

See AverageDataSpec for more info and a code sample.

static dated(calculation: Calculation, datedBreakdown: DatedBreakdown) DatedDataSpec

Returns a DatedDataSpec object with the specified Calculation and DatedBreakdown.

Parameters:
  • calculation (Calculation) – defines the way in which the degree days should be calculated in terms of their base temperature and whether they should be heating degree days or cooling degree days.

  • datedBreakdown (DatedBreakdown) – defines the way in which the data should be broken down and the period that it should cover.

Raises:

TypeError – if calculation is not a Calculation object, or if datedBreakdown is not a DatedBreakdown object.

See DatedDataSpec for more info and a code sample.

static timeSeries(timeSeriesCalculation: TimeSeriesCalculation, datedBreakdown: DatedBreakdown) TimeSeriesDataSpec

Returns a TimeSeriesDataSpec object with the specified TimeSeriesCalculation and DatedBreakdown.

Parameters:
Raises:

TypeError – if timeSeriesCalculation is not a TimeSeriesCalculation object or datedBreakdown is not a DatedBreakdown object.

See TimeSeriesDataSpec for more info and a code sample.

class degreedays.api.data.DataSpecs(*args: DataSpec | Iterable[DataSpec] | Mapping[str, DataSpec])

Defines up to 120 sets of data that should be generated to fulfil a LocationDataRequest.

Parameters:

*args – up to 120 DataSpec objects, or one or more sequences (e.g. lists or tuples) containing a total of up to 120 DataSpec objects. In rare cases you may want to pass in a Mapping[str, DataSpec] of string keys to DataSpec objects.

Raises:
  • TypeError – if you pass in anything that isn’t a DataSpec object, a sequence (e.g. list or tuple) of DataSpec objects, or a single Mapping[str, DataSpec] (in the unlikely event that you are defining your own keys).

  • ValueError – if you pass in zero DataSpec objects or more than 120 of them.

When requesting multiple sets of data for a particular location, it makes sense to use multiple DataSpec objects to retrieve as much data as possible in a single request. This is cheaper and faster than fetching the same data across multiple requests. For example, for a particular location you might want to request heating and cooling degree days at 5 different base temperatures each. This would require 10 different DataSpec objects in total. By putting all 10 DataSpec objects in a single DataSpecs object, you could fetch all 10 sets of data in a single request.

For example, if ds1 to ds10 are DataSpec objects you have created already:

dataSpecs = DataSpecs(ds1, ds2, ds3, ds4, ds5, ds6, ds7, ds8, ds9, ds10)

For most use cases, all you will ever need to do with this class is pass in the DataSpec objects you want, then pass the DataSpecs into a LocationDataRequest object. However, there is a small chance that you could find it useful to understand about keys and the uniqueness of DataSpec objects:

Keys

The XML request (which this Python library generates internally) requires that each DataSpec be assigned a unique string key attribute so that its corresponding DataSet can be found in the XML response (which this Python library parses internally). This DataSpecs class is central to the way that the keys are created and managed. It essentially gives you two options for key management:

  1. You can let this DataSpecs class generate and manage the keys entirely, such that you never have to see them or use them directly. You can fetch your DataSet objects out of the LocationDataResponse using the DataSpec objects that you created for the request (and kept on hand for this purpose). We recommend this approach in virtually all cases.

  2. You can specify your own keys explicitly. If this is the case, create a DataSpecs object by passing in a Mapping (e.g. a dict) with your own custom key-to-DataSpec mappings.

Uniqueness of DataSpec objects

There is no point in submitting a request containing multiple identical DataSpec objects, as it would lead to unnecessary duplication in the results and unnecessary bandwidth usage for the communication between the client and API servers.

Unless you are specifying your own keys (by passing in a Mapping[str, DataSpec]), this class is set up to prevent duplicate DataSpec objects from being submitted as part of a request. It does this by filtering and discarding all duplicate DataSpec objects passed in when an instance is created. So, if you pass in 10 DataSpec objects, and only 5 of those are unique, then only the 5 unique DataSpec objects will be stored internally and sent as part of the XML request.

Because discarded duplicate DataSpec objects are identical to their retained counterparts, you can still use the duplicates to retrieve fetched data out of the LocationDataResponse. In other words, the internal process of filtering and discarding duplicate DataSpec objects should not affect the way in which you use this API.

If you create your own keys, then duplicate DataSpec objects are not filtered. Filtering duplicate DataSpec objects would also involve filtering the keys associated with the duplicates, and this could cause problems for code of yours that was expecting those keys in the XML response. You can rely on the fact that, if you define your own DataSpec keys, your XML response will contain entries for all of your keys, irrespective of whether some of the data associated with those keys is duplicated.

See also: DataApi.getLocationData(locationDataRequest)

getKey(dataSpec: DataSpec) str

Returns the non-empty string key associated with dataSpec.

Parameters:

dataSpec (DataSpec) – a a data specification held by this DataSpecs.

Raises:

See also: keys

property keys: Collection[str]

Gives access to the string keys that are assigned to the DataSpec objects stored within this DataSpecs.

See also: keys

class degreedays.api.data.DataValue(value: float, percentageEstimated: float)

Contains a value (e.g. an HDD or CDD value) and an approximate indication of its accuracy.

Inheritance diagram of DataValue, DatedDataValue, TimeSeriesDataValue

property percentageEstimated: float

A number between 0 and 100 (both inclusive), indicating the extent to which the calculated value is based on estimated data.

Generally speaking, a value with a lower percentage-estimated figure is likely to be more reliable than one with a higher percentage-estimated figure.

property value: float

The value, which will never be NaN or infinity, and, for degree days, will always be zero or greater.

class degreedays.api.data.DatedBreakdown

Defines how a set of dated degree days (in a DatedDataSet) should be broken down, including the period in time they should cover.

Inheritance diagram of DatedBreakdown, degreedays.api.data.impl.DailyBreakdown, degreedays.api.data.impl.WeeklyBreakdown, degreedays.api.data.impl.MonthlyBreakdown, degreedays.api.data.impl.YearlyBreakdown, degreedays.api.data.impl.CustomBreakdown

This is the abstract superclass of the types of Breakdown that can be taken by a DatedDataSpec.

To create a DatedBreakdown object you can use the static factory methods of this class. For example:

dailyBreakdownOverLast30Days = DatedBreakdown.daily(
        Period.latestValues(30))

weeklyBreakdownFor2024WithWeeksStartingOnMonday = DatedBreakdown.weekly(
        Period.dayRange(degreedays.time.DayRange(
            datetime.date(2024, 1, 1),
            datetime.date(2024, 12, 31))),
        DayOfWeek.MONDAY)

monthlyBreakdownOverLast12Months = DatedBreakdown.monthly(
        Period.latestValues(12))

yearlyBreakdownOverLast5Years = DatedBreakdown.yearly(
        Period.latestValues(5))

As you can see from the above examples, defining the Period is an important part of defining a DatedBreakdown. See that class for more information on the relevant options.

property allowPartialLatest: bool

True if the latest day range can be partially filled (i.e. incomplete); False otherwise (the default case).

When specifying time-series data (like hourly temperature data) via a TimeSeriesDataSpec, you can specify a breakdown with this allowPartialLatest property set to True, to tell the API to include values for the current day so far. For example:

hourlyTempsIncludingToday = DataSpec.timeSeries(
    TimeSeriesCalculation.hourlyTemperature(TemperatureUnit.CELSIUS),
    DatedBreakdown.daily(Period.latestValues(31), allowPartialLatest=True))

If you requested the above-specified hourly temperature data at, say, 11:42 on any given day, you could expect it to include values for 00:00, 01:00, 02:00 through to 11:00 on that day (bearing in mind that some stations are slower to report than others so you won’t always get the absolute latest figures).

Please note that the most recent time-series data can be a little volatile, as weather stations sometimes send multiple reports for the same time, some delayed, and some marked as corrections for reports they sent earlier. Our system generates time-series data using all the relevant reports that each weather station has sent, but the generated figures may change if delayed or corrected reports come through later. If you are storing partial-latest time-series data we suggest you overwrite it later with figures generated after the day has completed and any delayed/corrected reports have had time to filter through.

This allowPartialLatest property exists mainly for time-series data, but you can also set it to True on a breakdown for degree days, to specify that the data can include a value for the latest partial week/month/year. For example:

monthlyHddIncludingPartialLatest = DataSpec.dated(
    Calculation.heatingDegreeDays(Temperature.fahrenheit(65)),
    DatedBreakdown.monthly(Period.latestValues(12), allowPartialLatest=True))

If you requested the above-specified monthly degree-day data on, say, June 22nd, you could expect the last of the 12 values returned to cover from the start of June 1st through to the end of June 21st (assuming a good weather station with frequent reporting and minimal delays). If you left allowPartialLatest with its default value of False, the last of the 12 values returned would be for the full month of May (i.e. from the start of May 1st to the end of May 31st), as the monthly figure for June wouldn’t become available until June ended.

Unlike for time-series data (specified with TimeSeriesDataSpec), this property will never cause degree days to be calculated for partial days. So for degree days this property will only make a difference on weekly, monthly, yearly, and custom breakdowns with day ranges covering multiple days.

Any partial-latest day range will always start on the usual day (i.e. it will never be cut short at the start), it’s only the end that can be cut short. This is true for both degree days and time-series data.

static custom(dayRanges: degreedays.time.DayRanges, allowPartialLatest: bool = False) impl.CustomBreakdown

Returns a CustomBreakdown object that specifies degree days broken down to match the degreedays.time.DayRanges passed in.

Parameters:
  • dayRanges (DayRanges) – specifying the dates that each degree-day figure should cover (typically matching the dates of your energy-usage records).

  • allowPartialLatest (bool) – True to specify that the latest specified day range can be partially filled (i.e. incomplete), or False if it must be complete (the default behaviour). Setting this to True is mainly useful if you are fetching time-series data (via TimeSeriesDataSpec) and you want it to include values for the current (incomplete) day. See allowPartialLatest for more on this.

Raises:

TypeError – if dayRanges is not a degreedays.time.DayRanges object, or if allowPartialLatest is not a bool.

static daily(period: Period, allowPartialLatest: bool = False) impl.DailyBreakdown

Returns a DailyBreakdown object that specifies daily degree days covering the specified period in time.

Parameters:
  • period (Period) – the period in time that the daily breakdown should cover.

  • allowPartialLatest (bool) – True to specify that the latest day of data can be partially filled (i.e. incomplete), or False if the latest day must be complete (the default behaviour). Setting this to True is mainly useful if you are fetching time-series data (via TimeSeriesDataSpec) and you want it to include values for the current (incomplete) day. See allowPartialLatest for more on this.

Raises:

TypeError – if period is not a Period object, or allowPartialLatest is not a bool.

static monthly(period: Period, startOfMonth: degreedays.time.StartOfMonth = StartOfMonth(1), allowPartialLatest: bool = False) impl.MonthlyBreakdown

Returns a MonthlyBreakdown object that specifies monthly degree days covering the specified period in time, with each “month” starting on the specified day of the month.

Parameters:
  • period (Period) – the period in time that the monthly breakdown should cover.

  • startOfMonth (StartOfMonth) – specifying which day (between 1 and 28 inclusive) should be taken as the first of each “month”. StartOfMonth(1) is the default value and specifies regular calendar months.

  • allowPartialLatest (bool) – True to specify that the latest month of data can be partially filled (i.e. incomplete), or False if the latest month must be complete (the default behaviour). Setting this to True is mainly useful if you are fetching time-series data (via TimeSeriesDataSpec) and you want it to include the current (incomplete) month, including values for the current (incomplete) day. See allowPartialLatest for more on this.

Raises:

TypeError – if period is not a Period object, or if startOfMonth is not a degreedays.time.StartOfMonth object, or if allowPartialLatest is not a bool.

static weekly(period: Period, firstDayOfWeek: degreedays.time.DayOfWeek, allowPartialLatest: bool = False) impl.WeeklyBreakdown

Returns a WeeklyBreakdown object that specifies weekly degree days covering the specified period in time.

To avoid the potential for confusion, you must explicitly specify the day of the week that you want each “week” to start on. For example, if a “week” should run from Monday to Sunday (inclusive), specify the firstDayOfWeek parameter as degreedays.time.DayOfWeek.MONDAY.

Parameters:
  • period (Period) – the period in time that the weekly breakdown should cover.

  • firstDayOfWeek (DayOfWeek) – indicates which day should be taken as the first of each week.

  • allowPartialLatest (bool) – True to specify that the latest week of data can be partially filled (i.e. incomplete), or False if the latest week must be complete (the default behaviour). Setting this to True is mainly useful if you are fetching time-series data (via TimeSeriesDataSpec) and you want it to include the current (incomplete) week, including values for the current (incomplete) day. See allowPartialLatest for more on this.

Raises:

TypeError – if period is not a Period object, or if firstDayOfWeek is not a degreedays.time.DayOfWeek object, or if allowPartialLatest is not a bool.

static yearly(period: Period, startOfYear: degreedays.time.StartOfYear = StartOfYear(1, 1), allowPartialLatest: bool = False) impl.YearlyBreakdown

Returns a YearlyBreakdown object that specifies yearly degree days covering the specified period in time, with each “year” starting on the specified day of the year.

Parameters:
  • period (Period) – the period in time that the yearly breakdown should cover.

  • startOfYear (StartOfYear) – specifying which day of the year (month and day of the month) should be taken as the first of each “year”. StartOfYear(1, 1) is the default value and specifies regular calendar years.

  • allowPartialLatest (bool) – True to specify that the latest year of data can be partially filled (i.e. incomplete), or False if the latest year must be complete (the default behaviour). Setting this to True is mainly useful if you are fetching time-series data (via TimeSeriesDataSpec) and you want it to include the current (incomplete) year, including values for the current (incomplete) day. See allowPartialLatest for more on this.

Raises:

TypeError – if period is not a Period object, or if startOfYear is not a degreedays.time.StartOfYear object, or if allowPartialLatest is not a bool.

class degreedays.api.data.DatedDataSet(percentageEstimated: float, values: Sequence[DatedDataValue])

Contains a set of dated data (e.g. daily/weekly/monthly degree days) generated to fulfil a DatedDataSpec for a specific Location.

Inheritance diagram of DatedDataSet

See DatedDataSpec for example code showing how to fetch a DatedDataSet of degree days from the API.

See also: AverageDataSet, TimeSeriesDataSet

property fullRange: DayRange

The degreedays.time.DayRange object indicating the period of time that is covered by this DataSet.

property isContiguous: bool

True if each contained DatedDataValue starts the day after the previous DatedDataValue ended (i.e. no gaps); False otherwise.

This will always be True for daily, weekly, monthly, or yearly data returned by the API.

property percentageEstimated: float

A number between 0 and 100 (both inclusive), indicating the overall extent to which this DataSet is based on estimated data.

Generally speaking, data with a lower percentage-estimated figure is likely to be more reliable than data with a higher percentage-estimated figure.

property values: Sequence[DatedDataValue]

A non-empty chronologically-ordered Sequence of the DatedDataValue objects that make up this DatedDataSet.

class degreedays.api.data.DatedDataSpec(calculation: Calculation, datedBreakdown: DatedBreakdown)

Defines a specification for a set of dated data such as daily, weekly, or monthly degree days covering a specific period in time.

Inheritance diagram of DatedDataSpec

To create a DatedDataSpec object use DataSpec.dated.

A DatedDataSpec specifies a set of degree days in terms of:

  • its calculation process (heating or cooling, and the base temperature); and

  • its breakdown (e.g. daily, weekly, or monthly, and the period covered).

Example DatedDataSpec code:

Here’s how you could specify monthly heating degree days with a base temperature of 15.5°C covering the whole of 2019:

datedDataSpec = DataSpec.dated(
        Calculation.heatingDegreeDays(Temperature.celsius(15.5)),
        DatedBreakdown.monthly(Period.dayRange(degreedays.time.DayRange(
            datetime.date(2019, 1, 1), datetime.date(2019, 12, 31)))))

You could then send that DatedDataSpec to the API as part of a LocationDataRequest, and get a response containing an DatedDataSet back:

api = DegreeDaysApi.fromKeys(AccountKey(yourStringAccountKey),
        SecurityKey(yourStringSecurityKey))
request = LocationDataRequest(
        Location.stationId("KHYA"),
        DataSpecs(datedDataSpec))
response = api.dataApi.getLocationData(request)
datedData = response.dataSets[datedDataSpec]
for v in datedData.values:
    print('%s: %g' % (v.dayRange, v.value))

To request multiple sets of dated data with different calculation processes (e.g. multiple different base temperatures) and/or different breakdowns, simply put multiple DatedDataSpec objects into the DataSpecs object that you pass into your LocationDataRequest.

See also: AverageDataSpec, TimeSeriesDataSpec

property breakdown: DatedBreakdown

The DatedBreakdown object that defines the way in which the degree days should be broken down and the period in time that they should cover.

property calculation: Calculation

The Calculation object that defines the way in which the degree days should be calculated in terms of their base temperature and whether they should be heating degree days or cooling degree days.

class degreedays.api.data.DatedDataValue(value: float, percentageEstimated: float, dayRange: DayRange)

Contains a degree-day value for a specific dated period (a single day or a range of days like a specific week, month, or year).

Inheritance diagram of DatedDataValue

property dayRange: DayRange

The DayRange object indicating the period in time that this DatedDataValue covers.

property firstDay: date

Returns the first datetime.date of the period covered by this DatedDataValue.

This is a convenience/performance property that should run faster (or at least not slower) than accessing .dayRange.first, since accessing the dayRange property may result in a temporary degreedays.time.DayRange object being created. We say “may” because, although at the time of writing it does create a temporary object, this is an implementation detail that might change in the future.

A large request for data (daily data in particular) can easily result in hundreds of thousands of these DatedDataValue objects, each of which occupies memory and typically needs to be used by code that is repeating its operations on every single value in a set. So we’ve tried to design DatedDataValue to minimize memory footprint and enable fast access to its properties.

If all you want is the first day of the range, then this is the optimal property to access. If you want the last day of the range, then lastDay is the optimal property to access. But if you need more information then don’t hesitate to access the dayRange property as the degreedays.time.DayRange object is lightweight and creating it is a very fast operation. Choosing the optimal access pattern is very unlikely to make a practical difference unless performance is critically important to you and you’re iterating over large quantities of daily data for which the dayRange property is largely irrelevant anyway (since for daily data the DayRange would only cover a single day).

property lastDay: date

The last datetime.date of the period covered by this DatedDataValue.

See also: firstDay

property percentageEstimated: float

A number between 0 and 100 (both inclusive), indicating the extent to which the calculated value is based on estimated data.

Generally speaking, a value with a lower percentage-estimated figure is likely to be more reliable than one with a higher percentage-estimated figure.

property value: float

The value, which will never be NaN or infinity, and, for degree days, will always be zero or greater.

class degreedays.api.data.GeographicLocation

Defines a location in terms of a longitude/latitude or postal/zip code, leaving it to the API to find the nearest good weather station.

Inheritance diagram of GeographicLocation, degreedays.api.data.impl.LongLatLocation, degreedays.api.data.impl.PostalCodeLocation

To create a GeographicLocation object you can use the Location.postalCode and Location.longLat factory methods. For example:

empireStateBuilding = Location.postalCode("10118", "US")

trafalgarSquare = Location.longLat(LongLat(-0.12795, 51.50766))

Geographic locations provide a powerful way to request degree-day data. Manually selecting the best weather station to provide data for a specific building or region can be tricky, because different stations have different levels of data coverage and quality. With a geographic location, the API will automatically select the weather station that is best equipped to provide the data you want.

At some point the API might average data from multiple weather stations around the specified location if it thinks that would significantly improve results. If we do add this feature, it will be optional, and disabled by default, so the behaviour of your system won’t change unless you want it to.

Make sure to specify the full range of data that you want when using a geographic location… Some weather stations have less data than others so it’s important for the API to know the full range of interest when it’s choosing which station(s) to use.

Fetching more data for the same location (e.g. daily updates)

The LocationDataResponse contains a station ID that will enable you to fetch more data for the weather station(s) that were selected for your geographic location. If just one station was used (the normal case), then the station ID will simply be the canonical ID of that one station. If data from multiple stations was combined to satisfy your request (an option that isn’t available now but that we may cater for in future), then the station ID will be for a “virtual station” that represents the specific combination of weather stations used.

Either way, you can use the station ID from the LocationDataResponse to fetch more data for the same location in the future. For example, you might initially want to fetch the last 5 years of data for a geographic location, and then use the returned station ID to fetch updates each day, week, or month going forward. If you’re storing the generated data, then doing it this way will ensure that your data updates are generated using the same weather station(s) as your initial request.

However, if you’re following this model, bear in mind that a weather station that works today won’t necessarily be working next month or next year. It’s unusual for stations to go out of service, but it does happen. It might not be worth worrying about the possibility of stations going down if you’re only tracking data for a handful of locations, but it’s definitely worth planning for if you’re tracking data for hundreds or thousands of locations. To prepare for a station going down, it makes sense to store the details of the geographic location together with the station ID that you’re using for updates. That way, if the station fails, you can use the geographic location to find a replacement.

Note that a simple alternative is to use the geographic location to fetch a full set of data each time it is needed. You might not get data from the same station(s) each time (particularly if you vary the Period that you request data for), but the station-selection algorithm will ensure that you’re getting the best available data for each individual request that you make.

Two-stage data fetching for geographic locations

If you are using geographic locations, but storing data by station ID (as described above), you may be able to save request units and improve the efficiency of your system with two-stage data fetching. When you want to add a new location into your system (e.g. if a new user signs up with a new address), you can do the following:

Stage 1: Call DataApi.getLocationInfo with a LocationInfoRequest containing the geographic location and the specification of the data that you want. You won’t get any data back, but you should get the station ID that the system would use for an equivalent LocationDataRequest. If you already have data stored for that station ID, use it; if not, progress to stage 2.

Stage 2: Call DataApi.getLocationData with a LocationDataRequest that specifies the station ID from stage 1. (You could use the geographic location again, but using the station ID will save a request unit, such that your two-stage fetch will take the same number of request units in total as it would if you had just submitted a LocationDataRequest with the geographic location in the first place.)

Two-stage fetching will only improve efficiency and save request units if/when you have enough geographic locations in your system that some of them end up sharing weather stations. But, if that is the case, two-stage fetching can really help your system to scale well as more and more geographic locations are added in.

class degreedays.api.data.Location

Defines a location for which degree days should be calculated.

Inheritance diagram of Location, degreedays.api.data.impl.StationIdLocation, degreedays.api.data.impl.LongLatLocation, degreedays.api.data.impl.PostalCodeLocation

To create a Location object you can use the static factory methods of this class. For example:

losAngelesAirportWeatherStation = Location.stationId("KLAX")

empireStateBuilding = Location.postalCode("10118", "US")

trafalgarSquare = Location.longLat(LongLat(-0.12795, 51.50766))

empireStateBuilding and trafalgarSquare are examples of geographic locations, which provide a powerful way to get data for locations around the world. See GeographicLocation for more on how these geographic locations work.

static longLat(longLat: degreedays.geo.LongLat) impl.LongLatLocation

Returns a LongLatLocation object with the specified longitude and latitude position.

Parameters:

longLat (LongLat) – the longitude/latitude position.

Raises:

TypeError – if longLat is not a degreedays.geo.LongLat object.

See also: GeographicLocation

static postalCode(postalCode: str, countryCode: str) impl.PostalCodeLocation

Returns a PostalCodeLocation object with a postal code (or zip code, post code, or postcode) and a two-letter country code representing the country that the postal code belongs to.

Parameters:
  • postalCode (str) – the postal code (or zip code, post code, or postcode) of the location you want data for. Cannot be empty, cannot be longer than 16 characters (a length that we believe allows for all current postal codes worldwide), and cannot contain any characters other than [- 0-9a-zA-Z].

  • countryCode (str) – the ISO 3166-1-alpha-2 country code of the country that postalCode belongs to. It must be a two-character string comprised of only characters A-Z (i.e. upper case only). For example, pass “US” if postalCode is a US zip code, pass “GB” (for “Great Britain”) if postalCode is a UK post code, and pass “CA” if postalCode is a Canadian zip code.

Raises:
  • TypeError – if postalCode or countryCode is not a str.

  • ValueError – if tests indicate that postalCode or countryCode fails to match the specifications detailed above.

See also: GeographicLocation

static stationId(stationId: str) impl.StationIdLocation

Returns a StationIdLocation object with the specified weather station ID.

Parameters:

stationId (str) – the ID of the weather station that the StationIdLocation should represent (i.e. the station you want data for). Cannot be empty, cannot contain contain any characters other than [-_0-9a-zA-Z], and cannot contain more than 60 characters (a limit that is significantly larger than the length of any station ID that we are currently aware of, but that is set high to allow for “virtual station IDs” to be introduced in the future, combining data from multiple stations).

Raises:
  • TypeError – if stationId is not a str.

  • ValueError – if tests indicate that stationId fails to match the specification detailed above.

class degreedays.api.data.LocationDataRequest(location: Location, dataSpecs: DataSpecs)

Defines a request for one or more sets of data from a particular location. A successfully-processed LocationDataRequest will result in a LocationDataResponse containing the specified data.

Inheritance diagram of LocationDataRequest

Parameters:
  • location (Location) – the location for which the data should be generated.

  • dataSpecs (DataSpecs) – the specification of how the set(s) of data should be calculated and broken down.

Raises:

TypeError – if location is not a subclass of Location, or if dataSpecs is not a DataSpecs object.

Here’s an example showing how to create a request for one set of data from the EGLL weather station in London, UK. The request specifies heating degree days with a base temperature of 15.5°C, broken down on a daily basis and covering the whole of July 2024:

period = Period.dayRange(degreedays.time.DayRange(
        datetime.date(2024, 7, 1), datetime.date(2024, 7, 31)))
breakdown = DatedBreakdown.daily(period)
baseTemp = Temperature.celsius(15.5)
calculation = Calculation.heatingDegreeDays(baseTemp)
dataSpec = DataSpec.dated(calculation, breakdown)
location = Location.stationId('EGLL')
request = LocationDataRequest(
        location, DataSpecs(dataSpec))

Here’s an example showing how to create a request for two sets of data from Times Square in New York. We specify the location as a zip code (a type of GeographicLocation), leaving it to the API to choose the most appropriate weather station in the area. The request specifies heating degree days with a base temperature of 55°F, and cooling degree days with a base temperature of 65°F, both broken down on a monthly basis and covering the last 12 months:

breakdown = DatedBreakdown.monthly(Period.latestValues(12))
hddSpec = DataSpec.dated(
        Calculation.heatingDegreeDays(Temperature.fahrenheit(55)),
        breakdown)
cddSpec = DataSpec.dated(
        Calculation.coolingDegreeDays(Temperature.fahrenheit(65)),
        breakdown)
request = LocationDataRequest(
        Location.postalCode('10036', 'US'),
        DataSpecs(hddSpec, cddSpec))

See DataApi.getLocationData(locationDataRequest) for an example of how to submit a LocationDataRequest to the API and get a LocationDataResponse back containing the data you requested.

property dataSpecs: DataSpecs

The DataSpecs object that specifies how the set(s) of data should be calculated and broken down.

property location: Location

The Location object that specifies the location for which the data should be generated.

class degreedays.api.data.LocationDataResponse(metadata: api.ResponseMetadata, stationId: str, targetLongLat: degreedays.geo.LongLat, sources: Sequence[Source], dataSets: DataSets)

Contains the data generated to fulfil a LocationDataRequest.

Inheritance diagram of LocationDataResponse

See DataApi.getLocationData(locationDataRequest) for more info and code samples.

property dataSets: DataSets

The DataSets object containing the sets of data generated to fulfil the DataSpecs from the LocationDataRequest that led to this response.

property metadata

An object containing metadata sent back with every response from the API servers, including details of the account’s current rate limit.

property sources: Sequence[Source]

The non-empty Sequence of source(s) (essentially weather stations) that were used to generate the data in this response.

At the time of writing there will only be one source for any given response (so sources[0]) is the way to get it)… But at some point we might start combining data from multiple sources to satisfy requests for data from GeographicLocation types. If we do add this feature, it will be optional, and disabled by default, so the behaviour of your system won’t change unless you want it to.

property stationId: str

The non-empty canonical ID of the weather station or combination of weather stations that were used to calculate the returned data.

If the Location in the LocationDataRequest was a StationIdLocation (created via Location.stationId), this method will simply return the canonical form of that weather station’s ID. We say “canonical” because it’s possible for a station ID to be expressed in more than one way, like upper case or lower case. The canonical form of the station ID is the form that you should display in a UI or store in a database if appropriate.

If the Location in the LocationDataRequest was a GeographicLocation, then:

  • If the data was calculated using a single station (the usual case), this method will return the canonical form of that station’s ID.

  • If the data was calculated using multiple stations (something that the API might optionally start doing at some point in the future), this method will return the ID of a “virtual station” that represents the specific combination of weather stations used.

Either way, the station ID returned by this method can be used to fetch more data from the same station(s) that were used to generate the data in this response. For example, you might want to request data using a GeographicLocation initially, and then use the returned station ID to fetch updates each day, week, or month going forward.

property targetLongLat: LongLat

The degreedays.geo.LongLat object that specifies the geographic position of the Location from the LocationDataRequest that led to this response.

If the Location from the request was a PostalCodeLocation (created via Location.postalCode), this will be the LongLat that the API determined to be the central point of that postal code.

If the Location from the request was a StationIdLocation (created via Location.stationId), this will be the LongLat of that station (also accessible through sources).

If the Location from the request was a LongLatLocation (created via Location.longLat), this will simply be the LongLat that was originally specified. (Bear in mind that the longitude and latitude may have been rounded slightly between the request and the response. Such rounding would only introduce very small differences that would be insignificant as far as the real-world position is concerned, but it’s worth bearing this in mind in case you are comparing for equality the returned LongLat with the LongLat from the request. The two positions will be close, but they might not be equal.)

exception degreedays.api.data.LocationError(failureResponse: FailureResponse)

Indicates a Failure in the API’s processing of a request, caused by problems with the Location that the request specified.

Inheritance diagram of LocationError

This exception corresponds to any failure code code starting with “Location”.

You can interrogate the isDueToXXX properties of this exception to find out more about the cause. But do note that it is possible for none of those properties to be True if a relevant new failure code is added into the API. Be prepared for this in your handling.

You might not need to pay any attention to the specific code behind the LocationError. For many use-cases, the best way to handle a LocationError is just to try again with an alternative location. For example, if a request for data from a specific Location.stationId fails, you might want to try a request for the GeographicLocation of the real-world building you ultimately want data for, so that the API can find a replacement station for you automatically.

property failure: Failure

The Failure object containing details of the failure on the API servers that led to this exception on the client.

property isDueToLocationNotRecognized: bool

True if this failure came in response to a request for data from a location that the API did not recognize as a weather station or real-world geographic location; False otherwise.

This type of failure will occur if you specify a Location.stationId with an unrecognized ID, or a Location.postalCode with an unrecognized postal code.

property isDueToLocationNotSupported: bool

True if this failure came in response to a request for data from a location that is recognized but not currently supported by the API; False otherwise.

This can happen if you request data from a GeographicLocation for which the API is unable to find a good weather station. In this instance it is possible that a weather station with usable data does exist in the area - you could try visiting www.degreedays.net and searching manually to see if you can find a good weather station to use for future API requests (referencing it by station ID). But you will probably have to look some distance from the location of interest to find a usable station.

This can also happen if you request data from a weather station that has not sent any usable weather reports for roughly 10 days or more (10 being an approximate number that is subject to change). We call such stations “inactive”.

Some stations have never reported valid temperatures, and so have always been inactive/not supported.

It’s fairly rare for active stations to go inactive, but it does happen, and it’s best to be prepared for the possibility, particularly if you’re handling data from hundreds or thousands of locations. Stations can be taken down, they can break (and not be fixed in a timely manner), or they can be moved to another location and assigned a new ID. Unfortunately not even the best “airport” stations managed by organizations such as the NWS or UK Met Office are exempt from these sorts of problems.

Short periods of station inactivity should not result in this failure. Up to a point, the API will fill in missing data with estimates. It’s only when a station fails to send any usable data for roughly 10 days or more that the API will consider it inactive. (10 being an approximate number that is subject to change.)

It’s possible for an inactive station to become active (again) at some point in the future. However, if you get this failure for a station, you’re probably best off finding another nearby station to use as an alternative/replacement (e.g. by requesting data using the GeographicLocation of the real-world building of interest). If an active station goes inactive, and then later makes a revival, there will probably be a gap in its data coverage that will make it impractical for use in most forms of degree-day-based analysis until it has accumulated more uninterrupted data.

property responseMetadata: ResponseMetadata

The metadata from the FailureResponse that brought details of this failure back from the API servers.

class degreedays.api.data.LocationInfoRequest(location: Location, dataSpecs: DataSpecs)

Defines a request for info about the station(s) that would be used to fulfil an equivalent LocationDataRequest.

Inheritance diagram of LocationInfoRequest

Parameters:
  • location (Location) – the location for which data is desired.

  • dataSpecs (DataSpecs) – the specification of the data that is desired from the specified location.

Raises:

TypeError – if location is not a subclass of Location, or if dataSpecs is not a DataSpecs object.

LocationInfoRequest is effectively a lightweight alternative to LocationDataRequest, primarily for use when you want to know what station ID the API would use for a given GeographicLocation and DataSpecs, without the overhead of actually fetching the specified data. A LocationInfoRequest will only ever take one request unit, whilst a LocationDataRequest can take many more (depending on how much data it fetches).

A successfully-processed LocationInfoRequest will result in a LocationInfoResponse.

See DataApi.getLocationInfo(locationInfoRequest) for an example of how to create a LocationInfoRequest, submit it to the API, and get a LocationInfoResponse back.

property dataSpecs: DataSpecs

The DataSpecs object that specifies the data that is desired from the specified location.

property location: Location

The Location object for which data is desired.

class degreedays.api.data.LocationInfoResponse(metadata: api.ResponseMetadata, stationId: str, targetLongLat: degreedays.geo.LongLat, sources: Sequence[Source])

Contains the location/station-related info returned in response to a LocationInfoRequest.

Inheritance diagram of LocationInfoResponse

This mirrors LocationDataResponse, except it doesn’t actually contain any data (i.e. no DataSets).

See DataApi.getLocationInfo(locationInfoRequest) for more info and code samples.

property metadata

An object containing metadata sent back with every response from the API servers, including details of the account’s current rate limit.

property sources: Sequence[Source]

The non-empty Sequence of source(s) (essentially weather stations) that would be used to generate data for an equivalent LocationDataResponse.

At the time of writing there will only be one source for any given response (so sources[0]) is the way to get it)… But at some point we might start combining data from multiple sources to satisfy requests for data from GeographicLocation types. If we do add this feature, it will be optional, and disabled by default, so the behaviour of your system won’t change unless you want it to.

property stationId: str

The non-empty canonical ID of the weather station or combination of weather stations that would be used to generate data for an equivalent LocationDataResponse.

If the Location in the LocationInfoRequest was a StationIdLocation (created via Location.stationId), this method will simply return the canonical form of that weather station’s ID. We say “canonical” because it’s possible for a station ID to be expressed in more than one way, like upper case or lower case. The canonical form of the station ID is the form that you should display in a UI or store in a database if appropriate.

If the Location in the LocationDataRequest was a GeographicLocation, then:

  • If the data for an equivalent LocationDataResponse would be calculated using a single station (the usual case), this method will return the canonical form of that station’s ID.

  • If the data for an equivalent LocationDataResponse would be calculated using multiple stations (something that the API might optionally start doing at some point in the future), this method will return the ID of a “virtual station” that represents the specific combination of weather stations used.

Either way, the station ID returned by this method can be used to fetch data from the same station(s) that would have been used to generate data in response to an equivalent LocationDataRequest. Typically you would use this to get the station ID to best represent a GeographicLocation, and then use that ID in each LocationDataRequest for that location going forward.

property targetLongLat: LongLat

The degreedays.geo.LongLat object that specifies the geographic position of the Location from the LocationInfoRequest that led to this response.

If the Location from the request was a PostalCodeLocation (created via Location.postalCode), this will be the LongLat that the API determined to be the central point of that postal code.

If the Location from the request was a StationIdLocation (created via Location.stationId), this will be the LongLat of that station (also accessible through sources).

If the Location from the request was a LongLatLocation (created via Location.longLat), this will simply be the LongLat that was originally specified. (Bear in mind that the longitude and latitude may have been rounded slightly between the request and the response. Such rounding would only introduce very small differences that would be insignificant as far as the real-world position is concerned, but it’s worth bearing this in mind in case you are comparing for equality the returned LongLat with the LongLat from the request. The two positions will be close, but they might not be equal.)

class degreedays.api.data.Period

Defines the period in time that a set of degree days should cover.

Inheritance diagram of Period, degreedays.api.data.impl.LatestValuesPeriod, degreedays.api.data.impl.DayRangePeriod

To create a Period object you can use the static factory methods of this class. For example:

latest7Values = Period.latestValues(7)
yearOf2024 = Period.dayRange(degreedays.time.DayRange(
        datetime.date(2024, 1, 1), datetime.date(2024, 12, 31)))
static dayRange(dayRange: degreedays.time.DayRange, minimumDayRangeOrNone: degreedays.time.DayRange | None = None) impl.DayRangePeriod

Returns a DayRangePeriod object that specifies the period covered by dayRange.

Parameters:
Raises:
static latestValues(numberOfValues: int, minimumNumberOfValuesOrNone: int | None = None) impl.LatestValuesPeriod

Returns a LatestValuesPeriod object that automatically resolves to a date range including the latest available data and the specified number of degree-day values.

Parameters:
  • numberOfValues (int) – a number, greater than zero, that specifies how many degree-day values the period should cover (see degreedays.api.data.impl.LatestValuesPeriod for an explanation of how the values in question can be daily, weekly, monthly, or yearly). This is effectively an upper bound: if there isn’t enough good data to cover the numberOfValues specified, the API will assemble and return what it can.

  • minimumNumberOfValuesOrNone (int | None) – you can specify this if you would rather have a failure than a partial set of data with less than your specified minimum number of values. Otherwise you may get back less data than you asked for if there aren’t enough weather-data records to generate a full set for your specified location.

Raises:
  • TypeError – if numberOfValues is not an int, or if minimumNumberOfValuesOrNone is not an int or None.

  • ValueError – if numberOfValues is less than 1, or if minimumNumberOfValuesOrNone is an int that is less than 1 or greater than numberOfValues.

class degreedays.api.data.Source(station: Station, distanceFromTarget: Distance)

Contains basic information about a source of data that was used to satisfy a request.

On the surface this appears very similar to the Station object that it contains, but there is a key difference: the information contained in a Source is specifically relevant to a particular request for data, whilst the Station is independent of that. For example, the Source contains the distanceFromTarget, which is the distance of the Station from the target location that was specified in the request.

property distanceFromTarget: Distance

The distance of the station from the target location that was specified in the original request for data.

property station: Station

The Station that this source represents.

exception degreedays.api.data.SourceDataError(failure: Failure)

Indicates a Failure to generate a DataSet caused by problems with the source temperature data for the Location and Period requested.

Inheritance diagram of SourceDataError

For a LocationDataRequest containing specifications for multiple sets of data, it is possible for some to succeed and others to fail if they are sufficiently different (e.g. covering different periods in time).

This exception corresponds to any failure code starting with “SourceData”.

You can interrogate the isDueToXXX properties of this exception to find out more about the cause. But do note that it is possible for none of those properties to be True if a relevant new failure code is added into the API. Be prepared for this in your handling.

property failure: Failure

The Failure object containing details of the failure on the API servers that led to this exception on the client.

property isDueToSourceDataCoverage: bool

True if the requested data could not be generated because the source weather station’s recorded temperature data did not cover the requested period in time; False otherwise.

This will arise if you request data too early (because the weather station hasn’t yet recorded the necessary temperature readings or they haven’t yet filtered into our system), or if the weather station didn’t exist or wasn’t recording for the requested period in time.

property isDueToSourceDataErrors: bool

True if the requested data could not be generated because of errors in the recorded temperature data of the source weather station; False otherwise.

Generally speaking, if a request for data from a weather station results in this error, it is probably best to find an alternative weather station nearby. But a weather station with data errors is not necessarily totally useless - it may make a revival at some point in the future, and it’s possible that data requests covering a different period in time will work OK.

property isDueToSourceDataGranularity: bool

True if the requested data could not be generated because the source weather station’s recorded temperature data was not fine-grained/detailed enough; False otherwise.

This will arise if you request detailed time-series data (e.g. hourly temperature data) for a station without sufficiently fine-grained weather reports.

class degreedays.api.data.Station(id: str, longLat: LongLat, elevation: Distance, displayName: str)

Contains basic information about a weather station.

property displayName: str

A string representing the name of the weather station.

The display name should not include the id, the longLat or the elevation. So, for displaying the name of a station in a UI, at a minimum you’ll probably want to do something like:

station.id + ': ' + station.displayName

You might also want to display the longitude/latitude and elevation too.

We’d love to be able to break the details within this name down further (e.g. offering separate fields for city, state, and country)… But the recorded details for weather-station names are generally too unreliable for this to make sense. Fields are often misspelled, mixed up (e.g. the state might have been entered into the city field), and required fields like country are often missing. And there’s little consistency in the naming (e.g. United Kingdom, Great Britain, GB, UK etc.). This, unfortunately, is a limitation of the original data sources - it’s typical for the IDs to be monitored and controlled closely, but it’s rare for the names to receive the same level of attention.

Please be aware that a weather station’s display name may change over time, both in content and format. We strongly suggest that you don’t try to parse these names. But please do let us know if there’s something in particular that you need access to.

property elevation: Distance

The elevation of the weather station. The degreedays.geo.Distance object returned can easily be converted into units of your choice (typically metres or feet for an elevation).

Note that the reliability of these measurements is variable. Note also that they may change over time as the underlying data sources are updated with more accurate measurements.

property id: str

The canonical ID of the weather station. (See LocationDataResponse.stationId for an explanation of what “canonical” means in this context.)

The ID can be used to request data from this station.

property longLat: LongLat

The longitude/latitude location of the weather station.

Note that a weather station’s recorded location might not be a particularly accurate representation of its real-world location. Many stations have recorded longitude/latitude locations that are accurate to within a few metres, but there are some that are out by as much as a kilometre or so. This is worth bearing in mind when you’re calculating the distances between locations.

Note also that the recorded location of weather stations will often change over time as the underlying data sources are updated with more accurate measurements.

class degreedays.api.data.Temperature(value: float, unit: TemperatureUnit)

Defines a temperature value, typically the base temperature of a degree-day calculation.

To create a Temperature object you can use the static factory methods celsius and fahrenheit. For example:

celsiusTemp = Temperature.celsius(15.5)
fahrenheitTemp = Temperature.fahrenheit(65)

You can pass a temperature into a Calculation to define the base temperature that that Calculation should use to calculate degree days. Use a Celsius temperature for Celsius degree days, and a Fahrenheit temperature for Fahrenheit degree days.

Rounding

This class enables temperatures to be defined to the nearest 0.1 degrees (either 0.1°C or 0.1°F). Given the limitations of temperature-recording equipment and methods, it is meaningless to consider temperature differences smaller than this.

When creating a Temperature object, you can pass in any float value within the maximum and minimum limits set by the temperature unit. But the value you pass in will always be rounded to the nearest 0.1 degrees.

For example, a Celsius Temperature created with a value of 15.5 will be equal to one created with 15.456 or 15.543. And a Fahrenheit Temperature created with 65 will be equal to one created with 64.96 or 65.04.

One benefit of this is that you can easily define a range of base temperatures in a loop without worrying too much about the inaccuracies of floating-point arithmetic. The automatic rounding should correct any such issues (e.g. rounding 69.9998 up to 70).

static celsius(value: float) Temperature

Returns a Temperature object with the specified Celsius temperature rounded to the nearest 0.1°C.

Parameters:
  • value (float) – a Celsius temperature that’s greater than or equal to -273°C (absolute zero) and less than or equal to 3000°C (hotter than the hottest blast furnaces). Note that the base-temperature range over which it would typically make sense to calculate degree days is much smaller than the range allowed by these limits.

  • TypeError – if value is not a float.

  • ValueError – if value is NaN, less than -273°C, or greater than 3000°C.

See also: Rounding, fahrenheit

static celsiusRange(firstValue: float, lastValue: float, step: float) Sequence[Temperature]

Returns a low-to-high sorted Sequence of Temperature objects, with Celsius values running from firstValue to lastValue (inclusive), and the specified step between each consecutive temperature.

For example, to get Celsius temperatures between 10°C and 30°C (inclusive), with each temperature being 5°C greater than the last (giving temperatures 10°C, 15°C, 20°C, 25°C, and 30°C):

temps = Temperature.celsiusRange(10, 30, 5)
Parameters:
  • firstValue (float) – the Celsius value of the first Temperature to be included in the returned Sequence. This must be greater than or equal to -273°C and less than or equal to 3000°C.

  • lastValue (float) – the Celsius value of the last Temperature to be included in the returned Sequence. This must be greater than or equal to firstValue. Also, like for firstValue, this must be greater than or equal to -273°C and less than or equal to 3000°C.

  • step (float) – the Celsius temperature difference between each temperature value to be included in the returned Sequence. Cannot be NaN, and must be greater than zero. It must also be a multiple of 0.1 (the smallest temperature difference allowed), though allowances are made for floating-point imprecision (so for example a step of 0.4999999 will be treated as 0.5).

Raises:
  • TypeError – if firstValue, lastValue, or step is not a float.

  • ValueError – if any of the parameters are NaN; if firstValue or lastValue are outside of their allowed range (-273°C to 3000°C inclusive); if lastValue is less than firstValue; if step is not positive; or if step is not a multiple of 0.1 (allowing for slight deviations caused by floating-point imprecision).

See also: fahrenheitRange, TemperatureUnit.range

static fahrenheit(value: float) Temperature

Returns a Temperature object with the specified Fahrenheit temperature rounded to the nearest 0.1°F.

Parameters:
  • value (float) – a Fahrenheit temperature that’s greater than or equal to -459.4°F (absolute zero) and less than or equal to 5432°F (hotter than the hottest blast furnaces). Note that the base-temperature range over which it would typically make sense to calculate degree days is much smaller than the range allowed by these limits.

  • TypeError – if value is not a float.

  • ValueError – if value is NaN, less than -459.4°F, or greater than 5432°F.

See also: Rounding, celsius

static fahrenheitRange(firstValue: float, lastValue: float, step: float) Sequence[Temperature]

Returns a low-to-high sorted Sequence of Temperature objects, with Fahrenheit values running from firstValue to lastValue (inclusive), and the specified step between each consecutive temperature.

For example, to get Fahrenheit temperatures between 50°F and 70°F (inclusive), with each temperature being 5°F greater than the last (giving temperatures 50°F, 55°F, 60°F, 65°F, and 70°F):

temps = Temperature.fahrenheitRange(50, 70, 5)
Parameters:
  • firstValue (float) – the Fahrenheit value of the first Temperature to be included in the returned Sequence. This must be greater than or equal to -459.4°F and less than or equal to 5432°F.

  • lastValue (float) – the Fahrenheit value of the last Temperature to be included in the returned Sequence. This must be greater than or equal to firstValue. Also, like for firstValue, this must be greater than or equal to -459.4°F and less than or equal to 5432°F.

  • step (float) – the Fahrenheit temperature difference between each temperature value to be included in the returned Sequence. Cannot be NaN, and must be greater than zero. It must also be a multiple of 0.1 (the smallest temperature difference allowed), though allowances are made for floating-point imprecision (so for example a step of 0.4999999 will be treated as 0.5).

Raises:
  • TypeError – if firstValue, lastValue, or step is not a float.

  • ValueError – if any of the parameters are NaN; if firstValue or lastValue are outside of their allowed range (-459.4°F to 5432°F inclusive); if lastValue is less than firstValue; if step is not positive; or if step is not a multiple of 0.1 (allowing for slight deviations caused by floating-point imprecision).

See also: celsiusRange, TemperatureUnit.range

property unit: TemperatureUnit

The unit of this temperature.

property value: float

A float representation of the 0.1-precision number stored internally. Because of rounding, this might not be exactly the same as the float that this instance was created with.

class degreedays.api.data.TemperatureUnit

Defines the units of temperature measurement with class constants TemperatureUnit.CELSIUS and TemperatureUnit.FAHRENHEIT.

CELSIUS: TemperatureUnit

For the Celsius temperature scale.

Access via: TemperatureUnit.CELSIUS

FAHRENHEIT: TemperatureUnit

For the Fahrenheit temperature scale.

Access via: TemperatureUnit.FAHRENHEIT

range(firstValue: float, lastValue: float, step: float) Sequence[Temperature]

Returns a low-to-high sorted Sequence of Temperature objects, with values running from firstValue to lastValue (inclusive), and the specified step between each consecutive temperature.

For example, to get Celsius temperatures between 10°C and 30°C (inclusive), with each temperature being 5°C greater than the last (giving temperatures 10°C, 15°C, 20°C, 25°C, and 30°C):

temps = TemperatureUnit.CELSIUS.range(10, 30, 5)
Parameters:
  • firstValue (float) – the value of the first Temperature to be included in the returned Sequence. If this range method is called on TemperatureUnit.CELSIUS, firstValue must be greater than or equal to -273°C and less than or equal to 3000°C; if it is called on TemperatureUnit.FAHRENHEIT then firstValue must be greater than or equal to -459.4°F and less than or equal to 5432°F.

  • lastValue (float) – the value of the last Temperature to be included in the returned Sequence. This must be greater than or equal to firstValue. Also, like for firstValue, if this range method is called on TemperatureUnit.CELSIUS, lastValue must be greater than or equal to -273°C and less than or equal to 3000°C; if it is called on TemperatureUnit.FAHRENHEIT then lastValue must be greater than or equal to -459.4°F and less than or equal to 5432°F.

  • step (float) – the temperature difference between each temperature value to be included in the returned Sequence. Cannot be NaN, and must be greater than zero. It must also be a multiple of 0.1 (the smallest temperature difference allowed), though allowances are made for floating-point imprecision (so for example a step of 0.4999999 will be treated as 0.5).

Raises:
  • TypeError – if firstValue, lastValue, or step is not a float.

  • ValueError – if any of the parameters are NaN; if firstValue or lastValue are outside of their allowed range (that’s -273°C to 3000°C inclusive if this range method is called on TemperatureUnit.CELSIUS, and -459.4°F to 5432°F inclusive if it is called on TemperatureUnit.FAHRENHEIT); if lastValue is less than firstValue; if step is not positive; or if step is not a multiple of 0.1 (allowing for slight deviations caused by floating-point imprecision).

See also: Temperature.celsiusRange, Temperature.fahrenheitRange

class degreedays.api.data.TimeSeriesCalculation

Defines how time-series data should be calculated e.g. temperature data, with hourly figures, in Celsius.

Inheritance diagram of TimeSeriesCalculation, degreedays.api.data.impl.TemperatureTimeSeriesCalculation

To create a TimeSeriesCalculation object you can use the static factory methods of this class. For example:

hourlyTempsCalculation = TimeSeriesCalculation.hourlyTemperature(
        TemperatureUnit.CELSIUS)

The docs for TimeSeriesDataSpec have more on how to actually fetch time-series data with your specified TimeSeriesCalculation.

Why is time-series data “calculated”?

You might wonder why time-series data such as hourly temperature data is “calculated” as opposed to just being returned…

The thing is that real-world weather stations hardly ever report exactly on the hour, every hour. Different stations have different reporting frequencies and schedules, they can change over time (e.g. if a station has its equipment upgraded), and gaps in routine reporting are fairly common too. Higher-quality stations do tend to report at least hourly, but it’s rarely exactly on the hour, and it’s rarely perfectly regular either. We take whatever data the weather stations do report, and use it to calculate (or interpolate) neat, perfectly-regular time-series data (such as hourly temperature data) with all gaps filled with estimated data (which is marked as such so you can easily identify it).

The end result is neat, regular data that is easy to store and easy to work with, but it actually takes a lot of processing to get it into that format. And this is why time-series data from our system is “calculated” as opposed to just being returned.

static hourlyTemperature(temperatureUnit: TemperatureUnit) impl.TemperatureTimeSeriesCalculation

Returns a TemperatureTimeSeriesCalculation object with TimeSeriesInterval.HOURLY and the specified TemperatureUnit.

Parameters:

temperatureUnit (TemperatureUnit) – specifies whether the data should be calculated in Celsius or Fahrenheit.

Raises:

TypeError – if temperatureUnit is not a TemperatureUnit object.

property interval: TimeSeriesInterval

The TimeSeriesInterval indicating the interval (e.g. hourly) that the time-series data should be calculated with.

class degreedays.api.data.TimeSeriesDataSet(percentageEstimated: float, values: Sequence[TimeSeriesDataValue])

Contains a set of time-series data (e.g. hourly temperature data) generated to fulfil a TimeSeriesDataSpec for a specific Location.

Inheritance diagram of TimeSeriesDataSet

See TimeSeriesDataSpec for example code showing how to get a TimeSeriesDataSet of hourly temperature data from the API.

property fullRange: DayRange

The degreedays.time.DayRange object indicating the period of time that is covered by this DataSet.

getValuesWithin(dateOrDayRange: datetime.date | degreedays.time.DayRange) Sequence[TimeSeriesDataValue]

Returns a possibly-empty chronologically-ordered Sequence of the TimeSeriesDataValue objects from this TimeSeriesDataSet that fall within the specified dateOrDayRange.

Parameters:

dayRange (datetime.date | degreedays.time.DayRange) – the date or day range of interest.

Raises:

TypeError – if dateOrDayRange is not a datetime.date or degreedays.time.DayRange.

property percentageEstimated: float

A number between 0 and 100 (both inclusive), indicating the overall extent to which this DataSet is based on estimated data.

Generally speaking, data with a lower percentage-estimated figure is likely to be more reliable than data with a higher percentage-estimated figure.

property values: Sequence[TimeSeriesDataValue]

A non-empty chronologically-ordered Sequence of the TimeSeriesDataValue objects that make up this TimeSeriesDataSet.

class degreedays.api.data.TimeSeriesDataSpec(timeSeriesCalculation: TimeSeriesCalculation, datedBreakdown: DatedBreakdown)

Defines a specification for a set of time-series data such as hourly temperature data covering a specific period in time.

Inheritance diagram of TimeSeriesDataSpec

To create a TimeSeriesDataSpec object use DataSpec.timeSeries.

A TimeSeriesDataSpec specifies a set of time-series data in terms of:

Example TimeSeriesDataSpec code:

Here’s how you could specify hourly temperature data, in Fahrenheit, covering the last 30 days:

timeSeriesDataSpec = DataSpec.timeSeries(
        TimeSeriesCalculation.hourlyTemperature(TemperatureUnit.FAHRENHEIT),
        DatedBreakdown.daily(Period.latestValues(30)))

You could then send that TimeSeriesDataSpec to the API as part of a LocationDataRequest, and get a response containing a TimeSeriesDataSet back:

api = DegreeDaysApi.fromKeys(AccountKey(yourStringAccountKey),
        SecurityKey(yourStringSecurityKey))
request = LocationDataRequest(
        Location.postalCode("02630", "US"),
        DataSpecs(timeSeriesDataSpec))
response = api.dataApi.getLocationData(request)
hourlyData = response.dataSets[timeSeriesDataSpec]
for v in hourlyData.values:
    print('%s: %g' % (v.datetime, v.value))

To include figures for the current day (as opposed to the default behaviour of covering full days only), you can specify a DatedBreakdown with its DatedBreakdown.allowPartialLatest property set to True. For example:

hourlyTempsIncludingToday = DataSpec.timeSeries(
    TimeSeriesCalculation.hourlyTemperature(TemperatureUnit.FAHRENHEIT),
    DatedBreakdown.daily(Period.latestValues(31), allowPartialLatest=True))

The docs for DatedBreakdown.allowPartialLatest explain more, including an important caution that the latest figures can be volatile, and, if stored, should be overwritten later when the weather stations have had time to send any delayed or corrected weather reports (which many weather stations send quite often).

Why does TimeSeriesDataSpec take a DatedBreakdown?

It might seem strange that TimeSeriesDataSpec takes a DatedBreakdown in the same way that DatedDataSpec (for degree days) does, because hourly data is broken down hourly, not by days, weeks, or months. However, it works this way to give you flexibility in how you specify the time-period that the time-series data should cover, and to make it easier for you to get time-series data that lines up with your degree days.

With a DatedBreakdown you can easily specify that you want hourly data covering e.g. the last 30 days, or the last 12 full calendar months, or the last 5 full calendar years, just like you do when you are using a DatedBreakdown to specify the degree days you want. You can take full advantage of widening rules if they make things more convenient for you. The data will always come back as hourly figures. Essentially a daily, weekly, monthly, or yearly breakdown in a TimeSeriesDataSpec is used only to determine the overall period of time that the time-series data should cover.

Custom breakdowns (made with DatedBreakdown.custom) deserve a special note, however… If you create a TimeSeriesDataSpec with a custom breakdown (made up of your own custom-specified degreedays.time.DayRanges), then your time-series data will cover only the day ranges you specify. If you specify day ranges with gaps between them, then your time-series data will have gaps too (just as your degree days would if you used the same breakdown for them). This is rather contrary to our general ethos of providing continuous data with no gaps, but it will only happen if you specifically request it (by specifying a custom breakdown with gaps between its day ranges).

See also: DatedDataSpec, AverageDataSpec

property breakdown: DatedBreakdown

The DatedBreakdown object that defines the period of time that the time-series data should cover (read more on how this works).

property calculation: TimeSeriesCalculation

The TimeSeriesCalculation object that defines how the time-series data should be calculated. For example it could specify hourly temperature data, in Celsius.

class degreedays.api.data.TimeSeriesDataValue(value: float, percentageEstimated: float, datetime: datetime)

Contains a value (e.g. a temperature value) for a specific point in time, and an approximate indication of its accuracy.

Inheritance diagram of TimeSeriesDataValue

property datetime: datetime

A datetime.datetime object indicating the YYYY-MM-DDThh:mm (i.e. minute precision) date-time of this TimeSeriesDataValue, in the local time-zone of the weather station.

This holds the full detail of the date-time associated with this TimeSeriesDataValue, including its time-zone info (accessible via the tzinfo property of the datetime.datetime object).

property percentageEstimated: float

A number between 0 and 100 (both inclusive), indicating the extent to which the calculated value is based on estimated data.

Generally speaking, a value with a lower percentage-estimated figure is likely to be more reliable than one with a higher percentage-estimated figure.

property value: float

The value, which will never be NaN or infinity, and, for degree days, will always be zero or greater.

class degreedays.api.data.TimeSeriesInterval

Defines the interval (e.g. hourly) that time-series data should be calculated with.

HOURLY: TimeSeriesInterval

For regular hourly time-series data i.e. readings on the hour every hour.

Access via: TimeSeriesInterval.HOURLY