FiberCAD is a fiber network planning engine built on a microkernel + plugin architecture. It automates the full FTTH distributed split design workflow: from raw address data to construction-ready DXF output with cryptographic integrity verification.
The kernel provides domain-agnostic services. Domain modules register as plugins and are dispatched through a chain-of-responsibility pattern.
| Module | Responsibility | Pattern |
|---|---|---|
plugin.py | Dispatch chain: Handled/NotHandled per domain | Chain of responsibility |
tool.py | MCP-compatible tool schema definitions + export | Schema registry |
config.py | Config profiles with precedence (CLI > env > profile > defaults) | Layered config |
http.py | HttpBackend protocol for testable I/O | Strategy pattern |
errors.py | Typed errors with exit codes + contextual enrichment | Error hierarchy |
security.py | Path sanitization, prompt injection stripping, secret redaction | Input filter chain |
circuit_breaker.py | Closed/Open/HalfOpen per endpoint | Circuit breaker |
rate_limiter.py | Token bucket (Nominatim: 1 req/s) | Token bucket |
checkpoint.py | Resumable batch operations keyed by input_hash | Checkpoint/resume |
audit.py | JSONL event log, daily rotation | Append-only log |
project.py | In-memory project container (working graph) | — |
class DispatchResult:
HANDLED = "handled"
NOT_HANDLED = "not_handled"
class Dispatcher(Protocol):
def dispatch(self, tool_name: str, args: dict) -> tuple[str, Any]:
"""Returns (HANDLED, result) or (NOT_HANDLED, None)"""
...
# Chain: ingest -> planning -> rules -> output -> integrity
def dispatch_chain(dispatchers: list[Dispatcher], tool_name: str, args: dict):
for d in dispatchers:
status, result = d.dispatch(tool_name, args)
if status == DispatchResult.HANDLED:
return security.strip_prompt_injection(result) # post-processing
raise ToolNotFoundError(tool_name)
Data flows through graduated enrichment layers. Each layer is a separate file, building on the previous. This enables selective regeneration, independent versioning, and full provenance tracking.
| Layer | Schema | Input From | Key Content |
|---|---|---|---|
| L0 | fibercad/input/v1 | User | Addresses, area polygon |
| L1 | fibercad/gis/v1 | L0 + OSM | Building footprints, road network, parcels |
| L2 | fibercad/topology/v1 | L1 + algorithm | Boundary groups, splitter tree, connectivity |
| L3 | fibercad/physical/v1 | L2 + algorithm | Conduit paths, equipment positions, splices |
| L4 | fibercad/compliance/v1 | L3 + rules profile | Validation results, design statistics |
| L5 | — | L3 + L4 | Generated artifacts (DXF, BOM, reports) |
Every layer file contains an _integrity header with SHA-256 content hash, Ed25519 signature, and provenance link to the previous layer.
{
"_integrity": {
"version": "1",
"content_hash": "sha256:a1b2c3...",
"signature": "ed25519:x9y8z7...",
"signer": {
"id": "fibercad-engine/1.0.0",
"public_key": "ed25519:pub_abc123..."
},
"timestamp": "2026-04-17T14:32:00Z",
"provenance": {
"previous_layer": "L1_gis.fiberjson",
"previous_hash": "sha256:d4e5f6...",
"previous_signature_valid": true
},
"generation": {
"tool_version": "0.1.0",
"algorithm_versions": {
"boundary_groups": "1.0.0",
"routing": "1.0.0"
},
"profile": "lumos_v6"
}
}
}
verify/fibercad_verify.py has zero FiberCAD dependencies (only cryptography + hashlib). Distributable independently for third-party audit.
~/.config/fibercad/keys/<project_id>.key (mode 0600)Constrained road-network agglomerative clustering. Partitions addresses into groups of ~32 for one 1x8 splitter each, with ~4 spare ports.
Two-stage: 1x8 at boundary group centroid (road-network), 1x4 at sub-cluster centroids. All snapped to nearest parcel line per standards.
Greedy Steiner tree approximation on road network. MST of splitter positions using Dijkstra road-network distances, mapped back onto actual road segments. Post-processing inserts handholes (every ≤500ft), assigns drops (max 6), verifies no T-connections.
Run between L2→L3 transition before committing the layer:
Deterministic tools that LLMs call via tool_use. The LLM never sees raw geometry — it receives structured summaries (~30 tokens instead of 3,000).
| Tool | Category | Why Baremetal |
|---|---|---|
project_summary | Summary | Compact stats, saves tokens |
design_stats | Summary | Aggregated metrics (math) |
query_elements | Spatial | O(log n) spatial index query |
measure_distance | Spatial | Geometry math |
find_nearest | Spatial | Spatial index lookup |
route_between | Graph | Dijkstra (O(E log V)) |
trace_path | Graph | Graph traversal |
validate_design | Rules | Deterministic rule checking |
suggest_placement | Planning | Centroid math + road-snap |
verify_chain | Integrity | Cryptographic verification |
"BG-03: 34 homes, 1x8 at (36.07, -79.82), 4 x 1x4, 2847 ft conduit, 0 violations" (~30 tokens) instead of the full GeoJSON geometry (~3,000 tokens).
Provider standards are encoded as pluggable profiles. The engine dispatches rules, returns typed ValidationResult structs, and never touches the filesystem.
| Rule ID | Check | Source | Status |
|---|---|---|---|
MAX_CONDUIT_RUNS | ≤3 runs per street | Lumos §18 | Implemented |
NO_T_CONNECTIONS | No conduit coupling outside equipment | Lumos §18 + Video | Implemented |
MAX_DROPS | ≤6 drops per conduit | Lumos §18 | Implemented |
MAX_RUN_LENGTH | ≤500ft warning, ≤650ft error (two-tier) | Lumos §18 + Video | Implemented |
HH_ON_PARCEL | Handholes on parcel lines | Lumos §18 | Implemented |
BORE_NOT_DRIVEWAY | No bore pits in driveways | Lumos §18 | Implemented |
DEDICATED_DIST_CONDUIT | Dist. cable in own conduit | Lumos §18 | Implemented |
SPARE_PORTS | ~4 spare per 1x8 | Lumos §13.1 | Implemented |
BOUNDARY_SIZE | Groups 28-36 homes | Lumos §13.1 | Implemented |
SLACK_LOOPS | 15ft/splitter, 50ft/3-way | Lumos §16.1 | Implemented |
SPLITTER_SEPARATION | 1x4 and 1x8 cannot share a handhole | Video corpus | Implemented |
HH_AUTO_SIZING | MED for 1x8, SM for 3-way, SPLT for 1x4 | Video corpus | Implemented |
STREET_CROSSING_HH | One HH per crossing (both for DOT/railroad) | Lumos §18 + Video | Implemented |
NO_PARALLEL_CONDUIT | Don’t parallel both sides of street | Lumos §18 | Implemented |
MAX_CONDUIT_RUNS | ≤3 runs per street | Lumos §18 | Implemented |
BEND_RULE | Bend w/o equipment if adjacent ≤150ft | Video corpus | MVP |
CUL_DE_SAC | Never fully circumnavigate cul-de-sac | Video corpus | MVP |
FIBER_NUMBERING | Highest number first, furthest address | Video corpus | Week 2 |
RIBBON_SEPARATION | Feeder/secondary cannot share ribbon | Video corpus | Week 2 |
Each violation includes: rule ID, severity (error/warning/info), affected element ID, human-readable message, and contextual fix guidance (no LLM needed for the explanation).
Generated via ezdxf. Layer mapping follows Lumos conventions:
| DXF Layer | Content |
|---|---|
P_CONDUIT | Conduit paths (polylines) |
FIBER_FOC | Fiber optic cable paths |
E_ROW / E_EOP | Right-of-way / edge of pavement |
SPLITTERS_1X8 / 1X4 | Splitter block references |
HANDHOLES | HH/SPLT/SM/MED blocks |
ADDRESSES | Address text labels (CAPITALS, masked) |
BOUNDARY_GROUPS | Green grouping polygons |
SLACK_LOOPS | Cyan fiber loops |
SPLICES | Midsheath (pink) / fiber (red) |
Self-contained HTML file with embedded Leaflet.js. All design layers on aerial imagery, clickable popups, validation results panel, design statistics. Zero server dependency.
Excel workbook (openpyxl): equipment summary, conduit footage, cable counts, address assignments, material cost estimate.
=, +, -, @)strip_prompt_injection applied at dispatch post-processing layer| Concern | Solution |
|---|---|
| Layer I/O | In-memory working graph; layer files are artifacts, not working storage |
| Hashing | Streaming SHA-256 via hashlib.update() during write |
| Spatial queries | rtree.STRtree for O(log n) nearest-neighbor |
| Large graphs | Dict-adjacency (not NetworkX dict-of-dicts) for memory efficiency |
| Geocoding | Checkpoint/resume for crash recovery; token bucket rate limiting |
| API resilience | Circuit breaker (Closed/Open/HalfOpen) per endpoint |
The LLM (Claude) is an optional review layer, not a core dependency. It calls baremetal tools and reasons about structured summaries.
The architecture is designed for migration to Rust (Go as fallback):
petgraph