Skip to content

Add BLN3 farm indicators with heatmap table and map view#615

Merged
SvenVw merged 24 commits into
developmentfrom
FDM575
May 15, 2026
Merged

Add BLN3 farm indicators with heatmap table and map view#615
SvenVw merged 24 commits into
developmentfrom
FDM575

Conversation

@SvenVw
Copy link
Copy Markdown
Collaborator

@SvenVw SvenVw commented May 12, 2026

Summary by CodeRabbit

  • New Features
    • Heatmap table of 28 BLN3 indicators with rotated tooltip headers, per-cell score/delta badges, pinned “Knelpunten” row, sticky header/first column, and pin/unpin indicator.
    • Map view rendering fields colored by selected indicator with hover popups, legend, and category-grouped indicator selector chips.
    • Aggregation cards for OBI and BBWP plus controls: category chips, measures toggle, search, and bufferstrip.
    • Built-in BLN3 help dialog explaining indicators and score interpretation.

Review Change Stack

Closes #575

@SvenVw SvenVw self-assigned this May 12, 2026
@sentry
Copy link
Copy Markdown

sentry Bot commented May 12, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 12, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: f35512e1-fc50-4703-a740-1e591383f9a4

📥 Commits

Reviewing files that changed from the base of the PR and between 9af5768 and 5b47ecc.

📒 Files selected for processing (4)
  • fdm-app/app/components/blocks/indicators/table.tsx
  • fdm-app/app/routes/farm.$b_id_farm.$calendar.indicators.atlas.tsx
  • fdm-app/app/routes/farm.tsx
  • fdm-app/app/tailwind.css
✅ Files skipped from review due to trivial changes (2)
  • fdm-app/app/routes/farm.tsx
  • fdm-app/app/tailwind.css
🚧 Files skipped from review as they are similar to previous changes (2)
  • fdm-app/app/routes/farm.$b_id_farm.$calendar.indicators.atlas.tsx
  • fdm-app/app/components/blocks/indicators/table.tsx

📝 Walkthrough

Walkthrough

Adds a BLN3 farm indicators feature: indicator taxonomy and scoring helpers, server-side BLN3 score orchestration, a TanStack heatmap table with painpoints and aggregation cards, a MapLibre atlas view, props-driven controls, route modules, sidebar/header integration, styling, and calculator type/input updates.

Changes

BLN3 Farm Indicators Feature

Layer / File(s) Summary
Indicator taxonomy and scoring helpers
fdm-app/app/lib/indicators.ts
Exports indicator/category types, the 28-indicator catalog and INDICATOR_CATEGORIES, CATEGORY_MAP_PROP, and helpers scoreToDisplay, getScoreTier, getScoreColor, getScoreVerdict, plus lookup helpers.
MapLibre field styling
fdm-app/app/components/blocks/atlas/atlas-styles.tsx
getFieldsScoreStyle and getFieldsScoreOutlineStyle produce MapLibre layer specs coloring fills/outlines by a numeric GeoJSON property (default avgScore), treating -1 as no-data grey.
Score display components
fdm-app/app/components/blocks/indicators/score-badge.tsx, table-cell.tsx, aggregation-card.tsx, bln3-help-dialog.tsx
ScoreBadge, HeatmapCell (circular score with optional +delta badge and tooltip), AggregationCard (OBI/BBWP progress/verdict/delta), and Bln3HelpDialog help dialog.
Filter and control components
fdm-app/app/components/blocks/indicators/category-filter.tsx, measures-toggle.tsx
Controlled, props-driven CategoryFilter chip group and MeasuresToggle switch with activeCategories/onToggle and withMeasures/onToggle contracts.
Heatmap table
fdm-app/app/components/blocks/indicators/table.tsx
HeatmapTable builds grouped columns by category, rotated tooltip headers, renders HeatmapCell per cell, computes per-indicator painpoint counts (score < 40) and optionally renders a pinned "Knelpunten" row; sticky header/first column layout.
MapLibre indicators map
fdm-app/app/components/blocks/indicators/atlas.tsx
IndicatorsMap renders GeoJSON fields colored by selectedProperty, handles hover tooltip (name/area/score/verdict or "Geen data"), restricts interactive layer IDs to score layer, navigates on click, and displays a legend and selector.
Server-side BLN3 score orchestration
fdm-app/app/integrations/bln3.server.ts, fdm-app/app/lib/bln3.ts
getIndicatorsForField and getIndicatorsForFarm compute per-field BLN3 results (resilient concurrent execution, per-field error mapping) and re-export computeFarmAggregation to aggregate farm-level scores by indicator lists and mode (score/index).
Route loaders and pages
fdm-app/app/routes/farm.$b_id_farm.$calendar.indicators.tsx, indicators._index.tsx, indicators.atlas.tsx
Parent layout route provides header and Outlet; index route loads fields and fieldScores, manages UI state (categories/measures/bufferstrip/search), computes OBI/BBWP aggregations client-side and renders aggregation cards + HeatmapTable; atlas route builds simplified GeoJSON with avgScore, per-category and per-indicator props (rounded or -1) and renders map with a property selector and lazy-loaded atlas block.
Navigation integration
fdm-app/app/components/blocks/header/indicators.tsx, fdm-app/app/components/blocks/sidebar/apps.tsx
HeaderIndicators breadcrumb/dropdown toggles between "Tabel" and "Kaart"; SidebarApps adds an "Indicatoren" collapsible with "Tabel" and "Kaart" sub-items, conditional enable/disable, and route-based active/open logic.
Calculator types and input mapping
fdm-calculator/src/bln3/types.d.ts, fdm-calculator/src/bln3/input.ts
Introduces explicit Bln3ScoreCollectedInputs and composes Bln3ScoreInputs = Bln3ScoreCollectedInputs & { nmiApiKey }; updates input mapping to use optional chaining with type assertions for soil/gwl fields.
CSS and documentation
fdm-app/app/tailwind.css, .changeset/green-fields-shine.md, fdm-app/app/routes/farm.tsx
Adds .vertical-header CSS for rotated column headers, a changeset documenting the new indicators table and map pages, and a minor SidebarInset layout prop update.

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant IndicatorsLoader
  participant Bln3Server
  participant Bln3Calculator
  participant UI as "Map/Table UI"

  Browser->>IndicatorsLoader: request /farm/:b_id_farm/:calendar/indicators (loader)
  IndicatorsLoader->>Bln3Server: getIndicatorsForFarm(b_id_farm, timeframe)
  Bln3Server->>Bln3Calculator: getBln3Score(inputs per field)
  Bln3Calculator-->>Bln3Server: Bln3Score (per-field)
  Bln3Server-->>IndicatorsLoader: FieldBln3Score[] (with errors mapped)
  IndicatorsLoader-->>Browser: fields + fieldScores (loader data)
  Browser->>UI: render HeatmapTable / IndicatorsMap with provided data
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

  • nmi-agro/fdm#457 — Related sidebar navigation changes touching sidebar/apps.tsx.
  • nmi-agro/fdm#608 — Related BLN3 scoring exports and integrations used by server orchestration.

Suggested labels

enhancement, fdm-app, fdm-calculator, branch:development

Suggested reviewers

  • BoraIneviNMI
  • gerardhros

Poem

🐰 I hopped across the code today, so green and bright,
A heatmap table, cards that glow, a map with fields in sight,
Chips and toggles, tooltips snug, scores dancing in a line,
From badge to atlas, every field now wears its shine,
Happy rabbit hops — release the signs! 🌱

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 64.52% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed PR implements all core objectives from issue #575: farm indicators route, sidebar integration, map/table views, aggregation cards, category filters, painpoints row, measures toggle, and BLN3 help dialog.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing BLN3 farm indicators feature. No unrelated or scope-creeping modifications detected in components, routes, types, or styling.
Title Check ✅ Passed Title check skipped as CodeRabbit has written the PR title.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch FDM575

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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 and usage tips.

@SvenVw SvenVw changed the base branch from FDM574 to development May 13, 2026 10:07
@SvenVw SvenVw changed the title FDM575 @coderabbitai May 13, 2026
@SvenVw SvenVw marked this pull request as ready for review May 13, 2026 10:07
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 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 `@fdm-app/app/components/blocks/header/indicators.tsx`:
- Around line 17-20: The link-generation is using the calendar from
useCalendarStore (const calendar = useCalendarStore(...)) which can drift from
the active URL; change the logic that builds links (where calendar is referenced
and where isKaart/currentName are used) to read the calendar value from the
route (e.g., useLocation/useParams or parse location.pathname) instead of the
store so generated links reflect the URL's calendar param; update all
occurrences in this file that reference the calendar variable (lines around the
useCalendarStore usage and other link builders between lines 26-45) to derive
calendar from the route before composing the link.

In `@fdm-app/app/components/blocks/indicators/table.tsx`:
- Around line 254-270: The header currently handles pin/unpin via an onClick on
the <th>, which is not keyboard-accessible; instead remove the onClick from the
th and wrap the interactive part in a real <button> (e.g., inside the header
render for header and header.column.id) with type="button" and the same toggle
logic calling onIndicatorClick(selectedIndicatorId === header.column.id ? null :
header.column.id). Move the conditional classes (cursor-pointer,
hover:bg-muted/40, selected bg/ring) and thBase styling as appropriate onto the
button (or apply both th and button classes) and add accessible attributes like
aria-pressed={selectedIndicatorId === header.column.id} and a descriptive
aria-label that includes the column id/title so screen readers can announce
pin/unpin state; ensure the <th> remains non-interactive (no onClick) so
keyboard and screen-reader users can operate the control.

In `@fdm-app/app/components/ui/breadcrumb.tsx`:
- Line 2: Replace the incorrect namespace access SlotPrimitive.Slot with the
Slot component itself by using SlotPrimitive when rendering conditional child
components; specifically update the expression that sets Comp (currently const
Comp = asChild ? SlotPrimitive.Slot : "a") to use const Comp = asChild ?
SlotPrimitive : "a", and make the same fix for other occurrences that use
SlotPrimitive.Slot (e.g., where asChild determines Comp in breadcrumb, sidebar,
button, form, and item components) so the component references the exported Slot
component rather than an undefined property.

In `@fdm-app/app/integrations/bln3.server.ts`:
- Around line 59-68: getIndicatorsForFarm currently always calls getFields
causing duplicated external I/O; modify getIndicatorsForFarm to accept an
optional preloadedFields parameter (e.g., preloadedFields?: Field[]) and only
call getFields(principal_id, b_id_farm, timeframe) when preloadedFields is
undefined, update its return/type handling accordingly, and change the
indicators loaders that already fetch fields to pass their preloaded fields into
getIndicatorsForFarm instead of letting it refetch; ensure you update all
callers and types where getIndicatorsForFarm is invoked and preserve timeframe
handling when deciding to use preloadedFields versus calling getFields.
- Around line 112-120: The current aggregation pushes values from
indicator.score or indicator.index into allValues without validating them, which
can yield null/NaN and cause the average to become NaN; update the block around
indicatorIds/indicator.indicator_id and the assignment const value = mode ===
"score" ? indicator.score : indicator.index to only push numeric, finite values
(e.g., use Number.isFinite(value) or typeof value === "number" && !isNaN(value))
into allValues, and keep the existing guard if (allValues.length === 0) return
null so invalid values are ignored before computing avg with
allValues.reduce(...).
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 2d96e489-3cbb-49c6-b2c1-021c0287c1d9

📥 Commits

Reviewing files that changed from the base of the PR and between 9ee8f2e and 3c9e510.

📒 Files selected for processing (19)
  • .changeset/green-fields-shine.md
  • fdm-app/app/components/blocks/atlas/atlas-styles.tsx
  • fdm-app/app/components/blocks/header/indicators.tsx
  • fdm-app/app/components/blocks/indicators/aggregation-card.tsx
  • fdm-app/app/components/blocks/indicators/atlas.tsx
  • fdm-app/app/components/blocks/indicators/bln3-help-dialog.tsx
  • fdm-app/app/components/blocks/indicators/category-filter.tsx
  • fdm-app/app/components/blocks/indicators/measures-toggle.tsx
  • fdm-app/app/components/blocks/indicators/score-badge.tsx
  • fdm-app/app/components/blocks/indicators/table-cell.tsx
  • fdm-app/app/components/blocks/indicators/table.tsx
  • fdm-app/app/components/blocks/sidebar/apps.tsx
  • fdm-app/app/components/ui/breadcrumb.tsx
  • fdm-app/app/integrations/bln3.server.ts
  • fdm-app/app/lib/indicators.ts
  • fdm-app/app/routes/farm.$b_id_farm.$calendar.indicators._index.tsx
  • fdm-app/app/routes/farm.$b_id_farm.$calendar.indicators.atlas.tsx
  • fdm-app/app/routes/farm.$b_id_farm.$calendar.indicators.tsx
  • fdm-app/app/tailwind.css

Comment thread fdm-app/app/components/blocks/header/indicators.tsx Outdated
Comment thread fdm-app/app/components/blocks/indicators/table.tsx
Comment thread fdm-app/app/components/ui/breadcrumb.tsx Outdated
Comment thread fdm-app/app/integrations/bln3.server.ts Outdated
Comment thread fdm-app/app/integrations/bln3.server.ts Outdated
@coderabbitai coderabbitai Bot changed the title @coderabbitai Add BLN3 farm indicators with heatmap table and map view May 13, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
fdm-app/app/routes/farm.$b_id_farm.$calendar.indicators._index.tsx (1)

208-208: 💤 Low value

Visual feedback for instant transitions may confuse users.

Line 208 applies opacity-50 pointer-events-none when isPending, but if the state updates are near-instant (as suggested by the current implementation), users won't perceive the feedback, or it will flicker distractingly.

Consider removing the transition-based dimming unless profiling confirms that renders are slow enough to warrant this UX pattern.

🤖 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 `@fdm-app/app/routes/farm`.$b_id_farm.$calendar.indicators._index.tsx at line
208, The UI applies instantaneous dimming via the section's className (the JSX
line using cn("space-y-3 transition-opacity duration-150", isPending &&
"opacity-50 pointer-events-none")) which can flicker; either remove the
conditional dimming by eliminating the isPending && "opacity-50
pointer-events-none" clause from that section's className, or implement a short
debounce before showing visual feedback (create a local showPending state that
sets true only after a ~150ms timeout when isPending becomes true and clears
immediately when false) and use showPending in the className instead of
isPending.
🤖 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 `@fdm-app/app/integrations/bln3.server.ts`:
- Around line 50-54: Remove the unnecessary TypeScript assertion when calling
getBln3Score: spread the inputs (typed Bln3ScoreCollectedInputs) and add
nmiApiKey directly so the combined object is inferred as Bln3ScoreInputs by the
compiler; specifically, in the call to getBln3Score in bln3.server.ts replace
the object "{ ...inputs, nmiApiKey } as Bln3ScoreInputs" with just "{ ...inputs,
nmiApiKey }" (no assertion) to let TypeScript infer the correct type from
Bln3ScoreCollectedInputs and Bln3ScoreInputs.

---

Nitpick comments:
In `@fdm-app/app/routes/farm`.$b_id_farm.$calendar.indicators._index.tsx:
- Line 208: The UI applies instantaneous dimming via the section's className
(the JSX line using cn("space-y-3 transition-opacity duration-150", isPending &&
"opacity-50 pointer-events-none")) which can flicker; either remove the
conditional dimming by eliminating the isPending && "opacity-50
pointer-events-none" clause from that section's className, or implement a short
debounce before showing visual feedback (create a local showPending state that
sets true only after a ~150ms timeout when isPending becomes true and clears
immediately when false) and use showPending in the className instead of
isPending.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 7da9e9a2-bf39-4f8e-a312-fb31093022f9

📥 Commits

Reviewing files that changed from the base of the PR and between 3c9e510 and a292d97.

📒 Files selected for processing (11)
  • fdm-app/app/components/blocks/header/indicators.tsx
  • fdm-app/app/components/blocks/indicators/aggregation-card.tsx
  • fdm-app/app/components/blocks/indicators/atlas.tsx
  • fdm-app/app/components/blocks/indicators/category-filter.tsx
  • fdm-app/app/components/blocks/indicators/measures-toggle.tsx
  • fdm-app/app/components/blocks/indicators/table-cell.tsx
  • fdm-app/app/components/blocks/indicators/table.tsx
  • fdm-app/app/integrations/bln3.server.ts
  • fdm-app/app/lib/bln3.ts
  • fdm-app/app/routes/farm.$b_id_farm.$calendar.indicators._index.tsx
  • fdm-app/app/routes/farm.$b_id_farm.$calendar.indicators.atlas.tsx
🚧 Files skipped from review as they are similar to previous changes (5)
  • fdm-app/app/components/blocks/header/indicators.tsx
  • fdm-app/app/components/blocks/indicators/table-cell.tsx
  • fdm-app/app/components/blocks/indicators/aggregation-card.tsx
  • fdm-app/app/components/blocks/indicators/atlas.tsx
  • fdm-app/app/components/blocks/indicators/table.tsx

Comment thread fdm-app/app/integrations/bln3.server.ts
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@fdm-calculator/src/bln3/input.ts`:
- Around line 92-93: The enum fields b_soiltype_agr and b_gwl_class currently
cast latestAnalysis?.b_* directly and can propagate null; restore nullish
normalization by applying a nullish coalescing before the cast (e.g.,
(latestAnalysis?.b_soiltype_agr ?? undefined) as
Bln3ScoreCollectedInputs["b_soiltype_agr"] and similarly for b_gwl_class) so
null values are normalized/omitted and request validation is preserved.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: e62a96ff-3b9d-4880-9547-57d75c47740c

📥 Commits

Reviewing files that changed from the base of the PR and between a292d97 and a034eb3.

📒 Files selected for processing (4)
  • fdm-app/app/integrations/bln3.server.ts
  • fdm-app/app/routes/farm.$b_id_farm.$calendar.indicators._index.tsx
  • fdm-calculator/src/bln3/input.ts
  • fdm-calculator/src/bln3/types.d.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • fdm-app/app/integrations/bln3.server.ts
  • fdm-app/app/routes/farm.$b_id_farm.$calendar.indicators._index.tsx

Comment thread fdm-calculator/src/bln3/input.ts Outdated
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Signed-off-by: Sven Verweij <37927107+SvenVw@users.noreply.github.com>
@SvenVw SvenVw requested a review from BoraIneviNMI May 15, 2026 10:25
Copy link
Copy Markdown
Collaborator

@BoraIneviNMI BoraIneviNMI left a comment

Choose a reason for hiding this comment

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

I haven't seen any big issues. Maybe the indicator select dropdown can be slightly improved in appearance but that is all.

Comment thread fdm-app/app/routes/farm.$b_id_farm.$calendar.indicators.atlas.tsx Outdated
Comment thread fdm-app/app/components/blocks/indicators/table.tsx
@SvenVw SvenVw merged commit 07a254a into development May 15, 2026
13 checks passed
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.

Indicatoren app — farm-level overview

2 participants