Skip to content

Add City of Austin Utilities (COA) DSS support#190

Open
grutamu wants to merge 3 commits into
tronikos:mainfrom
grutamu:fix-coa-customers-403
Open

Add City of Austin Utilities (COA) DSS support#190
grutamu wants to merge 3 commits into
tronikos:mainfrom
grutamu:fix-coa-customers-403

Conversation

@grutamu
Copy link
Copy Markdown

@grutamu grutamu commented May 6, 2026

Summary

City of Austin Utilities uses the Opower Digital Self-Service (DSS) portal at dss-coa.opower.com. The existing code was unable to get past account discovery because the multi-account-v1/customers endpoint always returns 403 EMPTY_AUTHORIZED_CUSTOMERS_LIST for Bearer token sessions — the server only populates the authorized-customers list during SAML cookie authentication, which is how the browser logs in but not how the library authenticates.

This PR makes COA fully functional for account discovery and bill history.

What changed

Account discovery via bill-trends-v1/serviceAgreements

The browser never calls /customers at all. It uses bill-trends-v1/serviceAgreements to discover the user's accounts. We do the same, mapping DSS service types (WATER, WASTE_WATER, ELECTRICITY, GAS, etc.) to opower MeterType values and constructing synthetic customer records the rest of the library can consume.

Bill history via bill-trends-v1/billHistory

DataBrowser-v1 also requires a SAML-established server-side session and returns 403 for Bearer token requests. Rather than failing silently, _async_fetch now tries DataBrowser-v1 first and falls back to bill-trends-v1/billHistory on 403 for utilities that opt in. This provides up to 36 months of monthly billing history with cost data. Consumption values are zero because the billing API does not expose metered usage (daily water reads live in a separate WaterSmart portal outside the opower integration).

New extension points

  • MeterType.WATER for water/wastewater utilities
  • UtilityBase.uses_bill_trends_for_reads() — utilities override this to True when DataBrowser-v1 is inaccessible; the fallback is generic and can be reused by other DSS utilities in the same situation

Login additions

After the OTT token exchange, the login flow fetches user-details to capture the webUserId (a real UUID) and calls customer-sync-v1 to establish session context on the server, mirroring what the browser does immediately after login.

What is not supported

Daily and hourly water usage reads are not available through dss-coa.opower.com. Austin Water exposes interval meter data through a separate WaterSmart portal (austintx.watersmart.com), which is outside the scope of this library.

Test plan

  • Login succeeds with valid COA credentials
  • async_get_accounts() returns WATER and WASTE_WATER accounts
  • async_get_cost_reads(account, AggregateType.BILL) returns up to 36 months of bill history with costs
  • async_get_cost_reads(account, AggregateType.DAY) falls back to bill history without crashing
  • Pre-commit (ruff, mypy, codespell) passes
  • Existing test suite passes

grutamu and others added 3 commits May 5, 2026 22:30
…headers

The /multi-account-v1/cws/coa/customers endpoint returns 403
EMPTY_AUTHORIZED_CUSTOMERS_LIST when Opower-Selected-Entities does not
include "urn:session:account:provider:dsst". Browser HAR analysis shows
that every successful DSS API call includes this entity in addition to
the numeric account ID. Adding it fixes the 403 for City of Austin
Utilities.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The browser performs a PUT to customer-sync-v1/.../coa/sync with
{"operations":[{"type":"USER_DETAILS"}]} immediately after obtaining
the sessionToken. Without this step, the server has no customer session
context and the /customers endpoint returns 403 EMPTY_AUTHORIZED_CUSTOMERS_LIST.
Adding this call mirrors the browser flow and should unblock data fetching.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
COA uses the Opower Digital Self-Service portal (dss-coa.opower.com) with
SAML-based SSO. The standard multi-account-v1/customers endpoint returns
403 EMPTY_AUTHORIZED_CUSTOMERS_LIST for Bearer token sessions because the
server's authorized-customers list is only populated via SAML cookie auth.
The browser avoids /customers entirely; we do the same.

Changes:
- Add MeterType.WATER for water/wastewater utilities
- Add uses_bill_trends_for_reads() hook to UtilityBase (default False);
  COA overrides to True
- Add _async_get_dss_customers(): discovers accounts via
  bill-trends-v1/serviceAgreements instead of multi-account-v1/customers;
  maps WATER/WASTE_WATER/ELECTRICITY/GAS service types to MeterType
- Capture webUserId (UUID) from user-details after OTT exchange; use it
  as the customer UUID in Opower-Selected-Entities (only when UUID-format)
- Add _async_fetch_dss_bills(): fetches 36 months of bill history via
  bill-trends-v1/billHistory; infers billing periods from consecutive bill
  dates; skips degenerate entries where two bills share the same date
- _async_fetch(): try DataBrowser-v1 first for all utilities; on 403 fall
  back to _async_fetch_dss_bills() for uses_bill_trends_for_reads utilities

Result: login, account discovery (WATER + WASTE_WATER), and 3-year bill
history with cost data all work. Sub-bill granularity is not available via
the opower portal (daily water reads live in a separate WaterSmart portal).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant