lockwire 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. lockwire/__init__.py +9 -0
  2. lockwire/__main__.py +9 -0
  3. lockwire/advice.py +119 -0
  4. lockwire/api.py +1216 -0
  5. lockwire/api_payload.py +985 -0
  6. lockwire/bench.py +182 -0
  7. lockwire/blame.py +100 -0
  8. lockwire/catalog.py +100 -0
  9. lockwire/catalog_seed.yaml +545 -0
  10. lockwire/checks.py +3483 -0
  11. lockwire/cli.py +1799 -0
  12. lockwire/cli_line.py +73 -0
  13. lockwire/constraints.py +313 -0
  14. lockwire/copy.py +302 -0
  15. lockwire/describe.py +682 -0
  16. lockwire/edit.py +2098 -0
  17. lockwire/emit_ts.py +150 -0
  18. lockwire/export_mhs.py +115 -0
  19. lockwire/factdiff.py +254 -0
  20. lockwire/forces.py +334 -0
  21. lockwire/gitref.py +188 -0
  22. lockwire/hw.py +388 -0
  23. lockwire/mcp_server.py +276 -0
  24. lockwire/mechanism.py +379 -0
  25. lockwire/mesh_dae.py +555 -0
  26. lockwire/mesh_simple.py +198 -0
  27. lockwire/page.py +1485 -0
  28. lockwire/plan.py +849 -0
  29. lockwire/plan_payload.py +151 -0
  30. lockwire/plugins.py +70 -0
  31. lockwire/report.py +848 -0
  32. lockwire/report_html.py +707 -0
  33. lockwire/report_json.py +168 -0
  34. lockwire/review_draft.py +315 -0
  35. lockwire/scan.py +732 -0
  36. lockwire/scene.py +535 -0
  37. lockwire/scene_import.py +309 -0
  38. lockwire/scene_urdf.py +750 -0
  39. lockwire/scene_view.py +376 -0
  40. lockwire/schema.py +3616 -0
  41. lockwire/sdf.py +240 -0
  42. lockwire/serve.py +1256 -0
  43. lockwire/start_operation.py +92 -0
  44. lockwire/statics.py +77 -0
  45. lockwire/units.py +115 -0
  46. lockwire/urdf.py +348 -0
  47. lockwire/vcs.py +402 -0
  48. lockwire/whatif.py +161 -0
  49. lockwire-0.2.0.dist-info/METADATA +192 -0
  50. lockwire-0.2.0.dist-info/RECORD +52 -0
  51. lockwire-0.2.0.dist-info/WHEEL +4 -0
  52. lockwire-0.2.0.dist-info/entry_points.txt +3 -0
lockwire/__init__.py ADDED
@@ -0,0 +1,9 @@
1
+ """Lockwire — the physical-assumption contract for robots, enforced in CI and at preflight."""
2
+
3
+ __version__ = "0.2.0"
4
+
5
+
6
+ def main() -> None:
7
+ from lockwire.cli import app
8
+
9
+ app()
lockwire/__main__.py ADDED
@@ -0,0 +1,9 @@
1
+ """``python -m lockwire`` — the same CLI the ``lockwire`` script runs.
2
+
3
+ Exists so ``jobs.py`` can start a run as an argv list against ``sys.executable``
4
+ without depending on a console script being on PATH.
5
+ """
6
+
7
+ from lockwire.cli import app
8
+
9
+ app()
lockwire/advice.py ADDED
@@ -0,0 +1,119 @@
1
+ """The alternatives half of the advice sentence.
2
+
3
+ ``check_budgets`` (``checks.py``) already knows a rail_power budget is over its
4
+ limit; this module answers the second half of the sentence — "what else could
5
+ carry it" — headroom on every OTHER rail_power budget, plus a projected
6
+ regulator suggestion. It computes nothing about rail membership itself:
7
+ Reuse means ``checks.power_rollup``'s own return values (rail totals, the
8
+ per-rail unknown-consumer sets) are passed in as plain data, never re-walked.
9
+ This module never imports ``checks`` — the call only ever goes the other way,
10
+ so there is no cycle.
11
+
12
+ Every number carries its ``kind`` (a catalog or projected
13
+ figure is shown only with its own visual mark and word, never in the same
14
+ style as a measured one): headroom is ``derived-by-<check>`` — the same
15
+ rollup ``check_budgets`` itself scores against; a suggested regulator rating
16
+ is ``projected``, a number this product is choosing, not measuring.
17
+
18
+ **Raising the over-budget rail's own declared capacity is never offered**
19
+ : that clears the finding by moving the limit rather than
20
+ the draw, which leaves a standing mark. Offering it as
21
+ advice would make the product recommend the exact thing that mark exists to flag.
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ from pydantic import BaseModel, ConfigDict, model_validator
27
+
28
+ from lockwire.schema import Contract
29
+
30
+
31
+ class Alternative(BaseModel):
32
+ """One remedy suggestion, its number, and the kind that number carries."""
33
+
34
+ model_config = ConfigDict(extra="forbid")
35
+
36
+ text: str
37
+ value: float
38
+ unit: str
39
+ kind: str # "derived-by-<check>" | "projected" | "none"
40
+ #: The word for a ``projected`` figure. Required when ``kind ==
41
+ #: "projected"`` — a projected figure reaching a door carries its mark and
42
+ #: its word, not a corrected sentence saying it is unenforced.
43
+ mark: str | None = None
44
+
45
+ @model_validator(mode="after")
46
+ def _carries_its_word(self) -> Alternative:
47
+ if self.kind == "projected" and not self.mark:
48
+ raise ValueError("a 'projected' number must carry its own word, e.g. mark='projected'")
49
+ return self
50
+
51
+
52
+ def build_rail_alternatives(
53
+ contract: Contract,
54
+ *,
55
+ over_rail_id: str,
56
+ deficit_w: float,
57
+ rail_totals: dict[str, float],
58
+ rail_unknown: dict[str, set[str]],
59
+ check_name: str = "check_budgets",
60
+ ) -> list[Alternative]:
61
+ """Alternatives for a rail_power budget over its limit on ``over_rail_id``.
62
+
63
+ ``rail_totals``/``rail_unknown`` must come from ``checks.power_rollup`` run
64
+ against the same contract and draw mode as the violation being explained —
65
+ the shared builder that every call site (and every mutation bite) goes
66
+ through, so a broken kind anywhere breaks every caller alike.
67
+ """
68
+ alternatives: list[Alternative] = []
69
+ for budget in contract.budgets:
70
+ if budget.kind != "rail_power" or budget.rail in (None, over_rail_id):
71
+ continue
72
+ if rail_unknown.get(budget.rail):
73
+ # a rail with any consumer of unknown draw cannot be summed,
74
+ # so it cannot be claimed to have headroom either.
75
+ continue
76
+ used = rail_totals.get(budget.rail, 0.0)
77
+ headroom_w = budget.limit.base() - used
78
+ if headroom_w > 0.0:
79
+ alternatives.append(
80
+ Alternative(
81
+ text=(
82
+ f"move the device to rail {budget.rail!r}, which has "
83
+ f"{headroom_w:.2f} W free"
84
+ ),
85
+ value=headroom_w,
86
+ unit="W",
87
+ kind=f"derived-by-{check_name}",
88
+ )
89
+ )
90
+
91
+ over_rail = contract.rail(over_rail_id)
92
+ source_rail_id = "battery"
93
+ if over_rail is not None and over_rail.upstream is not None:
94
+ source_rail_id = over_rail.upstream
95
+ rating_w = max(deficit_w, 0.0)
96
+ if rating_w > 0.0:
97
+ alternatives.append(
98
+ Alternative(
99
+ text=f"add a {rating_w:.2f} W regulator fed from rail {source_rail_id!r}",
100
+ value=rating_w,
101
+ unit="W",
102
+ kind="projected",
103
+ mark="projected",
104
+ )
105
+ )
106
+
107
+ if not alternatives:
108
+ alternatives.append(
109
+ Alternative(
110
+ text=(
111
+ f"no other rail has headroom for {over_rail_id!r} and no regulator "
112
+ "rating could be projected"
113
+ ),
114
+ value=0.0,
115
+ unit="W",
116
+ kind="none",
117
+ )
118
+ )
119
+ return alternatives