Skip to content

Maatregelencatalogus — measure detail pages & enriched catalogue data #614

@SvenVw

Description

@SvenVw

Background

User feedback indicates that advisors and farmers want to better understand the available soil management measures — what each measure entails, what it costs, which indicators it impacts, when it applies, and how it relates to other measures. Currently, the measures catalogue stores only basic fields (name, summary, description, source URL, conflicts). The NMI API provides much richer data (effect costs, applicability conditions, effect type/size, impacted indicators) that is not yet persisted or surfaced in the UI.

This issue addresses three needs:

  1. Enrich the catalogue data pipeline — store the full measure details from the NMI API so the UI has data to show
  2. Provide a standalone Maatregelencatalogus — a browsable, searchable overview of all available measures, accessible from the sidebar
  3. Make measure details accessible everywhere — wherever a measure is shown or selected in the app, users can navigate to its detail page for more information

User Stories

As an advisor, I want to browse the full catalogue of available measures with search and filters so that I can learn what measures exist and find relevant ones without being in the context of a specific field.

As an advisor, I want to see the full details of any measure (description, cost indication, which indicators it impacts, applicability conditions, conflicts) so that I can make informed decisions when selecting measures for fields.

As a farmer, I want a plain-language explanation of each measure so that I understand what it means in practice without needing expert knowledge.

As an advisor, when I see a measure in the add-measure dialog or on a field's active measures list, I want to be able to open its full detail page (in a new tab) without losing my current workflow.

Acceptance Criteria

1. Enriched catalogue data (fdm-data + fdm-core)

fdm-data — API mapping

  • CatalogueMeasureItem type in fdm-data/src/measures/d.ts includes the full NMI API fields: effect_costs, effect_type, impacts_score, applicability, effect_size, input_modified, status, bbwp_id
  • getMeasuresCatalogue() maps all fields from the API response (not just name/summary/description)
  • hashMeasure() includes the new fields in the hash computation so changes are detected on sync

fdm-core — schema extension

  • measures_catalogue table gains the following columns:
    • m_effect_costsnumericCasted(), nullable — cost index (0–15)
    • m_effect_typetext(), nullable — "qual", "quant", or "classed"
    • m_impacts_scoretext().array(), nullable — list of S_* score variables impacted
    • m_applicabilityjsonb(), nullable — applicability conditions array
    • m_effect_sizejsonb(), nullable — effect size definitions (for qual/classed)
    • m_input_modifiedjsonb(), nullable — input modifications (for quant)
    • m_statustext(), nullable — measure status from API (e.g., active, deprecated)
    • m_bbwp_idtext(), nullable — legacy BBWP reference ID
  • Drizzle migration generated and applied
  • syncMeasuresCatalogueArray upserts the new fields
  • MeasureCatalogue type in fdm-core/src/measure.types.ts updated with the new fields
  • getMeasuresFromCatalogue returns the enriched data
  • New function: getMeasureFromCatalogue(fdm, m_id): Promise<MeasureCatalogue> — fetch a single catalogue entry by m_id

Design note — why individual columns, not JSONB

The deferred columns were originally postponed pending a multi-source schema design. Since BLN is the only measure source for the foreseeable future and these fields have clear types (numeric, text, array, jsonb), individual typed columns are preferred over a generic m_metadata jsonb blob. The jsonb type is used only for truly variable-structure data (m_applicability, m_effect_size, m_input_modified). If a future source (e.g., ANLb) needs different fields, nullable columns or a side table can be added then.

2. Maatregelencatalogus overview page (fdm-app)

Route

  • Standalone route: measures-catalogue._index.tsx at /measures-catalogue
  • Accessible from the farm sidebar under "Maatregelen" as a secondary link: "Alle maatregelen bekijken →"
  • Also accessible from the organization sidebar (if org views are present)
  • Page is behind PostHog feature flag "bln3"

Layout & functionality

  • Page title: "Maatregelencatalogus"
  • Search box — fuzzy search (fuzzysort) on measure name, description, summary, and BM code
  • Filter chips — filter by:
    • Effect type: qual / quant / classed (labeled "Kwalitatief" / "Kwantitatief" / "Geclassificeerd")
    • Cost range: low (0–5) / medium (6–10) / high (11–15)
    • Impacted indicator category: derived from m_impacts_score mapping to indicator categories (Bodem, Koolstof, Productie, Grondwater, Nutriënten, Oppervlaktewater)
  • Measures list — each entry shows:
    • BM code (e.g., "BM07") + measure name
    • Summary text (truncated)
    • Cost indicator (visual bar or dots, 0–15 scale)
    • Number of impacted indicators (badge)
    • Effect type badge
    • Link to detail page
  • Sort options: alphabetical (default), cost (ascending/descending), number of impacted indicators
  • Deprecated measures (m_status = deprecated) shown at bottom with visual indication, or behind a "Toon inactieve maatregelen" toggle
  • Results count: "X maatregelen gevonden"

3. Measure detail page (fdm-app)

Route

  • Route: measures-catalogue.$m_id.tsx at /measures-catalogue/:m_id
  • URL uses the m_id value (e.g., /measures-catalogue/bln_BM7) which is URL-safe
  • Page renders for any valid m_id in the catalogue; shows 404 for unknown IDs

Content sections

  • Header: BM code + full measure name, status badge (if deprecated), source badge ("BLN3")
  • Summary: the m_summary text prominently displayed
  • Description: full m_description text
  • Cost indication: visual representation of m_effect_costs (0–15 scale) with label (e.g., "Lage kosten", "Gemiddelde kosten", "Hoge kosten")
  • Effect type: explained in plain language:
    • qual → "Kwalitatief — deze maatregel heeft een direct positief effect op de bodemkwaliteit"
    • quant → "Kwantitatief — deze maatregel wijzigt invoerwaarden voor de berekening"
    • classed → "Geclassificeerd — het effect varieert afhankelijk van perceelcondities"
  • Impacted indicators: table/list mapping m_impacts_score S_* variables to human-readable indicator names (e.g., S_C_N → "Stikstofbeschikbaarheid"), with links to indicator detail pages (when available)
  • Applicability conditions: rendered as a human-readable structured table, not raw JSON:
    • Each condition shows: variable label (e.g., "Grondsoort", "Gewascategorie"), operator, value(s)
    • Variables like B_LU_CULTCAT4, B_SOILTYPE_AGR are mapped to Dutch labels
    • If no applicability conditions: "Deze maatregel is altijd van toepassing"
  • Effect size (for qual/classed): rendered as a table showing which score variables are affected and by how much, with conditions if classed
  • Input modifications (for quant): rendered showing which input variables are modified and how
  • Conflicts: list of conflicting measures with links to their detail pages; if none: "Geen conflicterende maatregelen"
  • Source: link to m_source_url if available, "Bron: NMI BLN3" label, last sync timestamp from updated field
  • Legacy reference: m_bbwp_id shown if present, for backward traceability

4. Measure links throughout the app (fdm-app)

Wherever a measure name/code appears in the app, it should be navigable to the detail page:

5. Indicator variable mapping utility

  • A shared mapping utility that resolves S_* score variable codes and B_* / A_* condition variables to human-readable Dutch labels
  • Used by: measure detail page, Add Measure dialog impact panel, indicator deep-dive
  • Located in a shared location (e.g., fdm-app/app/lib/bln3-labels.ts or fdm-data)
  • Includes at minimum: variable code → Dutch label, variable code → indicator category

Wireframes

Maatregelencatalogus overview

╔══════════════════════════════════════════════════════════════════════╗
║  📋 Maatregelencatalogus                              [BLN3]        ║
╠══════════════════════════════════════════════════════════════════════╣
║  🔍 [Zoek op naam, code of beschrijving...                      ]   ║
║  [Kwalitatief][Kwantitatief][Geclassificeerd] [Kosten ▾] [Thema ▾] ║
║  42 maatregelen gevonden                   Sorteer: [Alfabetisch ▾] ║
╠══════════════════════════════════════════════════════════════════════╣
║  ┌──────────────────────────────────────────────────────────────┐   ║
║  │ BM07  Groenbemester inzaaien                            →   │   ║
║  │ Draagt bij aan organische stof en bodembiodiversiteit       │   ║
║  │ 💰 Laag  │  🎯 5 indicatoren  │  🏷️ Kwalitatief            │   ║
║  ├──────────────────────────────────────────────────────────────┤   ║
║  │ BM12  Niet-kerende grondbewerking                       →   │   ║
║  │ Minimale verstoring van de bodemstructuur                   │   ║
║  │ 💰 Gemiddeld  │  🎯 8 indicatoren  │  🏷️ Kwalitatief       │   ║
║  ├──────────────────────────────────────────────────────────────┤   ║
║  │ BM23  Organische stof aanvoer                           →   │   ║
║  │ Verhoog het OS-gehalte door toevoeging van organisch mat.  │   ║
║  │ 💰 Gemiddeld  │  🎯 4 indicatoren  │  🏷️ Kwantitatief      │   ║
║  └──────────────────────────────────────────────────────────────┘   ║
╚══════════════════════════════════════════════════════════════════════╝

Measure detail page

╔══════════════════════════════════════════════════════════════════════╗
║  ← Terug naar catalogus                                            ║
║  BM07 — Groenbemester inzaaien                    [BLN3] [Actief]  ║
╠══════════════════════════════════════════════════════════════════════╣
║                                                                     ║
║  Draagt bij aan organische stof en bodembiodiversiteit door het     ║
║  inzaaien van een groenbemester na het hoofdgewas.                  ║
║                                                                     ║
║  ┌─────────────────────────────────────────────────────────────┐    ║
║  │ BESCHRIJVING                                                │    ║
║  │ [Full m_description text...]                                │    ║
║  └─────────────────────────────────────────────────────────────┘    ║
║                                                                     ║
║  ┌──────────────┐  ┌───────────────┐  ┌─────────────────────┐      ║
║  │ KOSTEN       │  │ EFFECT TYPE   │  │ BBWP REFERENTIE     │      ║
║  │ 💰💰░░░     │  │ Kwalitatief   │  │ BBWP-BM07           │      ║
║  │ Laag (3/15)  │  │               │  │                     │      ║
║  └──────────────┘  └───────────────┘  └─────────────────────┘      ║
║                                                                     ║
║  ┌─────────────────────────────────────────────────────────────┐    ║
║  │ IMPACT OP INDICATOREN                                       │    ║
║  │ ┌──────────────────────────────┬───────────┬──────────────┐ │    ║
║  │ │ Indicator                    │ Variabele │ Categorie    │ │    ║
║  │ ├──────────────────────────────┼───────────┼──────────────┤ │    ║
║  │ │ Stikstofbeschikbaarheid      │ S_C_N     │ Chemisch     │ │    ║
║  │ │ Microbiele activiteit        │ S_B_SF    │ Biologisch   │ │    ║
║  │ │ Koolstofvastlegging          │ S_C_SEQ   │ Chemisch     │ │    ║
║  │ │ Aggregaatstabiliteit         │ S_P_AS    │ Fysisch      │ │    ║
║  │ │ Ziektewerendheid             │ S_B_DI    │ Biologisch   │ │    ║
║  │ └──────────────────────────────┴───────────┴──────────────┘ │    ║
║  └─────────────────────────────────────────────────────────────┘    ║
║                                                                     ║
║  ┌─────────────────────────────────────────────────────────────┐    ║
║  │ TOEPASBAARHEID                                              │    ║
║  │ ┌──────────────────────┬──────────┬───────────────────────┐ │    ║
║  │ │ Voorwaarde           │ Operator │ Waarde                │ │    ║
║  │ ├──────────────────────┼──────────┼───────────────────────┤ │    ║
║  │ │ Gewascategorie       │ is       │ Akkerbouw, Tuinbouw   │ │    ║
║  │ │ Grondsoort           │ is niet  │ Veen                  │ │    ║
║  │ └──────────────────────┴──────────┴───────────────────────┘ │    ║
║  └─────────────────────────────────────────────────────────────┘    ║
║                                                                     ║
║  ┌─────────────────────────────────────────────────────────────┐    ║
║  │ CONFLICTERENDE MAATREGELEN                                  │    ║
║  │ • BM11 — Kerende grondbewerking minimaliseren          →    │    ║
║  │ • BM34 — Diepe grondbewerking toepassen                →    │    ║
║  └─────────────────────────────────────────────────────────────┘    ║
║                                                                     ║
║  Bron: NMI BLN3  │  🔗 Meer informatie (extern)  │  Laatst bijgewerkt: 2025-04-10  ║
╚══════════════════════════════════════════════════════════════════════╝

Measure link in Add Measure dialog (enhanced)

╠══════════════════════════════════════════════════════╣
║  ┌──────────────────────────────────────┐            ║
║  │ BM07  Groenbemester inzaaien    ℹ️↗  │            ║
║  │       +8 impact op B_SF             │            ║
║  │       💰 laag                       │            ║
║  ├──────────────────────────────────────┤            ║
║       ℹ️ = info icon that shows quick popover       ║
║       ↗ = opens full detail page in new tab         ║

Technical Notes

Route structure

measures-catalogue._index.tsx              — overview with search/filters
measures-catalogue.$m_id.tsx               — detail page for a single measure

These are standalone routes (not farm-scoped) because the catalogue is global. Farm-scoped pages link to them with target="_blank" where appropriate.

Loader pattern

// measures-catalogue._index.tsx
export async function loader({ request }: LoaderFunctionArgs) {
    const fdm = await getFdm(request)
    const catalogue = await getMeasuresFromCatalogue(fdm)
    return { catalogue }
}

// measures-catalogue.$m_id.tsx
export async function loader({ request, params }: LoaderFunctionArgs) {
    const fdm = await getFdm(request)
    const measure = await getMeasureFromCatalogue(fdm, params.m_id!)
    if (!measure) throw new Response("Not Found", { status: 404 })
    return { measure }
}

Variable label mapping

// fdm-app/app/lib/bln3-labels.ts (or fdm-data)

export const scoreVariableLabels: Record<string, { label: string; category: string }> = {
    S_B_DI:  { label: "Ziektewerendheid",               category: "Biologisch" },
    S_B_SF:  { label: "Microbiele activiteit",           category: "Biologisch" },
    S_C_N:   { label: "Stikstofbeschikbaarheid",         category: "Chemisch" },
    S_C_P:   { label: "Fosfaatbeschikbaarheid",          category: "Chemisch" },
    // ... all 28 indicators
}

export const conditionVariableLabels: Record<string, string> = {
    B_SOILTYPE_AGR: "Grondsoort",
    B_LU_CULTCAT4:  "Gewascategorie",
    B_GWL_CLASS:    "Grondwatertrap",
    // ... all condition variables
}

Applicability rendering

The m_applicability JSONB column contains an array of conditions from the NMI API. The renderer should translate each condition into human-readable format:

// Example condition from API:
{ variable: "B_LU_CULTCAT4", operator: "in", values: ["akkerbouw", "tuinbouw"] }

// Rendered as:
// Gewascategorie: akkerbouw, tuinbouw

Conflict ID normalization

Conflicts from the NMI API use raw BLN IDs (e.g., "BM11"). These must be stored as FDM m_id format (e.g., "bln_BM11") for consistency with the m_id primary key. The detail page resolves conflict m_id values to names and links.

Dependencies

Notes

  • The standalone route does not require authentication/authorization beyond basic app login — the catalogue is the same for all users
  • This issue should be implemented after issue-bln3-1 and issue-bln3-2 so the data pipeline exists
  • Deprecated measures (if m_status indicates) should still have accessible detail pages for historical reference, but be visually marked and filtered out of the selection dialog by default
  • The variable label mapping utility is a one-time effort that benefits multiple features (measure details, indicator deep-dive, Add Measure dialog impact panel)
  • The quick preview popover in the Add Measure dialog is a nice-to-have; the core deliverable is the detail page + links

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions