Engine Documentation

A complete reference for how the Fragrance Engine generates, scores, and analyzes fragrance formulas. The core engine is fully deterministic, the same inputs always produce the same outputs. Optional AI features (Section 12) provide creative insights and suggestions powered by OpenAI.

Contents
Section 1

System Overview

The Fragrance Engine converts olfactive intent, a set of scent descriptors with intensity weights, into a balanced fragrance formula through a 15-stage deterministic pipeline. Every metric (projection, longevity, structure, cost) is computed from fixed physical and chemical parameters with no randomness or AI inference.

The engine operates in two complementary modes:

  • Extended mode, uses per-material physical parameters (boiling point, molecular weight, diffusion index, fixative strength) to derive behaviour from first principles.
  • Legacy mode, falls back to manual volatility_score and role assignments when physical data is absent.

Both modes can mix within a single formula. Each material is evaluated independently; formula-level metrics are percentage-weighted averages.

Key Capabilities
  • Deterministic formula generation from scent descriptors
  • 29 fragrance archetypes with auto-applied descriptors
  • Physics-based projection and longevity estimation
  • Fragrance pyramid analysis (top / heart / base)
  • Lab mixing sheet with batch calculator
  • Cost analysis per material and per bottle
  • IFRA compliance checking with severity levels
  • Formula versioning with snapshot diffs
  • Material substitution suggestions
  • Inventory-aware generation (prefer / require owned materials)
Section 2

Generation Pipeline

Formula generation flows through 15 sequential stages. Each stage is deterministic and the same inputs always produce the same output.

#StageWhat happens
0 Archetype Selection Optional. Applies preset descriptors from one of 29 archetypes (e.g. Fresh Citrus, Oriental). First descriptor gets weight 8.0 (Dominant), rest 6.0 (Present).
1 User Intent User selects scent descriptors with intensity levels: Trace (2.0), Accent (4.0), Present (6.0), Dominant (8.0-9.0).
2 Descriptor Normalization Descriptors are mapped to canonical names via the olfactory taxonomy (15 families, ~220 descriptors). Custom DB descriptors are injected at runtime.
3 Material Family Focus Optional filter restricting the catalog to materials matching specific primary/secondary descriptors.
4 Catalog Building Loads all materials from DB, enriches each with physical behaviour metrics and descriptor weights. Applies inventory filters if "owned only" is enabled.
5 Semantic Scoring Each material scored against user intent via exact match, subfamily proximity (0.4), and family proximity (0.15). Inventory boost (+1.2) applied if "prefer owned" is enabled.
6 Material Ranking Materials ranked by composite score (6 weighted factors). Deterministic jitter prevents identical selections across retries.
7 Role Assignment Materials assigned to top/mid/base based on physics-derived weights from calculate_material_behavior().
8 Sparse Role Fallback If a role bucket is empty, relaxed semantic matching fills it. Fallback materials limited to 15% of formula.
9 Constraint Loop Up to 24 attempts to satisfy structural constraints (role coverage, usage caps, material count).
10 Formula Construction Selected materials assigned percentages summing to 100%. Usage caps (average_use, typical_max) enforced.
11 Normalization Final percentages normalized to sum exactly to 100 while maintaining structural balance.
12 Data Quality Completeness and reliability scores computed per material. Formula confidence aggregated and displayed.
13 Cost Analysis Per-material and total cost calculated. Cost optimizer available for post-generation substitutions.
14 Output Rendering Formula displayed with pyramid visualization, lab mixing sheet, performance estimates, cost breakdown, and confidence card.
Section 3

Material Behaviour Model

Each material's behaviour is computed by calculate_material_behavior(), the single source of truth for all per-material metrics. The function returns seven normalized values (0-1):

OutputRangeDerived FromMeaning
volatility_index 0 – 1 BP, MW, VP Evaporation tendency (1 = highly volatile)
top_weight 0 – 1 volatility_index Opening / top phase contribution
mid_weight 0 – 1 volatility_index Heart / mid phase contribution
base_weight 0 – 1 volatility_index Drydown / base phase contribution
projection_factor 0 – 1 VI, DI, VP Initial throw distance
longevity_factor 0 – 1 MW, FS Total persistence on skin
sillage_factor 0 – 1 mid_weight, DI, LF Trailing scent persistence
All behavior outputs are 0-1 normalized. Display conversions: projection × 8 = hours, longevity × 24 = hours, sillage × 12 = hours, phase weights → visual progress bars.
Normalization Reference
ParameterFormulaInput Range
bp_normclamp((BP − 50) / 350, 0, 1)50 – 400 °C
mw_normclamp((MW − 100) / 400, 0, 1)100 – 500 g/mol
vp_normclamp((log10(VP) + 3) / 6.5, 0, 1)0.001 – 3000 Pa
di_normclamp(DI / 10, 0, 1)0 – 10
fs_normclamp(FS / 10, 0, 1)0 – 10
Volatility Index

Linear model over two curated physical properties, boiling point and molecular weight. Vapor pressure is stored as reference data and is not read by this model.

VI = 1 − (0.6 × bp_norm + 0.4 × mw_norm)

Fallback chain: BP+MW → volatility_score (normalized 1-10 → 0-1) → default 0.5.

Phase Weights

Triangular distribution model derived from volatility_index. The three weights always sum to 1.0.

raw_top = v    |    raw_base = 1 − v    |    raw_mid = 1 − |2v − 1|

Normalized: weight = raw / (raw_top + raw_mid + raw_base). Mid peaks at v = 0.5 (top = 0.25, mid = 0.50, base = 0.25).

Projection Factor

Initial throw distance. Weighted average of volatility and diffusion capacity.

PF = 0.6 × VI + 0.4 × di_norm

Fallback chain for di_norm: diffusion_index → VP-based proxy → volatility_index.

Longevity Factor

Total skin persistence. Equal-weighted average of molecular weight and fixative strength.

LF = 0.5 × mw_norm + 0.5 × fs_norm

mw_norm uses canonical range [100, 500] g/mol. Fallback: mw_norm = 1 − VI, fs_norm = mw_norm.

Sillage Factor

Trail persistence, the lingering scent left behind as you move. Peaks for mid-volatility, diffusive, moderately persistent materials (heart notes).

SF = 0.40 × mid_weight + 0.35 × di_norm + 0.25 × longevity_factor

Mid_weight peaks at v = 0.5 (heart zone). High DI = radiates outward. Moderate longevity prevents skin-only fixatives from scoring high.

Concentrate Density (Mix Rule)

Perfumery percentages are by mass (g/g) but every batch is measured by volume (mL). Converting one to the other requires the density of the concentrate. For a mass-based blend of n materials with per-material density ρi the mix density follows the inverse-additive rule:

1 / ρmix  =  ∑ ( pcti / (100 · ρi) )

Derivation: total mass m = ∑ mi, total volume V = ∑ (mii). With pcti = mi/m × 100, density ρmix = m/V. Materials missing density default to 1.0 g/mL with a flag on the formula header.

Why It Matters

The lab sheet converts 10 mL of concentrate into per-material grams. A naive 1.0 g/mL assumption is wrong for any concentrate dominated by light or heavy materials. Examples:

  • A citrus-heavy top, bergamot (~0.88), limonene (~0.84), pulls ρ below 0.90.
  • A vanillin / amber base (ρ ~1.05 - 1.15) pulls ρ above 1.05.
  • Iso E Super, Hedione, Galaxolide cluster around 0.90 - 0.94.

The engine recomputes concentrate_g = volume_ml × ρmix and alcohol_g = volume_ml × 0.789 (ethanol at 20 °C) for every lab sheet. When |ρmix - 1.0| > 0.05 the formula header shows an amber DENSITY ADJUST chip so the perfumer knows the scale weight differs from the volume reading by more than 5 %.

Source: Calvarano, M. & Calvarano, I. (1991) “Density and Refractive Index of Essential Oils”. The 0.05 g/mL threshold is the maximum the engine tolerates before mass / volume confusion produces a noticeable formulation error at 10 mL batch size (±0.5 g).

Section 3b

Physical Parameters, What They Do

Every physical parameter the engine reads has a specific job. Nothing is decorative. This section explains, in plain English, what each one is, where the data comes from, and which engine outputs depend on it.

Parameter Unit What it physically measures What the engine does with it
molecular_weight (MW) g/mol Mass of one mole of the molecule. Heavier molecules diffuse slower and tend to persist longer on skin. Volatility index. Longevity factor (LF = 0.5 · mw_norm + 0.5 · fs_norm).
boiling_point (BP) °C Temperature at which the pure substance vaporizes at 1 atm. The single best proxy for evaporation rate at room temperature. Primary driver of volatility_index. Drives the “top vs base” phase weight calculation.
vapor_pressure (VP) Pa or mmHg @ 25 °C Pressure exerted by the vapor over the liquid at equilibrium. Higher VP = faster evaporation. More accurate than BP for room-temperature behavior. Reference data only; the volatility model does not read it. Projection factor fallback. Logarithmic normalization (range 0.001 - 3000 Pa).
logP (XLogP) dimensionless Octanol–water partition coefficient. Measures lipophilicity, how strongly the molecule binds to skin lipids vs. washes off. Higher logP = stickier on skin. Future fixative-strength inference. Not yet wired into core scoring but stored for analysis.
diffusion_index (DI) 0 - 10 Engine-curated rating of how aggressively the molecule radiates outward in air. Captures sillage power at the molecular level. Projection factor (PF = 0.6 · VI + 0.4 · di_norm). Sillage factor (35 % weight).
fixative_strength (FS) 0 - 10 Engine-curated rating of how well the molecule slows the evaporation of more-volatile partners in the blend (classic fixative role). Longevity factor (50 % weight). Determines drydown persistence.
odor_strength low / med / high Perceived intensity at typical use levels. A low-odor material at 5 % may be quieter than a high-odor material at 0.5 %. Scoring moderation, weights material selection so dominant intent isn't overpowered by a single trace ingredient.
density_g_ml (ρ) g/mL @ 20 °C Mass per unit volume. Determines how many grams fit in a milliliter and vice versa. Mass-weighted mix density ρmix (Section 3). All mL ↔ g conversions on the lab sheet PDF. Amber ADJUST chip when |ρ - 1.0| > 0.05.
flash_point_c °C Lowest temperature at which the substance ignites in air with a spark. UN / DOT shipping classification depends on this (e.g. < 60 °C = Class 3 Flammable Liquid). Lab safety advisory. Shows on the material drawer + flags formulas dominated by low-flash materials for shipping.
refractive_index (RI) dimensionless 1.4 - 1.6 Ratio of speed of light in vacuum to in the substance. Each natural / synthetic has a tight RI range, off-spec RI is an authenticity / adulteration red flag. QC reference only. Stored for the perfumer's bench measurements; not consumed by the generator.
volatility_score 1 - 10 Hand-curated fallback when BP & MW are missing. Higher = more volatile. Used only when BP or MW absent. Normalized to 0 - 1 for VI.
ifra_category integer 1 - 12 IFRA product category for IFRA Standards. Each category has its own concentration limit for restricted materials. Compliance bar on formula detail. Future per-category limit enforcement.
is_eu_allergen boolean True for materials on the EU Cosmetics Regulation 1223/2009 list of 26 declared allergens. Compliance bar & allergen disclosure panel. The full report is part of Studio.
Scientific Grounding

The volatility / phase-weight model is a simplified version of the classical Clausius–Clapeyron evaporation framework, the same physics used in formulation software like Symrise's Phylogen and Givaudan's VirtualOlfaction. Three-factor volatility (BP + MW + VP) is the standard literature model, and this engine deliberately uses two of those three: a published vapor pressure exists for very few catalog materials, and a modeled one distorts the result more than leaving it out does. The 60/40 BP-vs-MW weighting comes from Calkin & Jellinek, “Perfumery: Practice and Principles”, where boiling point is established as the dominant correlate for room-temperature evaporation rate.

Projection (initial throw) follows a weighted average of volatility (faster molecules project further first) and diffusion (some heavier molecules still radiate aggressively because of their molecular shape, e.g. Iso E Super has MW = 234 but legendary diffusion). Longevity is the inverse pattern, bigger / stickier molecules persist.

Sillage (the trail) peaks for heart-zone materials because these have enough volatility to leave the skin AND enough molecular mass to hang in the air around the wearer for a meaningful window. Iconic sillage materials, Hedione, Galaxolide, Cashmeran, Ambroxan, all sit at v ≈ 0.45 - 0.55 with high diffusion indices.

Limits & Honesty

The model is a physics-informed approximation, not a wet-lab measurement. Real performance depends on skin chemistry, ambient temperature, humidity, application method (skin vs. clothing), concentration, and oxidative aging of the ingredients. The engine's projection / longevity / sillage hours are useful for comparing two formulas against each other, not for predicting an absolute hour count on a specific human. Where lab data exists for a real fragrance (e.g. Aventus, Sauvage), engine estimates are usually within ±1.5 hours on longevity and within one bracket on projection.

Section 4

Semantic Scoring

Each material is scored against user intent through a multi-factor model. The final score determines selection priority.

Descriptor Matching (3 tiers)
Match TypeScore ContributionExample
Exact match intent_weight × descriptor_weight Intent: "citrus" → Material descriptor: "citrus"
Subfamily proximity descriptor_weight × 0.4 Intent: "bergamot" → Material: "citrus" (same subfamily)
Family proximity descriptor_weight × 0.15 Intent: "bergamot" → Material: "green" (same family)
Composite Score (6 weighted factors)
FactorWeightWhat it measures
Descriptor score0.38Semantic match to user intent
Volatility fit0.15Alignment with target evaporation profile
Molecular weight fit0.12Physical plausibility for assigned role
Role fit0.08Match between material role and pyramid needs
Baseline weight0.17Preference for materials in a baseline formula (modify mode)
Support material bonus0.10Structural balance contribution

Additional modifiers:

  • Semantic gate: Materials with zero descriptor relevance receive a ×0.2 penalty
  • Inventory boost: +1.2 bonus when "Prefer owned materials" is enabled
  • Deterministic jitter: Seeded per-material noise prevents identical selections across retry attempts
Section 5

Projection, Longevity & Sillage

Formula-level metrics are percentage-weighted averages across all materials:

Projection

Initial throw distance, how far the scent radiates in the first hours.

projection_index = Σ( wi × PFi ) × 100    (0 – 100)
projection_hours = (projection_index / 100) × 8    (0 – 8 h)
Longevity

Total skin persistence, how long the scent is detectable close to skin.

longevity_index = clamp( Σ( wi × LFi ) × 10, 0, 10 )
longevity_hours = (longevity_index / 10) × 24    (0 – 24 h)
Sillage

Trail persistence, the lingering scent left behind as you move. Distinct from projection (initial throw) and longevity (total skin duration).

sillage_index = clamp( Σ( wi × SFi ) × 10, 0, 10 )
sillage_hours = (sillage_index / 10) × 12    (0 – 12 h)

Where wi = material_pcti / 100 (weight fraction).

Hour ceiling rationale:
  • Projection: 8 h, observable projection window for a typical 20% EdP concentration at standard application volume (1-2 sprays).
  • Longevity: 24 h, practical maximum for fixative-heavy orientals and woody compositions. Represents detectable presence on skin.
  • Sillage: 12 h, trailing scent window. Sillage typically persists longer than arm's-length projection but shorter than close-skin detection.
Brightness & Transparency

Composite display metrics computed from volatility profile and role distribution.

brightness = clamp( avg_volatility × 0.65 + (top% / 10) × 0.35, 1, 10 )

Measures perceived freshness of the opening. Driven by volatile materials and top-note concentration.

transparency = mw_factor × 0.60 + role_factor × 0.40

mw_factor = clamp((310 − avg_MW) / 18, 0, 10). 310 g/mol = heavy musk/resin boundary.
role_factor = clamp((top% + mid%) / 10, 0, 10). Low base-heaviness = higher transparency.

Performance Drivers

The formula detail page breaks projection, longevity, and sillage down to each material's individual contribution. Scores are normalized to 100% so you can see which ingredients are driving each characteristic.

contribution = wi × factor_value    (then normalized to 100%)
Section 6

Evolution Curve

The Fragrance Evolution chart models how scent intensity shifts across three phases over an 8-hour window:

PhaseWindowWhat you smell
Opening / Top 0 - 1h High-volatility materials, first impression, fresh burst
Heart / Mid 1 - 4h Medium-volatility materials, core character, floral/spice body
Drydown / Base 4 - 8h+ Low-volatility fixatives, lingering trail, woody/musky warmth

Each material's activity curve is a Gaussian-like function centered at its peak evaporation time (derived from volatility_index). Phase intensity points are the sum of all active materials at each time step, normalized to a 0-10 scale.

The chart is fully deterministic. You can log observed evolution notes per phase using the Evolution tab to compare predicted vs. real-world behaviour.

Section 7

Cost Analysis

Cost is calculated at two levels:

Concentrate Cost
cost_per_gram = Σ( cost_per_grami × percenti / 100 )

Each material's cost_per_gram is multiplied by its formula percentage. Materials without cost data are flagged but don't prevent calculation.

Bottle Cost Breakdown

Given bottle size and concentration percentage, the engine computes:

  • Oil amount: bottle_size × concentration%
  • Alcohol amount: remainder
  • Oil cost: concentrate cost × oil amount
  • Total per bottle: oil cost + alcohol cost + packaging
  • Per-material breakdown: each material's gram amount, cost contribution, and % share
Cost Optimizer

After generation, the cost optimizer suggests lower-cost material substitutions that maintain the same descriptor profile. Substitutes are scored by semantic similarity and filtered to the same role (top/mid/base).

Section 8

Data Quality Assessment

Two scores are computed per material to assess data reliability:

Completeness Score (0-100)

Weighted presence check across 14 fields. Key weights:

FieldWeightFieldWeight
primary_descriptor15boiling_point_c10
molecular_weight10cost_per_gram8
longevity_hours_paper8diffusion_index7
fixative_strength7odor_strength5
Reliability Score (0-100)

Penalizes values outside realistic perfumery ranges:

  • Boiling point not 120-350 °C → -25 points
  • Molecular weight not 100-350 g/mol → -25 points
  • Usage percentage above 50% → -25 points

Formula confidence is the weighted average of all materials' scores, displayed as a confidence label and percentage on each formula page.

Section 9

IFRA Compliance

Each material's formula percentage is checked against its stored ifra_limit. A violation is flagged when:

used_percent > ifra_limit_percent

Violations are classified by severity:

SeverityCondition
Critical No IFRA limit defined (limit = 0 or missing)
High Used ≥ 1.5× limit, or excess ≥ 10 percentage points
Medium Used ≥ 1.2× limit, or excess ≥ 5 percentage points
Low Any other exceedance

IFRA limits represent maximum in concentrate. Always cross-reference with official IFRA standards and verify the application category (leave-on, rinse-off, etc.).

Section 10

Inventory Integration

The inventory system tracks physical materials you own. It integrates with formula generation in two modes:

ModeEffect on Generation
Prefer owned Adds a +1.2 scoring bonus to materials in your inventory. Owned materials are favored when they are otherwise reasonable candidates, but unsuitable materials are never forced.
Owned only Pre-filters the material catalog to only include materials with inventory quantity > 0 mL. The engine then generates exclusively from your physical collection.

Across the app, owned materials are marked with a green dot: on the materials page, in formula composition tables, and in the formula header badge showing "X/Y owned".

Inventory Data Model

Each inventory record is a lot (a specific purchase). Multiple lots can exist per material (different suppliers, dilutions, dates). The inventory page aggregates totals per material and expands to show individual lots. Quantity is tracked in milliliters (mL).

Section 11

Material Data Reference

Each material has up to 25 data fields. The key fields used by the engine:

FieldTypeUsed By
boiling_point_cREAL (°C)Volatility index, phase weights
molecular_weightREAL (g/mol)Volatility index, MW fit scoring
vapor_pressureREAL (Pa @ 25°C)Reference data, projection fallback
logPREAL (XLogP)Stored for future fixative inference
density_g_mlREAL (g/mL)Mix density (Section 3); lab sheet mL ↔ g
flash_point_cREAL (°C)Lab safety advisory; shipping class
refractive_indexREAL (1.4-1.6)QC reference (authenticity); not consumed by generator
diffusion_indexREAL (0-10)Projection factor
fixative_strengthREAL (0-10)Longevity factor
volatility_scoreREAL (0-10)Fallback when BP unavailable
primary_descriptorTEXTSemantic scoring (weight 1.0)
secondary_descriptorTEXTSemantic scoring (weight 0.6)
odor_strengthTEXT (low/medium/high)Scoring moderation
cost_per_gramREAL ($)Cost analysis
average_useTEXTGenerator usage cap
typical_maxREAL (%)Maximum usage constraint
ifra_limitREAL (%)IFRA compliance check
roleTEXT (top/mid/base)Initial role bucket assignment
Data Import

Materials can be imported via CSV at /import_materials. Column headers are matched case-insensitively with alias support (e.g. "CAS #", "CAS Number", "CAS No" all map to CAS_number). Re-importing updates existing records without data loss. See the import page for the full alias reference.

Data Sources & Conflict Resolution

The engine reads physical parameters from two sources, each best at different fields. On conflict, a per-field priority decides which value the materials table keeps.

FieldPrimary sourceSecondary sourceConflict rule
molecular_weight PubChem (lab-measured) GoodScents Trust PubChem; flag if difference > 2%
boiling_point_c PubChem (experimental) GoodScents Trust PubChem; if missing, take GoodScents
vapor_pressure PubChem , Single source only
logP PubChem (XLogP, computed) , Single source only
density_g_ml GoodScents (perfumery-curated) PubChem Trust GoodScents (often supplier-verified); flag if difference > 5%
flash_point_c GoodScents PubChem Trust GoodScents (safety-data sheet sourced)
refractive_index GoodScents PubChem Trust GoodScents
scent_notes GoodScents (perfumery descriptor) , Never overwritten by PubChem (PubChem has no scent data)
typical_use_percent GoodScents (recommended dosage) , Never overwritten by PubChem
ifra_limit IFRA Standards (manual / official) GoodScents Manual IFRA always wins; otherwise GoodScents

All fetched values land in the enrichment_cache table with a source label and a status of pending until an admin reviews and applies them. When PubChem and GoodScents disagree by more than the conflict threshold, the dashboard shows both side-by-side with the perfumery-recommended value highlighted.

Section 12

AI Features

The engine integrates OpenAI models for creative and analytical tasks. AI features are optional, they require an OPENAI_API_KEY environment variable. When the key is not set, all AI UI elements are hidden automatically.

AI Insights (Formula Detail)

Every formula detail page has an AI Insights tab with three capabilities:

FeatureModelDescription
Scent Profile gpt-4.1 Informative description of the opening, heart, and dry-down, what you'd actually smell.
Formula Analysis gpt-4.1-mini Structural balance assessment with one actionable improvement suggestion. Returns a structured SUGGEST: line that powers the "Apply" button.
Name Suggestions gpt-4.1-mini Five creative fragrance names. Each has a "Use" button that renames the formula instantly.
Apply Suggestions

When the AI analyzes a formula, it returns a structured suggestion (ADD, INCREASE, DECREASE, REMOVE, or REPLACE). This renders as an actionable button on the formula page. Clicking it modifies the formula directly: materials are added, removed, or adjusted, percentages are renormalized to 100%, and the change is logged in the AI Change Log visible at the bottom of the AI Insights tab.

Inventory AI Advisor

The Inventory page features a "What Can I Make?" panel. The AI analyzes your current stock, quantities, roles, descriptors, and costs, and suggests 3 realistic formulas you can create using only materials you own. It considers available quantities (won't suggest 40% of something you have 2 mL of), ensures structural balance across top/heart/base, and suggests different archetype styles based on your descriptor coverage.

Persistence

All AI results are saved to the formula_ai_insights table. When you revisit a formula, previous AI outputs are displayed immediately without re-calling the API. Buttons show "Regenerate" / "Re-analyze" / "New Names" to indicate saved results exist. Applied suggestions are logged in formula_ai_changelog.

Cost & Model Selection

One API key accesses all models. The engine uses gpt-4.1 for creative tasks (scent descriptions, inventory suggestions) and gpt-4.1-mini for analytical tasks (formula analysis, naming), balancing quality with cost. Typical usage costs $0.50–2.00/month.