Skip to content

Skill: read a design result

For: an AI agent that just got a 200 back from a MagicON AI design engine and is about to tell a human what it means.

Load this before you summarise a result. A design response is not a number with some prose around it. It is a verdict, a list of things the engine chose for you, and a list of things it wants you to decide. Summarise only the numbers and you will hand your user a clean-looking report over a problem the engine already caught.

Every figure on this page was measured against the production API on 2026-09-09. Nothing here is illustrative.


The one rule

A 200 is not a verdict. It means the engine answered. It does not mean the design works, that every slot was filled, or that you asked a meaningful question.

Measured — an empty body to the transmit-chain route:

curl -X POST https://api.pcbgenerator.com/api/rf-chain/design-sync \
  -H "Content-Type: application/json" -d '{}'

200, "status": "success", a real catalogue PA (CG2H40010F) and an output of 1.0 dBm. You stated no frequency and no target, and it answered anyway — and it said so, in chain.params, which echoes the design point it actually used: {"frequency": null, "target_output_power": 0, "application": null, …}.

The status code tells you the transport worked. Everything that matters is in the body.

Read these four things, in this order, before you write a sentence:

Read Field If you skip it
1 needsPartFlags You report a complete module that has a hole in it
2 warnings and assumptions You present the engine's choices as your user's decisions
3 the power verdict You tell someone a chain met a target it missed
4 the as-built output You quote an aim instead of what the board ships

1. needsPartFlags — the honesty flag

A list of slot names the catalogue could not fill. [] means every slot was genuinely filled from real parts.

"needsPartFlags": []

A non-empty list looks like ["substrate"] — the slot name, and the slot left empty.

⚠ Which routes carry it — measured today: POST /api/receiver/design and POST /api/tr-module/design both return the key. POST /api/rf-chain/design-sync does not return it at all. Its absence there is not an empty list and not a statement that nothing is missing; there is simply no such field on that route, so read that response's warnings instead.

What it is not: a failure, and never a placeholder. The engines do not substitute a plausible part for a missing one. If no catalogue part satisfies a position in the chain, the position comes back named here and stays empty. That is the whole contract: an engine that will not invent a part has to have somewhere to say so.

What to do: name the flagged slots to your user as an open item. Do not fill them yourself. A part number you produce is not a part number the catalogue stands behind, and your user cannot tell the two apart in your prose.


2. warnings and assumptions are disclosures, not errors

Both are lists of plain-language sentences. Neither one means the call failed — a failure is a 422 or a 500, and it arrives as {"detail": "..."} instead of a result.

⚠ Which routes carry which, measured today: warnings is on all three design routes. assumptions is on the receiver route only — /api/rf-chain/design-sync and /api/tr-module/design do not return that key, and their equivalent disclosures ride inside warnings and inside the per-budget disclosures lists.

Measured on the 9.4 GHz receiver design (POST /api/receiver/design, {"rfFrequencyHz": 9400000000, "bandwidthHz": 10000000, "requiredSnrDb": 12}): 10 assumptions and 6 warnings, alongside a complete, fully-filled design.

assumptions are the things the engine chose because you did not state them:

"IF of 1128.0 MHz was CHOSEN BY THE ENGINE (~12% of RF, floored at 5x the signal bandwidth), not supplied. Pass an explicit IF to override."

"LO injection side was not specified; high-side chosen (deterministic default)."

"PAPR assumed 9 dB (modulated/pulsed); pass papr_db to override — ~3 dB would be right for CW."

Report a result without these and you are presenting our default as your user's decision. Each one names the field that would override it, so every assumption is also a question you can put back to your user.

Over MCP, every computing tool also returns the machine-readable form (issue 687): assumed_inputs lists the argument names that fell back to a stock default, and assumed_values gives the value each one stood for (a value the result already echoes, such as thermal's ambient_c, is not repeated there). Example, a bare run_stackup_solver call: target_thickness_mils, impedance_ohms, topology, … — the 62 mil board and the 50 Ω microstrip were ours, not your user's. Say so.

warnings are findings about the design as built:

"BFP840ESD's 21.5 dB is the MIDPOINT of a 9.5-33.5 dB range the record states as one block, not a gain measured at this frequency. The endpoints differ by 24.0 dB."

That is not noise. It says the headline noise figure rests on a mid-range gain number, which is exactly the caveat a human reviewer would want and exactly the sentence a tidy summary deletes.

Measured on the 9.4 GHz transmit chain: 8 warnings with a 200, including

"Isolator/circulator JIM9250T9750VM1R is rated 40.0 dBm average and this design delivers 43.4 dBm average (CW). That is an overstress on the rating's own terms."

A design that ships with a stated overstress is not a broken call. It is a design with a decision in it. Hand the decision over; do not resolve it silently.

Quote them. Do not paraphrase them into reassurance.


3. The power verdict — which field, on which surface

This is the section that matters most, because getting it wrong means telling someone a chain met a target it missed.

Two surfaces carry different field names

HTTPS (POST /api/rf-chain/design-sync) the design tool (design_rf_chain)
verdict field chain.meets_power_target powerCheck.meets_target
meets_power_target present? yes no — the key does not exist
shipped output chain.as_built_output_power achievedOutputDbm
delta chain.output_delta outputDeltaDb

If you are calling with curl, you are on the first column. Read chain.meets_power_target. Looking for powerCheck over HTTPS finds nothing; looking for meets_power_target in a tool result finds nothing.

The verdict is reference-relative, and the two references differ

The same helper judges the same part against two different references, so the two surfaces can return opposite verdicts for one design. Measured at 2.4 GHz, 30 dBm target, application: "wifi":

HTTPS chain.* tool powerCheck.*
selected PA MGA-242740-02 MGA-242740-02 — same part
verdict meets_power_target: false meets_target: true
judged against 33.65 dBm — the output required at the PA, i.e. your target plus the losses after it 30 dBm — your target at the antenna port
PA's usable linear output 33.0 dBm (power_range_floor) 33.0 dBm — same figure
result 0.65 dB short of 33.65 3.0 dB of margin over 30

Both are true. false means this PA cannot deliver the target through the losses that follow it. true means this PA is rated above the target itself. Neither one is the other's error.

⚠ Never report that the two surfaces agree, and never carry a verdict from one into a sentence about the other. The tool discloses its reference in its own note field:

"MGA-242740-02: 33 dBm linear (power-range floor) output — meets the 30 dBm target with 3 dB device-capability margin (PA rated output vs target, before chain losses); saturated 40 dBm."

The bare HTTPS boolean does not. When you have only the boolean, say which question it answers, or say nothing about it.

The HTTPS response tells you the same story in warnings, and this is the sentence to quote:

"PA power: MGA-242740-02 is 0.7 dB short of the 33.65 dBm required PA output (delivers 33 dBm linear output). The reported chain output is a gain-cascade (saturated/CW-basis) figure and does not reflect this shortfall; verify the PA's rated EVM/linear output or choose a higher-power PA."

The verdict is TRI-STATE, and null is the dangerous one

powerCheck.meets_target has three values, not two:

Value basis Means
true e.g. power_range_floor, rated_linear Proven adequate on a figure that is on record
false as above Proven short
null uncertain Unknown. Not "fine".

null happens on a modulated application when the part's record carries no linear or rated-average output figure at all — only saturated / P1dB. The engine will not invent a back-off to fill the gap, so it returns the honest unknown. Measured shape (ADPA9007, 30 dBm, application: "wifi"):

{
  "mpn": "ADPA9007",
  "target_output_dbm": 30.0,
  "basis": "uncertain",
  "usable_output_dbm": null,
  "saturated_dbm": 34.0,
  "p1db_dbm": 33.0,
  "meets_target": null,
  "note": "..."
}

…and the note spells the unknown out rather than papering over it:

"ADPA9007: saturated 34 dBm / P1dB 33 dBm; linear/modulated output for this application is not specified in catalog data — verify the datasheet's rated EVM output before relying on 30 dBm."

null is more dangerous than false, because a null reads as absence of a problem. An agent that treats a missing verdict as a passing verdict reports "no issues" where the honest answer is "unknown — verify the EVM rating". Say "unknown", and pass the note through verbatim.

⚠ On the HTTPS surface the unknown is not visible. chain.meets_power_target is false only on a proven shortfall; an unknown reads as true, and no warning is emitted for it. So on HTTPS:

  • meets_power_target: false → proven short. Lead with it.
  • meets_power_target: true → not proven short. That is not the same as proven adequate.
  • meets_power_target: null → no PA was selected at all, so there was no power judgement to make. Check chain.selected_pa and the warnings before saying anything else about the design.

For a modulated application (wifi, lte, 5g — anything with a high peak-to-average ratio), do not turn true into "the PA meets the target". Say the chain is not flagged short, and ask for the PA's rated EVM/linear output. For a constant-envelope application (radar, CW), the part can be driven to compression and true carries its ordinary meaning.


4. As-built is not the target, and the gap has no fixed sign

Three output figures travel together and they are not interchangeable:

Field What it is
chain.actual_output_power the convergence figure — what the design loop aimed at
chain.as_built_output_power the shipped figure — after the discrete attenuator pads that actually exist
chain.output_delta actual minus target — computed against the aim, not against as-built

Measured, both directions, same route, same day:

Design point target actual_output_power as_built_output_power output_delta as-built vs target
2.6 GHz, lte 30 31.0 30.65 1.0 +0.65
9.4 GHz, radar 40 41.0 41.15 1.0 +1.15
2.4 GHz, wifi (ISM ceiling) 30 30.0 30.0 0.0 0.0

Five things follow, and each one is a mistake agents actually make:

  1. Quote as_built_output_power. It is what the board ships. The design tool headlines this same figure as achievedOutputDbm.
  2. output_delta is not the shipped margin. At 2.6 GHz the delta is 1.0 while as-built is only 0.65 dB over target — the delta is measured against the aim, and the pads then took 0.35 dB more. The chain says so in a warning of the form "Discrete attenuator pads total X dB vs Y dB computed — downstream stages are driven Z dB low."
  3. The gap is not always a shortfall, and it is not always there. At 9.4 GHz as-built sits 0.15 dB above the design figure; at 2.6 GHz, 0.35 dB below. Never write a sentence implying the design always falls short of target, and never write one implying it always hits it exactly. Report the two numbers.
  4. At a conducted ceiling the engine makes up the pad itself — when it may. If the design sits on an FCC limit and the input level is the engine's to set (no real VCO, no stated input power), it raises that input by the pad shortfall so the board ships the limit exactly — the 2.4 GHz row. It says so: "Input drive raised …". When a stage would be pushed into compression it leaves the design as it was and says why: "Input drive not raised …", beside the "driven … low" warning. (Measured 2026-09-26, change two-way-source-trim.)
  5. Know whose input level it is. Unless the user states the power their radio or signal source delivers (input_power_dbm), the engine assumes one, and the FIRST warning says so: "Input power ASSUMED at 0.15 dBm (not stated) …" — the level the design ships with. The source's BOM line then reads "assumed input power", never "user-supplied". A stated level is fixed: the engine designs around it and never moves it, so at a ceiling the pads' rounding can leave the board short, disclosed by the "driven … low" warning. Measured 2026-09-27 on the 2.4 GHz / 30 dBm drone (change mcp-input-power):
Input Driver, pad Delivered Total
assumed (0.15 dBm) ADL5521, YAT-3A+ 30.0 $287.23
stated 5 dBm BT09E, MFA1007Q3A 29.3 $298.21
stated 0 dBm TQL9093, AC150-2R25 29.9 $406.12
stated −2 dBm TQP3M9028, YAT-9A+ 29.05 $289.93
stated −10 dBm BGAP2S20A, YAT-7A+ 29.85 $282.94

An input_power_dbm equal to the target output, or one the user never said, is not used and a warning says why.

The T/R module route reports the same idea under its own names — transmit.targetOutputDbm 40.0 against transmit.asBuiltOutputDbm 40.65, measured at 9.4 GHz.


5. A regulatory clamp is a compliance action, not a failure

When a design point sits in a band with a conducted-power ceiling, the engine holds the design at the limit rather than aiming past it, and says so. Measured at 2.4 GHz, 30 dBm:

"Output aim clamped to the FCC 15.247 conducted limit (30 dBm, 2400-2483.5 MHz ISM): the standard +1 dB convergence margin would exceed it"

That is the engine choosing to be legal. It is the reason the 2.4 GHz row above aims at 30.0 rather than 31.0, and it is why the 9.4 GHz radar row — outside any ISM band — keeps its +1 dB aim.

Do not report a clamp as an unmet target or a design defect. Report it as what it is: the target was held at the regulatory ceiling. If your user asked for more power than the band allows, that is the sentence they need, and the engine has already written it for you.


Over MCP: the same result, different places to look

If you reached these engines through the MagicON MCP server (https://api.pcbgenerator.com/mcp/, see the MCP guide) rather than with curl, everything above still holds — the engines are the same — but the fields sit in different places, and extra keys ride on the result.

Where the fields are

Tool Where its fields sit needsPartFlags
design_rf_chain top level: achievedOutputDbm, powerCheck, warnings not returned — the same as the HTTPS route. Read warnings
design_rf_receiver inside result: result.warnings, result.assumptions, result.cascade result.needsPartFlags
design_tr_module inside result: result.transmit.asBuiltOutputDbm, result.budgets, result.warnings result.needsPartFlags

Looking for needsPartFlags at the top level of a receiver or T/R result finds nothing. That is not an empty list. Look one level down.

Two output figures on a transmit chain, and which to quote

design_rf_chain returns both output figures, under names that say which is which:

Field What it is Its HTTPS twin
achievedOutputDbm as built — what the board ships. Quote this one chain.as_built_output_power
outputDeltaDb achievedOutputDbm minus the target —
engineDesignOutputDbm the design loop's aim chain.actual_output_power
engineDesignDeltaDb the aim minus the target chain.output_delta

⚠ outputDeltaDb and output_delta are measured from different references: the MCP one from what the board ships, the HTTPS one from the aim. They often agree. Measured on production at 3.5 GHz / 33 dBm, both were 1.0 (achievedOutputDbm 34.0, engineDesignOutputDbm 34.0), and at 2.4 GHz / 30 dBm both were 0.0 — but do not carry a delta from one surface into a sentence about the other without checking which one it is.

grounding — whose numbers went in

Every result carries a grounding key:

grounding.mode Means
user_stated You passed user_request, and the numbers you sent were checked against your user's own words
caller_asserted You did not. The numbers you sent were taken on your word

Either way, the figures the engines computed are unaffected. The label is about the inputs. If a result says caller_asserted, the design point it answered is yours, not your user's — say so, or go back and ask.

autoSolvedStackup — a board you did not ask for

A design_rf_chain call made before any stackup has been solved in the session comes back with an autoSolvedStackup key. It means the server also solved a PCB stackup, so that the PDN and compliance checks have a board to run on. Measured on production at 2.4 GHz:

"autoSolvedStackup": {
  "layerCount": 4,
  "layerCountSource": "server default — the caller stated none",
  "frequencyGhz": 2.4,
  "boardLaminate": {"core": "MEGTRON6", "prepreg": "FR370HR"}
}

Its own reason field says what to do: "Tell the user this happened — they did not ask for a stackup, and its layer count is a server default." Do.

It also names the board's laminate in boardLaminate. When the design ran before the solve, the BOM's substrate line is the catalog laminate the loss budget used, not the board's, and a laminateNote says so. Measured at 3.5 GHz / 33 dBm:

"boardLaminate": {"core": "MEGTRON6", "prepreg": "FR370HR"},
"laminateNote": "The BOM's substrate line is not the board's laminate — the
  design ran before this solve. The board laminate is MEGTRON6 per the solved
  stackup, but the chain's loss budget and thermal estimate were computed
  against the catalog laminate RO4003C-50ohm-SL. The chain was not re-solved
  against MEGTRON6."

When you list the parts, say which one the board is made of.

A refusal is a result, not a crash

Over MCP a refusal comes back as the tool result with the error flag set: a short code under error and, where the tool explains itself, the message under detail. Measured, a design_tr_module call with no arguments:

{"ok": false,
 "error": "needs_frequency_or_front_end",
 "detail": "design_tr_module needs either a frequencyGhz to design a module at,
            or an explicit txPeakPowerDbm and isolationDb for a front end you
            already have"}

Treat it exactly like a 422: the message names what is missing, and it is a question for your user. An over-limit call is different: it returns HTTP 429 with a Retry-After header — wait, do not retry in a loop.


Before you send your summary

  • needsPartFlags quoted where the route carries it — [], or the slot names as open items
  • Over MCP: needsPartFlags read from result.needsPartFlags on a receiver or T/R result, and a caller_asserted or autoSolvedStackup passed on to your user
  • Over MCP: the draft passed to check_grounding, and every figure it flagged either replaced with the engine's own value or marked as not tool-derived
  • Every assumptions / disclosures entry that shaped a headline figure passed through
  • warnings reported as findings, not deleted or softened
  • The power verdict named with its reference, or not stated at all
  • A null / uncertain verdict called unknown, never fine
  • The output figure you quoted is the as-built one — chain.as_built_output_power, or transmit.asBuiltOutputDbm on a T/R module
  • A regulatory clamp described as compliance, not as a miss
  • Every number in your summary came out of the response body — none of them came out of you

That last one is the whole point. The engines compute so that you do not have to, and the value of the result is that a human can trace every figure in it back to a published component spec. A figure you supplied looks exactly like one we computed once it is inside your paragraph.