1"""Verification checks on a finished Layer X burn: does it conserve what it must? (DATA-CONTRACT 3, ``vv``)
3A burn that converges, plots smoothly and violates no assertion is not evidence (CLAUDE.md). These
4checks re-derive three balances from the recorded result, by a path independent of the twin's own
5bookkeeping where one exists, and report how far each fails to close:
7* **Propellant mass**, per side: what was loaded = what the chamber burned + what is left in the
8 tank + what the drawing's feed lines hold. ``burned`` integrates the chamber's flow (or, with the
9 full network recorded, the injector branches') step by step; ``loaded`` and ``residual`` are the
10 tank's own inventory. The twin's network is algebraic (no liquid inventory in a line), so the
11 trapped mass is the same at T-0 and at burnout: it enters both sides, and the error is the tank
12 inventory against the integrated flow. It is reported because a stand fills its lines too.
13* **Pressurant mass**: gas out of the bottle = gas gained by the ullages + gas vented. The bottle
14 figure is the twin's vessel state; each ullage is **re-priced with CoolProp** from the recorded
15 pressure, gas temperature and fill (``rho(p, T) * V_tank * (1 - fill)``), so the check does not
16 read the twin's ullage mass back. Vented gas needs the full network recorder (``result.network``);
17 without it the vents are taken as shut, which the burn plan does.
18* **Gas-side energy** (first law over the pressurant: bottle, lines, ullages), reported, never
19 graded: see :func:`energy_balance` for what it closes and what it cannot.
20* **Convergence** of the outer loops, per pass, from ``result.passes``.
22Every relative error is in percent of the quantity that entered the balance (the energy residual: of
23the expulsion work, see :func:`energy_balance`). The thresholds that grade them live with every other
24limit, in :data:`engine.layerx.diag.limits.THRESHOLDS`.
26Sources: mass conservation for a control volume and the first law for an open system,
27dU/dt = sum(m_in h_in) - sum(m_out h_out) + Q - p dV/dt (Moran & Shapiro, Fundamentals of Engineering
28Thermodynamics, ch. 4, the control-volume energy rate balance). Properties: CoolProp
29(Bell et al., Ind. Eng. Chem. Res. 53(6), 2014).
32from __future__
import annotations
35from typing
import Any, Dict, List, Mapping, Optional, Sequence, Tuple
37PSI = 6894.757293168361
40COOLPROP = {
"oxygen":
"Oxygen",
"ethanol":
"Ethanol",
"nitrogen":
"Nitrogen",
"helium":
"Helium",
41 "water":
"Water",
"lox":
"Oxygen",
"ln2":
"Nitrogen",
"gn2":
"Nitrogen",
"he":
"Helium"}
44def _num(v: Any) -> Optional[float]:
45 if v
is None or isinstance(v, bool):
49 except (TypeError, ValueError):
51 return f
if math.isfinite(f)
else None
54def _dig(d: Any, path: str) -> Any:
55 for part
in path.split(
"."):
56 if not isinstance(d, Mapping):
62def _pct(err: Optional[float], ref: Optional[float]) -> Optional[float]:
63 if err
is None or ref
is None or ref == 0.0:
65 return 100.0 * err / ref
68def _species(name: Optional[str]) -> Optional[str]:
71 return COOLPROP.get(str(name).lower(), str(name))
74def _steps(series: Mapping[str, Any]) -> List[float]:
75 t = series.get(
"t")
or []
77 if isinstance(dt, list)
and len(dt) == len(t):
78 return [float(v)
for v
in dt]
80 out = [t[i] - t[i - 1]
for i
in range(1, len(t))]
81 return ([out[0]]
if out
else []) + out
87def feed_lines(document: Mapping[str, Any], tank_id: str) -> List[Dict[str, Any]]:
88 """The drawing's lines from ``tank_id`` to the engine: the walk the propellant takes, edge by
89 edge, while the next symbol carries the tank's fluid (a fill or vent line, which turns to the
90 pressurant, is not followed). Each line as ``{id, length_m, bore_m, volume_m3, provenance}``;
91 a line missing a length or bore is returned with ``volume_m3`` None."""
94 nodes = {n.get(
"id"): n
for n
in (document.get(
"nodes")
or [])
if isinstance(n, Mapping)}
95 edges = [e
for e
in (document.get(
"edges")
or [])
if isinstance(e, Mapping)]
96 fluid =
_dig(nodes.get(tank_id),
"data.fluid")
100 def is_engine(node_id: str) -> bool:
101 return str(
_dig(nodes.get(node_id),
"data.componentType")
or "").upper() ==
"ENGINE"
103 out: List[Dict[str, Any]] = []
107 here = frontier.pop()
109 if e.get(
"source") != here
or e.get(
"target")
in seen:
111 target = e.get(
"target")
112 if not (is_engine(target)
or _dig(nodes.get(target),
"data.fluid") == fluid):
114 params =
_dig(e,
"data.params")
or {}
116 def si(key: str) -> Tuple[Optional[float], Optional[str]]:
118 if not isinstance(p, Mapping)
or _num(p.get(
"value"))
is None:
121 return float(to_si(float(p[
"value"]), str(p.get(
"unit")
or ""))), str(p.get(
"source")
or "")
125 length, l_src = si(
"length")
126 bore, b_src = si(
"bore")
127 vol = math.pi * bore * bore / 4.0 * length
if length
is not None and bore
is not None else None
128 out.append({
"id": e.get(
"id"),
"from": here,
"to": target,
"length_m": length,
"bore_m": bore,
129 "volume_m3": vol,
"provenance": {
"length": l_src,
"bore": b_src}})
130 if not is_engine(target):
132 frontier.append(target)
136def _document(result: Mapping[str, Any], prep: Any, document: Optional[Mapping[str, Any]]) -> Optional[Mapping[str, Any]]:
137 if document
is not None:
139 payload = getattr(getattr(prep,
"drawing",
None),
"payload",
None)
140 if isinstance(payload, Mapping):
142 did =
_dig(result,
"provenance.drawing.id")
149 return d.payload
if d
is not None else None
158 document: Optional[Mapping[str, Any]] =
None) -> Dict[str, Any]:
159 """Per side: ``{loaded_kg, burned_kg, residual_kg, trapped_kg, error_pct, basis, lines}``.
161 ``loaded_kg`` is the tank at T-0 plus the feed lines' liquid; ``error_pct`` is
162 ``(loaded - burned - residual - trapped) / loaded``.
164 series = result.get(
"series")
or {}
165 summary = result.get(
"summary")
or {}
166 derived =
_dig(result,
"provenance.derived")
or getattr(prep,
"derived",
None)
or {}
167 roles = derived.get(
"roles")
or {}
168 species = derived.get(
"species")
or {}
169 firing = series.get(
"firing")
or []
172 net = result.get(
"network")
if isinstance(result.get(
"network"), Mapping)
else None
173 out: Dict[str, Any] = {}
174 for side, short
in ((
"oxidiser",
"ox"), (
"fuel",
"fuel")):
175 s = summary.get(short)
or {}
176 tank_kg, residual =
_num(s.get(
"loaded_kg")),
_num(s.get(
"residual_kg"))
177 if tank_kg
is None or residual
is None:
178 out[short] = {
"available":
False,
"error":
"the summary carries no tank inventory"}
180 burned, how =
None,
""
182 injectors = [b
for b
in (net.get(
"branches")
or {}).values()
183 if isinstance(b, Mapping)
and b.get(
"kind") ==
"injector" and b.get(
"side") == short]
185 burned = sum(sum((
_num(m)
or 0.0) * h
for m, h
in zip(b.get(
"mdot")
or [], steps))
for b
in injectors)
186 how =
"the injector branches' flow in the network record, each step times its length"
188 mdot = (series.get(short)
or {}).get(
"mdot")
or []
189 burned = sum((
_num(m)
or 0.0) * h
for m, h, f
in zip(mdot, steps, firing)
if f)
190 how =
"the chamber's flow on the firing steps, each step times its length (analysis.reduce_trace's rule)"
191 lines: List[Dict[str, Any]] = []
192 trapped: Optional[float] =
None
194 tank_id = roles.get(side)
195 if doc
is not None and tank_id:
197 vol = [ln[
"volume_m3"]
for ln
in lines]
198 liquid_K = [
_num(v)
for v
in ((series.get(short)
or {}).get(
"liquid_K")
or [])]
199 T = next((v
for v
in reversed(liquid_K)
if v
is not None),
None)
201 if lines
and None not in vol
and T
is not None and sp:
203 import CoolProp.CoolProp
as CP
207 rho = float(CP.PropsSI(
"D",
"T", T,
"Q", 0, sp))
208 trapped = rho * sum(vol)
211 loaded = tank_kg + (trapped
or 0.0)
212 err = loaded - burned - residual - (trapped
or 0.0)
216 vapour = bool(
_dig(result,
"provenance.setup.ullage_vapour")
or _dig(result,
"provenance.settings.ullage_vapour"))
219 "loaded_kg": loaded,
"tank_loaded_kg": tank_kg,
"burned_kg": burned,
"residual_kg": residual,
220 "trapped_kg": trapped,
"error_kg": err,
"error_pct":
_pct(err, loaded),
221 "liquid_density_kg_m3": rho,
223 "basis": (f
"Burned: {how}. Loaded and residual: the tank's own inventory at T-0 and at the last step. "
224 "Trapped: the drawing's tank-to-engine line volumes (pi/4 d^2 L) full of saturated liquid at "
225 "the last recorded liquid temperature (CoolProp); valve bodies and the injector manifold are "
226 "not in the drawing and not counted. The twin's lines hold no liquid inventory, so the "
227 "trapped mass is the same at both ends and the error is tank against flow."
228 + (
"" if trapped
is not None else " Lines not counted: the drawing was not available or a line "
229 "has no length or bore.")
230 + (
" Ullage vapour is on: propellant that evaporated into the ullage is in the error, not "
231 "lost." if vapour
else "")),
232 "includes_evaporation": vapour,
240def _ullage_masses(series: Mapping[str, Any], derived: Mapping[str, Any], gas: str,
241 i: int) -> Tuple[Optional[float], List[str]]:
242 import CoolProp.CoolProp
as CP
244 roles = derived.get(
"roles")
or {}
245 volumes = derived.get(
"tank_volumes_L")
or {}
248 for side, short
in ((
"oxidiser",
"ox"), (
"fuel",
"fuel")):
249 s = series.get(short)
or {}
250 v_L =
_num(volumes.get(roles.get(side)))
251 p =
_num((s.get(
"tank_psia")
or [
None] * (i + 1))[i])
252 T =
_num((s.get(
"ullage_K")
or [
None] * (i + 1))[i])
253 fill =
_num((s.get(
"fill_fraction")
or [
None] * (i + 1))[i])
254 if v_L
is None or p
is None or T
is None or fill
is None:
255 notes.append(f
"{short}: no tank volume, ullage pressure, temperature or fill recorded")
257 rho = float(CP.PropsSI(
"D",
"P", p * PSI,
"T", T, gas))
258 total += rho * v_L * 1e-3 *
max(1.0 - fill, 0.0)
262def _vented(result: Mapping[str, Any], steps: Sequence[float]) -> Tuple[Optional[float], Optional[float], str]:
263 """Gas that left the network to the atmosphere [kg] and the enthalpy it carried [J], from the
264 full network record: every gas branch whose downstream node is ambient (or a vent)."""
265 net = result.get(
"network")
266 if not isinstance(net, Mapping):
267 return None,
None,
"no network record: vents taken as shut (the burn plan holds them shut)"
268 nodes = net.get(
"nodes")
or {}
271 for bid, b
in (net.get(
"branches")
or {}).items():
272 if not isinstance(b, Mapping)
or b.get(
"side")
not in (
"gas",
None):
274 to = nodes.get(b.get(
"to"))
or {}
275 if str(to.get(
"kind")
or "").lower()
not in (
"ambient",
"vent"):
277 kg += sum(
max(
_num(m)
or 0.0, 0.0) * h
for m, h
in zip(b.get(
"mdot")
or [], steps))
278 found.append(str(bid))
279 return kg,
None, (
"vented through " +
", ".join(found)
if found
else "no gas branch to atmosphere in the network")
283 """``{bottle_out_kg, ullage_in_kg, vented_kg, error_pct, basis}`` over the recorded window."""
284 series = result.get(
"series")
or {}
285 derived =
_dig(result,
"provenance.derived")
or getattr(prep,
"derived",
None)
or {}
286 settings =
_dig(result,
"provenance.settings")
or {}
287 gas =
_species(derived.get(
"pressurant_gas"))
288 mass = [
_num(v)
for v
in (series.get(
"copv_mass_kg")
or [])]
289 if not gas
or len(mass) < 2
or mass[0]
is None or mass[-1]
is None:
290 return {
"available":
False,
"error":
"no bottle mass or pressurant species recorded"}
291 if settings.get(
"ullage_vapour"):
292 return {
"available":
False,
293 "error":
"the ullage carries propellant vapour (ullage_vapour on): pricing it as pure pressurant "
294 "would book the vapour as a pressurant error"}
297 if m0
is None or m1
is None:
298 return {
"available":
False,
"error":
"; ".join(notes0 + notes1)}
300 vented, _h, vent_note =
_vented(result, steps)
301 bottle_out = mass[0] - mass[-1]
303 err = bottle_out - gain - (vented
or 0.0)
307 "bottle_out_kg": bottle_out,
"ullage_in_kg": gain,
"vented_kg": vented,
308 "ullage_t0_kg": m0,
"ullage_end_kg": m1,
309 "error_kg": err,
"error_pct":
_pct(err, bottle_out),
310 "window_s": [series[
"t"][0], series[
"t"][-1]],
311 "basis": (
"Over the recorded window (lead-in and burn). Bottle: the twin's vessel mass. Ullages: re-priced "
312 "with CoolProp from the recorded ullage pressure and gas temperature over the drawing's tank volume "
313 "less the liquid's (1 - fill), pure pressurant. The network's lines hold no gas inventory. "
321def _bottle_wall(result: Mapping[str, Any], prep: Any, document: Optional[Mapping[str, Any]]) -> Tuple[Optional[float], str]:
322 """Bottle wall heat capacity m*c [J/K] and where it came from: the drawing's own wall, else
323 the twin's Setup default (``bottle_wall_kg_per_L`` times the bottle volume, at feedtwin's
324 ``BOTTLE_WALL`` capacity)."""
325 derived =
_dig(result,
"provenance.derived")
or getattr(prep,
"derived",
None)
or {}
327 bid = derived.get(
"copv_id")
328 if doc
is not None and bid:
331 node = next((n
for n
in (doc.get(
"nodes")
or [])
if isinstance(n, Mapping)
and n.get(
"id") == bid),
None)
332 params =
_dig(node,
"data.params")
or {}
333 m, c = params.get(
"wall_mass"), params.get(
"wall_capacity")
334 if isinstance(m, Mapping)
and isinstance(c, Mapping):
336 return (float(to_si(float(m[
"value"]), str(m.get(
"unit")
or "kg")))
337 * float(to_si(float(c[
"value"]), str(c.get(
"unit")
or "J/(kg.K)"))),
338 f
"the drawing's {bid} wall ({m.get('source') or 'no source'})")
341 setup =
_dig(result,
"provenance.setup")
or {}
342 per_L, vol =
_num(setup.get(
"bottle_wall_kg_per_L")),
_num(derived.get(
"copv_volume_L"))
343 if per_L
is not None and vol
is not None:
347 return per_L * vol * float(BOTTLE_WALL[
"capacity"]),
"feed-twin's Setup default bottle wall (assumed)"
350 return None,
"unknown"
354 document: Optional[Mapping[str, Any]] =
None) -> Dict[str, Any]:
355 """The first law over the pressurant, over the recorded window.
357 For the bottle, ``-dU_bottle + Q_bottle_wall`` is the enthalpy that left it. For the ullages,
358 ``dU_ullage + W`` (``W = integral p dV`` on the liquid) is what arrived, less the heat the tank
359 walls and the liquid surface gave the gas. Lines and regulators hold no gas and do no work
360 (isenthalpic throttling). So the unclosed part,
362 R = (-dU_bottle + Q_bottle_wall) - dU_ullage - W - H_vented,
364 is minus the heat the tank walls, the liquid surface and (with ``line_walls``) the line walls put
365 into the gas, plus integration error. **It closes** the bottle (its wall from the drawing or the
366 Setup default, its gas priced with CoolProp at the recorded pressure and m/V), the ullages
367 (CoolProp at the recorded p, T) and the boundary work (trapezoidal p dV). **It cannot** separate
368 the tank-wall and interface heat from numerical error, because the series records neither the tank
369 wall temperatures nor the interface: ``error_pct`` is therefore not a defect measure and the
370 limits report it as information only. Vent enthalpy is not in the network record, so a run that
371 vents carries that term in R too.
373 import CoolProp.CoolProp
as CP
375 series = result.get(
"series")
or {}
376 derived =
_dig(result,
"provenance.derived")
or getattr(prep,
"derived",
None)
or {}
377 settings =
_dig(result,
"provenance.settings")
or {}
378 gas =
_species(derived.get(
"pressurant_gas"))
379 p_b = [
_num(v)
for v
in (series.get(
"copv_psia")
or [])]
380 m_b = [
_num(v)
for v
in (series.get(
"copv_mass_kg")
or [])]
381 wall = [
_num(v)
for v
in (series.get(
"copv_wall_K")
or [])]
382 v_b =
_num(derived.get(
"copv_volume_L"))
383 if not gas
or v_b
is None or len(p_b) < 2
or None in (p_b[0], p_b[-1], m_b[0], m_b[-1]):
384 return {
"available":
False,
"error":
"no bottle pressure, mass, volume or pressurant recorded"}
385 if settings.get(
"ullage_vapour"):
386 return {
"available":
False,
"error":
"the ullage carries propellant vapour (ullage_vapour on)"}
389 def u_bottle(i: int) -> float:
390 return m_b[i] * float(CP.PropsSI(
"U",
"P", p_b[i] * PSI,
"D", m_b[i] / V, gas))
392 du_b = u_bottle(len(p_b) - 1) - u_bottle(0)
394 q_bw = -mc * (wall[-1] - wall[0])
if mc
is not None and wall
and wall[0]
is not None and wall[-1]
is not None else None
396 roles = derived.get(
"roles")
or {}
397 volumes = derived.get(
"tank_volumes_L")
or {}
400 for side, short
in ((
"oxidiser",
"ox"), (
"fuel",
"fuel")):
401 s = series.get(short)
or {}
402 v_L =
_num(volumes.get(roles.get(side)))
403 p = [
_num(v)
for v
in (s.get(
"tank_psia")
or [])]
404 T = [
_num(v)
for v
in (s.get(
"ullage_K")
or [])]
405 fill = [
_num(v)
for v
in (s.get(
"fill_fraction")
or [])]
406 if v_L
is None or not p
or None in (p[0], p[-1], T[0], T[-1], fill[0], fill[-1]):
407 return {
"available":
False,
"error": f
"{short}: no tank volume or ullage state recorded"}
408 vol = [v_L * 1e-3 *
max(1.0 - f, 0.0)
if f
is not None else None for f
in fill]
410 def u_ullage(i: int) -> float:
411 rho = float(CP.PropsSI(
"D",
"P", p[i] * PSI,
"T", T[i], gas))
412 return rho * vol[i] * float(CP.PropsSI(
"U",
"P", p[i] * PSI,
"T", T[i], gas))
414 du_u += u_ullage(len(p) - 1) - u_ullage(0)
415 for i
in range(1, len(p)):
416 if None in (p[i], p[i - 1], vol[i], vol[i - 1]):
418 work += 0.5 * (p[i] + p[i - 1]) * PSI * (vol[i] - vol[i - 1])
419 out_of_bottle = -du_b + (q_bw
or 0.0)
420 unclosed = out_of_bottle - du_u - work
423 "bottle_dU_J": du_b,
"bottle_wall_heat_J": q_bw,
"bottle_wall_basis": wall_src,
424 "ullage_dU_J": du_u,
"boundary_work_J": work,
"vented_enthalpy_J":
None,
425 "unclosed_J": unclosed,
429 "error_pct":
_pct(unclosed, work),
430 "basis": (
"First law over the pressurant, recorded window: R = (-dU_bottle + Q_bottle_wall) - dU_ullages - "
431 "integral p dV, CoolProp internal energies at the recorded states, in percent of the expulsion "
432 "work integral p dV. R is minus the heat the tank walls, the liquid surface and the line walls "
433 "gave the gas, plus integration error, which the series cannot separate (tank-wall and interface "
434 "temperatures are not recorded): not a defect measure. Bottle wall: " + wall_src +
"."),
441def convergence(result: Mapping[str, Any]) -> List[Dict[str, Any]]:
442 """Per outer pass: the throat schedule's and the flight acceleration's change against their
443 tolerances (``replay.THROAT_TOLERANCE``, ``flight.ACCEL_TOLERANCE``)."""
448 THROAT_TOLERANCE, ACCEL_TOLERANCE =
None,
None
450 for p
in result.get(
"passes")
or []:
451 if not isinstance(p, Mapping):
453 agree =
_dig(p,
"agreement.worst")
or {}
455 "pass": p.get(
"pass"),
456 "throat_residual":
_num(p.get(
"schedule_change")),
457 "throat_tolerance": THROAT_TOLERANCE,
458 "accel_residual":
_num(p.get(
"accel_change")),
459 "accel_tolerance": ACCEL_TOLERANCE
if p.get(
"accel_change")
is not None else None,
460 "throat_applied": p.get(
"throat_applied"),
"accel_applied": p.get(
"accel_applied"),
461 "burn_time_s":
_num(p.get(
"burn_time_s")),
462 "agreement_worst":
max((abs(
_num(v)
or 0.0)
for v
in agree.values()), default=
None)
if agree
else None,
470def check(result: Mapping[str, Any], prep: Any =
None, *,
471 document: Optional[Mapping[str, Any]] =
None) -> Dict[str, Any]:
472 """The ``diagnostics.vv`` block. Each part that cannot be computed says why; nothing raises.
474 ``prep`` (a Prepared) and ``document`` (the drawing's ``{nodes, edges}``) are optional: the
475 result's own provenance names the drawing, and a shipped one is found by id.
477 out: Dict[str, Any] = {}
478 for key, fn
in ((
"mass",
lambda:
mass_balance(result, prep, document)),
484 except Exception
as exc:
485 out[key] = {
"available":
False,
"error": f
"{type(exc).__name__}: {exc}"}
487 "name":
"layerx_vv_balances",
488 "source": (
"Control-volume mass conservation and the first law for an open system (Moran & Shapiro, "
489 "Fundamentals of Engineering Thermodynamics, ch. 4); CoolProp 6+ (Bell et al., Ind. Eng. Chem. "
490 "Res. 53(6), 2014) for the gas and liquid properties"),
492 "the twin's lines and regulators hold no gas or liquid inventory (an algebraic network)",
493 "the ullage is pure pressurant (checked: the balance is skipped when ullage_vapour is on)",
494 "trapped liquid fills the drawing's tank-to-engine lines at the saturated density of the last recorded "
495 "liquid temperature; valve bodies and the injector manifold are not counted",
496 "vented gas is read from the network record when present; otherwise the vents are shut",
499 "tank_volumes_L": {
"value":
_dig(result,
"provenance.derived.tank_volumes_L"),
"unit":
"L",
500 "provenance":
"drawing"},
501 "copv_volume_L": {
"value":
_dig(result,
"provenance.derived.copv_volume_L"),
"unit":
"L",
502 "provenance":
"drawing"},
503 "pressurant_gas": {
"value":
_dig(result,
"provenance.derived.pressurant_gas"),
"unit":
"",
504 "provenance":
"drawing"},
List[Dict[str, Any]] feed_lines(Mapping[str, Any] document, str tank_id)
Optional[float] _pct(Optional[float] err, Optional[float] ref)
Any _dig(Any d, str path)
Tuple[Optional[float], str] _bottle_wall(Mapping[str, Any] result, Any prep, Optional[Mapping[str, Any]] document)
Dict[str, Any] energy_balance(Mapping[str, Any] result, Any prep=None, Optional[Mapping[str, Any]] document=None)
Optional[Mapping[str, Any]] _document(Mapping[str, Any] result, Any prep, Optional[Mapping[str, Any]] document)
Tuple[Optional[float], List[str]] _ullage_masses(Mapping[str, Any] series, Mapping[str, Any] derived, str gas, int i)
Dict[str, Any] mass_balance(Mapping[str, Any] result, Any prep=None, Optional[Mapping[str, Any]] document=None)
List[float] _steps(Mapping[str, Any] series)
Dict[str, Any] pressurant_balance(Mapping[str, Any] result, Any prep=None)
Tuple[Optional[float], Optional[float], str] _vented(Mapping[str, Any] result, Sequence[float] steps)
List[Dict[str, Any]] convergence(Mapping[str, Any] result)
Optional[str] _species(Optional[str] name)
Optional[float] _num(Any v)
Dict[str, Any] check(Mapping[str, Any] result, Any prep=None, *, Optional[Mapping[str, Any]] document=None)