npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@vetwo/nutrition-units

v0.0.1

Published

Nutrition/feed-formulation calculation rules built on top of @vetwo/units. Contains ALL domain knowledge (nutrient reporting units, basis conversion, coefficient resolution for the LP solver). No generic unit-conversion logic lives here.

Readme

@vetwo/nutrition-units

Nutrition & feed-formulation calculation rules, built on top of @vetwo/units.

This package contains all the domain knowledge for animal-nutrition calculations: which unit each nutrient is reported in, how to convert between as-fed and dry-matter basis, how to read a feed/animal JSON schema into safe Quantity values, and how to turn that unit-safe math into plain numbers your LP solver (HiGHS or any other) can consume directly.

It contains zero generic unit-conversion logic — that all lives in @vetwo/units. This package only adds nutrition vocabulary on top, so it stays small, testable, and easy to extend as new species/nutrients are added.

import { Quantity } from "@vetwo/units";
import { nutritionMath } from "@vetwo/nutrition-units";

const cp = Quantity.of(12, "%");             // crude protein, as-fed
const intake = Quantity.of(10, "kg/day");    // feed intake

nutritionMath.calculate(intake, cp, "cp");   // Quantity(1200, "g/day")

No /100, no *1000, no manual unit math anywhere in your application code.


Table of contents


Installation

npm install @vetwo/units @vetwo/nutrition-units
# or
bun add @vetwo/units @vetwo/nutrition-units

@vetwo/units is a required peer dependency — both packages must be installed together.


Why a separate package

@vetwo/units knows how to convert and combine units — it has no idea that "cp" means crude protein, or that vitamins are reported in IU/day. That knowledge (reporting-unit conventions, as-fed/dry-matter conversion, solver-coefficient extraction) is domain-specific and lives here, cleanly separated so the core engine stays reusable for other fields (chemistry, finance, engineering, veterinary pharmacology...).


Quick start

import { Quantity } from "@vetwo/units";
import { nutritionMath, coefficientResolver, BasisConverter } from "@vetwo/nutrition-units";

const intake = Quantity.of(10, "kg/day");

// Nutrient contributions — reporting unit chosen automatically per nutrient
nutritionMath.calculate(intake, Quantity.of(12, "%"), "cp");         // 1200 g/day
nutritionMath.calculate(intake, Quantity.of(0.9, "%"), "ca");        // 90 g/day
nutritionMath.calculate(intake, Quantity.of(80, "mg/kg"), "fe");     // 800 mg/day
nutritionMath.calculate(intake, Quantity.of(3.2, "Mcal/kg"), "de");  // 32 Mcal/day
nutritionMath.calculate(intake, Quantity.of(5000, "IU/kg"), "vitA"); // 50000 IU/day

// Diet cost
coefficientResolver.getCostCoefficient(intake, Quantity.of(0.35, "cur/kg")); // 3.5 cur/day

// As-fed <-> dry-matter conversion
const dm = Quantity.of(88, "%");
const cpDmBasis = Quantity.of(15, "% DM");
BasisConverter.toAsFed(cpDmBasis, dm); // 13.2 % (as-fed)

// Plain numeric coefficient for an LP solver — units never reach the solver
coefficientResolver.getCoefficient(Quantity.of(1, "kg/day"), Quantity.of(12, "%"), "cp"); // 120

API reference

NutritionMath

The main facade — the only entry point application code should call for nutrient math. Wraps the generic nutrition.nutrientContribution and nutrition.dietCost rules and normalizes results to each nutrient's canonical reporting unit.

class NutritionMath {
  constructor(rules?: CalculationRuleRegistry, targetUnits?: TargetUnitRegistry);
  calculate(feedIntake: Quantity, nutrientConcentration: Quantity, nutrientKey: NutrientKey): Quantity;
  calculateCost(feedIntake: Quantity, pricePerKg: Quantity): Quantity;
  sum(contributions: Quantity[]): Quantity;
}

export const nutritionMath: NutritionMath; // ready-to-use default instance

.calculate(feedIntake, nutrientConcentration, nutrientKey)

Computes a nutrient's contribution from a feed's inclusion rate and its nutrient concentration, automatically converted to that nutrient's registered reporting unit (see Default reporting units).

nutritionMath.calculate(
  Quantity.of(10, "kg/day"),   // how much of this feed the animal eats
  Quantity.of(12, "%"),        // the feed's CP concentration
  "cp"                          // which nutrient — determines the output unit
); // Quantity(1200, "g/day")

Works identically for any nutrient/unit combination — percent, ppm, mg/kg, Mcal/kg, IU/kg — because the underlying math is generic dimensional analysis, not per-nutrient formulas.

.calculateCost(feedIntake, pricePerKg)

Computes the diet-cost contribution of one feed.

nutritionMath.calculateCost(
  Quantity.of(10, "kg/day"),
  Quantity.of(0.35, "cur/kg")
); // Quantity(3.5, "cur/day")

.sum(contributions)

Sums multiple Quantity contributions of the same nutrient across several feeds in a diet (e.g. total CP supplied by corn + soybean meal + premix). Throws if the array is empty; throws UnitMismatchError if the quantities aren't dimensionally compatible.

const totalCp = nutritionMath.sum([
  nutritionMath.calculate(cornIntake, cornCp, "cp"),
  nutritionMath.calculate(soyIntake, soyCp, "cp"),
]);

CoefficientResolver

Strips units away entirely — the hard boundary your LP solver should sit behind. Nothing downstream of this class should import @vetwo/units or @vetwo/nutrition-units at all; it should only ever see number.

class CoefficientResolver {
  constructor(math?: NutritionMath);
  getCoefficient(feedUnitIntake: Quantity, nutrientConcentration: Quantity, nutrientKey: NutrientKey): number;
  getCostCoefficient(feedUnitIntake: Quantity, pricePerKg: Quantity): number;
  getBound(quantity: Quantity, targetUnit: string): number;
}

export const coefficientResolver: CoefficientResolver;

.getCoefficient(feedUnitIntake, nutrientConcentration, nutrientKey)

Returns a plain number — the per-unit-of-decision-variable coefficient for an LP constraint row (e.g. "how many grams of CP does 1 kg/day of this feed contribute").

coefficientResolver.getCoefficient(
  Quantity.of(1, "kg/day"),
  Quantity.of(12, "%"),
  "cp"
); // 120

.getCostCoefficient(feedUnitIntake, pricePerKg)

Returns a plain number for the objective-function coefficient (cost per unit of decision variable).

coefficientResolver.getCostCoefficient(Quantity.of(1, "kg/day"), Quantity.of(0.35, "cur/kg")); // 0.35

.getBound(quantity, targetUnit)

Converts any bound (min/max inclusion, min/max nutrient requirement) to a plain number in a chosen target unit, for use as a constraint bound.

coefficientResolver.getBound(Quantity.of(2, "kg/day"), "kg/day"); // 2
coefficientResolver.getBound(Quantity.of(500, "g/day"), "kg/day"); // 0.5

BasisConverter

Converts a nutrient value between as-fed and dry-matter reporting basis. This requires the feed's dry-matter percentage as an extra input — it is domain logic, not a pure unit conversion (a plain Quantity.to() call deliberately refuses to bridge basis tags).

class BasisConverter {
  static toAsFed(dmQuantity: Quantity, dryMatterPercent: Quantity): Quantity;
  static toDryMatterBasis(asFedQuantity: Quantity, dryMatterPercent: Quantity): Quantity;
}

BasisConverter.toAsFed(dmQuantity, dryMatterPercent)

dmQuantity must carry a "DM" basis tag (e.g. parsed from "15 % DM"); dryMatterPercent must be a plain "%" quantity (the feed's dry-matter content). Returns the equivalent as-fed value (basis tag stripped).

const cpDmBasis = Quantity.of(15, "% DM");
const dm = Quantity.of(88, "%");
BasisConverter.toAsFed(cpDmBasis, dm); // Quantity(13.2, "%")

BasisConverter.toDryMatterBasis(asFedQuantity, dryMatterPercent)

The inverse operation — converts an as-fed value into its dry-matter-basis equivalent (adds the "DM" basis tag). Throws ConversionError if dryMatterPercent is zero.

BasisConverter.toDryMatterBasis(Quantity.of(13.2, "%"), Quantity.of(88, "%")); // Quantity(15, "% DM")

TargetUnitRegistry

Pure configuration — maps each nutrient key to its canonical reporting unit. Contains no calculation logic, so reporting conventions can change without touching any math.

type NutrientKey =
  | "cp" | "lys" | "methionine" | "metCys" | "ee" | "cf" | "ndf" | "adf" | "ash"
  | "starch" | "sugar" | "tdn"
  | "ca" | "p" | "availableP" | "mg" | "k" | "na" | "cl" | "s"
  | "fe" | "mn" | "cu" | "zn" | "co" | "i" | "se"
  | "vitA" | "vitD" | "vitE"
  | "de" | "me" | "nel"
  | "cost";

class TargetUnitRegistry {
  getTargetUnit(nutrientKey: NutrientKey): string;
  override(nutrientKey: NutrientKey, unitSymbol: string): void;
}

export const defaultTargetUnitRegistry: TargetUnitRegistry;

.getTargetUnit(nutrientKey)

Returns the unit string a nutrient should be reported in.

defaultTargetUnitRegistry.getTargetUnit("vitA"); // "IU/day"
defaultTargetUnitRegistry.getTargetUnit("fe");   // "mg/day"

.override(nutrientKey, unitSymbol)

Changes the reporting unit for one nutrient, without forking the package — useful if your product wants ME reported in Mcal instead of Kcal, for example.

defaultTargetUnitRegistry.override("me", "Mcal/day");
nutritionMath.calculate(intake, Quantity.of(3000, "Kcal/kg"), "me"); // now returns Mcal/day

FeedSchemaLoader

Reads a feed/animal/requirements JSON unit-map (as-fed and dry-matter basis, animal data, requirements, solver variables, feed constraints, minerals limits, economics) and returns Quantity factories per field — the single place in your codebase that needs to know your schema's shape.

interface UnitSchema {
  feed: { asFed: Record<string, string>; dryMatterBasis: Record<string, string> };
  animal: Record<string, string>;
  requirements: Record<string, string>;
  solver: Record<string, string>;
  feedConstraints: Record<string, string>;
  mineralsLimits: Record<string, string>;
  economics: Record<string, string>;
}

class FeedSchemaLoader {
  constructor(schema: UnitSchema);
  asFed(field: string, value: number): Quantity;
  dryMatterBasis(field: string, value: number): Quantity;
  requirement(field: string, value: number): Quantity;
  animal(field: string, value: number): Quantity;
  economics(field: string, value: number): Quantity;
  feedConstraint(field: string, value: number): Quantity;
}
import { FeedSchemaLoader } from "@vetwo/nutrition-units";
import schema from "./units-schema.json";

const loader = new FeedSchemaLoader(schema);

const cp = loader.asFed("cp", 12);                 // Quantity(12, "%")
const cpDm = loader.dryMatterBasis("cp", 13.6);     // Quantity(13.6, "% DM")
const cpRequirement = loader.requirement("cp", 480); // Quantity(480, "g/day")
const bodyWeight = loader.animal("bodyWeight", 450); // Quantity(450, "kg")
const feedPrice = loader.economics("feedPrice", 0.35); // Quantity(0.35, "cur/kg")
const maxInclusion = loader.feedConstraint("maxKg", 3); // Quantity(3, "kg/day")

Registered calculation rules

On import, this package registers two rules into @vetwo/units's shared CalculationRuleRegistry. NutritionMath is the recommended way to call them, but you can invoke them directly if you need to:

import { NUTRIENT_CONTRIBUTION_RULE, DIET_COST_RULE } from "@vetwo/nutrition-units";
import { defaultCalculationRuleRegistry } from "@vetwo/units";

defaultCalculationRuleRegistry.run(NUTRIENT_CONTRIBUTION_RULE, intake, concentration);
defaultCalculationRuleRegistry.run(DIET_COST_RULE, intake, price);

| Constant | Registry key | Inputs | Behavior | |---|---|---|---| | NUTRIENT_CONTRIBUTION_RULE | "nutrition.nutrientContribution" | (feedIntake, nutrientConcentration) | feedIntake.multiply(nutrientConcentration) | | DIET_COST_RULE | "nutrition.dietCost" | (feedIntake, pricePerKg) | feedIntake.multiply(pricePerKg) |


Default reporting units

| Category | Nutrients | Unit | |---|---|---| | Macro nutrients | cp, lys, methionine, metCys, ee, cf, ndf, adf, ash, starch, sugar, tdn | g/day | | Macro minerals | ca, p, availableP, mg, k, na, cl, s | g/day | | Trace minerals | fe, mn, cu, zn, co, i, se | mg/day | | Vitamins | vitA, vitD, vitE | IU/day | | Energy | de, nel | Mcal/day | | Energy | me | Kcal/day | | Economics | cost | cur/day |

Change any of these via TargetUnitRegistry.override() without forking the package.


Integration patterns

LP solver integration

import { Quantity } from "@vetwo/units";
import { coefficientResolver } from "@vetwo/nutrition-units";

function buildConstraintRow(feed: { name: string; cp: number; ca: number; price: number }) {
  const oneUnitIntake = Quantity.of(1, "kg/day"); // matches the solver's decision-variable unit

  return {
    feedName: feed.name,
    cpCoefficient: coefficientResolver.getCoefficient(oneUnitIntake, Quantity.of(feed.cp, "%"), "cp"),
    caCoefficient: coefficientResolver.getCoefficient(oneUnitIntake, Quantity.of(feed.ca, "%"), "ca"),
    costCoefficient: coefficientResolver.getCostCoefficient(oneUnitIntake, Quantity.of(feed.price, "cur/kg")),
  };
}

The returned object contains only numbers — pass it straight into your HiGHS (or any other LP) constraint matrix. The solver module itself never needs to import this package or @vetwo/units.

Validation engine integration

import { Quantity, DimensionError } from "@vetwo/units";

function validateRange(value: Quantity, min: Quantity, max: Quantity) {
  if (!value.hasSameDimension(min) || !value.hasSameDimension(max)) {
    throw new DimensionError(min.unit.symbol, value.unit.symbol);
  }
  const v = value.toBase().value;
  return v >= min.toBase().value && v <= max.toBase().value;
}

A mismatched comparison (e.g. a Mcal/day requirement checked against a g/day value) throws instead of silently producing a wrong diet.

Report generator integration

import { formatQuantity } from "@vetwo/units";
import { nutritionMath } from "@vetwo/nutrition-units";

const supplied = nutritionMath.calculate(intake, cp, "cp");
const required = Quantity.of(480, "g/day");
const pct = (supplied.toBase().value / required.toBase().value) * 100;

console.log(`CP: ${formatQuantity(supplied, { decimals: 1 })} / ${formatQuantity(required, { decimals: 1 })} (${pct.toFixed(0)}%)`);
// "CP: 1200.0 g/day / 480.0 g/day (250%)"

Design principles

  • All domain knowledge, zero generic unit logic. If a change is about how units convert, it belongs in @vetwo/units, not here.
  • Configuration over code. Reporting units are data (TargetUnitRegistry), never if/else branches.
  • Hard boundary at the solver. CoefficientResolver is the only place a Quantity becomes a number; nothing past that point should import either package.
  • One public facade. Application code should call NutritionMath and CoefficientResolver; it should not reach into nutrition-rules.ts or the shared CalculationRuleRegistry directly except in advanced cases.

Requirements

  • @vetwo/units (peer dependency)
  • TypeScript ^5.x

License

MIT