GeoTask Core ships with 6 built-in deterministic operators. This guide explains how to define and register additional operators.
An operator in GeoTask Core has three parts:
- Implementation. A pure Python function in
geotask_core/ops.py(or your own module). - Contract. An
OperatorContractdataclass that declares the operator's interface and semantics. - Registration. Adding the contract to the
OperatorRegistryso the dispatcher can find it.
Suppose you want to add distance_3d, which computes Euclidean distance between two 3D points.
Create a pure function that takes the declared input types and returns the declared output type:
# geotask_core/ops.py
import math
def distance_3d(a: list[float], b: list[float]) -> float:
"""Euclidean distance between two 3D points."""
dx = a[0] - b[0]
dy = a[1] - b[1]
dz = a[2] - b[2]
return math.sqrt(dx*dx + dy*dy + dz*dz)The function must be:
- Deterministic (same inputs always produce same output).
- Free of side effects (no I/O, no network, no random).
- Importable from the module path declared in the contract.
In geotask_core/v1/operator_contracts.py, add a new OperatorContract:
DISTANCE_3D = OperatorContract(
name="distance_3d",
version="1.0",
family="measurement",
description="Euclidean distance between two 3D points.",
arity=2,
input_types=["point", "point"],
output={
"type": "number",
"unit_behavior": "inherit_horizontal_unit",
},
deterministic=True,
semantics={
"formula": "sqrt((x2-x1)^2 + (y2-y1)^2 + (z2-z1)^2)",
"boundary_rules": [
"Distance is non-negative.",
"Identical points produce zero.",
],
},
invariants=[
{"id": "non_negative", "expression": "result >= 0"},
{"id": "symmetric", "expression": "distance(a,b) == distance(b,a)"},
],
error_codes=[
"invalid_coordinates",
"arity_mismatch",
],
implementation="geotask_core.ops.distance_3d",
)Add the new contract to the built-in list at the bottom of operator_contracts.py:
_BUILTIN_CONTRACTS: list[OperatorContract] = [
DISTANCE_2D,
LINE_INTERSECTS_RECT,
# ... existing contracts ...
DISTANCE_3D, # new
]The default_registry will pick it up at import time. External packages can also register contracts programmatically:
from geotask_core.v1.operator_contracts import OperatorRegistry, OperatorContract
registry = OperatorRegistry()
registry.register(my_custom_contract)Reference the new operator in your YAML document:
operator_set: [distance_3d]
tasks:
- id: "calc"
assertions:
- id: "dist"
operator: "distance_3d"
object_refs: ["point_a", "point_b"]The dispatcher resolves the operator, extracts data from both point objects, calls ops.distance_3d, and returns the result.
| Field | Type | Required | Description |
|---|---|---|---|
name |
str |
Yes | Unique identifier used in YAML operator fields |
version |
str |
No | Contract version, default "1.0" |
family |
str |
No | Category: measurement, topology, interval |
description |
str |
No | Human-readable explanation |
arity |
int |
Yes | Number of input objects |
input_types |
list[str] |
Yes | Ordered expected GeoObject types |
output |
dict |
No | type (number/boolean) and unit_behavior |
deterministic |
bool |
No | Must be True for Core operators |
semantics |
dict |
No | Formula, boundary rules, legacy aliases |
model_execution |
dict |
No | Hints for model-based execution (not used by Core) |
invariants |
list[dict] |
No | Properties that must hold for all inputs |
error_codes |
list[str] |
No | Diagnostic codes this operator may surface |
examples |
list[dict] |
No | Input/output examples for testing and documentation |
implementation |
str |
Yes | Module path to the bound function |
The AssertionDispatcher connects assertions to implementations. When dispatch(assertion, objects) is called:
- Lookup. Find the
OperatorContractby name in the registry. - Arity check. Verify
len(assertion.object_refs) == contract.arity. - Parameter extraction. For each
object_ref, resolve theGeoObjectfrom the objects dict and extract geometry data by its declaredinput_type:point->data["coordinates"](fallback:data["xy"])polyline->data["coordinates"](fallback:data["points"])rect->data["bbox"]time_interval->[data["start"], data["end"]]altitude_interval->[data["min"], data["max"]]
- Implementation binding. Parse
contract.implementation(e.g.,"geotask_core.ops.distance_2d"), import the module, and retrieve the callable. - Invocation. Call
impl(*extracted_params, **assertion.parameters).
The dispatcher raises ValueError for arity mismatches, missing objects, or type mismatches. It raises KeyError for unregistered operators. The executor catches these and converts them to error diagnostics.
- Operator implementations must not depend on
geotask_core.v1modules.ops.pyis a leaf. - Contracts live in
v1/operator_contracts.pyand depend only onv1/ir.pyfor theOperatorContracttype. - The registry and dispatcher are importable from
geotask_core.v1.operator_contractswithout pulling in the validator or executor.