{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "version": "2.0.0",
  "$id": "https://schemas.ectropy.ai/voxel/voxel-group.schema.json",
  "title": "Voxel Group Schema",
  "description": "A spatial voxel group (room, level, takt zone, decision zone, worker activity, flow, system, element, custom). Bounding box and centroid are structured world-space objects. Rolled-up state is two-axis: rolledUpStatus (work progress) and rolledUpHealth (risk); a legacy single rolled-up value AT_RISK maps to rolledUpHealth. derivationMode declares per group whether membership is materialized or tag-derived. memberCount is the group's size, computed at write time, so a client can tell small from large groups without resolving voxelUrns. rolledUpCost and rolledUpCarbonIntensity are write-time rollups over the same members: cost is a sum of member estimates carrying the group's lowest estimate confidence; carbon is an area-weighted mean intensity (kgCO2e per square foot of assembly face), never a total. Both are null when no member carries a value.",
  "type": "object",
  "required": [
    "$id",
    "$schema",
    "schemaVersion",
    "groupId",
    "groupType",
    "rolledUpStatus",
    "boundingBox"
  ],
  "definitions": {
    "graphMetadata": {
      "type": "object",
      "description": "Bidirectional graph traversal metadata",
      "properties": {
        "inEdges": {
          "type": "array",
          "items": {
            "$ref": "https://schemas.ectropy.ai/_definitions/urn.schema.json"
          },
          "uniqueItems": true
        },
        "outEdges": {
          "type": "array",
          "items": {
            "$ref": "https://schemas.ectropy.ai/_definitions/urn.schema.json"
          },
          "uniqueItems": true
        },
        "edges": {
          "type": "array",
          "items": {
            "type": "object",
            "required": [
              "from",
              "to",
              "type"
            ],
            "properties": {
              "from": {
                "$ref": "https://schemas.ectropy.ai/_definitions/urn.schema.json"
              },
              "to": {
                "$ref": "https://schemas.ectropy.ai/_definitions/urn.schema.json"
              },
              "type": {
                "type": "string"
              },
              "weight": {
                "type": "number",
                "minimum": 0,
                "maximum": 1
              },
              "label": {
                "type": "string"
              }
            }
          }
        }
      }
    },
    "point3": {
      "type": "object",
      "description": "World-space coordinate triple (maps to ROS geometry_msgs/Point). Distinct from per-voxel coordinates, which carry voxel resolution.",
      "required": [
        "x",
        "y",
        "z"
      ],
      "additionalProperties": false,
      "properties": {
        "x": {
          "type": "number"
        },
        "y": {
          "type": "number"
        },
        "z": {
          "type": "number"
        }
      }
    },
    "boundingBox": {
      "type": "object",
      "description": "Axis-aligned world-space bounds. Structured; on the ROS wire this is encoded inside metadata_json.",
      "required": [
        "min",
        "max"
      ],
      "additionalProperties": false,
      "properties": {
        "min": {
          "$ref": "#/definitions/point3"
        },
        "max": {
          "$ref": "#/definitions/point3"
        }
      }
    },
    "groupType": {
      "type": "string",
      "description": "Group taxonomy.",
      "enum": [
        "DECISION_ZONE",
        "WORKER_ACTIVITY",
        "FLOW",
        "TAKT_ZONE",
        "ROOM",
        "SYSTEM",
        "LEVEL",
        "ELEMENT",
        "CUSTOM"
      ]
    },
    "rolledUpStatus": {
      "type": "string",
      "description": "Work-progress axis, rolled up from member voxels. Aligned to the per-voxel reconciled status axis (+ON_HOLD, -REWORK).",
      "enum": [
        "PLANNED",
        "IN_PROGRESS",
        "COMPLETE",
        "BLOCKED",
        "ON_HOLD",
        "INSPECTION_REQUIRED"
      ]
    },
    "rolledUpHealth": {
      "type": "string",
      "description": "Risk axis, separate from work-progress. The legacy single rolled_up_status wire value AT_RISK maps here.",
      "enum": [
        "HEALTHY",
        "AT_RISK",
        "CRITICAL"
      ]
    },
    "display": {
      "type": "object",
      "description": "Denormalized presentation hints. Source of truth is rolledUpStatus + rolledUpHealth; display is a convenience projection (legacy display_color_hex).",
      "additionalProperties": false,
      "properties": {
        "colorHex": {
          "type": "string",
          "pattern": "^#[0-9a-fA-F]{6}$"
        },
        "opacity": {
          "type": "number",
          "minimum": 0,
          "maximum": 1
        },
        "icon": {
          "type": "string"
        }
      }
    },
    "derivationMode": {
      "type": "string",
      "enum": [
        "materialized",
        "tag-derived"
      ],
      "description": "materialized: voxelUrns is the authoritative membership, set when the group is provisioned (DECISION_ZONE, WORKER_ACTIVITY, FLOW, ELEMENT, CUSTOM). tag-derived: the group is a live query over voxel location tags (level, system, room, zone) -- LEVEL, SYSTEM, ROOM, TAKT_ZONE; voxelUrns, if present, is a cache snapshot, never the source of truth."
    }
  },
  "properties": {
    "$id": {
      "type": "string",
      "pattern": "^urn:luhtech:ectropy:voxel-group:[a-zA-Z0-9][a-zA-Z0-9_-]*(:[a-zA-Z0-9][a-zA-Z0-9_-]*)*$",
      "description": "The group's URN: urn:luhtech:ectropy:voxel-group:{groupId}, or a project-scoped extension with further segments."
    },
    "$schema": {
      "type": "string",
      "format": "uri",
      "description": "Schema declaration: the URI of the schema this instance follows."
    },
    "schemaVersion": {
      "type": "string",
      "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$"
    },
    "meta": {
      "type": "object",
      "properties": {
        "projectId": {
          "type": "string"
        },
        "sourceOfTruth": {
          "type": "string"
        },
        "lastUpdated": {
          "type": "string",
          "format": "date-time"
        },
        "syncStatus": {
          "type": "object",
          "properties": {
            "syncDirection": {
              "type": "string",
              "enum": [
                "v3-is-source-of-truth",
                "bidirectional"
              ]
            }
          }
        }
      }
    },
    "groupId": {
      "type": "string",
      "pattern": "^[A-Za-z0-9][A-Za-z0-9_-]*$",
      "description": "Machine-generated stable identity for indexing and search (a 12-character SHA-256-derived slug), embedded in the group URN. Not human-readable -- use label for display."
    },
    "groupType": {
      "$ref": "#/definitions/groupType"
    },
    "label": {
      "type": "string",
      "description": "Human-readable group label."
    },
    "voxelUrns": {
      "type": "array",
      "items": {
        "$ref": "https://schemas.ectropy.ai/_definitions/urn.schema.json"
      },
      "description": "Member voxel URNs. Authoritative when derivationMode=materialized; a cache snapshot (never source of truth) when tag-derived. The expensive join projection; omitted from the spatial render projection."
    },
    "memberCount": {
      "type": "integer",
      "minimum": 0,
      "description": "Count of member voxels, computed once at write time (materialize) using the same predicate that resolves voxelUrns on demand -- NOT a live COUNT at read time. LOD-0 group-snapshot metadata: lets a client classify a group as small/large (e.g. always-render vs LOD-distance-gated) without first resolving voxelUrns. Present on the spatial render projection (unlike voxelUrns, which is omitted there)."
    },
    "rolledUpCost": {
      "type": [
        "object",
        "null"
      ],
      "additionalProperties": false,
      "required": [
        "value",
        "currency",
        "confidence",
        "memberCount"
      ],
      "description": "Write-time sum of member voxels' estimated_cost (material cost, estimate-first). confidence is the LOWEST member estimate confidence, so a group total never reads more certain than its weakest part. memberCount counts members carrying an estimate (coverage against the group memberCount). null when no member carries an estimate -- never 0.",
      "properties": {
        "value": {
          "type": "number",
          "minimum": 0
        },
        "currency": {
          "const": "USD"
        },
        "confidence": {
          "enum": [
            "high",
            "medium",
            "low",
            null
          ]
        },
        "memberCount": {
          "type": "integer",
          "minimum": 0
        }
      }
    },
    "rolledUpCarbonIntensity": {
      "type": [
        "object",
        "null"
      ],
      "additionalProperties": false,
      "required": [
        "kgCo2ePerSf",
        "memberCount"
      ],
      "description": "Write-time area-weighted mean of member voxels' assembly embodied-carbon intensity (kgCO2e per square foot of assembly face, openEPD/EC3 via cis-carbon), weighted by each member's face area. An intensity, never a total: per-voxel totals are not derived until the quantity basis is verified. memberCount counts members carrying a carbon figure. null when none does.",
      "properties": {
        "kgCo2ePerSf": {
          "type": "number",
          "minimum": 0
        },
        "memberCount": {
          "type": "integer",
          "minimum": 0
        }
      }
    },
    "basePriority": {
      "type": "integer",
      "description": "Display/priority weight for the group type."
    },
    "rolledUpStatus": {
      "$ref": "#/definitions/rolledUpStatus"
    },
    "rolledUpHealth": {
      "$ref": "#/definitions/rolledUpHealth"
    },
    "boundingBox": {
      "$ref": "#/definitions/boundingBox"
    },
    "centroid": {
      "$ref": "#/definitions/point3"
    },
    "display": {
      "$ref": "#/definitions/display"
    },
    "timestamps": {
      "type": "object",
      "properties": {
        "createdAt": {
          "type": "string",
          "format": "date-time"
        },
        "updatedAt": {
          "type": "string",
          "format": "date-time",
          "description": "Last update; maps to the ROS builtin_interfaces/Time stamp."
        }
      }
    },
    "graphMetadata": {
      "$ref": "#/definitions/graphMetadata"
    },
    "derivationMode": {
      "$ref": "#/definitions/derivationMode"
    }
  },
  "additionalProperties": false
}
