← Work

Costing aspirin

One recorded run of a pharmaceutical costing engine, start to finish. A CAS number goes in; a quotable Excel sheet comes out. Every number below is the number the engine actually produced — including the one it labels as a placeholder, and the seven gaps it wrote into its own audit log.

SciQ v1.0 · deterministic engine over IBM RXN 1 Jun 2026 Aspirin, CAS 50-78-2, 100 kg basis 9 materials · 7 audit flags

The job

A contract manufacturer is asked to quote a molecule. Someone has to turn a synthetic route into a bill of materials, price every line, add the development and plant effort, and return a number per kilogram. Done by hand it takes days, and the arithmetic is spread across a spreadsheet nobody re-derives.

SciQ automates the business half of that, not the chemistry. It does not invent routes and it does not price by judgment. It takes a route, expands it into charges on a fixed template, and records where each figure came from. The design constraint that matters is the last one: a costing tool that cannot be audited is worth less than the chemist it replaced, because the chemist could at least be asked.

This page is a frozen run. Nothing on it computes; it replays a run recorded on 1 June 2026, and the two Excel files it produced are linked at the bottom so the formulas can be opened and checked.

Fifteen routes, and why a person still chooses

The first call is retrosynthesis: hand IBM RXN a target and get back candidate routes with confidence scores. For aspirin it returned fifteen. All fifteen scored above 0.995 and every one was tagged high-confidence; ten of them sit above 0.999.

Confidence of the fifteen returned routes All fifteen retrosynthetic routes score between 0.9957 and 0.9999. The spread across the whole ranked list is four ten-thousandths, so the ranking separates nothing. 0.995 0.996 0.997 0.998 0.999 1.000 worst 0.9957 best 0.9999 15 routes · total spread 0.0042 · every one tagged H
Every route IBM RXN returned for aspirin, by its own confidence score. Ten of the fifteen sit above 0.999 and the whole list spans 0.0042, so the ordering carries no information a chemist could act on.

That is not a ranking. The entire spread from best to worst is 0.0042, which is well inside the noise of a model scoring its own suggestions. A product that presented these as first, second and third choice would be inventing a distinction the numbers do not support.

Reading what the routes actually contain explains why. All fifteen are the same reaction — acetylation of salicylic acid — differing only in the additive:

Acetyl donorAdditive that distinguishes the routeRoutes
Acetic anhydridepyridine, sulfuric acid, sodium hydroxide, sodium acetate, acetic acid, HCl, water, or nothing13
Acetyl chloridenone1
[Ac]O[Ac]none1

The last row is the interesting one. In SMILES, a bracketed Ac is the element actinium, so [Ac]O[Ac] parses as an actinium oxide, not as acetic anhydride. The model emitted a route whose starting material does not exist in any catalogue, and scored it 0.9958 — a confidence indistinguishable from the fourteen valid ones.

Consequence for the product. Route selection is a human gate, permanently. The engine's job is not to pick; it is to make each candidate cheap to reject — which means showing the precursors, not just a score.

The run that was costed

The costed run took a different route from a separate retrosynthesis: a one-step oxidation of an aldehyde to the acid, from 2-acetoxybenzaldehyde, scored 0.964. From there everything is deterministic — no model touches a number again.

FieldValue
TargetAspirin · CAS 50-78-2 · MW 180.16 · PubChem CID 2244
RouteAldehyde to acid oxidation · 1 step · confidence 0.964
Basis100 kg, contingency 0.2 → 120 kg charged through the stage
Procedureadd · add · add (dropwise) · quench · add · extract · wash · wash · dry
RXN synthesis6a1d4b487f6b9b3ec2b3da83

What actually gets charged

Nine materials. Three are costed on a mass basis, from molecular weight and equivalents. Six are costed on a volume basis, from density and a litres-per-kilogram multiple — because liquids are bought by volume, and a cost sheet that converts them to moles is answering a question nobody asked.

Kilograms charged per material Nine materials sorted by charge. Solid bars are costed on a mass basis from molecular weight and equivalents; outlined bars are costed on a volume basis from density and a litres-per-kilogram multiple. Solvent and aqueous charges dominate the sheet. brine 1,440 water (add) 1,197.60 water (wash) 1,197.60 ethyl acetate 1,074 isopropanol 942 acetone 941.40 Jones reagent 170.62 2-acetoxybenzaldehyde 109.34 Na2SO4 94.61 solid = mass basis · outline = volume basis · 7,167 kg charged for 120 kg out
What the sheet charges, sorted. Solid bars are costed on a mass basis from molecular weight and equivalents; outlined bars on a volume basis from density and a litres-per-kilogram multiple. The substrate is the eighth-largest line.

The chart makes the shape of a real process visible: 7,167 kg of material is charged to produce 120 kg of product — sixty kilograms in for every kilogram out, almost all of it solvent, water and brine. The substrate is the eighth-largest line on the sheet. Any costing tool that models only the reactants is modelling the smallest part of the bill.

MaterialCASMWDensityBasisCharge (kg)
2-acetoxybenzaldehyde5663-67-2164.16mass · 1.0 eq109.34
Jones reagent256.15mass · 1.0 eq170.62
Na2SO47757-82-6142.04mass · 1.0 eq94.61
Acetone67-64-158.080.7845volume · 10 L/kg941.40
Isopropanol67-63-060.100.785volume · 10 L/kg942.00
Water (reaction)7732-18-518.020.998volume · 10 L/kg1,197.60
Ethyl acetate141-78-688.110.895volume · 10 L/kg1,074.00
Water (wash)7732-18-518.020.998volume · 10 L/kg1,197.60
Brine76.461.2volume · 10 L/kg1,440.00

Two formulas, written into the sheet

The engine does not compute quantities and paste values. It writes Excel formulas, so the sheet stays live in the customer's hands and every figure can be traced by clicking the cell. There are two, one per costing basis:

mass basis     J = ($J$28 / $D$28) * D<row> * H<row>
volume basis   J = $J$28 * E<row> * H<row>

$J$28  stage charge, kg      120     = 100 kg target + 20% contingency
$D$28  product MW            180.16
D      material MW           164.16  (2-acetoxybenzaldehyde)
E      density                       (volume rows only)
H      equivalents, or L/kg   1.0

Worked, for the substrate: (120 ÷ 180.16) × 164.16 × 1.0 = 109.34 kg. Six more formulas cover recovery, tax, line total, contribution and the roll-ups. Tax is 10% — the rate every historical sheet in the client corpus uses; earlier generic code had defaulted to 18%, which would have been wrong for these customers by a wide margin.

One thing the single-step case hides: the assumed molar yield (0.9 here) is recorded on the stage row but does not enter any quantity in this run. Yield propagates between stages, through a back-calculation from the stage above. With one stage there is nothing above it, so the target quantity enters directly and the yield is inert. It would bite on a four-stage route.

Where every number came from

This is the part that matters, and the reason the sheet is worth more than the number at the bottom of it. Each line carries the source it was resolved from, and each price will be routed to a source tier when the pricing layer is connected: a recent quote from this client's own history first, a catalogue price second, a bulk commodity price last.

MaterialIdentity (CAS, MW)DensityPrice routes to
2-acetoxybenzaldehydePubChem, by SMILESn/a — mass basisClient quote history
Jones reagentPubChem, by SMILES (no CAS)n/a — mass basisCatalogue
Na2SO4PubChem, by namen/a — mass basisCatalogue
BrinePubChem, by name (no CAS)solvent tableCatalogue
AcetonePubChem, by SMILESsolvent tableBulk commodity
IsopropanolPubChem, by namesolvent tableBulk commodity
Ethyl acetatePubChem, by namesolvent tableBulk commodity
Water ×2PubChem, by namesolvent tableBulk commodity
Client quote history — preferred, under 60 days old Catalogue Bulk commodity

Densities deserve a note. The retrosynthesis returned none — it gives amounts in millilitres and leaves the conversion to you. The six volume-costed lines were filled from a fixed table of solvent densities lifted from the customer's own template, and written into the sheet as numbers rather than as the template's original VLOOKUP. That is a deliberate trade: a lookup that misses a name returns #N/A and the sheet arrives at the customer broken. A resolved number cannot fail on their machine, and the audit log carries the record of where it came from.

The gap ledger

Seven gaps were recorded during the run. Six were closed; one was not. Exactly one cell in the entire spreadsheet is shaded red — H15, the equivalents figure the engine had to assume.

GapMaterialsResolutionStatus
No density supplied acetone, isopropanol, water ×2, ethyl acetate, brine Filled from the canonical solvent-density table Closed
No usable equivalent supplied Na2SO4 Written as 1.0, cell H15 shaded red, remark EQ_ASSUMED_1.0 Open

The drying agent is a real gap, not a rounding concern: at 1.0 equivalents it is charged at 94.61 kg, and the true figure depends on how wet the organic layer is — a number the model has no basis to produce. So the engine does not produce one. It writes the assumption, marks the cell, and puts the line in the audit log where a chemist will see it before the quote leaves the building.

Never let an assumed 1.0 pass as a real value.

That rule is the whole thesis in one line. The alternative — silently filling the number — produces a sheet that looks complete and is quietly wrong, which is the failure mode that makes chemists distrust automation in the first place.

From cost to price

The second sheet converts raw material cost into a quotable price: development and plant effort priced as full-time-equivalent weeks, then overhead and margin, then the fixed adders a plant actually bills for.

How the price per kilogram is built A waterfall from raw material cost to total price. The raw material bar is hatched because it is a labelled sample placeholder, not a quoted price; every bar above it inherits that placeholder. Raw material +85,000 Manufacturing +30,350 Cost of product 115,350 Overhead + margin +49,436 Adders +7,000 Total, rounded up 172,000 hatched = sample placeholder · 172,000 ÷ 100 kg = $1,720 / kg
Raw material cost to price per kilogram. The raw material bar is hatched because it is a labelled sample placeholder, not a quoted price — every figure stacked on top of it inherits that.
EffortWeeksFTE$/FTE-wkUSD
Process R&D52.07507,500
Analytical R&D80.47502,400
Process engineering80.27501,200
Project management40.5250500
Manufacturing support251.075018,750
Manufacturing cost30,350

Price is cost ÷ (1 − overhead − margin), at 15% and 15%, which grosses 115,350 up to 164,785.71. Adders — capex, effluent treatment, analytical columns and shipment — bring it to 171,785.71, and the sheet rounds that up to the nearest thousand: $172,000, or $1,720 per kilogram.

The number is not a quote, and the sheet says so. The raw material cost of $85,000 is a labelled sample. SciQ v1.0 deliberately ships with the unit-cost column blank — the raw material total in the bill of materials is $0, because pricing is a separate layer that was not yet connected. Every figure above inherits that placeholder. A costing demo that quietly substituted plausible prices would demo better and be worth nothing.

How it was checked

The engine writes formulas, so the obvious failure is a sheet full of #REF! that nobody opens until the customer does. The verification step is to make something other than the engine do the arithmetic: each generated file was recalculated headlessly in LibreOffice and read back.

Every cell resolved, no formula errors, and the recomputed values matched the engine's own — 109.342806394316 kg of substrate against a predicted 109.34, and $1,720 per kilogram. One discrepancy did surface and is worth naming: the engine's exported JSON records $1,717.86, because it omits the sheet's round-up. The Excel file is the source of truth by design, so $1,720 is the number — but the two artifacts disagreeing at all is exactly the kind of drift that only appears when you recompute rather than trust.

What this run does not show

One stage, one molecule, a public one. Aspirin was chosen because it is in the public domain and no client route is exposed by publishing it; the same engine reads four- and five-stage sheets, which are where yield propagation and stage roll-ups actually get tested.

Pricing is not connected. Sales-offer generation is not shown. And the architecture has since moved on: relying on a model to predict routes was demoted in June 2026 in favour of retrieving real, patent-cited reactions from a corpus — a decision this run helped make, since fifteen indistinguishable confidence scores and one actinium-based starting material are a poor foundation for a costing tool. The deterministic half below the route — the part on this page — is unchanged.

The artifacts

Both spreadsheets are the files the run produced, formulas intact. The JSON is the run record the two figures above are drawn from.

Bill of materials (.xlsx) Cost and price (.xlsx) Run record (.json)
Recorded 1 June 2026 · SciQ v1.0 · Gagan Gujral · other work