Handoff: rewire guardrails to modelling-export 0.10.0 (schema 1.1)
Summary
Goal: make the guardrails detector a schema-1.1 producer. Relock
iolabs-point-cloud-modelling-export from 0.7.1 to 0.10.0 (published to Nexus
2026-09-04, tag v0.10.0), then rewrite guardrails/export.py so the polyline
metadata that today silently never reaches the XML (guardrail_type, side, post cadence,
corridor keys…) is emitted as <Attributes>, and every guardrail polyline carries the
required <Annotation>. Finish with tests, a real-segment regeneration, and a
/wrap-up release.
Nothing is broken today. Guardrails already imports the Nexus writer unconditionally (the stand-in exporter mentioned in older notes is gone). Its 389 tests pass unchanged against 0.10.0 (probed 2026-09-04, lock reverted afterwards). The work is purely about adopting the new schema.
Current state
| Item | State |
|---|---|
| modelling-export on Nexus | done 0.10.0 wheel+sdist, repo master b8973a8, schema 1.1 writer + feature_classes.py |
| Other consumers | done modellinglines relocked (released 0.13.1), lanefinder relocked. asphaltedge is not a consumer. |
guardrails main | 81760b2 "AI3D-379 Relock iolabs-common 0.9.0", package version 0.4.1, clean tree (only untracked out_*/run_*.log scratch) |
| guardrails pin | pyproject.toml line 17: "iolabs-point-cloud-modelling-export" (unpinned), uv.lock = 0.7.1 |
| Writer call site | guardrails/export.py: instances_to_xml_dict() builds legacy 1.0 dicts, export_segment_xml() calls write_xml.prettify_xml(write_xml.create_xml(data)) |
| XML consumer inside guardrails | guardrails/lane_xml.py reads lane documents via iolabs_geometry_geometry.highway_xml + ElementTree; it does not read the guardrails output. Expected unaffected, still verify with a 1.1 lane file. |
| Active worktrees on this repo | /home/ai/dev/wt-gr-374 (AI3D-374-edge-distance-filters), /home/ai/dev/gr_r7b (r7b-fixes), plus detached prod/eval checkouts. Branch from main; do not touch those. |
What 0.10.0 does with today's guardrails dict (probed)
- Output is byte-identical to 0.9.0 except
MetaData/SchemaVersion= 1.1 (spec R-2.4.1). No<Attributes>, no<Annotation>appear: the legacy top-level polyline keys are ignored silently (R-8.6.2, N-8.6.3). - The writer emits one
XmlWriterWarningperguardrailpolyline:XmlWriterWarning: Feature/ID 'segment_066', geometry ID 'segment_066_guardrail_00': R-6.3.11 §7.1 requires Annotation on Type 'guardrail' and none is supplied (R-4.21.1)guardrail_support,guardrail_top_rail,noise_walldo not warn (their Annot. column is "—"). - Guardrails tests did not surface this because the only test that calls the writer (
tests/test_classify_export.py::test_export_segment_xml_accepts_walls_kwarg_and_writes_file) exports zero guardrail polylines. No[tool.pytest]filterwarnings config exists in the repo.
What schema 1.1 wants from guardrails
Normative source: 3dai.iolabs.pointcloud.modellingexport/docs/xml_schema_1_1_spec.md (HTML copy under docs/pages/xml-schema-1-1-spec-20260828.html). Relevant rules:
| Feature/Type | Geometry | Annotation | Attributes | Spec |
|---|---|---|---|---|
guardrail | Polylines, unchanged | required, display=true, one Text line = drawn label (EDSP, ESP 2,0 with decimal comma) | guardrail_type (machine value, e.g. EDSP, ESP 2.0) + migrated legacy keys: side, faces, length_m, feature_type, width_m, top_height_p90_m | §7.20, R-5.7.5, N-8.6.3, R-4.21.1 |
guardrail_support, guardrail_top_rail | Polylines, unchanged | must not carry Annotation | parent_guardrail_id, post_spacing_m, post_count, post_confidence, halfwidth_m, lateral_offset_m via uniform <Attribute name= type=> | §7.24 |
noise_wall | Polylines, name kept lower-case (decision Q7) | must not carry Annotation (label deferred, O-17) | MAY carry height_m, length_m | §7.8 |
LaneIDstays"0",RoadPointkept as today (§7.20).- Attribute values:
type ∈ string|integer|float|boolean, numpy scalars accepted (N-8.8.4),Noneskipped, nested values rejected. - A geometry that supplies any 1.1 key becomes "1.1-touched" (R-2.6.1):
<2vertices then rejects instead of warning (R-6.1.6). Guardrail polylines always have ≥2 vertices today, but guard it. - Producer builders live in
iolabs_point_cloud_modelling_export.feature_classes:polyline_feature(feature_type, feature_id, vertices, *, annotation, polyline_type=None, closed=None, sorting_code=None, attributes=None, lane_id="0", geometry_id=None, road_point=None) -> dict. It returns one feature dict with one polyline; guardrails groups many polylines per feature, so either call it per polyline and merge thepolylineslists, or build the dicts by hand and validate withcheck_profiles(data).
Next steps (ordered)
- Branch + relock. From
mainin/home/ai/dev/3dai.iolabs.pointcloud.guardrails:
Commit the relock on its own (message style in this repo:git switch -c ai3d-XXX-modelling-export-1-1 sed -i 's/"iolabs-point-cloud-modelling-export",/"iolabs-point-cloud-modelling-export>=0.10.0",/' pyproject.toml uv lock --upgrade-package iolabs-point-cloud-modelling-export grep -A1 '^name = "iolabs-point-cloud-modelling-export"' uv.lock # expect 0.10.0 uv run pytest -q # expect 389 passedAI3D-NNN Relock iolabs-point-cloud-modelling-export 0.10.0). Jira ticket: ask Miro; AI3D-376 was the modelling-export schema-1.1 ticket and may be reused. - Pin the current output first (TDD). Add a test that builds a realistic dict via
instances_to_xml_dictwith ≥1 rail, ≥1 support, ≥1 top rail, ≥1 wall, runscreate_xmlunderwarnings.simplefilter("error", XmlWriterWarning), and assertscheck_profiles(data) == []. It fails today with the R-6.3.11 message above. Also addfilterwarnings = ["error::iolabs_point_cloud_modelling_export.write_xml.XmlWriterWarning"]under[tool.pytest.ini_options]so regressions cannot hide again (tests only; the pipeline must not run with-W error, see README "Warnings"). - Rewrite
_feature_polylines()inguardrails/export.py. Keepid,lane_id,road_point,vertices; move every other key intoattributes={...}(dropNonevalues; keep numpy scalars or cast). Forkind == "guardrail"addannotation={"display": True, "text": [label]}; for supports / top rails / walls add no annotation. Delete_SUPPORT_ATTRIBUTE_KEYSand its comment about the stand-in; delete the stale docstring text about "both the Nexus and the stand-in exporter". Check the exactannotation/textkey shape againstfeature_classes.polyline_featureandtests/test_profile_builders.pyin the modellingexport repo rather than guessing. - Decide the label / machine value (see open question 1). Minimal safe interim:
guardrail_typeattribute = classify value (w_beam,concrete,cable_suspect,unknown) andText= the same string, plus a single mapping table_GUARDRAIL_LABELSthat is easy to fill once HBW answers. Do not invent an EDSP/ESP mapping silently. - Fix
software_version:instances_to_xml_dicthardcodes"guardrails-0.2.0"; usef"guardrails-{guardrails.__version__}"(package is at 0.4.1). - Verify on real data. Regenerate one segment (existing outputs to diff against:
out_bb3/segment_066,out_bb3/segment_105; the run entrypoint is documented in the repo README). Confirm:SchemaVersion1.1,<Attributes>on every polyline,<Annotation>only onguardrail, geometry bytes unchanged versus the previous output, zeroXmlWriterWarningin the run log. Optionally open it in lanefinder'sscripts/visualize_xml_open3d.py, which already renders guardrail bodies from 1.1 documents. - Consumer check. Run the guardrails detector against a lane XML that carries 1.1 elements (e.g. modellingexport
tests/fixtures/sample_v1_1.xmlor a fresh modellinglines 0.13.1 output) to confirmlane_xml.pyparsing is unaffected. - Review + release. Per Miro's review policy: Opus + Sol in parallel, then a Fable pass. Then
/wrap-up <JIRA> minor(new XML content ⇒ minor bump 0.4.1 → 0.5.0), and update the memory note~/.claude/projects/-home-ai-dev-3dai-iolabs-pointcloud-modellingexport/memory/modellingexport-nexus-release-status.md(guardrails no longer pending).
Verification
- done guardrails suite against 0.10.0: 389 passed in 15 s (temporary relock, reverted;
uv.lockonmainstill says 0.7.1). - done Direct probe of
create_xmlon a guardrails-shaped legacy dict: 1 warning per guardrail polyline (R-6.3.11), attributes dropped, SchemaVersion 1.1 present. - done modellinglines (211 tests) and lanefinder (182 tests) green on 0.10.0 with no XmlWriterWarning.
- not done Real-segment regeneration under 0.10.0. Consumer check of
lane_xml.pywith a 1.1 lane document. Any HBW confirmation of label vocabulary.
Risks and open questions
- open Label vocabulary. Spec §7.20 / R-5.7.5 expects
guardrail_type∈ {EDSP,ESP 2.0, …} andTextas drawn (ESP 2,0). The detector'sclassify.pyonly knowsw_beam,concrete,cable_suspect,unknown. The historic converter usedFeatureClass = Guard Rails_EDSP | Guard Rails_ESP 2.0. Which detector class maps to which customer label is undecided (spec decision Q8 settled the source, not the mapping). Needs Miro/HBW. - watch Pipelines running the exporter with warnings-as-errors would fail on the R-6.3.11 warning the moment guardrails relocks to 0.10.0, before step 3 lands. Land relock and export rewrite in the same release, or filter
XmlWriterWarningin the interim. - watch Downstream readers of the guardrails XML (HBW Grasshopper, lanefinder visualiser, any DXF step) now see
<Attributes>/<Annotation>children appended after the last 1.0 child. Index-based readers are safe by design (additive, appended); name-based readers ignore unknown tags. Still worth one smoke test. - watch Type coercion in attributes. Values like
faces,post_countmay be numpy ints (accepted), but nested lists/dicts are rejected by the writer. Keeppolyline_top_z_mand other arrays out ofattributes. - watch
noise_wallstays lower-case and polyline-based per Q7; do not rename toNoise Wall. Label for walls is deferred (O-17), so no annotation. - Older notes claimed guardrails had a "fallback writer"; it does not any more (
nexus_export_available()is a constantTruekept for run-summary consumers). Ignore that framing.
Key artifacts
- Guardrails repo:
/home/ai/dev/3dai.iolabs.pointcloud.guardrails(Bitbucketioholding/3dai.iolabs.pointcloud.guardrails), files:guardrails/export.py,guardrails/classify.py,guardrails/lane_xml.py,tests/test_classify_export.py. - Writer repo:
/home/ai/dev/3dai.iolabs.pointcloud.modellingexportatb8973a8(tagv0.10.0). Read:README.md("XML schema", "Warnings"),docs/xml_schema_1_1_spec.md(§4.21, §5.7, §7.1, §7.8, §7.20, §7.24, §8.6),src/iolabs_point_cloud_modelling_export/feature_classes.py,tests/test_profile_builders.py,tests/test_attributes.py,tests/test_profile_warnings.py,tests/legacy_data.py,tests/fixtures/sample_v1_1.xml. - Nexus index:
https://nexus.iolabs.ch/repository/pypi-private/simple/iolabs-point-cloud-modelling-export/(creds via. ./scripts/nexus_credentials.sh→UV_INDEX_NEXUS_USERNAME/PASSWORD). - Previous handoff (release side, now completed): handoff-modellingexport-version-check-20260904-1358.
- Related: xml-visualizer schema-1.1 handoff (lanefinder viewer side of the same schema).
- Reference outputs to diff against:
out_bb3/segment_066,out_bb3/segment_105in the guardrails repo (untracked scratch, may be regenerated).
Suggested skills / delegation
superpowers:test-driven-developmentfor step 2–3 (pin the warning first).- Implementation: Grok 4.6 via
cursor-delegate(worker); review Opus + Sol in parallel, Fable final pass (Miro's policy). wrap-upfor the release;bitbucket-prif a PR is wanted before merging tomain.