Two audiences, one page. The first half is for anyone using the database through the browser. The second half is for anyone wiring KiwiChem into another application — which is what it was built for: it is a parameter source that happens to have an interface, not the other way round.
1Getting started
Open index.html. No server, no build step, no network. The data are plain <script> tags rather than fetch() calls precisely so that the database works from a USB stick on a site visit with no signal.
Thirteen tabs. Everything KiwiChem holds on a chemical used to arrive in one Results tab, eleven sections deep, with the soil guideline values and the food limits in the same scroll; each dataset now has its own destination and Overview is the way in.
- Inputs
- The periodic table, the compound classes, and every parameter that shapes what the rest of the tabs report.
- Overview
- What the chemical is, the screening assessment if you have entered a concentration, and one line from each dataset with a button through to it.
- Background
- What is in New Zealand soil before anyone put anything there: nationally, by region and by parent material.
- Soil guidelines
- One row per jurisdiction and land-use scenario. The badge on the tab is how many.
- Drinking water
- Standards, guideline values and screening levels from nine instruments.
- Food
- Maximum levels by food group, and the health-based guidance values they derive from.
- Plants
- Leaf-tissue interpretation ranges, transfer factors, phytotoxic soil concentrations and hyperaccumulation thresholds.
- Kd & mobility
- How strongly the soil holds it, by soil type and by condition, and what that implies for leaching — see section 4b.
- Fate & toxicity
- Half-life, physicochemical constants, reference doses and slope factors.
- Cycle
- Where the selection comes from, what it turns into and where it goes, under conditions you set. Modelled, not looked up — see section 5.
- Orbitals
- Why the element bonds the way it does: the molecular orbital diagram of it and one thing it might be bound to. Also a construction — see section 6.
- Compare
- Up to eight chemicals side by side on the same twenty-three quantities.
- Sources
- The jurisdiction keys, the food group definitions, every correction this application has made to a published value, the instrument notes in full, and the reference list. Read this before comparing a value from one jurisdiction with a value from another.
2Picking a chemical
Three ways, all equivalent.
- The periodic table. Click any element. A cell with a heavier border and a red dot has guideline values of its own; a green bar along the foot means the element has a curated biogeochemical cycle; a plain cell has background and plant data but no guideline anywhere in the world. Greyed cells hold nothing. A cell is live if it has any of the four — which is how carbon gets in: it holds no guideline value, no background distribution and no plant concentration anywhere in this database, and it still has the most consequential cycle in it, since every organic Kd in this database is derived from soil organic carbon.
- The class lists. Everything that is not an element, grouped by chemical class — PAHs, BTEX and VOCs, chlorinated solvents, TPH fractions, phenols, PCBs and dioxins, PFAS, organochlorine pesticides, current-use pesticides, and the odds and ends.
- Search. The box in the sidebar takes a name, a CAS number or an element symbol.
Cd,cadmiumand7440-43-9all land in the same place.
Hold Shift, Ctrl or Cmd while clicking to add a chemical to the comparison set instead of replacing the selection. The chips under the heading show what is selected; the first is the one every data tab reports on, and clicking another chip promotes it.
3The inputs, and what each changes
| Input | What it changes |
|---|---|
| Measured soil concentration | Turns on the whole screening section: hazard quotients against every guideline value, position in the background distribution, implied porewater, indicative plant concentration. Leave it blank and none of that is shown, because there is nothing to screen. |
| New Zealand region | Which background distribution the comparison uses. The full regional table is shown either way. |
| Land use scenario | Which soil guideline value the exported parameter bundle picks. If the chemical has no value under that scenario, the bundle falls back to the most protective value in the dataset and says so. |
| Restrict to jurisdiction | Filters the soil, water and food tables and the screening assessment. |
| Protection basis | Human health, ecological, or both. A human-health value protects the people on the site; an ecological value protects the soil biota, plants and stock. |
| Soil organic carbon | Sets Kd for an organic compound through Kd = Koc × foc. Does nothing for a metal, and the application says so rather than pretending otherwise. |
| Soil pH | The Kd reported on the Kd & mobility tab, for the twelve metals with a pH-specific value published, and the pH the Cycle tab runs at. The same number drives both, so a soil set up on one tab is the soil on the other. |
| Bulk density, water content | The solid–solution split, the retardation factor and the porewater concentration. |
4Reading the results
Two conventions run through every table.
An empty cell means no value is published. It never means zero. Nothing in KiwiChem falls back to a default when a value is missing.
Every value carries its confidence, and there are four states. cited was read from the instrument named in the source column when the workbook was compiled. verify was compiled from the literature and not checked cell by cell. checked was compared with the instrument itself in September 2026 and agreed. corrected did not agree, and the value shown is the instrument's; hover it for the value the workbook had and the document it was read from. The Sources tab lists all 85 corrections to the soil guideline values and all 17 to the drinking-water standards.
A note sits on a row only if it is about that row. The source workbooks carry one note per guideline set and repeat it on every row, which is how the cadmium rows came to be annotated with chromium and BTEX. Each row now keeps the part of the note that names its own chemical, or names nothing; the whole note is on the Sources tab, once per instrument.
4bThe Kd & mobility tab
Kd is the ratio of what is on the solid to what is in the soil solution, in litres per kilogram. It is the hinge of every leaching question, and for a metal it is not a property of the element at all: cadmium's runs from about 15 L/kg in an acid mineral soil to 4300 at pH 8, and higher again in a peat. A single number for it is a placeholder for a soil you have not described.
So the tab reports the distribution, in four parts:
- At the soil you have set. Kd at the pH from the Inputs tab where a pH-specific value is published, with the retardation factor and the fraction on the solid phase that follow from it at your bulk density and water content.
- By soil type and condition. Every value the five compilations hold, plotted on a log axis and tabulated underneath: sand, loam, clay and organic soils; pH classes; cation exchange capacity for strontium; extractable iron for Cr(VI); and five soils from pH 4.5 to 8.2 measured the same way for seventy elements.
- What it implies for leaching. The soil concentration that would hold the most stringent drinking-water value in its porewater, worked out for each of those soils in turn, and compared with the New Zealand standard. This used to be a single line computed from a single Kd; the spread down the column is the honest answer.
- Every element. One row per element with a compiled Kd, the soil groups it is lowest and highest in, and what controls it. The noble gases are in it too, saying that nothing is sorbed — which is an answer, not a gap.
For an organic compound none of this applies: sorption really is to organic carbon, so the tab gives Kd = Koc × foc across the range of organic carbon a soil might have, with your own value marked.
5The Cycle tab
Select any element or compound and the Cycle tab shows where it comes from, what it turns into, and where it goes, in the water–soil–plant–atmosphere system. Five sliders set the soil it is doing that in: pH, redox potential, electrical conductivity, temperature and water flux. Every arrow is scaled to the size of its flux, so the diagram is a statement about magnitude and not only about direction, and the arrows redraw as you drag.
Reading the boxes
Each box is a chemical species: not “the solid phase” but Zn–SOM, ZnS, exchangeable Zn. They are coloured by phase — in solution, exchangeable, sorbed, mineral, organic, living biomass — because whether a species is in solution decides whether it leaches, and whether it is a mineral decides whether it is coming back. Hover a box for what the pool is and why it matters; the same notes are in the species table under the diagram.
The boxes are not sized to how much is in them. The model does not solve the equilibrium system and makes no claim about the split. Only the arrows carry magnitude.
Reading the arrows
There are two scales on the diagram and the legend names both.
Solid arrows are mass fluxes in g/ha/yr, computed from a database value through a formula printed further down the page, and drawn on one common scale — so a leaching arrow and an uptake arrow can be compared with each other directly by eye. Leaching, plant uptake, litter return and harvest removal are quantified whenever the underlying Kd or transfer factor exists.
Translucent, dashed arrows are relative indices in which the reference soil is ×1. They say which way a flux moves and roughly how far, and they are not rates. The inputs, the transformations and volatilisation are all indices.
Width is logarithmic in both cases: each doubling of width is roughly a tenfold flux. The fluxes on one diagram routinely differ by four orders of magnitude, and a linear scale would draw all but one of them as a hairline.
Arrows are coloured by what drives the reaction: physicochemical, microbial, plant, gaseous loss and leaching. That is the distinction worth having, because it says which slider to reach for — a microbial reaction responds to temperature, a sorption reaction to pH.
A reaction running at under a tenth of its reference rate keeps its arrow but loses its label. Methanogenesis in a well-drained soil, or sulphide precipitation at +450 mV, is a real pathway that is not currently doing anything, and naming it would crowd out the reactions that are. Drag the redox slider down and the labels appear as the reactions start.
Where the mass fluxes get their concentration
From the measured soil concentration on the Inputs tab if you have entered one. If you have not, from the New Zealand background median for that element — regional if you have chosen a region. The flag above the diagram always says which, and a cycle computed from a background median is a different statement from one computed from your own sample.
Carbon and nitrogen are not in the background survey, so both are tied to the organic-carbon fraction on the Inputs tab: carbon straight from it, nitrogen from it at a C:N ratio of 12. Change the organic carbon and the whole budget follows. If a chemical has no concentration from any route, the diagram still draws but every arrow becomes an index.
The presets
Five soils are worth looking at before you start dragging: well-drained pasture, limed, acid and unlimed, flooded, and saline irrigation. They exist because the interesting behaviour is at the ends of the ranges and it is easy to miss. Watch what flooding does to arsenic, what liming does to cadmium and to molybdenum in opposite directions, and what salinity does to cadmium through chloride complexation.
What is on the page under the diagram
- The form it is in. The dominant species at the pH and Eh set, with the other forms it takes and the conditions that produce them.
- The species, and where each one sits: every box on the diagram, its phase, and what the pool actually is.
- Every flux, with its magnitude, which sliders move it, and what it physically is.
- How the numbers are produced: the reference soil, the shift applied to Kd, each formula, the system assumptions, and an explicit account of what the model does not do.
6The Orbitals tab
Every other tab answers where an element goes. This one answers why. Pick an element on the Inputs tab, choose something for it to be bound to from the dropdown, and the tab draws the molecular orbital diagram of that pair: the valence orbitals of each atom on the left and the right, the molecular orbitals they combine into down the middle, and the electrons in them.
What it is doing
A two-centre extended Hückel calculation. Each valence orbital contributes its valence orbital ionisation energy on the diagonal; every pair of orbitals on different atoms contributes a Wolfsberg–Helmholz off-diagonal term; and the generalised eigenproblem is solved once for each symmetry species about the line between the two nuclei — σ from s, pz and dz2; π from px and dxz; δ from dxy. Orbitals of different symmetry about that axis cannot mix, so nothing is lost by splitting it up, and solving the whole σ block at once is what puts s–p mixing in.
The interaction parameters are not overlap integrals of a real geometry — there is no bond length in the calculation. They were fitted so that the construction reproduces the second-row homonuclear diatomics B2, C2, N2, O2 and F2 in all three respects at once: bond order, unpaired electrons, and the switch in level ordering between N2, where the π pair lies below the σ, and O2, where it does not. That is the only claim they support, and you can check it yourself: run KiwiChem.orbitalSelfTest() in the browser console.
The two bond orders
The tab reports both, because one will not do. The formal count is what every textbook teaches: bonding electrons less antibonding, halved. It is exact for a homonuclear pair, where every orbital is shared evenly between the two atoms. It over-counts a polar bond, because an orbital that has all but collapsed onto one atom — a lone pair in everything but name — still shows a trace of positive overlap and gets counted as a whole bond. The bond order proper is the same count with each orbital weighted by how far it is actually shared. The two agree wherever the formal number means anything, and the weighted one is the one to read for a polar pair. Where the bonding electrons have collapsed onto one atom altogether the tab says so outright: that is an ion pair, not a bond with an order.
What it is good for
The comparison, mostly, and one comparison in particular. Draw cadmium against oxygen and then against sulphur: the bond order rises from 1.34 to 1.72 and the sharing of the occupied bonding orbitals — the number the ionic-versus-covalent flag is read off — from 0.34 to 0.43. Now do the same for calcium, and it barely moves: 0.17 against oxygen, 0.20 against sulphur, and both flagged ionic. Zinc, mercury, lead and copper all behave like cadmium, and copper most of all, its bonding electrons going from 28% on the metal against oxygen to 49% against sulphur. That is the soft-acid preference for a soft base, arrived at from orbital energies rather than asserted, and it is the shortest honest answer to why the chalcophile metals end up in sulphides under reducing conditions while calcium ends up in a carbonate. It is also why this tab is in a database of soil contaminants at all.
What does not carry over is the geometry. This is two atoms in the gas phase; in a soil the same element is six-coordinate, hydrated and sitting on a surface, and the honest picture there is a ligand-field one. Trust the ordering of the levels, the sign of every interaction, which orbital is the HOMO, and the polarisation. Treat the energies in electronvolts as indicative. Do not read a bond energy, a bond length or a transition energy off it: none of the three is computed.
Which elements it covers
Those with a tabulated set of valence orbital energies — 41 of the 71 the periodic table offers, covering the s-, p- and d-block elements that matter most in a soil. The f-block is excluded on purpose: bonding through f orbitals is not a two-centre σ–π–δ problem and drawing it as one would be wrong rather than merely rough. The rest are simply not held, and rather than estimate them the tab says so and draws nothing.
Which pairs it offers
Only pairs that bond. The construction will draw a diagram for any two atoms it has parameters for, and for most pairs that diagram would be a fiction, so the second dropdown lists only elements the first one forms a known compound with, and names the compound where a short one exists — S — Sulfur · CdS, greenockite. Four rules decide it, and each is a claim about chemistry rather than about the model:
- Helium bonds to nothing, so it offers no partners at all and says why. Neither does any pair whose only compound is unknown — mercury and antimony, thallium and phosphorus, sulphur and iodine.
- Two metals are never offered. Brass and bronze are held together by electrons delocalised over the whole lattice, not by a bond between one pair of atoms, and a two-centre diagram of an alloy would say something false.
- An element pairs with itself only where the neutral diatomic is really bound. Be₂, Mg₂, Zn₂, Cd₂ and Hg₂ are van der Waals pairs with a bond order of zero; the Hg–Hg bond is real only in the mercury(I) ion Hg₂²⁺, which is not the neutral pair.
- A metal with a non-metal is offered unless no binary compound or characterised molecule of the two is known. The exceptions are listed in
js/orbitals.js, each one checkable.
Change the element and a partner it cannot bond to gives way to oxygen, which bonds to everything that bonds at all.
7Getting the data out
Export JSON writes the current selection, the inputs, the full parameter bundle for the primary chemical, a bundle for each chemical in the comparison set, the screening result if a concentration was entered, and the complete underlying record. That file is the same structure the API returns, so anything that reads one reads the other.
Copy link puts the whole state in the URL. Everything the application knows lives in the fragment, so a link reopens the same chemical, the same region and the same inputs on someone else's machine:
index.html#chemical=cadmium®ion=Waikato&conc=1.5&foc=0.03
Multiple chemicals are comma-separated, and any of region, landUse, jurisdiction, basis, foc, bulkDensity, waterContent and conc may be given. A query string works as well as a fragment, which is what another application should use when linking in.
The Cycle tab's five conditions travel in the same link, so a soil you have set up can be sent to someone else exactly as you left it:
index.html#chemical=arsenic&pH=6.5&eh=-150&ec=0.3&temp=15&water=1200
pH, eh (mV), ec (dS/m), temp (°C) and water (mm/yr) are omitted from the link when they are at their defaults.
Print lays every tab out on the page at once, including the ones not currently open, so the printed record is complete.
8Using KiwiChem from another application
Load the data files, js/lookup.js and js/api.js. You do not need app.js, the stylesheets or any of the HTML: the interface depends on the data layer and nothing depends on the interface.
<script src="KiwiChem/data/elements.js"></script> <script src="KiwiChem/data/contaminants.js"></script> <script src="KiwiChem/data/properties.js"></script> <script src="KiwiChem/data/toxicity.js"></script> <script src="KiwiChem/data/plants.js"></script> <script src="KiwiChem/data/background.js"></script> <script src="KiwiChem/data/kd.js"></script> <script src="KiwiChem/data/geology.js"></script> <script src="KiwiChem/data/soil.js"></script> <script src="KiwiChem/data/water.js"></script> <script src="KiwiChem/data/food.js"></script> <script src="KiwiChem/data/sources.js"></script> <script src="KiwiChem/js/lookup.js"></script> <script src="KiwiChem/js/api.js"></script>
Everything is then on window.KiwiChem. Names, CAS numbers, element symbols and internal ids are all accepted wherever a chemical is asked for.
The parameter bundle
The call most host applications want. It returns a flat object in which every field carries its value, its unit and its provenance — a parameter without provenance is not usable in a report.
var cd = KiwiChem.parameters("cadmium", {
foc: 0.04, // fraction organic carbon
bulkDensity: 1.1, // g/cm3
waterContent: 0.35, // cm3/cm3
region: "Waikato", // for the background distribution
landUse: "NZ-RURRES", // which soil guideline to pick
jurisdiction: "New Zealand"
});
cd.kd.value // 790 L/kg, the whole-soil median
cd.kd.kind // "total" — see kdProfile for the sorption end
cd.kdProfile.sorption.gm // 150 L/kg, geometric mean across soils (IAEA TRS-472)
cd.kdProfile.sorption.low // { kd: 15, label: "pH < 6.5" }
cd.kdProfile.sorption.high // { kd: 650, label: "Organic" }
cd.kdProfile.controls // why it moves: pH, organic matter, chloride, sulphide
cd.background.median // 0.37 mg/kg, Waikato topsoil
cd.background.p95 // 1.21
cd.soilGuideline.value // 0.8 mg/kg
cd.soilGuideline.note // "Most protective value matching the filter"
cd.plant.transferFactor // 1.0
cd.rfd.value // 0.001 mg/kg bw/day
cd.iarc // "1"Screening
var s = KiwiChem.screen("cadmium", 1.5, { region: "Waikato" });
s.exceedances // 6
s.evaluated // 49
s.worst.row.jurisdiction // "Denmark"
s.worst.quotient // 3.0
s.background.band // "p95"
s.porewater.porewaterUgL // 2.99The rest of the surface
| Call | Returns |
|---|---|
list(group?) | Every chemical, or every chemical in one class. |
groups() | The classes, in display order, with counts. |
elements() | The periodic table, with grid positions and abundances. |
regions() | The New Zealand regions the background is broken down by. |
search(text, limit?) | Names, CAS numbers, classes and symbols. |
resolve(query) | Any of those to the canonical id. |
get(query) | The whole joined record. |
element(symbol) | The same for an element, listed contaminant or not. |
kd(query, foc?, opts?) | Kd with its basis and kind. opts.pH gives the pH-specific sorption value where one is tabulated; opts.kind is "sorption" or "total". |
kdProfile(query) | Everything held for that element: by soil group, by condition, the field compilation, five measured soils, and what controls it. |
kdRows(query, opts?) | The same flattened to one row per condition, each saying which kind of Kd it is and where it came from. |
kdAtPH(symbol, pH, species?) | Sorption Kd at a soil pH, interpolated on log Kd (12 metals, pH 4.9 to 8.0). |
partition(query, opts) | Solid–solution split, retardation, porewater. |
decay(query, fraction?) | Time to a remaining fraction, from the DT50. |
background(symbol, conc?, region?) | The distribution, or a position in it. |
leachingScreen(query, opts) | Soil concentration in equilibrium with the drinking-water standard. |
soilGuidelines(query, opts) | Filtered rows: jurisdiction, scenario key, basis. |
waterStandards(query, opts) | The same for drinking water. |
foodStandards(query, opts) | The same for food, with a food-group filter. |
plant(symbol) | The plant interpretation ranges. |
properties(query), toxicity(query) | The compiled tables. |
jurisdiction(key) | What a scenario code such as NZ-RES means. |
landUses() | Every scenario key, for building a picker. |
reference(key) | A source key to its full citation and URL. |
data | The raw datasets, for anything the helpers do not cover. |
orbitals(a, b), orbitalElements() | The molecular orbital construction, and the elements it can be drawn for. |
selfTest(), orbitalSelfTest() | Counts and referential integrity across every table; and the five diatomics the orbital parameters were fitted to. |
Embedding in an iframe
A host page that does not want to load the data files itself can put the application in an iframe and ask it questions. The reply carries whatever requestId was sent, so several requests can be in flight at once.
var frame = document.querySelector("iframe"); // src="KiwiChem/index.html"
window.addEventListener("message", function (e) {
var m = e.data;
if (m && m.kiwichemReply && m.requestId === 1) console.log(m.result);
});
frame.contentWindow.postMessage({
kiwichem: "parameters",
chemical: "cadmium",
requestId: 1,
options: { foc: 0.04, region: "Waikato" }
}, "*");The requests understood are parameters, get, screen, list and search, taking the same arguments as the direct calls. A reply carries kiwichemReply: true, the requestId it answers, and either result or error. Match on kiwichemReply rather than on kiwichem alone: the reply repeats the request name, so a handler that only checks the name will see its own replies.
KiwiChem/index.html?chemical=cadmium®ion=Waikato&conc=1.5. The application opens on the Overview tab with that selection already made.A subset of the data files is enough when only part of the API is used: a missing dataset reads as empty rather than failing. Leave out kd.js, though, only if no Kd is wanted: without it parameters() falls back to a coarser compiled Kd and kdProfile is missing, without any error.
On this site the API is used by three tools, each loading only the files it needs as <script> tags, and only when first wanted: KiwiSpec fills a trace metal's soil content from the New Zealand background and compares its computed Kd with the published range; KiwiFert's Contaminants tab takes cadmium's background and soil guideline values; and KiwiPest shows drinking-water standards, soil guideline values and fate data in the details of the 24 pesticides both hold. Each pesticide's page here links back to it with Assess in KiwiPest.
9Questions this raises
- Why do twelve jurisdictions disagree by two orders of magnitude about the same element?
- Because they are answering different questions. Some values are screening levels set at a 1-in-a-million cancer risk with no site knowledge at all; some are investigation triggers; some are remediation standards negotiated against what is achievable. The Sources tab says which is which for every one of them, and the spread column on the source workbooks' comparison sheets is there to make the disagreement visible rather than hide it.
- My soil is above the guideline value. What now?
- Nothing that this application can tell you. Exceeding a screening level means the screening assumptions no longer suffice, not that harm is occurring — that is the point at which a site-specific assessment starts. In New Zealand, whether the NES-CS applies at all turns on the land use and the HAIL activity history, not on the concentration alone.
- Why is there no value for my compound?
- Most likely nobody has set one. Of the 273 contaminants here, 214 have no food limit in any of the eight jurisdictions covered, and glyphosate has no numeric soil guideline value anywhere in the twelve. An empty table is a finding.
- Can I use the background concentrations as a cleanup target?
- The 95th percentile of the relevant distribution is the usual upper limit of background, and section 5(9) of the NES-CS turns on whether contaminants are at or below background. Use the geology-based figure rather than the regional one where the element is covered: that is the comparison the Eco-SGVs are built on.
- How do I keep it current?
- Edit the workbooks in
archive/and re-runscripts/extract.py. The files indata/are generated and should never be hand-edited — every one of them says so at the top.