1"""Where Layer X's drawings, state machines and combustion tables come from.
3A drawing is the hardware record (ADR-0002): topology, line geometry, tank and
4bottle volumes, the regulator. Layer X never edits one. It reads drawings from
5these places and treats them identically once read, because what identifies a
6drawing is its **bytes**:
8* the feed-twin set shipped in ``feed-twin/backend/diagrams`` (the stand and the
9 two COPV-study drawings), listed as benchmarks;
10* the feed twin's own library, and the sample set it bundles in
11 ``feed-twin/backend/sample`` (the team's current stand, which is *featured*:
12 a browser with no drawing of its own opens on it);
13* drawings uploaded to Layer X, kept per user;
14* pid-designer documents, pulled by the router (it owns the HTTP) and stored
15 here like an upload, with where they came from recorded.
17Every record carries the sha256 of its bytes. A run quotes it, so "which drawing
18was this" is answerable without trusting a name.
21from __future__
import annotations
28from dataclasses
import dataclass, field
29from pathlib
import Path
30from typing
import Any, Dict, List, Mapping, Optional
32_ENGINE_ROOT = Path(__file__).resolve().parents[2]
33_STAR_ROOT = _ENGINE_ROOT.parent
37 """The feed-twin app checkout, whose shipped drawings and DAQ tables Layer X reads."""
38 return Path(os.environ.get(
"FEEDTWIN_ROOT")
or (_STAR_ROOT /
"feed-twin"))
42 return Path(os.environ.get(
"LAYERX_DRAWINGS")
or (
feedtwin_root() /
"backend" /
"diagrams"))
46 """The feed twin's own library: every drawing its cockpit can open (the shipped ones and what it
47 pulled from pid-designer), as ``manifest.json`` plus ``blobs/``."""
48 return Path(os.environ.get(
"LAYERX_FEEDTWIN_LIBRARY")
or (
feedtwin_root() /
"backend" /
"library"))
52 """The sample set feed-twin bundles (the team's current stand drawing and engine): the drawing
53 in it is the featured one, the stand a fresh browser opens on."""
54 return Path(os.environ.get(
"LAYERX_SAMPLE")
or (
feedtwin_root() /
"backend" /
"sample"))
58 """The DAQ's state-machine tables, as feed-twin ships them (see NEEDS-REPAIR.md there)."""
59 return Path(os.environ.get(
"LAYERX_STATEMACHINES")
or (
feedtwin_root() /
"backend" /
"statemachines"))
63 """The feed twin's own default for a parameter a drawing leaves out, as a difference in psi."""
68 value, unit = FALLBACKS[kind][name]
69 return to_si(float(value), unit) / 6894.757293168361
75 """Each place Layer X reads drawings from that is not there, in words, for the server log and
76 ``GET /api/layerx/status``. A missing one lists nothing and says nothing: the deployed image
77 resolved the twin's library and sample set to ``/feed-twin/backend/...`` (no checkout beside it),
78 so production Layer X never saw what the cockpit holds, nor the featured stand."""
82 (
"feed-twin's sample set (the featured stand)",
sample_dir(),
"LAYERX_SAMPLE"),
83 (
"the DAQ state-machine tables",
machines_dir(),
"LAYERX_STATEMACHINES")):
85 out.append(f
"{what}: {path} is not a directory (set {env})")
90 """The engine's own CEA cache, resolved the way EngineDesign's CEACache resolves it."""
92 name = config.combustion.cea.cache_file
93 except AttributeError:
98 candidates = [path]
if path.is_absolute()
else [Path.cwd() / path, _ENGINE_ROOT / path]
99 for candidate
in candidates:
100 if candidate.is_file():
101 return candidate.resolve()
109 return hashlib.sha256(data).hexdigest()
113 """Short content address. Sixteen hex characters: a collision needs ~2^32 drawings."""
118 """A pid-designer document reduced to what feed-twin reads, as stable bytes."""
120 {
"nodes": payload.get(
"nodes")
or [],
"edges": payload.get(
"edges")
or []},
125def summarize(payload: Mapping[str, Any], name: str =
"") -> Dict[str, Any]:
126 """What a person choosing a drawing needs to see, without building a network.
128 Read through feedtwin's own reader so a drawing it cannot read says so here,
129 not halfway into a run.
134 diagram = read_diagram(payload, name=name
or "drawing")
135 except DiagramError
as exc:
136 return {
"readable":
False,
"error": str(exc)}
138 def param(node: Any, key: str, unit: str) -> Optional[float]:
139 p = node.params.get(key)
144 return (float(p.si) - 101325.0) / 6894.757293168361
146 return float(p.si) / 6894.757293168361
147 return float(p.si
if unit ==
"si" else p.value)
155 vehicle = vehicle_ids(diagram)
156 ground: List[Dict[str, Any]] = []
157 tanks, bottles, regulators, engines, valves = [], [], [], [], []
158 for node
in diagram.nodes:
159 if node.type
in (
"TANK",
"KBOTTLE",
"DEWAR")
and vehicle
is not None and node.id
not in vehicle:
160 volume = node.params.get(
"volume")
161 ground.append({
"id": node.id,
"label": node.label,
"fluid": node.fluid,
"type": node.type,
162 "volume_L": (volume.si * 1e3)
if volume
is not None else None})
164 if node.type ==
"TANK":
165 volume = node.params.get(
"volume")
167 "id": node.id,
"label": node.label,
"fluid": node.fluid,
168 "volume_L": (volume.si * 1e3)
if volume
is not None else None,
169 "mawp_psi": param(node,
"MAWP",
"psig"),
171 elif node.type ==
"KBOTTLE":
172 volume = node.params.get(
"volume")
174 "id": node.id,
"label": node.label,
"fluid": node.fluid,
175 "volume_L": (volume.si * 1e3)
if volume
is not None else None,
176 "pressure_psi": param(node,
"pressure",
"psig"),
177 "mawp_psi": param(node,
"MAWP",
"psig"),
179 elif node.type ==
"PR":
180 droop = param(node,
"flow_droop",
"psi")
182 "id": node.id,
"label": node.label,
183 "dome_loaded": node.options.get(
"domeLoaded") ==
"yes",
184 "setpoint_psi": param(node,
"setpoint",
"psig"),
185 "dome_bias_psi": param(node,
"dome_bias",
"psi"),
186 "supply_coefficient": param(node,
"supply_coefficient",
"value"),
189 "flow_droop_psi": droop
if droop
is not None else _fallback_psi(
"regulator",
"flow_droop"),
190 "flow_droop_default": droop
is None,
192 elif node.type
in (
"ENGINE",
"INJECTOR"):
193 engines.append({
"id": node.id,
"label": node.label})
194 elif node.is_inline
and node.type
in (
"SOL",
"MAN",
"ROT",
"BV",
"AV"):
195 valves.append({
"id": node.id,
"label": node.label,
"type": node.type})
198 "symbols": len(diagram.nodes),
199 "lines": len(diagram.edges),
202 "regulators": regulators,
204 "valves": len(valves),
205 "ground_support": ground,
210_STEM_WORDS = {
"copv":
"COPV",
"gn2":
"GN2",
"he":
"helium",
"n2":
"N2",
"ln2":
"LN2",
"lox":
"LOX",
"le4":
"LE4"}
211_GAS_WORDS = {
"GN2",
"helium",
"N2",
"LN2"}
215 """A drawing's name as a person reads it. A file stem (``copv_study_he``, what a shipped drawing
216 is named by) comes back in words (``COPV study, helium``); any other name, which a person chose,
217 comes back as it is."""
218 if not re.fullmatch(
r"[a-z0-9]+(?:_[a-z0-9]+)+", name
or ""):
220 words = [_STEM_WORDS.get(w, w)
for w
in name.split(
"_")]
221 if len(words) > 2
and words[-1]
in _GAS_WORDS:
222 text =
" ".join(words[:-1]) +
", " + words[-1]
224 text =
" ".join(words)
225 return text[:1].upper() + text[1:]
229 """Where a drawing came from, in words, for the menu's second line (the raw ``source`` stays in
231 if source.startswith(
"shipped:"):
232 return "benchmark, shipped with the code"
233 if source.startswith(
"sample:"):
234 return "Hotbench sample stand"
235 if source.startswith(
"feed-twin library"):
236 rest = source.split(
":", 1)[1].strip()
if ":" in source
else ""
237 return "Feed Twin library, from pid-designer" if rest.startswith(
"pid-designer")
else "Feed Twin library"
238 if source.startswith(
"pid-designer:"):
239 return "pid-designer, " + (
"working copy" if source.endswith(
"working copy")
else "release")
240 if source ==
"upload":
241 return "uploaded here"
247 """One drawing, read and addressed by content."""
252 """``shipped:<file>``, ``sample:<file>``, ``feed-twin library: <its source>``, ``upload``, or
253 ``pid-designer:<owner>/<id>@<release|working copy>``."""
255 payload: Dict[str, Any] = field(repr=
False)
257 featured: bool =
False
258 """The team's current stand: the sample feed-twin bundles, or a library entry marked featured.
259 A browser with no drawing of its own opens on it."""
272 "group":
"Benchmarks" if self.
source.startswith(
"shipped:")
else "Stands",
281def _read(path: Path) -> Optional[bytes]:
283 return path.read_bytes()
289_SHIPPED_CACHE: Dict[Any, Any] = {}
293 """The shipped set plus one user's own drawings.
295 Uploads and pulls live in ``<user dir>/layerx/drawings`` as ``<id>.json`` with
296 a ``<id>.meta.json`` beside them. Shipped drawings are read in place and
300 def __init__(self, user_dir: Optional[Path]) ->
None:
301 self.
dir = (user_dir /
"layerx" /
"drawings")
if user_dir
is not None else None
304 out: List[Drawing] = []
306 if not base.is_dir():
308 for path
in sorted(base.glob(
"*.json")):
314 stamp = (str(path), st.st_mtime_ns, st.st_size)
315 hit = _SHIPPED_CACHE.get(stamp)
321 payload = json.loads(raw.decode(
"utf-8"))
322 except (UnicodeDecodeError, json.JSONDecodeError):
325 sha256=
sha256(raw), payload=payload, added=st.st_mtime)
326 _SHIPPED_CACHE[stamp] = hit
331 """The drawings in the feed twin's library, read in place: Layer X burns the feed system
332 the twin holds (2026-10-03, "pull from it and trust it"), including what the twin pulled
333 from pid-designer, not only the files feed-twin ships."""
334 out: List[Drawing] = []
336 manifest_path = base /
"manifest.json"
338 st = manifest_path.stat()
341 stamp = (str(manifest_path), st.st_mtime_ns, st.st_size)
342 hit = _SHIPPED_CACHE.get(stamp)
346 manifest = json.loads(manifest_path.read_text(
"utf-8"))
347 except (OSError, UnicodeDecodeError, json.JSONDecodeError):
349 for entry
in manifest
if isinstance(manifest, list)
else []:
350 if not isinstance(entry, Mapping)
or entry.get(
"kind") !=
"diagram" or not entry.get(
"filename"):
352 raw =
_read(base /
"blobs" / str(entry[
"filename"]))
356 payload = json.loads(raw.decode(
"utf-8"))
357 except (UnicodeDecodeError, json.JSONDecodeError):
359 if not summarize(payload, str(entry.get(
"name")
or "")).
get(
"readable"):
361 imported = str(entry.get(
"imported_at")
or "")
362 out.append(
Drawing(id=
drawing_id(raw), name=str(entry.get(
"name")
or entry[
"filename"]),
363 source=f
"feed-twin library: {entry.get('source') or 'library'}",
364 sha256=
sha256(raw), payload=payload,
365 added=time.mktime(time.strptime(imported[:19],
"%Y-%m-%dT%H:%M:%S"))
if imported
else 0.0,
366 featured=entry.get(
"featured")
is True))
367 _SHIPPED_CACHE[stamp] = tuple(out)
371 """The drawings in feed-twin's sample set, read in place: featured. A library that already
372 holds the same bytes lists it under the library's name (``list`` keeps the first met)."""
373 out: List[Drawing] = []
375 if not base.is_dir():
377 for path
in sorted(base.glob(
"*.json")):
382 stamp = (
"sample", str(path), st.st_mtime_ns, st.st_size)
383 hit = _SHIPPED_CACHE.get(stamp)
389 payload = json.loads(raw.decode(
"utf-8"))
390 except (UnicodeDecodeError, json.JSONDecodeError):
393 if not isinstance(payload, dict)
or not isinstance(payload.get(
"nodes"), list) \
395 _SHIPPED_CACHE[stamp] =
False
398 sha256=
sha256(raw), payload=payload, added=st.st_mtime, featured=
True)
399 _SHIPPED_CACHE[stamp] = hit
408 meta: Dict[str, Any] = {}
409 meta_raw =
_read(path.with_suffix(
".meta.json"))
412 meta = json.loads(meta_raw.decode(
"utf-8"))
413 except (UnicodeDecodeError, json.JSONDecodeError):
416 payload = json.loads(raw.decode(
"utf-8"))
417 except (UnicodeDecodeError, json.JSONDecodeError):
420 id=
drawing_id(raw), name=str(meta.get(
"name")
or path.stem),
421 source=str(meta.get(
"source")
or "upload"), sha256=
sha256(raw),
422 payload=payload, added=float(meta.get(
"added")
or path.stat().st_mtime),
425 def _own(self) -> List[Drawing]:
426 out: List[Drawing] = []
427 if self.
dir is None or not self.
dir.is_dir():
429 for path
in sorted(self.
dir.glob(
"*.json")):
430 if path.name.endswith(
".meta.json"):
435 def list(self) -> List[Drawing]:
436 from dataclasses
import replace
438 seen: Dict[str, Drawing] = {}
443 featured = {d.id
for d
in everything
if d.featured}
444 for drawing
in everything:
445 seen.setdefault(drawing.id, drawing)
446 out = [replace(d, featured=
True)
if d.id
in featured
and not d.featured
else d
for d
in seen.values()]
449 from collections
import Counter
451 count = Counter(d.name
for d
in out)
452 named: Dict[str, int] = {}
453 for k, d
in enumerate(out):
454 if count[d.name] > 1:
455 named[d.name] = named.get(d.name, 0) + 1
456 if named[d.name] > 1
and d.added:
457 out[k] = replace(d, name=f
"{d.name} ({time.strftime('%b %d %H:%M', time.localtime(d.added))})")
460 def get(self, drawing_id_: str) -> Optional[Drawing]:
462 if self.
dir is not None and re.fullmatch(
r"[0-9a-zA-Z_-]{1,80}", drawing_id_
or ""):
463 path = self.
dir / f
"{drawing_id_}.json"
466 if drawing.id == drawing_id_:
469 if drawing.id == drawing_id_:
471 for drawing
in self.
_own():
472 if drawing.id == drawing_id_:
476 def add(self, payload: Mapping[str, Any], *, name: str, source: str) -> Drawing:
477 """Store a drawing. Same bytes, same id: adding twice is a no-op that
478 updates nothing but the name a person sees."""
480 raise RuntimeError(
"no user directory to store drawings in")
482 summary =
summarize(json.loads(raw), name)
483 if not summary.get(
"readable"):
484 raise ValueError(f
"feed-twin cannot read that drawing: {summary.get('error')}")
485 self.
dir.mkdir(parents=
True, exist_ok=
True)
487 (self.
dir / f
"{ident}.json").write_bytes(raw)
488 meta = {
"name": name,
"source": source,
"added":
time.time()}
489 (self.
dir / f
"{ident}.meta.json").write_text(json.dumps(meta))
490 return Drawing(id=ident, name=name, source=source, sha256=
sha256(raw),
491 payload=json.loads(raw), added=meta[
"added"])
None __init__(self, Optional[Path] user_dir)
List[Drawing] _sample(self)
Drawing add(self, Mapping[str, Any] payload, *, str name, str source)
Optional[Drawing] get(self, str drawing_id_)
List[Drawing] _own_one(self, Path path)
List[Drawing] _shipped(self)
List[Drawing] _feedtwin_library(self)
Dict[str, Any] summary(self)
Dict[str, Any] listing(self)
Path shipped_drawings_dir()
str display_name(str name)
Optional[Path] cea_table_path(Any config)
str drawing_id(bytes data)
Dict[str, Any] summarize(Mapping[str, Any] payload, str name="")
Optional[bytes] _read(Path path)
Optional[float] _fallback_psi(str kind, str name)
List[str] source_problems()
str origin_words(str source)
Path feedtwin_library_dir()
bytes normalise(Mapping[str, Any] payload)
TOML_NODISCARD_CTOR time() noexcept=default