Open Data Schema for Energy

Python SDK Reference

Complete API reference for the odse Python package. Install with pip install odse.

import odse
print(odse.__version__)  # "0.4.0"

Transform

transform(data, source, asset_id=None, timezone=None, **kwargs)

Transform OEM data to ODS-E records.

Parameter Type Default Description
data str or Path required File path or raw data string (CSV or JSON)
source str required OEM source identifier (see table below)
asset_id str None Asset identifier to include in each record
timezone str None Timezone offset (e.g., "+02:00") for naive timestamps
**kwargs     Source-specific arguments (see below)

Returns: List[Dict[str, Any]] — list of ODS-E records.

Raises: ValueError if source is not recognized.

Supported sources:

Source Aliases Input Format Source-Specific kwargs
Huawei FusionSolar huawei CSV interval_minutes (default: 5)
Enphase Enlighten enphase JSON expected_devices (int)
Solarman solarman CSV interval_minutes
Switch switch CSV  
SolaX Cloud solaxcloud, solax JSON  
Fimer/Aurora Vision fimer, auroravision JSON  
SolarEdge solaredge JSON interval_minutes
Fronius fronius JSON  
SMA sma JSON interval_minutes
Solis solis, soliscloud JSON interval_minutes
Generic CSV csv, generic CSV mapping (required), default_error_type, interval_minutes

Example:

from odse import transform

# OEM-specific
rows = transform("huawei_export.csv", source="huawei", asset_id="SITE-001")

# Generic CSV with column mapping
rows = transform("scada.csv", source="csv", mapping={
    "timestamp": "Timestamp",
    "kWh": "Energy_kWh",
    "asset_id": "SiteTag",
})

transform_stream(data, source, **kwargs)

Streaming variant of transform(). Yields records one at a time for memory-bounded processing of large files.

Parameter Type Default Description
data str or Path required File path or raw data string
source str required OEM source identifier
**kwargs     Same as transform()

Yields: Dict[str, Any] — one ODS-E record at a time.

Example:

from odse import transform_stream

for record in transform_stream("large_export.csv", source="huawei"):
    process(record)

Generic CSV mapping Format

The mapping argument for source="csv" can be a dict or a path to a JSON/YAML file:

Key Type Required Description
timestamp str yes CSV column name containing timestamps
kWh str no CSV column for energy (if absent, computed from kW)
kW str no CSV column for power (used to compute kWh if kWh absent)
error_type str no CSV column for device status
error_code str no CSV column for error/status code
asset_id str no CSV column for asset identifier
extra dict no Map of {odse_field: csv_column} for additional numeric fields

Validate

validate(data, level="schema", capacity_kw=None, latitude=None, longitude=None, profile=None)

Validate a single ODS-E record.

Parameter Type Default Description
data dict, str, or Path required ODS-E record (dict), JSON string, or file path
level str "schema" Validation level: "schema" or "semantic"
capacity_kw float None System capacity in kW (for semantic checks)
latitude float None Site latitude (for geographic checks)
longitude float None Site longitude
profile str None Conformance profile name

Returns: ValidationResult

@dataclass
class ValidationResult:
    is_valid: bool
    errors: List[ValidationError]
    warnings: List[ValidationError]
    level: str

@dataclass
class ValidationError:
    path: str       # JSONPath, e.g., "$.timestamp"
    message: str    # Human-readable description
    code: str       # Machine-readable code (see below)

Error codes:

Code Meaning
REQUIRED_FIELD_MISSING A required field is absent
TYPE_MISMATCH Field has wrong type
ENUM_MISMATCH Value not in allowed enum
OUT_OF_BOUNDS Numeric value outside valid range
PATTERN_MISMATCH String doesn’t match required pattern
EXCEEDS_PHYSICAL_MAXIMUM kWh exceeds what capacity allows (semantic, warning)
PROFILE_FIELD_MISSING Profile-required field is absent
PROFILE_VALUE_MISMATCH Field value doesn’t match profile constraint
UNKNOWN_PROFILE Requested profile doesn’t exist

Example:

from odse import validate

result = validate({
    "timestamp": "2026-02-09T14:00:00Z",
    "kWh": 5.0,
    "error_type": "normal",
})
print(result.is_valid)  # True

validate_file(file_path, level="schema", **kwargs)

Validate a JSON file containing an ODS-E record.

Parameter Type Default Description
file_path str or Path required Path to JSON file
level str "schema" Validation level
**kwargs     Same as validate()

Returns: ValidationResult

validate_batch(records, level="schema", profile=None, **kwargs)

Validate a list of records and return aggregate results.

Parameter Type Default Description
records List[dict] required List of ODS-E records
level str "schema" Validation level
profile str None Conformance profile
**kwargs     Additional args passed to validate()

Returns: BatchValidationResult

@dataclass
class BatchValidationResult:
    total: int                              # Total records validated
    valid: int                              # Records that passed
    invalid: int                            # Records that failed
    errors: List[Tuple[int, ValidationError]]  # (record_index, error) pairs
    summary: str                            # Human-readable summary string

Example:

from odse import validate_batch

result = validate_batch(records, level="semantic", capacity_kw=10.0)
print(result.summary)  # "3/3 valid (schema)"

PROFILES

Dictionary of available conformance profiles. Keys are profile names, values are dicts with required_fields and field_constraints.

from odse import PROFILES

print(list(PROFILES.keys()))
# ['bilateral', 'wheeling', 'sawem_brp', 'municipal_recon']

Enrich

enrich(rows, context=None, *, override=False)

Inject market context metadata into ODS-E records.

Parameter Type Default Description
rows List[Dict] required ODS-E records from transform()
context dict or None None Metadata key-value pairs to inject
override bool (keyword-only) False If True, context values overwrite existing fields

Returns: List[Dict] — the same list, modified in place.

Behavior:

Example:

from odse import transform, enrich

rows = transform("data.csv", source="huawei")
enriched = enrich(rows, {
    "seller_party_id": "nersa:gen:SOLARPK-001",
    "settlement_type": "bilateral",
    "tariff_currency": "ZAR",
})

Output

to_json(records, output_path)

Write records as newline-delimited JSON (JSONL).

Parameter Type Description
records Iterable[dict] ODS-E records
output_path str File path (parent dirs created automatically)

to_csv(records, output_path)

Write records as CSV with ODS-E field names as headers.

Parameter Type Description
records Iterable[dict] ODS-E records
output_path str File path

to_parquet(records, output_path, partition_by=None, mode="overwrite")

Write partitioned Parquet files. Requires pip install odse[parquet] (pyarrow + pandas).

Parameter Type Default Description
records Iterable[dict] required ODS-E records
output_path str required Output directory path
partition_by List[str] None Fields to partition by (e.g., ["asset_id", "year", "month"])
mode str "overwrite" "overwrite" or "append"

Automatically derives year, month, day, hour fields from timestamp for partitioning.

Example:

from odse import to_parquet

to_parquet(records, "output/lake/", partition_by=["asset_id", "year", "month", "day"])
# Creates: output/lake/asset_id=SITE-001/year=2026/month=02/day=09/part-00000.parquet

to_dataframe(records)

Convert records to a pandas DataFrame with canonical dtypes. Requires pip install odse[dataframe].

Parameter Type Description
records Iterable[dict] ODS-E records

Returns: pandas.DataFrame with timestamp as datetime64[ns, UTC] and kWh as float64.


CLI

The odse command-line tool is installed with pip install odse.

odse transform

odse transform --source SOURCE --input FILE [options]
Flag Short Required Description
--source -s yes OEM source identifier
--input -i yes Input file path
--output -o no Output file path (default: stdout)
--format -f no Output format: json (default), csv, parquet
--column-map   no Inline column mapping for generic_csv (e.g., timestamp=Ts,kWh=Energy)
--asset-id   no Asset identifier
--timezone   no Timezone offset (e.g., +02:00)
--interval-minutes   no Interval in minutes for kW→kWh conversion (default: 5)

odse validate

odse validate --input FILE [options]
Flag Short Required Description
--input -i yes Input JSON file with ODS-E records
--level -l no schema (default) or semantic
--profile -p no Conformance profile name
--capacity-kw   no System capacity in kW
--latitude   no Site latitude
--longitude   no Site longitude

Output: JSON report to stdout with total, passed, failed, errors, warnings.

Exit code: 0 if all records pass, 1 if any fail.

odse version

Print the installed ODS-E version.

odse --version

Print version in odse X.Y.Z format.