param.typ

a datasheet parameter's TYPICAL value in the SI base unit, the third member of the min/typ/max triple. A typical value is what the part usually does, never a guaranteed limit, so it is its own relation rather than a column on param.range (needs --params)

What it is

param.typ(mpn, symbol, typ) yields one row per parameter of a datasheet spec that states a TYPICAL value, carrying that value in the SI base unit. It is the third member of the min/typ/max triple, and the only one param.range(mpn, symbol, kind, min, max) does not carry.

A parameter stating no typ emits no row here, so absence stays absence rather than arriving as a zero. Like every relation in this tier it is EMPTY unless agni runs with --params <dir> pointing at a seeded PartSpec corpus.

For hardware engineers

An electrical characteristics table usually prints three columns. The MIN and MAX are what the vendor guarantees across the stated conditions, and they are what a limit check compares against. The TYP is what a part off the middle of the distribution actually does at room temperature, and it is not a promise about the part on your bench.

That distinction is why this is a separate relation rather than another column. A typical quiescent current tells you what the board will draw in the usual case, which is exactly what you want when sizing a battery or estimating thermals. It is the wrong number to design a protection threshold against, because a part at the edge of the distribution is still in spec and still ruins the calculation. Reach for param.range when you need a guarantee, and for this when you need an expectation.

For software engineers

facts.Row carries two numeric slots, Num and Min, and param.range spends both on the two bounds. So a typ had nowhere to land and was dropped, surviving only inside a rendered string where nothing could bind or compare it. Nothing documented that it was gone (agni issue 545).

The obvious repair is a third numeric slot on the row, and it is the wrong one. The tuple is shared by 44 graph-tier relations that need nothing of the sort, and the datasheet record has thirteen fields and four repeated parts, so no arity fixes it. The relational answer to a wide record is a KEY rather than a wide tuple: several narrow relations sharing (mpn, symbol), joined when a query wants more than one of them. param.unit was already that shape, adopted under CONSTRAINTS C24 for a safety reason rather than to conserve a slot. See DECISIONS.md, “The datasheet tier is normalized into narrow relations, never flattened into a wider tuple”.

Separating it is also the safer shape on its own terms. A typ sitting between min and max in one tuple reads as a bound, and a rule comparing a rail against it would look perfectly ordinary while reporting a confident wrong answer. Its own name puts that choice at the call site.

The number is reduced to its SI base unit by param.InBaseUnit, the same conversion param and param.range go through, and the row carries BaseUnit so an ordering comparison can refuse amps against volts. A parameter whose printed unit has no known scale yields a row with no number, so its symbol and citation survive.

Go projector

paramTypFacts in stdlib/relations/facts.go goes through perJoinedSpec, which iterates Model.Components(), reads each component’s MPN via Model.ComponentMPN and its spec via Model.PartSpec, and dedupes by MPN. specParamTypRows does the per-spec work and is shared with SpecLibFacts, so agni query --speclib answers the same relation with no design loaded. One row per parameter that states a typ, none for a parameter that does not, and empty without --params.

Datalog

Needs --params. Every typical value in the joined parts:

param.typ(?mpn, ?sym, ?typ) => ?mpn, ?sym, ?typ

The typ beside the window it sits inside, which is the join that shows what the relation is for. A part whose typical value sits near one edge of its own guaranteed range is worth a second look:

param.typ(?mpn, ?sym, ?typ), param.range(?mpn, ?sym, "recommended_operating", ?min, ?max)
  => ?mpn, ?sym, ?min, ?typ, ?max

Parts stating a typ the conversion table could not scale, so the row exists with no number:

param.typ(?mpn, ?sym, ?typ), absent(?typ) => ?mpn, ?sym