Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
outputs

# Python
__pycache__/
*.py[cod]
Expand Down
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ With uv:
```bash
uv run tariff-fetch [OPTIONS]
uv run tariff-fetch-gas [OPTIONS]
uv run tariff-fetch-arcadia-urdb MASTER_TARIFF_ID YEAR [OPTIONS]
```

With Just:
Expand Down Expand Up @@ -116,3 +117,18 @@ uv run tariff-fetch.cli \

The CLI suggests filenames like `outputs/openei_Utility_sector_detail-0_2024-03-18.json` before writing each file so you
can accept or override them.

## Direct Arcadia to URDB Conversion

For direct conversion of a single Arcadia master tariff to URDB JSON:

```bash
uv run tariff-fetch-arcadia-urdb 522 2025
```

Useful options:

- `--output` / `-o`: output file path
- `--apply-percentages` / `--no-apply-percentages`
- `--charge-class`: repeat to include multiple charge classes
- `--force` / `-f`: overwrite an existing output file
1 change: 1 addition & 0 deletions docs/providers/arcadia/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Arcadia's Signal Tariffs API (formerly Genability) provides comprehensive US uti

The Signal API uses a custom JSON objects to represent tariffs, with a complex hierarchical structure.

- **[Arcadia to URDB converter](urdb-converter.md)**
- **[Short guide to the Tariff JSON](tariff-json-structure.md)**
- [Official API docs on Tariff JSON](https://docs.arcadia.com/v2022-12-21-Signal/reference/tariff)

Expand Down
149 changes: 149 additions & 0 deletions docs/providers/arcadia/urdb-converter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# Arcadia to URDB Converter

This page explains what the Arcadia-to-URDB converter does, what it asks for, and which Arcadia tariffs it can convert reliably today.

The goal of the converter is to turn Arcadia electricity tariffs into a URDB-style rate record that is useful for downstream analysis. It is intentionally conservative: if the tariff uses features that are not yet handled safely, the converter should stop instead of silently producing a misleading result.

## What it converts

The converter is designed for Arcadia electricity tariffs with:

- energy charges based on kWh
- optional time-of-use structure
- optional tiered consumption bands
- a customer fixed charge
- optional percentage-based adjustments that can be applied on top of matching charges

The result is a URDB-style record with:

- tariff name
- utility name
- weekday schedule
- weekend schedule
- energy rate structure
- fixed monthly charge

## What the converter asks for

When converting a tariff, you may be asked for:

- the tariff to convert
- the year to convert it for
- which charge classes to include
- whether percentage-based rates should be applied
- any Arcadia tariff property values needed to resolve the tariff

Some Arcadia tariffs depend on user inputs such as territory or service configuration. If those values are required and not already known, the converter prompts for them interactively.

## How the output should be understood

The converter produces a URDB-style approximation of the Arcadia tariff for a chosen year.

That matters because Arcadia tariffs can contain more detail than the URDB schedule model can represent directly. To bridge that gap, the converter samples the tariff across the year and collapses the result into:

- one weekday schedule for each month
- one weekend schedule for each month
- one fixed monthly charge

For many residential and other relatively simple tariffs, this is a reasonable representation. For more complex tariffs, not every Arcadia detail can be preserved exactly.

## Supported tariff behavior

The converter currently works best for tariffs with:

- standard kWh-based energy charges
- monthly billing periods
- seasonal rates
- weekday/weekend time-of-use patterns
- tiered energy rates based on consumption thresholds
- monthly customer charges
- daily customer charges that can be converted to a monthly equivalent
- simple percentage adders applied to supported charge classes

## Current limitations

Some Arcadia tariff features are not supported yet. The converter is expected to stop when they appear instead of guessing.

Examples include:

- rates whose tier limits come from a variable lookup
- rates whose value must be multiplied by an additional variable factor
- rates that depend on a quantity such as number of meters
- demand-based rate bands
- property-limited bands and formula-driven band logic

In practice, this means the converter currently targets simpler electricity tariffs first and does not claim full Arcadia schema coverage.

## Fixed charges

The converter outputs one fixed charge in monthly units.

- monthly fixed charges are used directly
- daily fixed charges are converted to a monthly equivalent before being averaged into the final output

If a tariff includes fixed-charge structures that do not fit this model, the converter should stop rather than emit an ambiguous result.

## Percentage-based charges

Some Arcadia tariffs include percentage-based adjustments. These can be included or excluded during conversion.

- if percentage application is enabled, supported percentage adjustments are applied to matching charge classes
- if percentage application is disabled, those adjustments are left out

This makes it easier to compare a cleaner base energy schedule against a schedule that includes percentage adders.

Percentage handling should be understood as an approximation. The converter treats supported percentage rates as linear modifiers on matching energy charges. That is often reasonable for simple tariffs, but it is not the same as reproducing Arcadia's full bill-calculation logic in every case.

The approximation is most trustworthy when:

- the percentage rate is a straightforward adder or surcharge
- the affected charges are ordinary energy charges
- the intended percentage base lines up well with the selected charge classes

It is less trustworthy when the percentage is really meant to apply to a bill subtotal with exclusions, ordering rules, caps, floors, taxes, or other bill-level logic.

## Debug output

For troubleshooting, the converter saves fetched Arcadia data under `outputs/arcadia_library/` by default.

This includes:

- tariff data
- variable lookup data
- prompted property values

This folder is useful when checking why a tariff converted a certain way or why the converter stopped on a particular feature.

## Warnings and skipped items

Some Arcadia features are not converted exactly, but also do not stop the whole conversion.

Current examples:

- inaccessible rider tariffs that Arcadia returns as unauthorized
- time-of-use calendars attached through `calendar_id`

When this happens, the converter records a warning for the conversion run.

- inaccessible riders are skipped rather than retried forever
- TOU calendar ids are ignored, and the converter falls back to the ordinary TOU period definitions

These warnings are meant to make the approximation visible without turning every such case into a hard failure.

## Partial conversions

The current interactive flow may allow you to continue after one part of the conversion fails.

That is useful while developing or investigating difficult tariffs, but it also means you can end up with a partial URDB record if you explicitly choose to continue. For normal usage, treat a clean conversion with no skipped sections as the reliable result.

## When to trust the output

You should have the most confidence in the output when the tariff:

- is an electricity tariff
- uses standard consumption-based charges
- has ordinary seasonal or time-of-use structure
- has a simple fixed customer charge
- does not rely on advanced Arcadia-only features

If the tariff uses more advanced logic, the safer expectation is that the converter may stop and require additional support to be implemented first.
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ nav:
- Arcadia (Genability):
- Overview: providers/arcadia/index.md
- Access & Credentials: providers/arcadia/access.md
- Arcadia to URDB Converter: providers/arcadia/urdb-converter.md
- Tariff JSON Structure: providers/arcadia/tariff-json-structure.md
- NREL / OpenEI:
- Overview: providers/nrel/index.md
Expand Down
32 changes: 28 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "tariff_fetch"
version = "0.1.1"
version = "0.4.9"
description = "A CLI tool and a python library to simplify downloading electric and gas utility tariff data."
authors = [{ name = "Switchbox", email = "hello@switch.box" }]
readme = "README.md"
Expand Down Expand Up @@ -39,6 +39,7 @@ Documentation = "https://switchbox-data.github.io/tariff_fetch/"
[project.scripts]
tariff-fetch = "tariff_fetch.cli:main_cli"
tariff-fetch-gas = "tariff_fetch.cli_gas:main_cli"
tariff-fetch-arcadia-urdb = "tariff_fetch.cli_arcadia_urdb:main_cli"

[dependency-groups]
dev = [
Expand All @@ -64,17 +65,40 @@ testpaths = ["tests"]
addopts = ["-q"]

[tool.ruff]
target-version = "py310"
target-version = "py311"
line-length = 120
fix = true

[tool.ruff.lint]
select = ["E", "W", "F", "B", "I", "UP", "SIM", "C4", "RUF"]
ignore = ["E501"]
ignore = ["E501", "SIM103"]

[tool.ruff.lint.per-file-ignores]
"tests/*" = ["S101"]

[tool.basedpyright]
include = ["tariff_fetch"]
include = ["tariff_fetch", "tests"]
pythonVersion = "3.11"
venvPath = "."
venv = ".venv"

[[tool.basedpyright.executionEnvironments]]
root = "tests"
extraPaths = [".."]
reportUnknownVariableType = "none"
reportUnknownMemberType = "none"
reportUnknownParameterType = "none"
reportUnknownLambdaType = "none"
reportUnknownArgumentType = "none"
reportArgumentType = "none"
reportAny = "none"
reportExplicitAny = "none"
reportMissingParameterType = "none"
reportMissingTypeArgument = "none"
reportMissingTypeStubs = "none"
reportUnusedParameter = "none"
reportUnusedImport = "none"
reportUnusedCallResult = "none"
reportUnannotatedClassAttribute = "none"
reportUnnecessaryTypeIgnoreComment = "none"
reportPrivateUsage = "none"
Loading
Loading