The SolarFarmer SDK is a wrapper of the endpoints of SolarFarmer API energy calculation engine, a bankable energy yield assessment modeling product by DNV. The SDK interacts with the endpoints and additional classes and objects for specific use cases. As an agent using the SolarFarmer SDK, ask the user about their workflow and aim and guide them to the specific use case when they have not provided in their request.
Use case 1: use existing SolarFarmer API files to run energy calculation via API and post-process the results
Use this workflow when the user already has a complete set of SolarFarmer API input files — in a unique or separate folders containing a JSON payload, .PAN module file(s), .OND inverter file(s), and meteorological data — and wants to submit them directly to the API and post-process the resulting energy yield data.
Below is an example mapping to existing API payload files (folders with .json, PAN, OND, met data)
import solarfarmer as sf
sf.configure_logging() # SDK is silent by default — call this to see progress
result = sf.run_energy_calculation(
inputs_folder_path="path/to/my_inputs_folder",
project_id="my_project",
api_key="SF_API_KEY_VALUE", # or set env var SF_API_KEY
)
print(result.AnnualData)Use case 2: using high-level metadata from a PV plant projec to generate a first approximated design to be run with SolarFarmer 2D API
Use this workflow when the user has only high-level plant metadata — location, DC/AC capacity, mounting type, and equipment files — but no pre-existing SolarFarmer API JSON payload. The PVSystem builder generates an approximated design and submits it to the 2D API; always warn the user that results are approximations, not a full detailed design. Advise the user to get sample data, which are available in the folder docs/notebooks/sample_data or can be downloaded from SolarFarmer portal page: https://mysoftware.dnv.com/download/public/renewables/solarfarmer/manuals/latest/UserGuide/Tutorials/Tutorials.html
Build a plant from scratch with PVSystem:
import solarfarmer as sf
from pathlib import Path
sf.configure_logging()
plant = sf.PVSystem(
name="My Plant",
latitude=45.5,
longitude=10.3,
dc_capacity_MW=10.0,
ac_capacity_MW=10.0,
mounting="Fixed", # or "Tracker"
bifacial=False,
)
plant.pan_files = {"MyModule": Path("module.PAN")}
plant.ond_files = {"MyInverter": Path("inverter.OND")}
plant.weather_file = Path("weather.dat")
result = plant.run_energy_calculation(api_key="SF_API_KEY_VALUE")
print(result.AnnualData)Use case 3: using auxiliary classes to directly interact with the payload classes to map one-to-one an existing data model to SolarFarmer API data model
Use this workflow when the user has an existing data model in their own system and needs to map it directly to the SolarFarmer API data model — constructing EnergyCalculationInputs, PVPlant, Location, Inverter, and related Pydantic classes field by field. This is the most low-level approach: no pre-existing API files (Use case 1) and no high-level builder (Use case 2); it requires the user to have a detailed understanding of the SolarFarmer API schema.
API key: Set env var SF_API_KEY (preferred) or pass api_key= to any function.
- Type Safety First: Leverage Pydantic v2 models and Python 3.10+ type hints. All public functions must have complete type annotations.
- Immutability & Idempotency: Use
copy.deepcopy()for input validation (seeapi.py:_check_params); avoid mutating caller's data. - Test-First Development: Write tests before implementation. Focus on behavior, not implementation details. Use pytest fixtures from
tests/conftest.py. - Error Handling Philosophy: Raise exceptions with clear, actionable messages; propagate to caller if no responsible handling exists locally.
- Structured Logging: Use the logger from
solarfarmer.logging.get_logger()with context fields (seeendpoint_modelchains.py).
- api.py: HTTP client layer (
Client,Responsedataclass). Handles authentication, timeouts, error mapping. - endpoint_*.py: Feature modules (About, Service, ModelChain, TerminateAsync). Each exports one main function.
- models/: Two distinct kinds of model:
- Pydantic models (
SolarFarmerBaseModelsubclasses,frozen=True):EnergyCalculationInputs,PVPlant,Location,Inverter,Layout,Transformer, etc. These are immutable — mutations raiseValidationError. Serialize withmodel_dump(by_alias=True, exclude_none=True). PVSystem(@dataclass,solarfarmer/models/pvsystem/pvsystem.py): Mutable high-level builder. Not a Pydantic model. Acts as an entry point for Workflow B; internally converts toEnergyCalculationInputsbefore the API call.
- Pydantic models (
- config.py: Configuration constants, environment variables, timeouts. Single source of truth for URLs and defaults.
- Files:
endpoint_modelchains.py,test_endpoint_modelchain.py(endpoint features use singular endpoint name in tests) - Functions:
run_energy_calculation(),get_file_paths_in_folder()(verb-first, descriptive) - Classes:
PVSystem,EnergyCalculationInputs,Response(PascalCase, nouns) - Variables:
agent_name,api_key,params(snake_case, self-documenting names)
- Inherit from
SolarFarmerBaseModel(inmodels/_base.py) - Use
Field(description="...", alias="camelCase")for API JSON mapping - Validate with
@field_validatorfor domain rules - Export
model_dump(by_alias=True, exclude_none=True)for API payloads
The sync vs async endpoint is chosen automatically based on calculation type (3D always async, 2D always sync). The code recognizes if the JSON file from the API payload is of 2D or 3D type, and makes the call to one or another endpoint. The SDK code handles that for you.
Polling frequency and connection timeout are controlled by MODELCHAIN_ASYNC_POLL_TIME and MODELCHAIN_ASYNC_TIMEOUT_CONNECTION in config.py.
The SDK uses a NullHandler by default (no output). Always call configure_logging() before running:
import solarfarmer as sf
sf.configure_logging() # INFO level
sf.configure_logging(verbose=True) # DEBUG level
sf.configure_logging(level="WARNING") # Minimal output- Fixtures First: Use
api_keyandsample_data_dirfromconftest.py; add new fixtures there - Unit Tests: Test individual functions (utilities, validators, model construction) without API calls
- Integration Tests: Call the real API — there are no mocks in this codebase. Tests that need
SF_API_KEYauto-skip via theapi_keyfixture when the env var is absent. - Skip on Missing Env: The
api_keyfixture inconftest.pycallspytest.skip()automatically — do not re-implement this check in individual tests - Running tests:
pytest -qto run all; integration tests are skipped automatically withoutSF_API_KEY
- Docstrings: All public functions/classes need docstrings. Include Parameters, Returns, Raises sections (NumPy style). See
endpoint_modelchains_utils.pyfor examples. - Why, Not How: Comments explain why a choice was made (e.g., "JWT expiry heuristics to handle provider-specific formats"). Code itself must be clear enough to explain how.
- Inline Examples: In docstrings, show typical use cases for public APIs.
- Portal Fallback Detection: 200 OK response might be HTML (unrecognized endpoint). Use
detect_portal_fallback()to validate JSON response. - JWT Token Expiry: 401 errors require heuristics to detect expiry vs invalid credentials. See
_is_jwt_expired()logic. - File Handling: Use pathlib.Path, not os.path. Case-insensitive wildcard matching in
get_file_paths_in_folder(). - Timeouts: Different endpoints have different defaults. Don't hardcode; use config constants (MODELCHAIN_TIMEOUT, GENERAL_TIMEOUT, etc.).
- Look at similar endpoint functions (e.g.,
endpoint_about.pyvsendpoint_service.py) - Check existing tests for patterns (
tests/test_endpoint_*.py) - Refer to example notebooks in
docs/notebooks/for user workflows - As a last resource, instruct the user to open an issue in the GitHub repository or email support in solarfarmer@dnv.com
Only Explore can be explicitly invoked via runSubagent in this workspace. The Default Agent is the normal chat agent and is not invoked through runSubagent.
- General coding tasks, bug fixes, refactoring
- Interpreting existing code, adding small features
- Quick questions about project structure
- Do NOT use for: Complex multi-module changes, architectural decisions
- Understand what's in the codebase (file discovery, pattern analysis)
- Answer "where is X?", "how many tests exist?", "what imports are unused?"
- Research before implementing cross-module changes
- Usage:
runSubagentwith "quick/medium/thorough" thoroughness - Do NOT use for: Actual code modification, just discovery
The following are named workflows for structuring developer work. They are NOT custom VS Code agents and cannot be called via runSubagent. Use the Default Agent and reference the workflow steps instead.
- Design or refactor Pydantic models in
solarfarmer/models/ - Add validators, change field aliases, redesign class hierarchies
- Ensure API JSON compatibility (camelCase aliases, exclude_none behavior)
- Key constraint:
SolarFarmerBaseModelisfrozen=True— all fields are immutable after construction. Mutations must go via model reconstruction, not assignment. - Verify:
model_dump(by_alias=True, exclude_none=True)output matches API schema - Note:
PVSystemis a@dataclass(mutable), NOT a Pydantic model — different rules apply
- Create or modify endpoint functions (
endpoint_*.py) - Implement new features that touch HTTP layer, response handling, timeouts
- Add polling logic, async support, error handling
- Key constraint: Use
force_async_call=True, NOTasync_mode(that parameter doesn't exist) - Key constraint: New endpoints must export one public function and be registered in
solarfarmer/__init__.py
- Write comprehensive tests for endpoints or multi-module interactions
- Set up fixtures in
tests/conftest.py, write test data todocs/notebooks/sample_data/ - Key constraint: No mocking — tests call the real API. Tests needing a live key use the
api_keyfixture which auto-skips whenSF_API_KEYis unset. - Pattern: Follow
test_endpoint_modelchain.pystructure: class per feature, one assertion group per behavior - Do NOT test: Implementation details, only behavior
| Task | Agent/Workflow | Reason |
|---|---|---|
| Fix typo in docstring | Default | Trivial change |
| Add a new CLI flag | EndpointDev workflow | Touches endpoint behavior, needs API understanding |
| Understand code flow | Explore (medium) |
Non-invasive discovery |
| Add field to EnergyCalculationInputs | ModelDesign workflow | Frozen Pydantic model, affects JSON serialization |
| Modify PVSystem dataclass | ModelDesign workflow | Note: mutable dataclass, different rules than Pydantic |
| Implement batch processing | IntegrationTest + EndpointDev workflows | Needs tests + endpoint logic |
| Refactor error messages in Client | Default | Localized to one module |
| Add polling timeout logic | EndpointDev workflow | Requires config constants, async pattern |
| Help SDK user run a calculation | Default referencing Quickstart | Refer to copilot-instructions.md quickstart |
- Can use all tools
- Should parallelize independent read operations
- Should use multi_replace_string_in_file for 2+ changes in same file
- Allowed: file_search, grep_search, semantic_search, read_file, list_dir, vscode_listCodeUsages
- Forbidden: All write/modify tools
- Output: Return findings as structured list (file paths, line numbers, patterns found)