Reports whether two variants represent the same purchasable unit — used to detect a supplier-listed parent among the variants when their ids/skus differ. Compares pack size (quantity + case-insensitive uom); when either lacks a size, falls back to rounded USD price equality.
Resolves the images to display for a product, falling back from real photos to a single NCI CACTUS structure depiction built from the first available chemical identifier (CAS → SMILES → IUPAC name).
Reports whether a product has any content worth revealing in the expanded
detail panel: a resolvable image, at least one variant, or any populated
detail field. Drives both getRowCanExpand and the expander column so the
toggle only appears when expansion would show something.
Reports whether a variant represents the same purchasable unit as its parent product — i.e. the supplier lists the parent among its own variants. Matches on price-history identity first (variantSeriesKey folds in the genuine vs. inherited id, then title/quantity/sku); some suppliers give the parent its own id/sku that differs from the matching variant's, so it also matches when the two are the same purchasable unit (see samePurchasableUnit).
Resolves the distinct purchasable units to show for a product: its variants, with the parent product prepended unless a variant already represents the same unit (see isParentPurchasableUnit), avoiding a duplicate row.
Builds the display title for a flattened variant row. Some suppliers name a
variant with the full product name plus size ("NaBH4, min 95%"), others with
just the size ("100g"), which is meaningless as a standalone row title. When
the variant title doesn't already contain the product name, the product name
(with its own quantity stripped) is prepended. Both titles have their quantity
removed before the containment check, so "Some Product 10g" (parent) and
"Some Product 100g" (variant) still count as already-named and aren't doubled
up into "Some Product 10g Some Product 100g".
Flattens each product into standalone Product rows for the results
table's ungrouped display mode. Every product yields a parent row (the
product itself, keeping its variants so the detail panel can still list them)
plus one variant row per variant that isn't the parent's own purchasable
unit. Each variant row inherits the product's shared fields (CAS, formula,
images…) with the variant's own price/quantity/identity overlaid, carries a
Product.parentProduct back-reference, carries its resolved
Product.priceSeriesKey (so the results table can look up the variant's
own price history), gets a disambiguated title (see variantRowTitle), and
drops the nested variants array so it stays a leaf. Products without variants
pass through unchanged. This is a presentational transform only — it never mutates
the inputs or the stored product data.
Builds a copy/export-friendly plain object from a product: internal fields are
dropped, the price is rendered with its currency symbol, and every image list
(on the product and each variant) is split into images/thumbnails href
arrays so the copied output isn't cluttered with { href, type } pairs.
A resolved image for a product's detail panel: the source shown in the thumbnail box and the (usually larger) source shown enlarged in the gallery modal when the thumbnail is clicked.
Reports whether a value is meaningfully populated: not null/undefined, not an empty string, and not
NaN. Used to gate optional detail rows so blank fields are skipped in the panel.