Skip to content

Add Renewable Component Library (RCL) integration - #37

Open
javlor wants to merge 13 commits into
mainfrom
feature/add-rcl-support
Open

Add Renewable Component Library (RCL) integration#37
javlor wants to merge 13 commits into
mainfrom
feature/add-rcl-support

Conversation

@javlor

@javlor javlor commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Adds support for searching and downloading PV module (PAN) and inverter (OND) files from DNV's API curated component database.

New functions:
Catalog search

  • sf.rcl.list_modules() - Search modules by manufacturer, model, power, bifaciality, technology
  • sf.rcl.list_inverters() - Search inverters by manufacturer, model, power, efficiency, MPPT specs
  • Filtering, sorting, pagination, and field selection via output_parameter

File downloads

  • sf.rcl.download_file() - Download with automatic local caching
  • Cached files don't consume monthly quota on re-download

Rate limit management

  • sf.rcl.get_rate_limit_status() - Zero-cost endpoint to check remaining downloads
  • Rate limit info included in all catalog responses

PVSystem integration

  • plant.set_module_from_rcl() - Search + download + assign module
  • plant.set_inverter_from_rcl() - Search + download + assign inverter
  • strict parameter controls error handling (raise vs return None) when multiple components match requested filtered query.

Includes documentation, example notebook, and full test coverage.

Comment thread solarfarmer/rcl.py


@dataclass
class RCLCatalogItem:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a pydantic model subclassing from solarfarmerbasemodel would keep things consistent and remove the need for the manual snake case handling

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

like this

class RCLCatalogItemBase(SolarFarmerBaseModel):
    file_uuid: str
    component_id: str
    filename: str
    manufacturer: str
    model: str

class RCLModuleItem(RCLCatalogItemBase):
    p_nom: float | None = None
    bifaciality_factor: float | None = None
    technol: str | None = None

class RCLInverterItem(RCLCatalogItemBase):
    p_nom_conv: float | None = None
    effic_max: float | None = None

Comment thread solarfarmer/rcl.py
return self.raw.items()


class RCLCatalogResponse(TypedDict):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same pydantic model suggestion

Comment thread solarfarmer/rcl.py
Properties (inverters, if requested)
------------------------------------
p_nom_conv : float | None
Rated AC power in kW. Aliases: ``pNomConv``.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was noted as units of watts elsewhere. If there's a conversion I missed it

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch. Yes, the PVSystem property had in Watts. I will update it to be all consistent (in kW).

Comment thread solarfarmer/rcl.py Outdated
Comment on lines +82 to +83
@property
def is_low(self) -> bool:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a strange feature in a python sdk. I recommend we delete it. Leave it to the user to implement with their own judgement instead of imposing ours upon them

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I will remove the is_low() method.

Comment thread solarfarmer/config.py
"pandas is required for this function. Install it with: pip install 'dnv-solarfarmer[weather]'"
)

# RCL (Renewable Component Library) configuration

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why put this in a generic config.py instead of where it's actually used in rcl.py?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RCL_BASE_URL would need to be stay to avoid a circular import of api.py's RCLClient and rcl.py, which import api.py. However, the rest of the variables can be simplified.

Comment thread solarfarmer/api.py
return base


def _build_auth_headers(api_key: str | None = None) -> dict[str, str]:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

refactor Client._check_params to use this so they don't drift apart

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I will make this a shared validation logic.

Comment thread solarfarmer/rcl.py Outdated
from pathlib import Path
from typing import TypedDict

import requests

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't import requests here. The other modules rightly don't import requests. Keep that concern specific to api.py

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's needed for the RCLRateLimitInfo in _extract_rate_limit so that it can parse the headers that indicate remaining, limit and reset details. I can move it to a Type_Checking import and add a note for the import reason.

Comment thread solarfarmer/rcl.py Outdated
dest = None

# Check local cache
if use_cache and dest is not None and dest.exists():

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should remove the cache feature at this time. There's a tension here between caching against file name and against uuid. Today it probably doesn't matter, but it will matter in the future as the library evolves. In any case, it's also easy enough for a user to roll their own cache in a way that makes more sense to them (e.g. maybe they want to put it in ~/.rcl/cache, maybe it should be coordinated with SF Desktop cache). Don't want to download twice? Don't call download_file unless you need to! If users really want a the sdk to provide a cache later then we can put more thought into how to do it.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, I will remove the cache feature from download_file()

Comment thread tests/test_rcl.py Outdated
Comment thread solarfarmer/rcl.py
javlor added 5 commits August 19, 2026 15:39
…ngs and disambiguation labels corrected).

Remove RCLRateLimitInfo.is_low property. Remove RCL_RATE_LIMIT_WARNING_THRESHOLD and the low-quota warning; always log quota info instead.
Remove local file caching from download_file (drop use_cache parameter).
Fix _build_query_params mangling raw dot-notation filter keys (filter.filter.x).
Drop module-level requests import; only import it under TYPE_CHECKING. Add User-Agent header to RCLClient.get() requests.
Add _raise_for_status helper so RCL errors surface the API's own message/problem details to align with the rest of the SDK.
Fix synthetic/misleading 500 status code when rate-limit headers can't be parsed.
Make _extract_rate_limit raise on missing/invalid headers instead of silently returning None; catch and log at call sites where quota info is secondary (catalog search, download), propagate where it's the primary result (get_rate_limit_status).
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.

2 participants