Skip to content

feat: add France (FR) market support#132

Merged
Veldkornet merged 3 commits into
HiDiHo01:mainfrom
Veldkornet:feat/add-france-support
Jul 22, 2026
Merged

feat: add France (FR) market support#132
Veldkornet merged 3 commits into
HiDiHo01:mainfrom
Veldkornet:feat/add-france-support

Conversation

@Veldkornet

@Veldkornet Veldkornet commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Add FR to CountryCode (alongside NL/BE); France is served through the same x-country header mechanism already used for Belgium.
  • Generalize be_prices() into a shared country_prices(country, ...) helper (be_prices()/new fr_prices() are now thin wrappers), and MarketPrices.from_be_dict() into from_country_dict(data, country).
  • Fix Price.__init__ crashing (TypeError: unsupported operand type(s) for +: 'float' and 'NoneType') when marketPriceTax/sourcingMarkupPrice/ energyTaxPrice are null — which France's public marketPrices response always does (BE always returns numeric values here). They now default to 0.0.
  • Fix the x-country marketPrices query never requesting a resolution — it silently always returned hourly data regardless of the configured resolution. This affected BE too, just never surfaced. country_prices()/be_prices()/ fr_prices() now accept and forward a resolution parameter (default PT15M).

Test plan

  • pytest tests/ — 264 passed
  • ruff format / ruff check — clean
  • Live-verified against Frank's public GraphQL API (x-country: FR) and against a running Home Assistant dev container using this library — price fetch succeeds and resolution is honored (96 × 15-min entries/day)

Summary by Sourcery

Voeg Frankrijk (FR) toe als ondersteunde x-country markt en generaliseer het ophalen en parsen van land-gescope prijzen.

Nieuwe features:

  • Introduceer FR als ondersteunde CountryCode naast NL en BE.
  • Voeg fr_prices helper toe die marktprijzen voor Frankrijk ophaalt via de x-country header.
  • Voeg gedeelde country_prices API toe voor het ophalen van marktprijzen voor markten die via x-country worden bediend.

Bugfixes:

  • Standaardiseer belasting- en opslagvelden op 0,0 wanneer ze ontbreken of null zijn in Price om crashes op Franse payloads te voorkomen.
  • Zorg ervoor dat land-gescope marketPrices-queries een resolution-parameter bevatten en respecteren in plaats van altijd uurlijkse data terug te geven.
  • Verbeter foutafhandeling en meldingen bij het parsen van land-gescope MarketPrices voor ontbrekende data of marketPrices-velden.

Verbeteringen:

  • Refactor de BE-specifieke MarketPrices.from_be_dict naar een generieke from_country_dict die wordt gebruikt door BE en FR.
  • Maak het ophalen van Belgische prijzen uniform door de generieke country_prices helper te gebruiken, terwijl de bestaande be_prices API behouden blijft.

Tests:

  • Voeg tests toe voor het parsen van FR-marktprijzen en foutafhandeling.
  • Voeg regressietests toe die het gedrag van Price valideren bij null belasting-/opslagvelden.
  • Voeg tests toe die garanderen dat country_prices en FR/BE helpers standaard naar en de gevraagde resolution doorgeven.
Original summary in English

Summary by Sourcery

Add France (FR) as a supported x-country market and generalize country-scoped price fetching and parsing.

New Features:

  • Introduce FR as a supported CountryCode alongside NL and BE.
  • Add fr_prices helper that fetches France market prices via the x-country header.
  • Add shared country_prices API for fetching market prices for x-country-served markets.

Bug Fixes:

  • Default tax and markup fields to 0.0 when missing or null in Price to avoid crashes on French payloads.
  • Ensure country-scoped marketPrices queries include and respect a resolution parameter instead of always returning hourly data.
  • Improve error handling and messaging in country-scoped MarketPrices parsing for missing data or marketPrices fields.

Enhancements:

  • Refactor BE-specific MarketPrices.from_be_dict into a generic from_country_dict used by BE and FR.
  • Unify Belgium price fetching to use the generic country_prices helper while preserving existing be_prices API.

Tests:

  • Add tests for FR market prices parsing and error handling.
  • Add regression tests validating Price behavior with null tax/markup fields.
  • Add tests ensuring country_prices and FR/BE helpers default to and forward the requested resolution.

Summary by CodeRabbit

  • New Features

    • Added support for retrieving French (FR) electricity market prices.
    • Added country-specific price retrieval with configurable date ranges and time resolution, including 15-minute and 60-minute intervals.
    • Belgium price retrieval now supports configurable resolution options.
  • Bug Fixes

    • Improved handling of responses with missing tax or markup fields.
    • Added clearer validation for unavailable or malformed market-price data.

Adds "FR" to CountryCode alongside NL/BE. France is served through the
same 'x-country' header mechanism already used for Belgium, via a new
shared country_prices() helper (be_prices()/fr_prices() are now thin
wrappers), and MarketPrices.from_be_dict() is generalized into
from_country_dict(data, country).

Also fixes two bugs surfaced by live-testing against a real France
account:

- Price.__init__ crashed with `TypeError: unsupported operand type(s)
  for +: 'float' and 'NoneType'` because France's public marketPrices
  response leaves marketPriceTax/sourcingMarkupPrice/energyTaxPrice as
  null (BE always returns numeric values for these). They now default
  to 0.0 when absent.

- The 'x-country' marketPrices query never sent a $resolution argument
  at all, so it silently always returned the backend's default hourly
  data regardless of the configured resolution. This affected BE too,
  just never surfaced. country_prices()/be_prices()/fr_prices() now
  accept and forward a resolution parameter (default PT15M).
@sourcery-ai

sourcery-ai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Handleiding voor reviewers

Voegt ondersteuning voor de Franse (FR) markt toe via het bestaande x-country-mechanisme, generaliseert BE-specifieke marktprijs-parsing en -ophaling naar land-agnostische helpers, en verhelpt de verwerking van null-waarden voor belasting/opslagvelden en ontbrekende resolution in marketPrices-queries.

Sequentiediagram voor country_prices marketPrices-query met resolution en FR-ondersteuning

sequenceDiagram
    actor User
    participant FrankEnergieClient as FrankEnergie
    participant GraphQLAPI
    participant MarketPrices

    User->>FrankEnergieClient: country_prices(country, start_date, end_date, resolution)
    FrankEnergieClient->>FrankEnergieClient: FrankEnergieQuery(date, resolution)
    FrankEnergieClient->>GraphQLAPI: _query(FrankEnergieQuery, headers x-country=country)
    GraphQLAPI-->>FrankEnergieClient: response
    FrankEnergieClient->>MarketPrices: from_country_dict(response, country)
    MarketPrices-->>User: MarketPrices instance
Loading

Wijzigingen per bestand

Wijziging Details Bestanden
Generaliseer BE-specifieke parsing van marktprijzen naar een land-agnostische helper en hergebruik deze voor BE en FR.
  • Vervang MarketPrices.from_be_dict door een nieuwe MarketPrices.from_country_dict(data, energy_country) die payloads met landscope valideert en parseert
  • Werk logging en foutmeldingen bij zodat ze geparametriseerd zijn met energy_country in plaats van hardcoded BE
  • Implementeer MarketPrices.from_be_dict als een dunne wrapper die doorverwijst naar from_country_dict met energy_country ingesteld op BE
python_frank_energie/models.py
Introduceer een gedeelde country_prices-clientmethode met resolution-ondersteuning en voeg Frankrijk-specifieke prijsophaling daarbovenop toe.
  • Voeg FrankEnergie.country_prices(country, start_date, end_date, resolution) toe die de x-country-header zet en een resolution-argument doorgeeft aan de marketPrices GraphQL-query
  • Breid de marketPrices-query uit zodat deze een PriceResolution-variabele accepteert en deze doorgeeft als het resolution-veld
  • Refactor be_prices zodat deze doorverwijst naar country_prices met country='BE' en standaard 15-minutenresolution
  • Voeg fr_prices toe die doorverwijst naar country_prices met country='FR' en standaard 15-minutenresolution
python_frank_energie/frank_energie.py
Maak Price robuust voor null-waarden in belasting- en opslagvelden die door de API worden teruggegeven.
  • Pas Price.init aan om dict.get(...) te gebruiken met een fallback van 0.0 voor marketPriceTax, sourcingMarkupPrice en energyTaxPrice
  • Zorg dat de afgeleide velden market_price_including_tax en market_price_including_tax_and_markup correct worden berekend wanneer de uitsplitsingsvelden null zijn
python_frank_energie/models.py
Breid de ondersteunde landcodes uit met Frankrijk (FR).
  • Werk het CountryCode Literal-type bij om 'FR' op te nemen
  • Voeg FR toe aan de CountryCode StrEnum die in de domain-laag wordt gebruikt
python_frank_energie/types.py
python_frank_energie/domain.py
Voeg tests toe voor FR-marktprijsparsing, verwerking van null-belasting/opslag en het doorgeven van resolution voor x-country marketPrices-queries.
  • Voeg tests toe voor MarketPrices.from_country_dict wanneer aangeroepen met FR, inclusief zowel error- als happy-path-payloads
  • Voeg een regressietest toe die verzekert dat Price None voor belasting-/opslagvelden tolereert en inclusieve prijzen correct berekent
  • Voeg tests toe die verifiëren dat fr_prices standaard datumafhandeling toepast en de x-country: FR-header zet
  • Voeg tests toe die valideren dat country_prices standaard PT15M als resolution gebruikt en een expliciet opgegeven resolution-waarde doorgeeft
tests/test_models.py
tests/test_frank_energie.py

Tips en commando’s

Interactie met Sourcery

  • Een nieuwe review starten: Plaats de comment @sourcery-ai review op de pull request.
  • Discussies voortzetten: Reageer direct op de reviewcomments van Sourcery.
  • Een GitHub-issue genereren vanuit een reviewcomment: Vraag Sourcery om een
    issue te maken vanuit een reviewcomment door erop te reageren. Je kunt ook
    reageren op een reviewcomment met @sourcery-ai issue om er een issue van te maken.
  • Een titel voor de pull request genereren: Schrijf @sourcery-ai ergens in de
    titel van de pull request om op elk moment een titel te genereren. Je kunt ook
    @sourcery-ai title commenten op de pull request om de titel op elk moment
    (opnieuw) te genereren.
  • Een samenvatting van de pull request genereren: Schrijf @sourcery-ai summary
    ergens in de body van de pull request om op elk moment een PR-samenvatting
    te genereren precies waar je die wilt hebben. Je kunt ook @sourcery-ai summary
    commenten op de pull request om de samenvatting op elk moment (opnieuw) te genereren.
  • Reviewer's guide genereren: Comment @sourcery-ai guide op de pull request om
    de reviewer's guide op elk moment (opnieuw) te genereren.
  • Alle Sourcery-comments oplossen: Comment @sourcery-ai resolve op de
    pull request om alle Sourcery-comments als resolved te markeren. Handig als je
    alle comments al hebt verwerkt en ze niet meer wilt zien.
  • Alle Sourcery-reviews verwerpen: Comment @sourcery-ai dismiss op de
    pull request om alle bestaande Sourcery-reviews te verwerpen. Vooral handig
    als je opnieuw wilt beginnen met een nieuwe review – vergeet niet om
    @sourcery-ai review te commenten om een nieuwe review te starten!

Je ervaring aanpassen

Ga naar je dashboard om:

  • Review-features zoals de door Sourcery gegenereerde pull request-samenvatting,
    de reviewer's guide en andere in- of uit te schakelen.
  • De reviewtaal te wijzigen.
  • Aangepaste reviewinstructies toe te voegen, verwijderen of te bewerken.
  • Andere reviewinstellingen aan te passen.

Hulp krijgen

Original review guide in English

Reviewer's Guide

Adds France (FR) market support via the existing x-country mechanism, generalizes BE-specific market price parsing and retrieval into country-agnostic helpers, and fixes handling of null tax/markup fields and missing resolution in marketPrices queries.

Sequence diagram for country_prices marketPrices query with resolution and FR support

sequenceDiagram
    actor User
    participant FrankEnergieClient as FrankEnergie
    participant GraphQLAPI
    participant MarketPrices

    User->>FrankEnergieClient: country_prices(country, start_date, end_date, resolution)
    FrankEnergieClient->>FrankEnergieClient: FrankEnergieQuery(date, resolution)
    FrankEnergieClient->>GraphQLAPI: _query(FrankEnergieQuery, headers x-country=country)
    GraphQLAPI-->>FrankEnergieClient: response
    FrankEnergieClient->>MarketPrices: from_country_dict(response, country)
    MarketPrices-->>User: MarketPrices instance
Loading

File-Level Changes

Change Details Files
Generalize BE-specific market prices parsing into a country-agnostic helper and reuse it for BE and FR.
  • Replace MarketPrices.from_be_dict with a new MarketPrices.from_country_dict(data, energy_country) that validates and parses country-scoped payloads
  • Update logging and error messages to be parameterized by energy_country instead of hardcoded BE
  • Implement MarketPrices.from_be_dict as a thin wrapper that delegates to from_country_dict with energy_country set to BE
python_frank_energie/models.py
Introduce a shared country_prices client method with resolution support and add France-specific price retrieval on top of it.
  • Add FrankEnergie.country_prices(country, start_date, end_date, resolution) that sets the x-country header and passes a resolution argument to the marketPrices GraphQL query
  • Extend the marketPrices query to accept a PriceResolution variable and forward it as the resolution field
  • Refactor be_prices to delegate to country_prices with country='BE' and default 15-minute resolution
  • Add fr_prices to delegate to country_prices with country='FR' and default 15-minute resolution
python_frank_energie/frank_energie.py
Make Price robust to null tax and markup fields returned by the API.
  • Change Price.init to use dict.get(...) with a fallback of 0.0 for marketPriceTax, sourcingMarkupPrice, and energyTaxPrice
  • Ensure derived fields market_price_including_tax and market_price_including_tax_and_markup compute correctly when breakdown fields are null
python_frank_energie/models.py
Extend supported country codes to include France (FR).
  • Update the CountryCode Literal type to include 'FR'
  • Add FR to the CountryCode StrEnum used in the domain layer
python_frank_energie/types.py
python_frank_energie/domain.py
Add tests covering FR market price parsing, null tax/markup handling, and resolution-forwarding behavior for x-country marketPrices queries.
  • Add tests for MarketPrices.from_country_dict when called with FR, including both error and happy-path payloads
  • Add a regression test ensuring Price tolerates None for tax/markup fields and computes inclusive prices correctly
  • Add tests verifying fr_prices defaults date handling and sets the x-country: FR header
  • Add tests validating that country_prices defaults to PT15M resolution and forwards an explicitly provided resolution value
tests/test_models.py
tests/test_frank_energie.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@Veldkornet, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 38 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 40b5cd58-d4d7-4fd8-9fab-02b4975351e4

📥 Commits

Reviewing files that changed from the base of the PR and between ca46c35 and 3ccaea5.

📒 Files selected for processing (4)
  • python_frank_energie/frank_energie.py
  • python_frank_energie/models.py
  • tests/test_frank_energie.py
  • tests/test_models.py
📝 Walkthrough

Walkthrough

France support was added to country types and parsing. Market-price retrieval was generalized across countries with configurable resolution, while Belgium and France helpers delegate to the shared method. Nullable price fields now default to zero.

Changes

Country market-price support

Layer / File(s) Summary
Country contracts and payload parsing
python_frank_energie/domain.py, python_frank_energie/types.py, python_frank_energie/models.py, tests/test_models.py
Adds the FR country code, introduces country-scoped market-price parsing and validation, and tests French payloads and errors.
Generic country price queries
python_frank_energie/frank_energie.py, tests/test_frank_energie.py
Adds configurable country and resolution query handling, with be_prices and fr_prices delegating to the shared method.
Nullable price field handling
python_frank_energie/models.py, tests/test_models.py
Treats missing or null tax and markup fields as 0.0 and verifies derived price values.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant FrankEnergie
  participant GraphQLAPI
  participant MarketPrices
  Caller->>FrankEnergie: country_prices(country, dates, resolution)
  FrankEnergie->>GraphQLAPI: Query with country header and variables
  GraphQLAPI-->>FrankEnergie: Country marketPrices payload
  FrankEnergie->>MarketPrices: Parse country payload
  MarketPrices-->>Caller: MarketPrices result
Loading

Possibly related PRs

Suggested labels: enhancement, python

Suggested reviewers: hidiho01, varashi

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding France market support with FR-specific pricing.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - ik heb één issue gevonden en wat algemene feedback achtergelaten:

  • In country_prices/be_prices/fr_prices is het resolution-argument geannoteerd als str | None, maar wordt het altijd doorgegeven als een niet-None PriceResolution-string; overweeg het type aan te scherpen (bijv. naar PriceResolution) en dit in lijn te brengen met de bestaande PriceResolution Literal voor sterkere typecontrole.
  • Het gebruik van data.get(... ) or 0.0 in Price.__init__ zal elke falsy waarde (bijv. 0, lege string) omzetten naar 0.0; als het doel specifiek is om None af te handelen, overweeg dan een expliciete if value is None-controle om te voorkomen dat onverwachte datatypen ongemerkt worden gemaskeerd.
  • De end_date-parameter in country_prices, be_prices en fr_prices wordt geaccepteerd maar niet gebruikt in de queryvariabelen; als deze niet meer nodig is, overweeg dan deze te verwijderen of in de query op te nemen om verwarring bij aanroepende code te voorkomen.
Prompt voor AI-agents
Los alsjeblieft de opmerkingen uit deze code review op:

## Algemene opmerkingen
- In `country_prices`/`be_prices`/`fr_prices` is het `resolution`-argument geannoteerd als `str | None`, maar wordt het altijd doorgegeven als een niet-None `PriceResolution`-string; overweeg het type aan te scherpen (bijv. naar `PriceResolution`) en dit in lijn te brengen met de bestaande `PriceResolution` Literal voor sterkere typecontrole.
- Het gebruik van `data.get(... ) or 0.0` in `Price.__init__` zal elke falsy waarde (bijv. `0`, lege string) omzetten naar `0.0`; als het doel specifiek is om `None` af te handelen, overweeg dan een expliciete `if value is None`-controle om te voorkomen dat onverwachte datatypen ongemerkt worden gemaskeerd.
- De `end_date`-parameter in `country_prices`, `be_prices` en `fr_prices` wordt geaccepteerd maar niet gebruikt in de queryvariabelen; als deze niet meer nodig is, overweeg dan deze te verwijderen of in de query op te nemen om verwarring bij aanroepende code te voorkomen.

## Individuele opmerkingen

### Opmerking 1
<location path="python_frank_energie/frank_energie.py" line_range="2223-2232" />
<code_context>
+        country: str,
+        start_date: date | None = None,
+        end_date: date | None = None,
+        resolution: str | None = "PT15M",
+    ) -> MarketPrices:
+        """Get market prices for a country served via the 'x-country' header (e.g. BE, FR)."""
         if start_date is None:
             start_date = datetime.now(UTC).date()
         if end_date is None:
             end_date = start_date + timedelta(days=1)

-        headers = {"x-country": "BE"}
+        headers = {"x-country": country}

         query = FrankEnergieQuery(
             """
-            query MarketPrices ($date: String!) {
-                marketPrices(date: $date) {
+            query MarketPrices ($date: String!, $resolution: PriceResolution!) {
+                marketPrices(date: $date, resolution: $resolution) {
                     electricityPrices {
</code_context>
<issue_to_address>
**issue (bug_risk):** Het toestaan dat `resolution` `None` is, conflicteert met de niet-null `PriceResolution!` GraphQL-variabele.

Omdat `$resolution` is gedeclareerd als `PriceResolution!`, zal het versturen van `None` een GraphQL-validatiefout veroorzaken. Deze parameter zou niet-optioneel moeten zijn (bijv. `PriceResolution` of `str` zonder `None`), en alle standaardwaardes of normalisatie zouden moeten plaatsvinden vóór het bouwen van de variablenedict.
</issue_to_address>

Sourcery is gratis voor open source - als je onze reviews waardeert, overweeg dan om ze te delen ✨
Help me nuttiger te zijn! Klik alsjeblieft op 👍 of 👎 bij elke opmerking en ik zal de feedback gebruiken om je reviews te verbeteren.
Original comment in English

Hey - I've found 1 issue, and left some high level feedback:

  • In country_prices/be_prices/fr_prices, the resolution argument is annotated as str | None but always passed as a non-None PriceResolution string; consider tightening the type (e.g., to PriceResolution) and aligning with the existing PriceResolution Literal for stronger typing.
  • The use of data.get(... ) or 0.0 in Price.__init__ will coerce any falsy value (e.g., 0, empty string) to 0.0; if the intent is specifically to handle None, consider an explicit if value is None check to avoid inadvertently masking unexpected data types.
  • The end_date parameter in country_prices, be_prices, and fr_prices is accepted but not used in the query variables; if no longer needed, consider removing it or wiring it into the query to avoid confusion for callers.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `country_prices`/`be_prices`/`fr_prices`, the `resolution` argument is annotated as `str | None` but always passed as a non-None `PriceResolution` string; consider tightening the type (e.g., to `PriceResolution`) and aligning with the existing `PriceResolution` Literal for stronger typing.
- The use of `data.get(... ) or 0.0` in `Price.__init__` will coerce any falsy value (e.g., `0`, empty string) to `0.0`; if the intent is specifically to handle `None`, consider an explicit `if value is None` check to avoid inadvertently masking unexpected data types.
- The `end_date` parameter in `country_prices`, `be_prices`, and `fr_prices` is accepted but not used in the query variables; if no longer needed, consider removing it or wiring it into the query to avoid confusion for callers.

## Individual Comments

### Comment 1
<location path="python_frank_energie/frank_energie.py" line_range="2223-2232" />
<code_context>
+        country: str,
+        start_date: date | None = None,
+        end_date: date | None = None,
+        resolution: str | None = "PT15M",
+    ) -> MarketPrices:
+        """Get market prices for a country served via the 'x-country' header (e.g. BE, FR)."""
         if start_date is None:
             start_date = datetime.now(UTC).date()
         if end_date is None:
             end_date = start_date + timedelta(days=1)

-        headers = {"x-country": "BE"}
+        headers = {"x-country": country}

         query = FrankEnergieQuery(
             """
-            query MarketPrices ($date: String!) {
-                marketPrices(date: $date) {
+            query MarketPrices ($date: String!, $resolution: PriceResolution!) {
+                marketPrices(date: $date, resolution: $resolution) {
                     electricityPrices {
</code_context>
<issue_to_address>
**issue (bug_risk):** Allowing `resolution` to be `None` conflicts with the non-null `PriceResolution!` GraphQL variable.

Because `$resolution` is declared as `PriceResolution!`, sending `None` will cause a GraphQL validation error. This parameter should be non-optional (e.g. `PriceResolution` or `str` without `None`), and any defaulting or normalization should happen before building the variables dict.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread python_frank_energie/frank_energie.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
tests/test_models.py (1)

581-586: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use approximate comparisons for floating-point values.

These assertions validate calculated floats; use pytest.approx(...) to avoid brittle equality checks if the calculation or numeric representation changes.

Suggested adjustment
-    assert price.market_price == 0.15423
-    assert price.market_price_tax == 0.0
-    assert price.sourcing_markup_price == 0.0
-    assert price.energy_tax_price == 0.0
-    assert price.market_price_including_tax == 0.15423
-    assert price.market_price_including_tax_and_markup == 0.15423
+    assert price.market_price == pytest.approx(0.15423)
+    assert price.market_price_tax == pytest.approx(0.0)
+    assert price.sourcing_markup_price == pytest.approx(0.0)
+    assert price.energy_tax_price == pytest.approx(0.0)
+    assert price.market_price_including_tax == pytest.approx(0.15423)
+    assert price.market_price_including_tax_and_markup == pytest.approx(0.15423)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_models.py` around lines 581 - 586, Update the floating-point
assertions in the price model test to compare calculated values with
pytest.approx(...), including market_price, market_price_including_tax, and
market_price_including_tax_and_markup, while preserving the expected numeric
values and exact zero checks where appropriate.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@python_frank_energie/frank_energie.py`:
- Around line 2221-2224: Update the market-prices request flow and prices() so a
None resolution is normalized to the existing PT15M default before constructing
GraphQL variables, satisfying the non-null $resolution requirement. Also address
the unused end_date parameter in the surrounding API by wiring it into the
request or removing it consistently.

In `@tests/test_frank_energie.py`:
- Around line 1350-1367: Update test_fr_prices_with_default_dates to compute the
expected date with date.today(), matching the fallback used by fr_prices().
Import date alongside the existing datetime import if needed, and keep the
variables["date"] assertion unchanged.

---

Nitpick comments:
In `@tests/test_models.py`:
- Around line 581-586: Update the floating-point assertions in the price model
test to compare calculated values with pytest.approx(...), including
market_price, market_price_including_tax, and
market_price_including_tax_and_markup, while preserving the expected numeric
values and exact zero checks where appropriate.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e4cdfe6e-92eb-44a7-ac34-f2fb98bb4b42

📥 Commits

Reviewing files that changed from the base of the PR and between 2d06117 and ca46c35.

📒 Files selected for processing (6)
  • python_frank_energie/domain.py
  • python_frank_energie/frank_energie.py
  • python_frank_energie/models.py
  • python_frank_energie/types.py
  • tests/test_frank_energie.py
  • tests/test_models.py

Comment thread python_frank_energie/frank_energie.py
Comment thread tests/test_frank_energie.py
- Tighten resolution parameter on country_prices()/be_prices()/
  fr_prices()/prices() from `str | None` to the non-optional
  `PriceResolution` Literal (default "PT15M"). Sending None would
  violate the GraphQL schema's non-null `$resolution: PriceResolution!`
  and fail server-side instead of catching the mistake locally.
- Replace `data.get(...) or 0.0` with explicit `is None` checks in
  Price.__init__ so a legitimate falsy API value (e.g. an integer 0)
  can't be silently confused with a missing field.
- Use pytest.approx() for float comparisons in the new null-field
  regression test (SonarCloud python:S1244).
@Veldkornet
Veldkornet requested a review from HiDiHo01 July 22, 2026 08:42
SonarCloud flagged end_date (python:S1172) as an unused function
parameter. Confirmed it was genuinely dead, not just unwired: Frank's
marketPrices GraphQL query only accepts a single $date argument.
Live-tested adding an $endDate variable and got back
"Graphql validation error" -- the API has no such field to receive it.

country_prices()/be_prices()/fr_prices() still accept end_date for
signature consistency with their existing be_prices()-derived callers;
only prices() (the one flagged) is changed here.
@sonarqubecloud

Copy link
Copy Markdown

@Veldkornet
Veldkornet merged commit 3314bc4 into HiDiHo01:main Jul 22, 2026
7 checks passed
@Veldkornet
Veldkornet deleted the feat/add-france-support branch July 22, 2026 10:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants