vital_solver library
vital_solver — Pure-Dart nutritional solver for the VITAL FlutterFlow app.
This package contains all the pure-logic of the solver pipeline: quality rules → coordinate-descent optimizer → pre-solve report.
It has zero dependencies on Firebase, FlutterFlow, or any UI framework,
which makes it fully testable via dart test and reusable in other
contexts (CLI, server, alternate UI, etc.).
Surface API
Phase 2 — types + helpers (active)
- IngredientInput, Per100g — ingredient input
- SolverTargets — macro targets
- RecipeContext — solver options bag
- EnrichedRow — quality-rules output row
- SolverOutput, MacroTotals, SolverErrors — solver result
- PreSolveReport, ReportItem — UI gate report
Phases 3-5 — pipeline (TODO)
- applyQualityRules(input) → enrichedRows
- runOptimizer(enrichedRows, targets, options) → SolverOutput
- solveSingleMeal(input) → SolverOutput (full pipeline)
- buildPreSolveReport(input) → PreSolveReport (UI gate)
Classes
- EnrichedRow
-
Row emitted by
applyQualityRulesBeforeSolver, consumed by the optimizer. - IngredientInput
- Single ingredient as supplied to the solver.
- MacroTotals
- Macro totals over a solved recipe.
- Per100g
- Per-100g macro values. All values clamp at 0 (legacy parity).
- PreSolveReport
- Top-level pre-solve report.
- PreSolveReportResult
-
Result of the pre-solve report. The
rawmap is the legacy-shape output ready to be returned to the FF UI; the typed fields are convenience accessors. - QualityResult
- Result of applying the quality rules pipeline to a set of rows.
- RecipeContext
- Wrapper around the recipe-context option map.
- ReportItem
- Single-entry reason / warning / suggestion in the report.
- ReportMessage
- A single report item (blocking, warning, or suggestion).
- SolvePipelineResult
- Result of the solve pipeline. Wraps the legacy-shape solver output along with the intermediate quality-rules result so callers can introspect.
- SolverErrors
- Errors vs targets after the solver finishes.
- SolverOutput
- Top-level solver output (post-corrected if applicable).
- SolverTargets
- Macro + fiber targets supplied to the solver.
- SupportVegCulinaryDecision
- Decision produced by computeSupportVegCulinaryDecision.
- SweetBatchV22bDecision
- Result of the V22b cascade decision — all the values the orchestrator needs to apply bounds to rows + build diagnostics.
- VegetableOnlyBalancedDecision
- Decision produced by computeVegetableOnlyBalancedDecision. Carries the activation flag, per-group candidates, target shares, and (if applicable) the escape-overflow allocations.
Constants
Functions
-
applyCarbBounds(
List< Map< rows, Map<String, dynamic> >String, dynamic> ctx, {required String selectedCarbAnchorId}) → List<Map< String, dynamic> > - Applies per-class caps for carb candidates:
-
applyCarbMainMinimumWhenLegumePresent(
List< Map< rows, Map<String, dynamic> >String, dynamic> recipeContext) → List<Map< String, dynamic> > -
applyFatCaps(
List< Map< rows, Map<String, dynamic> >String, dynamic> ctx) → List<Map< String, dynamic> > - Applies absolute max caps to fat rows: oil ≤ 20g, butter ≤ 18g, other fat ≤ 120g. Sets fatArchitectureClass.
-
applyProteinSupplementBounds(
List< Map< rows, Map<String, dynamic> >String, dynamic> ctx) → List<Map< String, dynamic> > - Applies the soft-dose bounds for whey-style protein supplements: minG (default 15g), softCenter (default 20g), softMax (default 20g), rescueMax (default 240g, no hard max unless explicitly enabled).
-
applyQualityRules(
{required List< Map< rows, required Map<String, dynamic> >String, dynamic> targets, required Map<String, dynamic> ctx}) → QualityResult - Applies the quality rules pipeline to a set of rows.
-
applySupportVegCulinaryBounds(
{required List< Map< rows, required SupportVegCulinaryDecision decision, required Map<String, dynamic> >String, dynamic> ctx}) → List<Map< String, dynamic> > -
Apply the decision to the rows in place (returns the same list).
Safe to call when
decision.active == false(no-op). -
applyV18OptionDefaults(
Map< String, dynamic> options, Map<String, dynamic> targets) → void -
applyV22bMutations(
{required SweetBatchV22bDecision decision, required List< Map< rows, required Map<String, dynamic> >String, dynamic> targets, required Map<String, dynamic> ctx}) → List<Map< String, dynamic> > - Applies the V22b cascade decision to the rows, mutating each one's bounds (minG, maxG, softCenterG, weights, diag fields) in place according to its category.
-
applyVegetableOnlyBalancedBounds(
{required List< Map< rows, required VegetableOnlyBalancedDecision decision, required Map<String, dynamic> >String, dynamic> ctx}) → List<Map< String, dynamic> > -
Apply the bounds policy described by
decisiontorows. Rows not touched by the mode (proteins, fats, fixed rows, recipe blocks, etc.) are returned unchanged. Whendecision.active is false, the rows are returned unchanged. -
asList(
dynamic v) → List -
Coerces
vintoList<dynamic>. Decodes JSON strings. Returns an empty list for non-list inputs. -
asMap(
dynamic v) → Map< String, dynamic> -
Coerces
vintoMap<String, dynamic>. Decodes JSON strings, handles non-typed Map (e.g.Map<dynamic, dynamic>coming from JSON.decode), and falls back to.toMap()for FlutterFlow data structs. Returns an empty map ifvis null, not a Map, or a Map-like object cannot be extracted. -
buildPreSolveReport(
{required List< Map< rows, required Map<String, dynamic> >String, dynamic> targets, required Map<String, dynamic> options}) → PreSolveReportResult - Build a pre-solve report from raw rows, targets, and options.
-
carbAnchorBoundViolationCheck(
List< Map< rows, Map<String, dynamic> >String, double> gramsById, Map<String, dynamic> recipeContext) → Map<String, dynamic> -
carbAnchorCheck(
List< Map< rows, Map<String, dynamic> >String, double> gramsById, Map<String, dynamic> recipeContext) → Map<String, dynamic> -
cFromG(
Map< String, dynamic> r, double g) → double -
Returns net carbs in grams for
ggrams ofr. -
clampD(
double v, double lo, double hi) → double -
Clamps
vto the range`lo`..`hi`. Returnslofor non-finite values (legacy behavior: NaN/Infinity are treated as the lower bound). -
cleanMapForDiag(
Map< String, dynamic> m) → Map<String, dynamic> -
Cleans a diagnostic map: drops keys whose value is non-finite (the
firestoreSafewould return null for them, but we keep explicit nulls because they may carry meaning in diag). -
computeDynamicFruitBounds(
Map< String, dynamic> fruit, Map<String, dynamic> fruitPolicy, Map<String, dynamic> ctx, bool hasSimpleCarb) → Map<String, dynamic> - Computes dynamic min/center/max bounds for a fruit row based on its density and fiber profile.
-
computeMicrosSnapshot(
{required List< Map< rows, required Map<String, dynamic> >String, dynamic> gramsById}) → Map<String, dynamic> -
Aggregates the four micronutrient groups (
vitamins,minerals,fiber,lipidProfil) from solvedrowsweighted by their grams ingramsById. -
computeSupportVegCulinaryDecision(
{required List< Map< rows, required Map<String, dynamic> >String, dynamic> targets, required Map<String, dynamic> ctx}) → SupportVegCulinaryDecision - Compute the decision for the current row set.
-
computeSweetBatchV22bCascade(
{required List< Map< rows, required Map<String, dynamic> >String, dynamic> targets, required Map<String, dynamic> ctx}) → SweetBatchV22bDecision - Computes the V22b cascade decision for a recipe.
-
computeVegetableOnlyBalancedDecision(
{required List< Map< rows, required Map<String, dynamic> >String, dynamic> targets, required Map<String, dynamic> ctx}) → VegetableOnlyBalancedDecision -
Compute the VegetableOnlyBalancedDecision from rows that have already
passed through
applyCarbBounds. Returns an inactive decision when any activation condition fails. -
decodeIfJsonString(
dynamic v) → dynamic -
If
vis a JSON-encoded string, decodes it. Otherwise returnsvunchanged. Empty strings are returned as-is. Decoding errors returnvunchanged (best-effort). -
fatPer100(
Map< String, dynamic> r) → double -
Reads
fatPer100g(or legacy aliaslipidsPer100g) and clamps at 0. -
fiberFromG(
Map< String, dynamic> r, double g) → double -
Returns fiber in grams for
ggrams ofr. -
fiberPer100(
Map< String, dynamic> r) → double -
Reads fiber per 100g, checking the long alias chain:
fiberPer100g→fiber_g→microsPer100g.fiber.fiber_g→microsPer100g_fiber_fiber_g. Clamps at 0. -
firestoreSafe(
dynamic v) → dynamic -
Recursively converts
vinto a Firestore-safe value: -
hasAnyTag(
Map< String, dynamic> r, List<String> tags) → bool -
True if any of
tags(after norming) appears intagsOf(r). -
isCarbCandidate(
Map< String, dynamic> r) → bool -
True if the row is a candidate for the carb anchor selection — it must
be variable, not a recipe block, not a sweet simple carb, have non-
negligible net carbs, and have a
genericCarbClassin the UI carb set. -
isCarbUiClass(
String cls) → bool -
True if
clsis one of the carb UI buckets (carb_main, legumineuse, carb_secondary, veg_structural, leafy_finish). -
isCondimentRole(
String role) → bool -
True if
role(already normalized) is one of the condiment / seasoning roles. -
isFatButter(
Map< String, dynamic> r) → bool - True if the row is butter (beurre / butter).
-
isFatOil(
Map< String, dynamic> r) → bool - True if the row is a fat oil (huile / oil).
-
isFruitRow(
Map< String, dynamic> r) → bool -
True if the row is a fruit (lexical match on name/category OR
mealUiRole/smartCarbGroup/tags). Excludes sweet simple carbs to avoid honey/maple syrup being miscategorized. -
isPreparedSweetBatchRow(
Map< String, dynamic> r) → bool -
True if the row is a prepared sweet batch (pâte à gauffre, pancake
batter, etc.) — a recipe block whose
blockTypecontains "batter" or "pate", or whose branch indicates sweet/batter, or that is explicitly markedfromPreparedBatch. -
isProteinSupplement(
Map< String, dynamic> r) → bool - True if the row is a protein supplement (whey, isolate, plant-based protein powder, etc.).
-
isRecipeBlockRow(
Map< String, dynamic> r) → bool - True if the row represents a recipe block (synthetic prepared batch row aggregating multiple base ingredients).
-
isSweetSimpleCarbDoc(
Map< String, dynamic> r) → bool - True if the row is a sweet simple carb (sucre/miel/sirop/confiture/ caramel/agave/maple).
-
kcalPer100(
Map< String, dynamic> r) → double - Reads kcal per 100g if present, else estimates from macros via Atwater approximation (4 kcal/g protein + 4 kcal/g net-carbs + 9 kcal/g fat).
-
lFromG(
Map< String, dynamic> r, double g) → double -
Returns fat in grams for
ggrams ofr. -
netCarbsPer100(
Map< String, dynamic> r) → double -
Reads net carbs per 100g (= total - fiber when not provided explicitly).
Checks
carbsForSolverPer100g→carbsPer100g_solver→netCarbsPer100gbefore computing the difference. Clamps at 0. -
norm(
dynamic v) → String - Normalizes a string for case- and accent-insensitive comparison. French-specific accents are folded to ASCII (legacy behavior).
-
normalizeMacrosAndRoles(
List< Map< rows, Map<String, dynamic> >String, dynamic> ctx) → List<Map< String, dynamic> > -
Normalizes macros (sets canonical per100g fields), infers culinary role
(protein_main / fat / carb_main / etc.), and sets
genericCarbClass,section,creationSection,foodRole,anchorRole. -
normalizeProteinSupplementBoundsAfterQuality(
List< Map< rows, Map<String, dynamic> >String, dynamic> recipeContext) → List<Map< String, dynamic> > -
parseStringList(
dynamic v) → List< String> - Parses a tag-list value that may be either a JSON-encoded array, a comma- separated string, or already a List. Each element is normalized via norm; empty entries are dropped.
-
pFromG(
Map< String, dynamic> r, double g) → double -
Returns protein in grams for
ggrams ofr. -
proteinMainCheck(
List< Map< rows, Map<String, dynamic> >String, double> gramsById, Map<String, dynamic> recipeContext) → Map<String, dynamic> -
proteinPer100(
Map< String, dynamic> r) → double -
Reads
proteinPer100g(or legacy aliasproteinsPer100g) and clamps at 0. -
rowIsVariable(
Map< String, dynamic> r) → bool -
True if the row is variable (the solver may adjust its grams). False
for explicit fixed rows (
role == 'fixed'). -
runMacroOptimizer(
List ingredientsParam, dynamic targetsParam) → Map< String, dynamic> -
Run the macro optimizer on a list of ingredient row maps with the given
targets/options payload. Byte-equivalent to legacy
macroOptimizerPQDynamicMaxAction(ingredientsParam, targetsParam). -
selectCarbAnchorId(
List< Map< rows, Map<String, dynamic> >String, dynamic> targets, Map<String, dynamic> ctx) → String - Selects the carb anchor C using priority chain: carb_main → légumineuse → carb_secondary → veg_structural → leafy_finish. Within a class, prefers fruit rows, then highest density. Returns the stableId of the selected anchor, or '' if none.
-
slug(
dynamic v) → String -
Slugifies via norm then collapses non-alphanumeric runs to a single
underscore. Returns
'item'for empty inputs (legacy fallback). -
solvePipeline(
{required List< Map< rows, required Map<String, dynamic> >String, dynamic> targets, required Map<String, dynamic> options}) → SolvePipelineResult - Run the full pre-solve → solve pipeline on a list of legacy-shape rows.
-
stableId(
Map< String, dynamic> r) → String -
Resolves a stable identifier for a row by checking the legacy alias chain:
rowId → ingredientId → id → docId → recipeBlockId → name. Returns
''if none of these are set. -
sugarsPer100(
Map< String, dynamic> r) → double - Reads sugars per 100g (similar alias chain to fiber). Clamps at 0.
-
syncContextFromPreSolveDiag(
List< Map< rows, Map<String, dynamic> >String, dynamic> options, Map<String, dynamic> targets) → void -
Extracts and normalizes the union of
qualityTags,tags, andTagsfields on a row. Order is preserved (insertion order via Set/List). -
toB(
dynamic v, [bool fallback = false]) → bool -
Coerces
vto bool using the legacy semantics: -
toD(
dynamic v, [double fallback = 0.0]) → double -
Coerces
vto a finite double. Returnsfallbackfor null, non-numeric, non-finite (NaN, Infinity), or unparseable values. -
toI(
dynamic v, [int fallback = 0]) → int -
Coerces
vto an int. Numeric values are rounded (legacy behavior). Returnsfallbackfor null or unparseable strings. -
toS(
dynamic v, [String fallback = '']) → String -
Coerces
vto a non-empty String. Returnsfallbackwhen the string representation ofvis empty orvis null. -
totalCarbsPer100(
Map< String, dynamic> r) → double -
Reads total carbs per 100g via alias chain:
totalCarbsPer100g→carbsTotalPer100g→glucidesTotauxPer100g→carbsPer100g→C→Ca. Clamps at 0. -
totalCFromG(
Map< String, dynamic> r, double g) → double -
Returns total carbs in grams for
ggrams ofr(legacy alternate path used in some scoring code).