Handoff: rewire guardrails to modelling-export 0.10.0 (schema 1.1)

2026-09-04 · for the next agent session in 3dai.iolabs.pointcloud.guardrails · written by Claude (Fable 5.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

ItemState
modelling-export on Nexusdone 0.10.0 wheel+sdist, repo master b8973a8, schema 1.1 writer + feature_classes.py
Other consumersdone modellinglines relocked (released 0.13.1), lanefinder relocked. asphaltedge is not a consumer.
guardrails main81760b2 "AI3D-379 Relock iolabs-common 0.9.0", package version 0.4.1, clean tree (only untracked out_*/run_*.log scratch)
guardrails pinpyproject.toml line 17: "iolabs-point-cloud-modelling-export" (unpinned), uv.lock = 0.7.1
Writer call siteguardrails/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 guardrailsguardrails/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)

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/TypeGeometryAnnotationAttributesSpec
guardrailPolylines, unchangedrequired, 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_railPolylines, unchangedmust not carry Annotationparent_guardrail_id, post_spacing_m, post_count, post_confidence, halfwidth_m, lateral_offset_m via uniform <Attribute name= type=>§7.24
noise_wallPolylines, name kept lower-case (decision Q7)must not carry Annotation (label deferred, O-17)MAY carry height_m, length_m§7.8

Next steps (ordered)

  1. Branch + relock. From main in /home/ai/dev/3dai.iolabs.pointcloud.guardrails:
    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 passed
    Commit the relock on its own (message style in this repo: AI3D-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.
  2. Pin the current output first (TDD). Add a test that builds a realistic dict via instances_to_xml_dict with ≥1 rail, ≥1 support, ≥1 top rail, ≥1 wall, runs create_xml under warnings.simplefilter("error", XmlWriterWarning), and asserts check_profiles(data) == []. It fails today with the R-6.3.11 message above. Also add filterwarnings = ["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").
  3. Rewrite _feature_polylines() in guardrails/export.py. Keep id, lane_id, road_point, vertices; move every other key into attributes={...} (drop None values; keep numpy scalars or cast). For kind == "guardrail" add annotation={"display": True, "text": [label]}; for supports / top rails / walls add no annotation. Delete _SUPPORT_ATTRIBUTE_KEYS and its comment about the stand-in; delete the stale docstring text about "both the Nexus and the stand-in exporter". Check the exact annotation/text key shape against feature_classes.polyline_feature and tests/test_profile_builders.py in the modellingexport repo rather than guessing.
  4. Decide the label / machine value (see open question 1). Minimal safe interim: guardrail_type attribute = classify value (w_beam, concrete, cable_suspect, unknown) and Text = the same string, plus a single mapping table _GUARDRAIL_LABELS that is easy to fill once HBW answers. Do not invent an EDSP/ESP mapping silently.
  5. Fix software_version: instances_to_xml_dict hardcodes "guardrails-0.2.0"; use f"guardrails-{guardrails.__version__}" (package is at 0.4.1).
  6. 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: SchemaVersion 1.1, <Attributes> on every polyline, <Annotation> only on guardrail, geometry bytes unchanged versus the previous output, zero XmlWriterWarning in the run log. Optionally open it in lanefinder's scripts/visualize_xml_open3d.py, which already renders guardrail bodies from 1.1 documents.
  7. Consumer check. Run the guardrails detector against a lane XML that carries 1.1 elements (e.g. modellingexport tests/fixtures/sample_v1_1.xml or a fresh modellinglines 0.13.1 output) to confirm lane_xml.py parsing is unaffected.
  8. 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

Risks and open questions

  1. open Label vocabulary. Spec §7.20 / R-5.7.5 expects guardrail_type ∈ {EDSP, ESP 2.0, …} and Text as drawn (ESP 2,0). The detector's classify.py only knows w_beam, concrete, cable_suspect, unknown. The historic converter used FeatureClass = 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.
  2. 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 XmlWriterWarning in the interim.
  3. 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.
  4. watch Type coercion in attributes. Values like faces, post_count may be numpy ints (accepted), but nested lists/dicts are rejected by the writer. Keep polyline_top_z_m and other arrays out of attributes.
  5. watch noise_wall stays lower-case and polyline-based per Q7; do not rename to Noise Wall. Label for walls is deferred (O-17), so no annotation.
  6. Older notes claimed guardrails had a "fallback writer"; it does not any more (nexus_export_available() is a constant True kept for run-summary consumers). Ignore that framing.

Key artifacts

Suggested skills / delegation