Skip to content

Implement Organic Matter balance calculation system#343

Merged
SvenVw merged 36 commits into
developmentfrom
FDM342
Nov 24, 2025
Merged

Implement Organic Matter balance calculation system#343
SvenVw merged 36 commits into
developmentfrom
FDM342

Conversation

@SvenVw
Copy link
Copy Markdown
Collaborator

@SvenVw SvenVw commented Nov 20, 2025

Summary by CodeRabbit

  • New Features

    • Added an Organic Matter Balance app with farm and field-level calculations, chart visualizations and detailed breakdowns (supply, degradation, per-source items).
    • Navigation updated: Balans now exposes both Stikstof and Organische stof routes and sidebar entries; field-level pages and links added for organic matter.
  • Documentation

    • New documentation page describing the organic matter balance methodology and inputs.

✏️ Tip: You can customize this high-level summary in your review settings.

Closes #342

@SvenVw SvenVw self-assigned this Nov 20, 2025
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Nov 20, 2025

🦋 Changeset detected

Latest commit: e16b86d

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 5 packages
Name Type
@svenvw/fdm-app Minor
@svenvw/fdm-data Minor
@svenvw/fdm-calculator Minor
@svenvw/fdm-docs Minor
@svenvw/fdm-core Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Nov 20, 2025

Walkthrough

Adds an Organic Matter (OM) balance feature end-to-end: new database/catalogue fields for cultivation EOM, calculator modules (input collection, supply, degradation, aggregation, conversion), app UI/pages/components for farm and field OM balances, shared soil/conversion utilities, docs, and changesets.

Changes

Cohort / File(s) Summary
Changesets
.changeset/*.md
Adds multiple changeset entries bumping packages and noting OM balance feature and catalogue parameter additions.
DB schema & core types
fdm-core/src/db/schema.ts, fdm-core/src/cultivation.d.ts, fdm-core/src/cultivation.ts, fdm-core/src/cultivation.test.ts
Adds numeric fields b_lu_eom and b_lu_eom_residues to cultivations_catalogue and propagates them through cultivation types, queries, insertion and tests.
Data layer
fdm-data/src/cultivations/*
Adds b_lu_eom / b_lu_eom_residue to BRP catalogue transform, updates CatalogueCultivationItem typings and test fixtures/hashes.
Calculator — types & exports
fdm-calculator/src/balance/organic-matter/types.d.ts, fdm-calculator/src/index.ts
Adds comprehensive OM balance types and exports calculate/get/collect functions.
Calculator — input collection
fdm-calculator/src/balance/organic-matter/input.ts, .../input.test.ts
Adds collectInputForOrganicMatterBalance to gather fields, cultivations, soil analyses, fertilizers, and catalogue details within a transaction; tests added.
Calculator — supply calculations
fdm-calculator/src/balance/organic-matter/supply/*
Implements supply aggregator and subcalculations for fertilizers, cultivations, residues with unit tests.
Calculator — degradation
fdm-calculator/src/balance/organic-matter/degradation.ts, .../degradation.test.ts
Implements SOM degradation formula (depth by land use, temperature correction, cap, timeframe scaling) and tests.
Calculator — core orchestration
fdm-calculator/src/balance/organic-matter/index.ts, .../index.test.ts
Implements field-level balance, farm aggregation (area-weighted), batching, error aggregation, cached getter and tests.
Calculator — shared utilities & nitrogen refactor
fdm-calculator/src/balance/shared/{soil,conversion}.ts, .../soil.test.ts, .../conversion.ts, balance/nitrogen/index.ts
Adds combineSoilAnalyses and Decimal→number conversion utilities; nitrogen module refactored to reuse these utilities and tests adjusted.
Calculator — supply/residues tests
fdm-calculator/src/balance/organic-matter/supply/*.{test,ts}
Adds unit tests covering fertilizer, cultivation, residues supply and aggregator error paths.
App — UI components
fdm-app/app/components/blocks/balance/organic-matter-chart.tsx, .../organic-matter-details.tsx
Adds chart and detailed accordion components for OM balance display.
App — navigation & sidebar
fdm-app/app/components/blocks/header/balance.tsx, .../sidebar/apps.tsx
Adds Balans group with sub-items for Stikstof and Organische stof; updates header and sidebar routing/link behavior.
App — routes/pages
fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.*
Adds organic-matter parent route, farm overview, and per-field detail routes with loaders, error handling, Suspense fallbacks and UI rendering. Minor nitrogen route header change.
App — additional UI fixes
fdm-app/app/components/blocks/sidebar/{farm,platform}.tsx, .changeset/clear-yaks-create.md
Adds isActive handling to sidebar buttons and related changesets.
Docs
fdm-docs/docs/insights/balance/02-organic-matter.md, fdm-docs/docs/insights/balance/_category_.json, fdm-docs/docs/README.md
Adds OM balance documentation, category file and README link update.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant App as fdm-app
    participant Route as Balance Route
    participant Core as fdm-core
    participant Calc as fdm-calculator
    participant DB as Database

    User->>App: open OM balance page
    App->>Route: route loader
    Route->>Core: request farm & fields
    Core->>DB: query farm/fields
    DB-->>Core: farm/fields
    Core-->>Route: field list

    Route->>Calc: collectInputForOrganicMatterBalance
    Calc->>Core: fetch cultivations, soil analyses, fertilizers (per-field)
    Core->>DB: queries
    DB-->>Core: data
    Core-->>Calc: assembled input

    Route->>Calc: calculateOrganicMatterBalance(input)
    loop batch per 50 fields
      Calc->>Calc: calculateOrganicMatterBalanceField
      Calc->>Calc: calculateOrganicMatterSupply (fertilizers/cultivations/residues)
      Calc->>Calc: calculateOrganicMatterDegradation (soil analysis → annual & timeframe)
      Calc->>Calc: balance = supply + degradation
    end
    Calc->>Calc: aggregate area-weighted farm result
    Calc->>Calc: convertOrganicMatterBalanceToNumeric
    Calc-->>Route: numeric result

    Route-->>App: render overview (stats, chart, fields)
    App-->>User: display
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Areas to pay extra attention:

  • fdm-calculator/src/balance/organic-matter/degradation.ts (formula, caps, units)
  • fdm-calculator/src/balance/shared/soil.ts (combine/estimate soil params and validation)
  • Field-to-farm aggregation & error propagation in organic-matter/index.ts
  • DB schema additions in fdm-core/src/db/schema.ts (migration impact, numericCasted usage)
  • App route loaders and Suspense/error flows in fdm-app routes

Possibly related PRs

Suggested reviewers

  • gerardhros
  • BoraIneviNMI

Poem

🐇
I hop through soil and tally the yield,
From furrow to field, each bale in the field,
Supply meets decay in a gentle ballet,
Numbers and charts show the earthy display,
A carrot-sized cheer for balance today! 🌱

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Implement Organic Matter balance calculation system' accurately summarizes the main change: adding a complete organic matter balance feature.
Linked Issues check ✅ Passed The PR successfully implements all major coding requirements from issue #342: data layer updates (b_lu_eom fields), calculator module with balance/supply/degradation logic, UI pages for farm and field views, and documentation.
Out of Scope Changes check ✅ Passed All changes directly support organic matter balance implementation. Minor sidebar active-state fixes are closely related UI improvements. No unrelated or scope-creeping changes detected.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch FDM342

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b176f32 and e16b86d.

📒 Files selected for processing (4)
  • .changeset/clear-yaks-create.md (1 hunks)
  • fdm-app/app/components/blocks/sidebar/apps.tsx (6 hunks)
  • fdm-app/app/components/blocks/sidebar/farm.tsx (4 hunks)
  • fdm-app/app/components/blocks/sidebar/platform.tsx (3 hunks)
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 343
File: fdm-calculator/src/balance/organic-matter/types.d.ts:12-132
Timestamp: 2025-11-21T10:02:25.556Z
Learning: In the organic matter balance calculation system (fdm-calculator), degradation values are negative by definition. This means the balance calculation using supply.total.plus(degradation.total) is correct, as it effectively computes supply - |degradation|. This follows the same pattern as the nitrogen balance system.
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 343
File: fdm-app/app/components/blocks/balance/organic-matter-chart.tsx:19-25
Timestamp: 2025-11-24T10:43:09.278Z
Learning: In the organic matter balance chart (fdm-app), degradation values are multiplied by -1 before plotting to show supply and degradation bars side-by-side with positive magnitudes, enabling direct visual comparison. This is a deliberate design choice for the organic matter chart visualization, different from the nitrogen balance chart which uses separate stacks.
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 343
File: fdm-calculator/src/balance/organic-matter/types.d.ts:12-132
Timestamp: 2025-11-21T10:02:25.556Z
Learning: In the organic matter balance calculations, EOM (Effective Organic Matter) and OM (Organic Matter) are the same units and can be used together in calculations. EOM refers to the organic matter that remains in the soil after one year.
📚 Learning: 2025-01-09T16:03:37.764Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 42
File: fdm-app/app/routes/farm/_b_id_farm/layout.tsx:46-95
Timestamp: 2025-01-09T16:03:37.764Z
Learning: A shared layout component `FarmLayoutBase` has been created in `components/custom/farm-layout-base.tsx` to maintain consistency across farm-related pages. The component handles farm selection dropdown, breadcrumb navigation, and provides a common layout structure.

Applied to files:

  • fdm-app/app/components/blocks/sidebar/apps.tsx
  • fdm-app/app/components/blocks/sidebar/farm.tsx
📚 Learning: 2025-01-09T16:03:37.764Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 42
File: fdm-app/app/routes/farm/_b_id_farm/layout.tsx:46-95
Timestamp: 2025-01-09T16:03:37.764Z
Learning: The `FarmLayout` component in `components/custom/farm-layout.tsx` provides a reusable layout structure for farm-related pages, with support for farm selection dropdown, customizable breadcrumb titles, and flexible content rendering through either children or Outlet components.

Applied to files:

  • fdm-app/app/components/blocks/sidebar/apps.tsx
  • fdm-app/app/components/blocks/sidebar/farm.tsx
📚 Learning: 2025-09-23T12:29:34.184Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 274
File: fdm-app/app/routes/farm.$b_id_farm._index.tsx:160-163
Timestamp: 2025-09-23T12:29:34.184Z
Learning: In the FDM application, the fertilizer application route intentionally uses `${calendar}/field/fertilizer` instead of the originally planned `/farm/{farmId}/add/fertilizer` structure. This design decision prioritizes starting from the field list view to provide better field selection workflow before applying fertilizer, rather than direct dashboard-to-action navigation.

Applied to files:

  • fdm-app/app/components/blocks/sidebar/apps.tsx
📚 Learning: 2025-01-09T16:03:37.764Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 42
File: fdm-app/app/routes/farm/_b_id_farm/layout.tsx:46-95
Timestamp: 2025-01-09T16:03:37.764Z
Learning: A comprehensive farm layout system has been created in `components/custom/farm-layouts/` with `BaseFarmLayout` and `FarmSidebarLayout` components. The system supports both simple and sidebar-based layouts while maintaining consistent header and farm selection functionality across all farm routes.

Applied to files:

  • fdm-app/app/components/blocks/sidebar/farm.tsx
📚 Learning: 2025-01-09T16:03:37.764Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 42
File: fdm-app/app/routes/farm/_b_id_farm/layout.tsx:46-95
Timestamp: 2025-01-09T16:03:37.764Z
Learning: The farm layout system has been reorganized into separate components (`FarmHeader`, `ContentLayout`, `PaginationLayout`) to support different navigation patterns (sidebar, pagination) while maintaining consistent styling. Each layout component is designed to be used independently or combined as needed.

Applied to files:

  • fdm-app/app/components/blocks/sidebar/farm.tsx
📚 Learning: 2025-06-02T10:31:27.097Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 151
File: fdm-app/app/routes/signin._index.tsx:101-101
Timestamp: 2025-06-02T10:31:27.097Z
Learning: In fdm-app/app/routes/signin._index.tsx, the redirect destinations are intentionally inconsistent by design: the component defaults new sign-ins to "/welcome" (line 101) while the loader redirects authenticated users to "/farm" (line 80) and the action uses "/farm" as fallback (line 434). This creates appropriate user flows where new users complete their profile via the welcome page, while existing authenticated users bypass it and go directly to the main application.

Applied to files:

  • fdm-app/app/components/blocks/sidebar/farm.tsx
🔇 Additional comments (5)
.changeset/clear-yaks-create.md (1)

1-5: LGTM!

The changeset clearly documents the sidebar active state behavior enhancement.

fdm-app/app/components/blocks/sidebar/farm.tsx (2)

93-106: LGTM!

The farm link active state logic correctly handles both the main farm page and the settings subpage, ensuring the sidebar item remains highlighted in both contexts.


196-206: LGTM!

The active state handling for fields, rotation, and fertilizers links is consistent and properly checks pathname inclusion.

fdm-app/app/components/blocks/sidebar/apps.tsx (2)

49-56: Previous issue resolved: nitrogen balance path is correct.

The nitrogen balance link now correctly points to /balance/nitrogen (line 53), addressing the previous review feedback about avoiding the redirect from /balance.


112-178: LGTM! Collapsible balance navigation implemented correctly.

The collapsible "Balans" section properly implements:

  • Consistent use of SidebarMenuSubButton for both nitrogen and OM sub-items (previously flagged issue is now resolved)
  • Appropriate Plus/Minus icon toggling based on collapsible state
  • Proper active state handling for both sub-items using pathname inclusion checks
  • Correct conditional rendering with disabled state fallbacks

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.

@codecov
Copy link
Copy Markdown

codecov Bot commented Nov 20, 2025

Codecov Report

❌ Patch coverage is 84.23237% with 38 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.34%. Comparing base (24f1cbb) to head (e16b86d).
⚠️ Report is 37 commits behind head on development.

Files with missing lines Patch % Lines
fdm-calculator/src/balance/organic-matter/index.ts 55.93% 26 Missing ⚠️
fdm-calculator/src/balance/shared/soil.ts 82.50% 7 Missing ⚠️
...lculator/src/balance/organic-matter/degradation.ts 87.50% 3 Missing ⚠️
...ator/src/balance/organic-matter/supply/residues.ts 94.73% 1 Missing ⚠️
fdm-calculator/src/balance/shared/conversion.ts 96.15% 1 Missing ⚠️
Additional details and impacted files
@@               Coverage Diff               @@
##           development     #343      +/-   ##
===============================================
- Coverage        87.70%   87.34%   -0.36%     
===============================================
  Files               70       79       +9     
  Lines             3675     3872     +197     
  Branches          1045     1099      +54     
===============================================
+ Hits              3223     3382     +159     
- Misses             452      490      +38     
Flag Coverage Δ
fdm-calculator 87.25% <84.23%> (-0.86%) ⬇️
fdm-core 87.03% <ø> (ø)
fdm-data 92.12% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@SvenVw SvenVw changed the title FDM342 @coderabbitai Nov 21, 2025
@SvenVw SvenVw marked this pull request as ready for review November 21, 2025 09:37
@coderabbitai coderabbitai Bot changed the title @coderabbitai Implement Organic Matter balance calculation system Nov 21, 2025
@coderabbitai coderabbitai Bot added branch:development Issue only affecting development, not the main branch (yet) enhancement New feature or request fdm-app fdm-calculator fdm-core labels Nov 21, 2025
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: 0

♻️ Duplicate comments (2)
fdm-docs/docs/insights/balance/02-organic-matter.md (2)

94-94: Fix temperature constant to match implementation.

The documentation states "currently a constant of 11.7°C is used", but the implementation in degradation.ts (line 54) uses 9.3°C.

Apply this diff:

-    3. **Temperature Correction:** A correction factor is calculated to adjust the degradation rate based on the average yearly temperature (currently a constant of 11.7°C is used).
+    3. **Temperature Correction:** A correction factor is calculated to adjust the degradation rate based on the average yearly temperature (currently a constant of 9.3°C is used).

99-111: Fix markdown formatting and list structure.

Multiple formatting issues remain:

  1. Line 103: Heading is indented with spaces (violates MD023)
  2. Lines 105-106: Bullets are inconsistently indented (violates MD007)
  3. Line 99: Step 6 appears nested under step 5

Apply this diff to fix:

     5. **Capping:** The calculated annual degradation is capped at a maximum of `3500` kg OM/ha/year to prevent unrealistic values.
-        6. **Total Degradation:** The annual degradation rate is multiplied by the number of years in the calculation `Timeframe` to get the total degradation for the period. The result of this calculation is then multiplied by -1 to represent a loss from the system.
+    6. **Total Degradation:** The annual degradation rate is multiplied by the number of years in the calculation `Timeframe` to get the total degradation for the period. The result of this calculation is then multiplied by -1 to represent a loss from the system.
     
     *Note: The degradation formula is based on models developed for specific regions (e.g., Flanders, Belgium). The dimensional consistency of the empirical formula should be considered within the context of its source.*
-    
-  ## 4. Field and Farm Level Balance
-  
-  * **Field Balance:** For each field, the balance is calculated as:
-        `OM Balance = Total EOM Supply + Total OM Degradation`
-* **Farm Balance:**
+
+## 4. Field and Farm Level Balance
+
+- **Field Balance:** For each field, the balance is calculated as:  
+  `OM Balance = Total EOM Supply + Total OM Degradation`
+- **Farm Balance:**
     1. The total EOM supply and OM degradation for each field are weighted by the field's area (e.g., `supply_kg_per_field = supply_kg_per_ha * area_ha`).
     2. These weighted totals are summed across all fields that were successfully calculated.
     3. The total farm supply and degradation (in kg) are then divided by the total area of the calculated fields to provide an average farm-level balance in kg/ha.

This fixes heading indentation, aligns list markers, and corrects step 6 nesting.

🧹 Nitpick comments (1)
fdm-calculator/src/balance/organic-matter/degradation.ts (1)

61-63: Consider removing redundant null check.

Lines 61-63 check for null a_som_loi and a_density_sa, but these are already checked and return early at lines 38-40. This is likely defensive programming, but it's redundant since no intervening code can nullify these values.

If you prefer to keep this as a defensive safeguard, consider adding a comment explaining it's intentional:

+    // Defensive check: ensure soil properties are still present after temperature calculation
     if (soilAnalysis.a_som_loi == null || soilAnalysis.a_density_sa == null) {
         throw new Error(`"Soil analysis data (SOM or bulk density) is missing."`)
     }

Alternatively, remove the redundant check:

-    if (soilAnalysis.a_som_loi == null || soilAnalysis.a_density_sa == null) {
-        throw new Error(`"Soil analysis data (SOM or bulk density) is missing."`)
-    }
-
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bbf9eb6 and b176f32.

📒 Files selected for processing (14)
  • .changeset/slimy-parks-shine.md (1 hunks)
  • fdm-app/app/components/blocks/balance/organic-matter-details.tsx (1 hunks)
  • fdm-app/app/components/blocks/sidebar/apps.tsx (4 hunks)
  • fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.organic-matter.$b_id.tsx (1 hunks)
  • fdm-calculator/src/balance/organic-matter/degradation.test.ts (1 hunks)
  • fdm-calculator/src/balance/organic-matter/degradation.ts (1 hunks)
  • fdm-calculator/src/balance/organic-matter/index.test.ts (1 hunks)
  • fdm-calculator/src/balance/organic-matter/supply/fertilizers.ts (1 hunks)
  • fdm-calculator/src/balance/organic-matter/supply/residues.ts (1 hunks)
  • fdm-calculator/src/balance/organic-matter/types.d.ts (1 hunks)
  • fdm-calculator/src/balance/shared/soil.ts (1 hunks)
  • fdm-docs/docs/README.md (1 hunks)
  • fdm-docs/docs/insights/balance/02-organic-matter.md (1 hunks)
  • fdm-docs/docs/insights/balance/_category_.json (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • fdm-docs/docs/insights/balance/category.json
🚧 Files skipped from review as they are similar to previous changes (5)
  • fdm-calculator/src/balance/organic-matter/supply/fertilizers.ts
  • fdm-calculator/src/balance/organic-matter/supply/residues.ts
  • fdm-app/app/components/blocks/balance/organic-matter-details.tsx
  • .changeset/slimy-parks-shine.md
  • fdm-calculator/src/balance/organic-matter/degradation.test.ts
🧰 Additional context used
🧠 Learnings (22)
📓 Common learnings
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 343
File: fdm-calculator/src/balance/organic-matter/types.d.ts:12-132
Timestamp: 2025-11-21T10:02:25.491Z
Learning: In the organic matter balance calculation system (fdm-calculator), degradation values are negative by definition. This means the balance calculation using supply.total.plus(degradation.total) is correct, as it effectively computes supply - |degradation|. This follows the same pattern as the nitrogen balance system.
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 343
File: fdm-app/app/components/blocks/balance/organic-matter-chart.tsx:19-25
Timestamp: 2025-11-24T10:43:09.262Z
Learning: In the organic matter balance chart (fdm-app), degradation values are multiplied by -1 before plotting to show supply and degradation bars side-by-side with positive magnitudes, enabling direct visual comparison. This is a deliberate design choice for the organic matter chart visualization, different from the nitrogen balance chart which uses separate stacks.
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 0
File: :0-0
Timestamp: 2025-08-13T10:33:05.313Z
Learning: In the fdm project, fdm-calculator integration for new features like b_lu_variety is handled in separate updates from the core data model changes. When fdm-core functions are updated to support new fields, fdm-calculator can consume these enhanced APIs without requiring changes in the same PR that introduces the core functionality.
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 343
File: fdm-calculator/src/balance/organic-matter/types.d.ts:12-132
Timestamp: 2025-11-21T10:02:25.491Z
Learning: In the organic matter balance calculations, EOM (Effective Organic Matter) and OM (Organic Matter) are the same units and can be used together in calculations. EOM refers to the organic matter that remains in the soil after one year.
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 274
File: fdm-app/app/routes/farm.$b_id_farm.$calendar.field._index.tsx:113-148
Timestamp: 2025-09-23T12:37:58.711Z
Learning: In the FDM application, the current field data fetching implementation using Promise.all with individual API calls (getCultivations, getFertilizerApplications, getCurrentSoilData) performs acceptably even with farms containing 90+ fields. No performance issues have been observed in practice with this approach.
📚 Learning: 2024-11-27T12:15:36.425Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 9
File: fdm-data/src/cultivations/index.test.ts:57-59
Timestamp: 2024-11-27T12:15:36.425Z
Learning: In `fdm-data/src/cultivations/index.test.ts`, the `fdm` object created by `drizzle` does not have an `.end()` method. Cleanup code should not attempt to call `fdm.end();`.

Applied to files:

  • fdm-calculator/src/balance/organic-matter/index.test.ts
📚 Learning: 2025-08-11T11:55:26.053Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 233
File: fdm-app/app/integrations/nmi.ts:54-0
Timestamp: 2025-08-11T11:55:26.053Z
Learning: The NMI API Estimates endpoint (`https://api.nmi-agro.nl/estimates`) always returns the fields `b_gwl_ghg`, `b_gwl_glg`, and `cultivations` according to its specification. These fields should be kept as required (not optional) in the TypeScript return type and Zod validation schema in `fdm-app/app/integrations/nmi.ts`.

Applied to files:

  • fdm-calculator/src/balance/organic-matter/index.test.ts
  • fdm-calculator/src/balance/shared/soil.ts
  • fdm-calculator/src/balance/organic-matter/degradation.ts
  • fdm-calculator/src/balance/organic-matter/types.d.ts
📚 Learning: 2025-02-13T09:03:11.890Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 71
File: fdm-app/app/routes/farm.create.$b_id_farm.cultivations.$b_lu_catalogue.crop.harvest._index.tsx:111-135
Timestamp: 2025-02-13T09:03:11.890Z
Learning: When adding multiple harvests in fdm-app, use Promise.all instead of Promise.allSettled to ensure atomic behavior - if one harvest addition fails, all should fail and rollback to maintain data consistency.

Applied to files:

  • fdm-calculator/src/balance/organic-matter/index.test.ts
📚 Learning: 2025-11-21T10:02:25.491Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 343
File: fdm-calculator/src/balance/organic-matter/types.d.ts:12-132
Timestamp: 2025-11-21T10:02:25.491Z
Learning: In the organic matter balance calculation system (fdm-calculator), degradation values are negative by definition. This means the balance calculation using supply.total.plus(degradation.total) is correct, as it effectively computes supply - |degradation|. This follows the same pattern as the nitrogen balance system.

Applied to files:

  • fdm-calculator/src/balance/organic-matter/index.test.ts
  • fdm-docs/docs/insights/balance/02-organic-matter.md
  • fdm-docs/docs/README.md
  • fdm-calculator/src/balance/organic-matter/degradation.ts
  • fdm-calculator/src/balance/organic-matter/types.d.ts
📚 Learning: 2025-11-24T10:43:09.262Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 343
File: fdm-app/app/components/blocks/balance/organic-matter-chart.tsx:19-25
Timestamp: 2025-11-24T10:43:09.262Z
Learning: In the organic matter balance chart (fdm-app), degradation values are multiplied by -1 before plotting to show supply and degradation bars side-by-side with positive magnitudes, enabling direct visual comparison. This is a deliberate design choice for the organic matter chart visualization, different from the nitrogen balance chart which uses separate stacks.

Applied to files:

  • fdm-calculator/src/balance/organic-matter/index.test.ts
  • fdm-docs/docs/insights/balance/02-organic-matter.md
  • fdm-calculator/src/balance/organic-matter/degradation.ts
  • fdm-calculator/src/balance/organic-matter/types.d.ts
📚 Learning: 2025-03-06T15:23:48.352Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 95
File: fdm-core/src/catalogues.ts:134-170
Timestamp: 2025-03-06T15:23:48.352Z
Learning: When writing tests for fdm-core, avoid using Vitest's `vi` mocking utilities and prefer manual JavaScript mocks.

Applied to files:

  • fdm-calculator/src/balance/organic-matter/index.test.ts
📚 Learning: 2025-03-06T14:58:48.603Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 95
File: fdm-core/src/cultivation.ts:67-73
Timestamp: 2025-03-06T14:58:48.603Z
Learning: When writing unit tests for the fdm project, avoid using Vitest's mocking utilities (vi) as it has caused problems in the past not related to the actual code. Instead, use simple object literals with methods that throw errors to test error handling.

Applied to files:

  • fdm-calculator/src/balance/organic-matter/index.test.ts
📚 Learning: 2025-11-21T10:02:25.491Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 343
File: fdm-calculator/src/balance/organic-matter/types.d.ts:12-132
Timestamp: 2025-11-21T10:02:25.491Z
Learning: In the organic matter balance calculations, EOM (Effective Organic Matter) and OM (Organic Matter) are the same units and can be used together in calculations. EOM refers to the organic matter that remains in the soil after one year.

Applied to files:

  • fdm-docs/docs/insights/balance/02-organic-matter.md
  • fdm-calculator/src/balance/organic-matter/types.d.ts
📚 Learning: 2025-05-26T10:32:00.674Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 134
File: fdm-calculator/src/balance/nitrogen/index.ts:162-168
Timestamp: 2025-05-26T10:32:00.674Z
Learning: In the nitrogen balance calculation system (fdm-calculator), removal and volatilization values are negative by definition. This means the balance calculation using supply.total.add(removal.total).add(volatilization.total) is correct, as it effectively computes supply - |removal| - |volatilization|.

Applied to files:

  • fdm-docs/docs/insights/balance/02-organic-matter.md
  • fdm-calculator/src/balance/organic-matter/types.d.ts
📚 Learning: 2025-08-14T14:31:55.384Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 236
File: fdm-calculator/src/balance/nitrogen/index.ts:173-0
Timestamp: 2025-08-14T14:31:55.384Z
Learning: In nitrogen balance calculations for agricultural systems, the balance should only include ammonia emissions (emission.ammonia.total) and should not include nitrate leaching from the emission calculation. The nitrate component (emission.nitrate) should be excluded from the balance formula.

Applied to files:

  • fdm-docs/docs/insights/balance/02-organic-matter.md
  • fdm-docs/docs/README.md
📚 Learning: 2025-01-09T16:03:37.764Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 42
File: fdm-app/app/routes/farm/_b_id_farm/layout.tsx:46-95
Timestamp: 2025-01-09T16:03:37.764Z
Learning: The `FarmLayout` component in `components/custom/farm-layout.tsx` provides a reusable layout structure for farm-related pages, with support for farm selection dropdown, customizable breadcrumb titles, and flexible content rendering through either children or Outlet components.

Applied to files:

  • fdm-app/app/components/blocks/sidebar/apps.tsx
  • fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.organic-matter.$b_id.tsx
📚 Learning: 2025-01-09T16:03:37.764Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 42
File: fdm-app/app/routes/farm/_b_id_farm/layout.tsx:46-95
Timestamp: 2025-01-09T16:03:37.764Z
Learning: A shared layout component `FarmLayoutBase` has been created in `components/custom/farm-layout-base.tsx` to maintain consistency across farm-related pages. The component handles farm selection dropdown, breadcrumb navigation, and provides a common layout structure.

Applied to files:

  • fdm-app/app/components/blocks/sidebar/apps.tsx
  • fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.organic-matter.$b_id.tsx
📚 Learning: 2025-09-23T12:29:34.184Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 274
File: fdm-app/app/routes/farm.$b_id_farm._index.tsx:160-163
Timestamp: 2025-09-23T12:29:34.184Z
Learning: In the FDM application, the fertilizer application route intentionally uses `${calendar}/field/fertilizer` instead of the originally planned `/farm/{farmId}/add/fertilizer` structure. This design decision prioritizes starting from the field list view to provide better field selection workflow before applying fertilizer, rather than direct dashboard-to-action navigation.

Applied to files:

  • fdm-app/app/components/blocks/sidebar/apps.tsx
  • fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.organic-matter.$b_id.tsx
  • fdm-docs/docs/README.md
📚 Learning: 2025-01-09T16:03:37.764Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 42
File: fdm-app/app/routes/farm/_b_id_farm/layout.tsx:46-95
Timestamp: 2025-01-09T16:03:37.764Z
Learning: A comprehensive farm layout system has been created in `components/custom/farm-layouts/` with `BaseFarmLayout` and `FarmSidebarLayout` components. The system supports both simple and sidebar-based layouts while maintaining consistent header and farm selection functionality across all farm routes.

Applied to files:

  • fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.organic-matter.$b_id.tsx
📚 Learning: 2025-06-02T10:31:27.097Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 151
File: fdm-app/app/routes/signin._index.tsx:101-101
Timestamp: 2025-06-02T10:31:27.097Z
Learning: In fdm-app/app/routes/signin._index.tsx, the redirect destinations are intentionally inconsistent by design: the component defaults new sign-ins to "/welcome" (line 101) while the loader redirects authenticated users to "/farm" (line 80) and the action uses "/farm" as fallback (line 434). This creates appropriate user flows where new users complete their profile via the welcome page, while existing authenticated users bypass it and go directly to the main application.

Applied to files:

  • fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.organic-matter.$b_id.tsx
📚 Learning: 2024-12-19T13:20:44.152Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 23
File: fdm-app/app/routes/app.addfarm.new.tsx:15-17
Timestamp: 2024-12-19T13:20:44.152Z
Learning: Authentication for the “app.addfarm.new” route is already handled globally in “fdm-app/app/routes/app.tsx,” automatically redirecting unauthenticated users to the SignIn page.

Applied to files:

  • fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.organic-matter.$b_id.tsx
📚 Learning: 2025-09-23T12:27:07.391Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 274
File: fdm-app/app/routes/farm.$b_id_farm._index.tsx:151-204
Timestamp: 2025-09-23T12:27:07.391Z
Learning: In the FDM application, field overview functionality is implemented as a dedicated page accessible via `farm/{farmId}/{calendar}/field` rather than as a direct listing on the dashboard. The dashboard includes a "Perceelsoverzicht" quick action card that provides navigation to this comprehensive field management interface.

Applied to files:

  • fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.organic-matter.$b_id.tsx
📚 Learning: 2025-03-04T10:56:35.540Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 88
File: fdm-calculator/src/doses/calculate-dose.ts:18-18
Timestamp: 2025-03-04T10:56:35.540Z
Learning: In the FDM calculator, fertilizer nutrient rates (p_n_rt, p_p_rt, p_k_rt) are measured in g/kg, and are converted to fractions by dividing by 10 during dose calculations.

Applied to files:

  • fdm-docs/docs/README.md
📚 Learning: 2025-08-13T10:33:05.313Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 0
File: :0-0
Timestamp: 2025-08-13T10:33:05.313Z
Learning: In the fdm project, fdm-calculator integration for new features like b_lu_variety is handled in separate updates from the core data model changes. When fdm-core functions are updated to support new fields, fdm-calculator can consume these enhanced APIs without requiring changes in the same PR that introduces the core functionality.

Applied to files:

  • fdm-docs/docs/README.md
  • fdm-calculator/src/balance/organic-matter/types.d.ts
📚 Learning: 2025-07-21T12:06:07.070Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 156
File: fdm-calculator/src/norms/nl/2025/stikstofgebruiksnorm.ts:295-303
Timestamp: 2025-07-21T12:06:07.070Z
Learning: Functions in the fdm-calculator with "NL2025" in their names are specifically designed for Netherlands 2025 agricultural norms calculation and hardcoded 2025 dates are appropriate in this context, as different years would have separate calculation modules.

Applied to files:

  • fdm-docs/docs/README.md
📚 Learning: 2025-01-31T15:05:14.310Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 67
File: fdm-app/app/routes/farm.create.$b_id_farm.fields.$b_id.tsx:601-610
Timestamp: 2025-01-31T15:05:14.310Z
Learning: The `updateField` function in fdm-core has optional parameters after `fdm` and `b_id`. The TypeScript definitions might show 8 required parameters due to a potential version mismatch.

Applied to files:

  • fdm-calculator/src/balance/organic-matter/types.d.ts
🧬 Code graph analysis (3)
fdm-calculator/src/balance/organic-matter/index.test.ts (3)
fdm-calculator/src/balance/organic-matter/index.ts (2)
  • calculateOrganicMatterBalanceField (130-186)
  • calculateOrganicMatterBalancesFieldToFarm (201-263)
fdm-calculator/src/balance/organic-matter/types.d.ts (2)
  • OrganicMatterBalanceFieldResult (153-162)
  • OrganicMatterBalanceField (138-147)
fdm-calculator/src/balance/shared/conversion.ts (1)
  • convertOrganicMatterBalanceToNumeric (64-89)
fdm-calculator/src/balance/shared/soil.ts (3)
fdm-calculator/src/balance/organic-matter/types.d.ts (1)
  • SoilAnalysisPicked (186-191)
fdm-calculator/src/conversions/soil.ts (4)
  • calculateOrganicCarbon (13-30)
  • calculateOrganicMatter (40-57)
  • calculateCarbonNitrogenRatio (69-89)
  • calculateBulkDensity (102-131)
fdm-core/src/db/schema.ts (1)
  • soilAnalysis (721-764)
fdm-calculator/src/balance/organic-matter/types.d.ts (1)
fdm-calculator/src/index.ts (10)
  • FieldInput (9-9)
  • OrganicMatterBalanceInput (33-33)
  • OrganicMatterSupplyFertilizersNumeric (37-37)
  • OrganicMatterSupplyCultivationsNumeric (36-36)
  • OrganicMatterSupplyResiduesNumeric (39-39)
  • OrganicMatterSupplyNumeric (38-38)
  • OrganicMatterDegradationNumeric (35-35)
  • OrganicMatterBalanceFieldNumeric (31-31)
  • OrganicMatterBalanceFieldResultNumeric (32-32)
  • OrganicMatterBalanceNumeric (34-34)
🔇 Additional comments (9)
fdm-app/app/components/blocks/sidebar/apps.tsx (4)

1-27: LGTM! Imports support the new collapsible balance menu.

All new imports (Plus/Minus icons, Collapsible components, and SidebarMenuSub* components) are necessary for implementing the nested balance menu structure with expand/collapse functionality.


49-56: LGTM! Nitrogen balance link now points directly to the nitrogen route.

The link correctly points to /balance/nitrogen (line 53), avoiding the unnecessary redirect through /balance. This addresses the previous review concern and maintains consistency with the OM balance link pattern.


76-83: LGTM! OM balance link initialization follows established patterns.

The link initialization correctly handles the create farm wizard case and invalid farm IDs, and points to the expected /balance/organic-matter route. Implementation is consistent with other link initialization logic in this component.


109-165: LGTM! Collapsible balance menu correctly implemented with previous issues resolved.

The collapsible structure properly replaces the single balance item with a nested menu containing Stikstof and Organische stof sub-items. Key improvements:

  • Both sub-items now correctly use SidebarMenuSubButton (lines 126, 146), addressing the previous review concern
  • Plus/Minus icons properly indicate expand/collapse state using CSS group data selectors
  • Consistent enabled/disabled state handling for both sub-items
  • Proper component nesting follows the UI library patterns
fdm-docs/docs/README.md (1)

18-18: LGTM!

The link update correctly reflects the new directory structure for balance-related documentation.

fdm-calculator/src/balance/organic-matter/index.test.ts (1)

1-195: LGTM!

The test suite comprehensively covers the organic matter balance calculation workflow with proper mocking, negative-by-design degradation values, weighted aggregation logic, error handling, and numeric conversion. All test expectations align with the implementation.

fdm-calculator/src/balance/shared/soil.ts (1)

26-114: LGTM!

The soil analysis aggregation function correctly preserves zero values using nullish coalescing, validates missing parameters by checking for both null and undefined, and provides robust estimation logic for missing soil properties.

fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.organic-matter.$b_id.tsx (1)

1-336: LGTM!

The route component is well-structured with proper error handling, async data loading with Suspense, user-friendly error messages, and a clean UI for displaying the organic matter balance. The NavLink path correctly uses an absolute path to avoid navigation issues.

fdm-calculator/src/balance/organic-matter/types.d.ts (1)

1-330: LGTM!

The type definitions are comprehensive, well-documented, and correctly structured. The unit specifications clearly distinguish between EOM (supply) and OM (degradation), and the numeric counterparts properly mirror the Decimal-based types for final outputs.

@SvenVw SvenVw requested a review from gerardhros November 24, 2025 11:03
`/balance/nitrogen/${b_id}`,
`/balance/nitrogen/${option.b_id}`,
)}
to={`/farm/${b_id_farm}/${calendar}/balance/${currentPath.includes("nitrogen") ? "nitrogen" : "organic-matter"}/${option.b_id}`}
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.

are the ifelse statements in this balance.tsx the best approach to use same page for N and OM? if you later expand to other nutrients, is this still valid?

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.

Yes, when other nutrients are added we have to use a different method

`Organic matter balance data not found for field ${b_id}`,
)
}
if (fieldResult.errorMessage) {
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.

does this imply that if an error occurs, the page remains empty?

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.

No, we show than a card with an error message. For example if data of required soil parameters are missing we tell them to fill in those values. No tractor page is shown

)

// Expect degradation over 3 years to be 3 times the degradation of 1 year.
expect(degradationMultiYear.total.toNumber()).toBeCloseTo(
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 to you take the sum and not the average?

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.

The unit is kg / ha not kg/ha / yr. Otherwise the supply has also to be averaged first. However in fdm-app we have currently only single year timeframes

.times(b_depth)
.times(a_density_sa)
.times(a_som_loi.ln().times(-0.008934).add(0.038228))
.times(temperatureCorrection)
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.

can you add documentation of the empirical formula as comment above? this function reads as you do first the ln transformation, then multiply and then add.

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.

Yes, it follows the order according to decimal.js


// Calculate the average values per hectare for the entire farm.
const avgFarmSupply = totalFarmArea.isZero()
? new Decimal(0)
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.

should you also divide over number of years?

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.

Nope


// Calculate the EOM supply for this specific application.
// (g EOM / kg product) * (kg product / ha) / (1000 g / kg) = kg EOM / ha
const applicationValue = p_amount.times(p_eom).dividedBy(1000)
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.

please check if EOM and EOC are not mixed up (just to be sure). See handboekbodemenbemesting.nl

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.

The OM balance is expressed as OM not OC


/**
* Represents the organic matter supply from crop residues.
* All values are in kilograms of effective organic matter per hectare (kg EOM / ha).
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.

the kluit en perspotjes will become a separate supply category besides fertilizer, crop residue?

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.

Yes

}

/** Numeric version of `OrganicMatterSupply`. */
export type OrganicMatterSupplyNumeric = {
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.

can this be non-numeric then?

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.

Yes as Decimal which is the js type to prevent floating point issues, but this type can not be used on the web page so it needs to converted in the end to a standard numeric class

Copy link
Copy Markdown
Collaborator

@gerardhros gerardhros left a comment

Choose a reason for hiding this comment

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

LGTM. A few minor questions.

@gerardhros gerardhros self-requested a review November 24, 2025 14:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

branch:development Issue only affecting development, not the main branch (yet) enhancement New feature or request fdm-app fdm-calculator fdm-core fdm-data fdm-docs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement Organic Matter Balance Calculation

2 participants