fidgate-sdk
A standalone, stable public data-model surface for FIDgate clients: the DTOs, inter-BC contracts, configuration schemas, and value types that cross the system boundary — and nothing else (no services, ports, adapters, or domain logic).
fidgate-sdk is fully independent: it carries its own standalone copies of the
data models and has no dependency on fidcore in either direction. Installing
the SDK pulls in only numpy, pydantic, httpx (the HTTP client), pyyaml
(config-document validation and serialization) and pyarrow (rendering the run's
Parquet result tables to CSV), never core's logic. The copies are
generated from the FIDgate engine's own model sources and checked for drift on
every release, so they never diverge by hand.
Install
pip install fidgate-sdk
# or
uv add fidgate-sdk
Requires Python >= 3.10. The only runtime dependencies are numpy, pydantic,
httpx, pyyaml and pyarrow.
Usage
from fidgate_sdk import AssetConfigSchema, ProjectResult, ProjectSnapshot
# or browse by category:
from fidgate_sdk.models import config, contracts, results, types
These schemas are independent copies, not the same classes as fidcore's
(fidgate_sdk.AssetConfigSchema is fidcore...AssetConfigSchema is False). The
engine's entry points validate dict payloads internally
(AssetConfigSchema.model_validate(...)), so the supported path is to pass plain
dicts — the SDK schemas are for typing, construction, and validation on the client
side, and serialise to the dicts the engine accepts.
Version configs: typed models or raw YAML
create_version / update_version accept a config as a path / raw bytes
(sent verbatim — comments survive), a wire-form dict, or a typed
ProjectConfig bundle the SDK serialises to YAML itself:
from fidgate_sdk import AssetConfigSchema, FidgateClient, ProjectConfig, ProjectTimeline
config = ProjectConfig(
timeline=ProjectTimeline(construction_start_year=2024,
operation_start_year=2025, operation_end_year=2044),
asset=AssetConfigSchema.model_validate({...}), # models or plain dicts per section
commercial={...}, # dicts pass through verbatim
)
with FidgateClient() as client:
client.create_version(project_id, config, data_dir="caso_01/")
Every flavor is schema-validated locally before the request against
fidgate_sdk.config_document.SECTION_SCHEMAS — the same models the API
enforces — raising ConfigValidationError (no request issued) on failure;
external_data file references are tolerated anywhere, since the engine
resolves them before validating sections. Pass validate=False to skip the
local check and let the API decide (the escape hatch when an older SDK meets a
newer server schema).
Fluent builder
ProjectBuilder assembles that document step by step — each call validates its
piece immediately, and the materialized document is sparse (unset optionals
are truly absent, so defaults stay defaults in the stored YAML). You describe the
project in the vocabulary you built it with; the builder translates that into
what the API and the engine need, so no example here writes a dotted config path
by hand. The builder only assembles: to_project_config() hands the result to
the client as a normal ProjectConfig, and everything still travels as ONE
create_version call:
from fidgate_sdk import FidgateClient, ProjectBuilder, constant_price, price_series_csv
builder = (
ProjectBuilder("planta_b")
.set_timeline(construction_start_year=2024,
operation_start_year=2025, operation_end_year=2044)
.set_grid_connection(max_injection_mw=40.0)
.add_solar("PV_1", rated_capacity_mw=30.0, profile_path="data/pv_profile.csv",
capex_eur=18_000_000, opex_eur=300_000)
.add_merchant_market("m_da", price=price_series_csv("data/da_prices.csv"))
.add_ppa("ppa_1", price=constant_price(55.0), percent=70.0, term_years=15)
.set_financial(discount_rate_project_pct=7.0, discount_rate_equity_pct=9.0)
)
with FidgateClient() as client:
version = client.create_version(project_id, builder.to_project_config(),
data_dir="caso_01/")
add_merchant_market declares the market and the plant's merchant exposure
on it in one call (the pair add_market + add_market_exposure used to spell) —
the engine never infers an exposure, so the declaration stays explicit. Call
order does not matter: a valid program emits the same document however its calls
are ordered, and a component added before set_timeline, or an exposure naming a
market declared later, resolves at to_project_config().
Sweeps and scenarios in builder vocabulary. A project_optimization batch
varies a knob by the id of the thing it belongs to and the kwarg it was built
with — no config path, and a typo fails locally in to_project_config(), never
after upload:
from fidgate_sdk import ProjectBuilder, debt, per_mw_cost, span
builder.add_kpis("npv_eur", "project_irr", "equity_irr", "lcoe_eur_mwh", "dscr_min")
builder.sweep("BESS_1", unit_duration_h=[2.0, 4.0], capex=[420_000, 700_000]) # one axis, zipped
builder.sweep_financial(leverage_pct=span(50.0, 70.0, 3)) # a separate axis
builder.scenario("ppa_65", ppa_1={"price": 65.0}) # one explicit variant
Kwargs in one sweep call are linked (zipped); separate calls multiply. The two
id-less sections have typed helpers, sweep_financial and
sweep_grid_connection; scenario reaches the same knobs by target. Debt,
depreciation, contract commitments and maintenance windows are typed factories
(debt(...), depreciation(...), baseload_commitment(...),
maintenance_window(...)), and builder.check() returns a report of what is
still missing, what defaulted, what a sweep auto-declared and the batch's scenario
count, without ever raising. builder.sweepable() lists the sweepable knobs each
declared target currently carries. lcoe_eur_mwh is the generation's LCOE only
(None without generation components or generated energy), lcos_eur_mwh the
storage's (None without storage or discharge, or when the battery is tolled
out), and the IRRs are nan unless the cashflow has exactly one.
Every section the engine accepts is reachable. A physics-modelled PV plant sharing its site with a tolled battery reads:
from fidgate_sdk import (
ProjectBuilder, area_sizing, cec_inverter, cec_module, constant_price,
per_mw_cost, price_series_csv, pvgis_tmy_meteo, pvlib_generation, sat_mount,
)
builder = (
ProjectBuilder("pv_bess_tolling")
.set_timeline(construction_start_year=2026,
operation_start_year=2027, operation_end_year=2051)
.set_site(lat=52.52, lon=13.405, tz="Europe/Berlin", area_net_ha=100.0,
meteo=pvgis_tmy_meteo(lat=52.52, lon=13.405, year=2027))
.set_grid_connection(max_injection_mw=100.0)
.add_pv("PV_75", relative_area=0.75,
module=cec_module("data/equipment/cec_modules.csv", "Trina Solar TSM-600NEG19RC.20",
length=2.172, width=1.303),
inverter=cec_inverter("data/equipment/cec_inverters.csv",
"Huawei Technologies Co - Ltd : SUN2000-100KTL-USH0 [800V]"),
mount=sat_mount(axis_tilt=0.0, axis_azimuth=0.0, max_angle=60.0, backtrack=True,
module_nb=2, module_height=1.5, racking_model="open_rack"),
sizing=area_sizing(modules_per_string=25, strings_per_inverter=8, gcr=0.6),
generation=pvlib_generation(temperature_model="pvsyst",
temperature_model_parameters={"u_c": 29.0, "u_v": 0.0,
"module_efficiency": 0.21,
"alpha_absorption": 0.9},
losses_model="no_loss", inv_to_poi_loss_pct=2.0),
capex=per_mw_cost(600_000), opex=per_mw_cost(12_000), annual_degradation_pct=0.4)
.add_battery("BESS_1", unit_rated_power_mw=2.5, unit_duration_h=2.0, nb_units=10,
capex=per_mw_cost(400_000), opex=per_mw_cost(10_000), lifetime_years=15)
.set_actors(["owner", "toller"], plant_owner="owner")
# one call for the market + the toller's exposure; a second exposure adds on
.add_merchant_market("m_da", price=price_series_csv("data/da_prices.csv"),
exposure_id="exp_toller", seller="toller",
effective_year=2027, term_years=10)
.add_market_exposure("exp_owner", market_id="m_da", seller="owner")
.add_tolling("c_tolling_bess", price=constant_price(60_000.0), ceded_components=["BESS_1"],
seller="owner", buyer="toller", term_years=10)
.add_ppa("c_ppa_solar", price=constant_price(40.0), quantity_reference="PRODUCTION",
of=["PV_75"], seller="owner", buyer="toller", term_years=10)
.set_dispatch(seed=7)
.set_financial(discount_rate_project_pct=7.0, discount_rate_equity_pct=9.0,
corporate_tax_rate_pct=20.0)
)
The helper families, all exported at the top level:
| Family | Helpers |
|---|---|
| price curves | constant_price (a number, 12 or 8760 values), escalating_price (escalation_pct=, a percent), price_series_csv, exogenous_price_csv / exogenous_price (fill=) |
| cost models | absolute_cost, per_mw_cost, composite_cost → a side-agnostic CostModel for capex= / opex= |
| site meteo | pvgis_tmy_meteo, weather_csv_meteo, pvgis_tmy (site-relative) → set_site(meteo=) |
| PV hardware / layout | cec_module, module_parameters, cec_inverter, fixed_mount, sat_mount, area_sizing, topology_sizing, pvlib_generation |
| ageing / availability | generation_degradation, linear_degradation, soh_curves_degradation, global_availability, maintenance_windows, maintenance_window |
| financial pieces | debt, depreciation → typed factories for set_financial(debt=…, depreciation=…) |
| contract commitments / axes | baseload_commitment, as_produced_commitment, quantity_reference, quantity_arc, quantity_expression, per_mwh_shortfall_penalty, buyback_penalty |
| value lists for a sweep | span, steps |
Contracts have one method per type — add_ppa, add_baseload,
add_baseload_buyback, add_tolling, add_custom_contract — so the rules the
lenient input schema leaves to the engine (a tolling fee accrues per period,
never hourly; a custom contract names its quantity; a financial settlement names
a declared reference market) fail at the call that broke them. A buyback names
its basis explicitly — add_baseload_buyback(..., buyback_basis=) /
buyback_penalty(market_id, basis=) with "fixed_cost" (buy − strike, signed)
or "replacement_cost" (max(0, buy − strike)); there is no default. Markets take
market_type="DA" | "ID" | "imbalance", and a project declares at most one market
for now (F-17 — a second market id raises; more exposures on it are fine);
set_dispatch(...) writes the optional dispatch: section;
set_financial(loss_treatment=...) qualifies the tax block.
Every helper returns a plain dict or a small frozen dataclass, and every slot
that takes one also takes the raw {model_name, model_parameters} node.
Assisted wizard
Don't know the builder API yet? ProjectBuilder.assisted() asks the questions
instead — main setup first (solar merchant, solar + PPA, solar + battery + PPA,
custom, or a physics-modelled PV plant with a battery under a tolling agreement),
then only what that setup needs, with optional extras as skippable follow-ups
(Enter omits the key, so engine defaults apply at run time). The custom setup
additionally offers a physics PV component (with the site's weather source), a
wind component, tolling and baseload contracts, an actor registry and the
dispatch knobs, each behind a yes/no gate. Its optimization step builds a menu of
the plant's sweepable knobs (from ProjectBuilder.sweepable()) and asks explicit
scenarios by target and knob — never a dotted config path:
from fidgate_sdk import FidgateClient, ProjectBuilder
config = ProjectBuilder.assisted() # interactive Q&A in the terminal
with FidgateClient() as client:
version = client.create_version(project_id, config, data_dir="caso_01/")
The wizard always returns the validated ProjectConfig; choosing the
"YAML file" delivery additionally writes it to a path of your choice. IO is
injectable (input_fn / output_fn), so the whole flow can be scripted —
see fidgate_sdk.wizard.run_wizard.
ParameterSweep is a different thing from ProjectBuilder.sweep: it is a client-side loop that stores and runs one version per value
over the built document — one version, one run each, headline KPIs (feasible,
irr, npv_eur, energy_to_grid_mwh) extracted into plain dict rows — whereas
builder.sweep(...) declares ONE server-side project_optimization batch. As a
raw client-side loop it addresses its parameter by config path:
from fidgate_sdk import ParameterSweep
sweep = ParameterSweep(
parameter="commercial.contracts.ppa_1.pricing.model_parameters.price",
values=[45.0, 55.0, 65.0],
)
rows = sweep.run(client, project_id, builder, into="sweep_results", data_dir="caso_01/")
Reading responses: JSON or text
Every object the client returns is a typed model (version.id keeps working)
that prints as its JSON body; ask for .to_text() when you want something
readable. Listings are ordinary lists with the same views:
version = client.get_version(project.id, version.id)
print(version) # indented JSON; version.to_dict() / .to_json() too
print(version.to_text()) # one summary line + indented file table
print(client.list_projects().to_text()) # NAME CREATED UPDATED ID table
Metadata
Release files for fidgate-sdk 1.16.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| fidgate_sdk-1.16.0.tar.gz | 147.3 kB | With provenance | Details for fidgate_sdk-1.16.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| fidgate_sdk-1.16.0-py3-none-any.whl | Python 3 | none | any | Details for fidgate_sdk-1.16.0-py3-none-any.whl |
Total release size: 309.9 kB
Release files / fidgate_sdk-1.16.0.tar.gz
| Download URL | fidgate_sdk-1.16.0.tar.gz |
|---|---|
| Size | 147.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b0431f5834ac5c88a9236c6d3ef4b09802d894ed8d210fcfadb3347697740adf
|
|
BLAKE2b-256 checksum How to use checksums |
1299c0893c98aeccfbb17d46868e8133ebd94eb14892d3e98b53e354164953a2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.
Transparency logRelease files / fidgate_sdk-1.16.0-py3-none-any.whl
| Download URL | fidgate_sdk-1.16.0-py3-none-any.whl |
|---|---|
| Size | 162.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
be1df6b93a6f99874a66af28435977ea616ce6ae5371eb5cb573856f9ade89ad
|
|
BLAKE2b-256 checksum How to use checksums |
0bd045049dbfffd8f65dde0463c1159130b98eed1f17d1cc609e867081090e20
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.
Transparency logSecurity for fidgate-sdk 1.16.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for fidgate_sdk-1.16.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.