Skip to content

Data Model

The Roboscope 2 data model consists of four core entities that work together to represent spatial inspection data:

  1. Spaces - Physical locations or rooms
  2. Work Sessions - Inspection or repair activities
  3. Markers - 3D spatial annotations
  4. Marker Details - Measurement data for markers
Space (1) ────┬───→ (N) Work Session
└───→ (1) Calibration Vector
Work Session (1) ───→ (N) Marker
Marker (1) ───→ (0..1) Marker Details

Represents a physical location or room where work sessions take place.

FieldTypeRequiredDescription
idUUIDYesUnique identifier
keyStringYesHuman-readable unique key (e.g., “room-101”)
nameStringYesDisplay name
descriptionStringNoDetailed description
model_glb_urlStringNoURL to GLB model file
model_usdc_urlStringNoURL to USDC model file
preview_urlStringNoURL to preview image
scan_urlStringNoURL to LiDAR scan data
calibration_vector[f64; 3]YesTranslation vector for coordinate calibration
metaJSONNoArbitrary metadata
created_atTimestampYesCreation time
updated_atTimestampYesLast update time

The calibration_vector is a 3D translation vector [x, y, z] used to transform raw AR coordinates into a calibrated coordinate system.

Default: [0.0, 0.0, 0.0] (no transformation)

Example: [1.5, 0.0, 2.0] translates all markers by +1.5m in X and +2.0m in Z

{
"id": "550e8400-e29b-41d4-a716-446655440000",
"key": "warehouse-a1",
"name": "Warehouse A1",
"description": "Main warehouse storage area",
"model_glb_url": "https://storage.example.com/models/warehouse-a1.glb",
"model_usdc_url": "https://storage.example.com/models/warehouse-a1.usdc",
"preview_url": "https://storage.example.com/previews/warehouse-a1.jpg",
"scan_url": "https://storage.example.com/scans/warehouse-a1.obj",
"calibration_vector": [1.5, 0.0, 2.0],
"meta": {
"building": "North Facility",
"floor": 1
},
"created_at": "2025-01-15T10:30:00Z",
"updated_at": "2025-01-15T10:30:00Z"
}

Represents an inspection, repair, or other work activity within a space.

FieldTypeRequiredDescription
idUUIDYesUnique identifier
space_idUUIDYesForeign key to space
session_typeEnumYesType: inspection, repair, other
statusEnumYesStatus: draft, active, done, archived
started_atTimestampNoWhen work started
completed_atTimestampNoWhen work completed
versioni64YesOptimistic concurrency version
metaJSONNoArbitrary metadata
created_atTimestampYesCreation time
updated_atTimestampYesLast update time
  • inspection: Assessment or survey work
  • repair: Maintenance or fix activity
  • other: Custom work type
  • draft: Being prepared, not yet started
  • active: Currently in progress
  • done: Completed successfully
  • archived: Historical record

The version field enables optimistic locking. When updating:

  1. Client reads current version
  2. Client sends update with version number
  3. Server only applies if version matches
  4. Server increments version on success
{
"id": "660e8400-e29b-41d4-a716-446655440001",
"space_id": "550e8400-e29b-41d4-a716-446655440000",
"session_type": "inspection",
"status": "active",
"started_at": "2025-01-16T09:00:00Z",
"completed_at": null,
"version": 3,
"meta": {
"inspector": "John Doe",
"weather": "clear"
},
"created_at": "2025-01-16T08:55:00Z",
"updated_at": "2025-01-16T09:15:00Z"
}

3D spatial annotations placed in AR. Each marker is defined by four corner points forming a quadrilateral.

FieldTypeRequiredDescription
idUUIDYesUnique identifier
work_session_idUUIDYesForeign key to work session
labelStringNoDisplay label
p1[f64; 3]YesFirst corner point [x, y, z]
p2[f64; 3]YesSecond corner point [x, y, z]
p3[f64; 3]YesThird corner point [x, y, z]
p4[f64; 3]YesFourth corner point [x, y, z]
colorStringNoHex color code (e.g., “#FF0000”)
versioni64YesOptimistic concurrency version
metaJSONNoArbitrary metadata
custom_propsJSONNoDomain-specific properties
calibrated_dataJSONNoCalibrated point coordinates
created_atTimestampYesCreation time
updated_atTimestampYesLast update time

Each point is a 3D coordinate in meters:

[x, y, z]

Coordinate System (ARKit conventions):

  • X-axis: Right (positive) / Left (negative)
  • Y-axis: Up (positive) / Down (negative)
  • Z-axis: Forward (negative) / Backward (positive)

When a space has a calibration vector, markers can store calibrated coordinates:

{
"p1": [x, y, z],
"p2": [x, y, z],
"p3": [x, y, z],
"p4": [x, y, z],
"center": [x, y, z]
}

Calibrated coordinates = Raw coordinates + Space calibration vector

Store arbitrary domain-specific metadata:

{
"severity": "high",
"category": "structural",
"inspector": "Jane Smith",
"priority": 1,
"tags": ["crack", "urgent"],
"measurements": {
"width_mm": 5.2,
"depth_mm": 12.5
}
}

Markers must pass validation:

  • All four edge lengths ≥ 5mm
  • Points must form a valid quadrilateral
  • Coordinates must be finite numbers
{
"id": "770e8400-e29b-41d4-a716-446655440002",
"work_session_id": "660e8400-e29b-41d4-a716-446655440001",
"label": "Crack in north wall",
"p1": [-0.230, 0.060, 0.030],
"p2": [-0.165, 0.057, 0.037],
"p3": [-0.167, 0.050, 0.262],
"p4": [-0.232, 0.053, 0.255],
"color": "#FF0000",
"version": 1,
"meta": {},
"custom_props": {
"severity": "medium",
"type": "crack",
"inspector": "John Doe"
},
"calibrated_data": {
"p1": [1.270, 0.060, 2.030],
"p2": [1.335, 0.057, 2.037],
"p3": [1.333, 0.050, 2.262],
"p4": [1.268, 0.053, 2.255],
"center": [1.302, 0.055, 2.146]
},
"created_at": "2025-01-16T09:30:00Z",
"updated_at": "2025-01-16T09:30:00Z"
}

One-to-one measurement data for markers. Stores calculated spatial metrics.

FieldTypeRequiredDescription
marker_idUUIDYesPrimary key & foreign key to marker
center_location_longf32YesCenter Z coordinate (longitudinal)
center_location_crossf32YesCenter X coordinate (cross)
x_negativef32YesX distance (left side, signed)
x_positivef32YesX distance (right side, signed)
z_negativef32YesZ distance (near edge)
z_positivef32YesZ distance (far edge)
long_sizef32YesLength along Z axis
cross_sizef32YesWidth along X axis
custom_propsJSONNoCustom measurement properties
created_atTimestampYesCreation time
updated_atTimestampYesLast update time

All metrics are calculated from the four marker corner points:

Center Location (Longitudinal)

center_location_long = (p1[2] + p2[2] + p3[2] + p4[2]) / 4

Center Location (Cross)

center_location_cross = (p1[0] + p2[0] + p3[0] + p4[0]) / 4

X Negative/Positive

x_negative = min_abs(p1[0], p2[0], p3[0], p4[0]) // keeps sign
x_positive = max_abs(p1[0], p2[0], p3[0], p4[0]) // keeps sign

Z Negative/Positive

z_negative = min(p1[2], p2[2], p3[2], p4[2])
z_positive = max(p1[2], p2[2], p3[2], p4[2])

Size Measurements

long_size = z_positive - z_negative
cross_size = max(p1[0], p2[0], p3[0], p4[0]) - min(p1[0], p2[0], p3[0], p4[0])
{
"marker_id": "770e8400-e29b-41d4-a716-446655440002",
"center_location_long": 0.146,
"center_location_cross": -0.198,
"x_negative": -0.232,
"x_positive": -0.165,
"z_negative": 0.030,
"z_positive": 0.262,
"long_size": 0.232,
"cross_size": 0.067,
"custom_props": {
"measurement_method": "automatic",
"confidence": 0.95
},
"created_at": "2025-01-16T09:31:00Z",
"updated_at": "2025-01-16T09:31:00Z"
}
-- Enums
CREATE TYPE work_session_status AS ENUM ('draft', 'active', 'done', 'archived');
CREATE TYPE work_session_type AS ENUM ('inspection', 'repair', 'other');
-- Tables
CREATE TABLE spaces (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
key VARCHAR(255) UNIQUE NOT NULL,
name VARCHAR(255) NOT NULL,
description TEXT,
model_glb_url TEXT,
model_usdc_url TEXT,
preview_url TEXT,
scan_url TEXT,
calibration_vector DOUBLE PRECISION[3] NOT NULL DEFAULT '{0,0,0}',
meta JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE work_sessions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
space_id UUID NOT NULL REFERENCES spaces(id) ON DELETE CASCADE,
session_type work_session_type NOT NULL,
status work_session_status NOT NULL DEFAULT 'draft',
started_at TIMESTAMPTZ,
completed_at TIMESTAMPTZ,
version BIGINT NOT NULL DEFAULT 1,
meta JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE markers (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
work_session_id UUID NOT NULL REFERENCES work_sessions(id) ON DELETE CASCADE,
label TEXT,
p1 DOUBLE PRECISION[3] NOT NULL,
p2 DOUBLE PRECISION[3] NOT NULL,
p3 DOUBLE PRECISION[3] NOT NULL,
p4 DOUBLE PRECISION[3] NOT NULL,
color VARCHAR(7),
version BIGINT NOT NULL DEFAULT 1,
meta JSONB NOT NULL DEFAULT '{}',
custom_props JSONB NOT NULL DEFAULT '{}',
calibrated_data JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE marker_details (
marker_id UUID PRIMARY KEY REFERENCES markers(id) ON DELETE CASCADE,
center_location_long REAL NOT NULL,
center_location_cross REAL NOT NULL,
x_negative REAL NOT NULL,
x_positive REAL NOT NULL,
z_negative REAL NOT NULL,
z_positive REAL NOT NULL,
long_size REAL NOT NULL,
cross_size REAL NOT NULL,
custom_props JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- Improve query performance
CREATE INDEX idx_work_sessions_space_id ON work_sessions(space_id);
CREATE INDEX idx_work_sessions_status ON work_sessions(status);
CREATE INDEX idx_markers_work_session_id ON markers(work_session_id);
-- JSON property queries
CREATE INDEX idx_markers_custom_props ON markers USING GIN(custom_props);
CREATE INDEX idx_marker_details_custom_props ON marker_details USING GIN(custom_props);
Rust TypePostgreSQL TypeNotes
UuidUUIDStandard UUID
StringTEXT / VARCHARUTF-8 strings
i64BIGINT64-bit integer
f64DOUBLE PRECISION64-bit float
f32REAL32-bit float
[f64; 3]DOUBLE PRECISION[3]Fixed-size array
serde_json::ValueJSONBJSON with binary storage
DateTime<Utc>TIMESTAMPTZTimezone-aware timestamp
WorkSessionStatuswork_session_statusCustom enum
WorkSessionTypework_session_typeCustom enum
Swift TypeJSON TypeNotes
UUIDStringUUID string format
StringStringUTF-8 strings
Int / Int64NumberInteger values
Float / DoubleNumberFloating-point values
[Double]ArrayNumber array
[String: AnyCodable]ObjectFlexible JSON object
DateStringISO 8601 format
EnumsStringLowercase snake_case
  • All primary keys are UUIDs
  • Generated server-side or client-side
  • Enables distributed systems and offline support
  • Store domain-specific data in custom_props
  • Keep core schema clean and focused
  • Use GIN indexes for JSON queries
  • Always include version in updates
  • Handle version conflicts gracefully
  • Retry with fresh data on conflict
  • Check edge lengths (minimum 5mm)
  • Ensure points form valid quadrilateral
  • Validate coordinate ranges
  • Set calibration vector per space
  • Store both raw and calibrated coordinates
  • Transform at API layer, not client
  • Add indexes on foreign keys
  • Use GIN for JSON property searches
  • Consider partial indexes for common filters