feat(platform): add replaceable geozone data layer
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
# Gelios provider package v6
|
||||
|
||||
Adds the bounded, offset-paged `gelios.geozone` read capability and maps a
|
||||
complete accepted source generation into the provider-neutral
|
||||
`map.zones.current.v1` Data Product. The runtime publisher must use replace
|
||||
mode: a partial provider page set never reaches Publish, while a complete newer
|
||||
generation atomically upserts present zones and emits remove patches for zones
|
||||
that disappeared.
|
||||
|
||||
`gelios-rest-v1` and `mmap-snapshot-v1` are source adapters, not Foundry
|
||||
bindings. Both must preserve the native Gelios zone id (or use an explicit
|
||||
crosswalk) and produce the same `map.zone` facts. Foundry only consumes the
|
||||
provider-neutral product.
|
||||
@@ -0,0 +1,9 @@
|
||||
export {
|
||||
GELIOS_PROVIDER_PACKAGE_ID,
|
||||
GELIOS_PROVIDER_PACKAGE_VERSION,
|
||||
GELIOS_ZONES_DATA_PRODUCT_ID,
|
||||
GELIOS_ZONES_DATA_PRODUCT_VERSION,
|
||||
GELIOS_ZONES_ONTOLOGY_REVISION,
|
||||
GELIOS_ZONE_SOURCE_ID_PREFIX,
|
||||
geliosProviderPackageV6,
|
||||
} from "./package.mjs";
|
||||
@@ -0,0 +1,192 @@
|
||||
import { geliosProviderPackageV5 } from "../v5/package.mjs";
|
||||
|
||||
export const GELIOS_PROVIDER_PACKAGE_ID = "gelios.provider.v6";
|
||||
export const GELIOS_PROVIDER_PACKAGE_VERSION = "6.0.0";
|
||||
export const GELIOS_ZONES_DATA_PRODUCT_ID = "map.zones.current.v1";
|
||||
export const GELIOS_ZONES_DATA_PRODUCT_VERSION = "1.0.0";
|
||||
export const GELIOS_ZONES_ONTOLOGY_REVISION = "ontology.map.zone.v1";
|
||||
export const GELIOS_ZONE_SOURCE_ID_PREFIX = "gelios-zone-";
|
||||
|
||||
const ZONES_CAPABILITY_ID = "gelios.geozones.current.read";
|
||||
const ZONES_FIELD_POLICY_ID = "gelios.geozones.current.fields.v1";
|
||||
const ZONES_PROFILE_ID = "gelios.geozones.current.realtime.v1";
|
||||
const ZONES_MAPPING_ID = "gelios.geozones.to.map.zones.current.v1";
|
||||
const ZONES_TEMPLATE_ID = "gelios.geozones.current.l2.v1";
|
||||
|
||||
const value = structuredClone(geliosProviderPackageV5);
|
||||
const authModeId = value.authModes[0].id;
|
||||
const zonesFields = [
|
||||
"area_square_meters",
|
||||
"description",
|
||||
"display_name",
|
||||
"geometry",
|
||||
"geometry_kind",
|
||||
"max_speed_kph",
|
||||
"perimeter_meters",
|
||||
"source_kind",
|
||||
"source_revision",
|
||||
"style_color",
|
||||
];
|
||||
|
||||
value.id = GELIOS_PROVIDER_PACKAGE_ID;
|
||||
value.version = GELIOS_PROVIDER_PACKAGE_VERSION;
|
||||
value.manifest = {
|
||||
...value.manifest,
|
||||
id: "gelios.provider.manifest.v6",
|
||||
version: GELIOS_PROVIDER_PACKAGE_VERSION,
|
||||
capabilityIds: [...value.manifest.capabilityIds, ZONES_CAPABILITY_ID],
|
||||
fieldPolicyIds: [...value.manifest.fieldPolicyIds, ZONES_FIELD_POLICY_ID],
|
||||
collectionProfileIds: [...value.manifest.collectionProfileIds, ZONES_PROFILE_ID],
|
||||
dataProductIds: [...value.manifest.dataProductIds, GELIOS_ZONES_DATA_PRODUCT_ID],
|
||||
mappingContractIds: [...value.manifest.mappingContractIds, ZONES_MAPPING_ID],
|
||||
l2TemplateIds: [...value.manifest.l2TemplateIds, ZONES_TEMPLATE_ID],
|
||||
};
|
||||
value.capabilities.push({
|
||||
id: ZONES_CAPABILITY_ID,
|
||||
classification: "read",
|
||||
status: "implemented",
|
||||
authModeId,
|
||||
request: {
|
||||
method: "GET",
|
||||
baseUrl: "https://api.geliospro.com",
|
||||
path: "/api/v1/geozones",
|
||||
query: { pl: 100, po: 0 },
|
||||
response: {
|
||||
collectionPaths: ["items"],
|
||||
pagination: {
|
||||
mode: "offset",
|
||||
limitParameter: "pl",
|
||||
offsetParameter: "po",
|
||||
pageSize: 100,
|
||||
itemsPath: "items",
|
||||
totalPath: "paginationMetadata.totalCount",
|
||||
maxPages: 50,
|
||||
maxItems: 5000,
|
||||
maxResponseBytes: 16 * 1024 * 1024,
|
||||
},
|
||||
},
|
||||
},
|
||||
entityScope: {
|
||||
mode: "all_visible_to_credential",
|
||||
refresh: "each_collection_run",
|
||||
businessEntityFilter: "forbidden",
|
||||
},
|
||||
});
|
||||
value.fieldPolicies.push({
|
||||
id: ZONES_FIELD_POLICY_ID,
|
||||
version: GELIOS_ZONES_DATA_PRODUCT_VERSION,
|
||||
dataProductId: GELIOS_ZONES_DATA_PRODUCT_ID,
|
||||
targetFields: [...zonesFields],
|
||||
unknownSourceFields: "drop",
|
||||
dynamicSourceFields: "drop_until_classified",
|
||||
restrictedSourcePaths: ["accessToken", "authorization", "refreshToken", "userToken"],
|
||||
});
|
||||
value.collectionProfiles.push({
|
||||
id: ZONES_PROFILE_ID,
|
||||
version: GELIOS_PROVIDER_PACKAGE_VERSION,
|
||||
mode: "realtime",
|
||||
schedule: { intervalMs: 3_600_000 },
|
||||
capabilityIds: [ZONES_CAPABILITY_ID],
|
||||
dataProductId: GELIOS_ZONES_DATA_PRODUCT_ID,
|
||||
mappingContractId: ZONES_MAPPING_ID,
|
||||
fieldPolicyId: ZONES_FIELD_POLICY_ID,
|
||||
l2TemplateId: ZONES_TEMPLATE_ID,
|
||||
entityScope: {
|
||||
mode: "all_visible_to_credential",
|
||||
refresh: "each_collection_run",
|
||||
businessEntityFilter: "forbidden",
|
||||
},
|
||||
batching: { maxFacts: 5000 },
|
||||
cardinality: { maxCurrentEntities: 5000, onExceed: "require_partitioned_data_product" },
|
||||
retry: { maxAttempts: 4, backoff: "exponential_with_jitter" },
|
||||
});
|
||||
value.dataProducts.push({
|
||||
id: GELIOS_ZONES_DATA_PRODUCT_ID,
|
||||
version: GELIOS_ZONES_DATA_PRODUCT_VERSION,
|
||||
ontologyRevision: GELIOS_ZONES_ONTOLOGY_REVISION,
|
||||
deliveryMode: "snapshot+patch",
|
||||
semanticTypes: ["map.zone"],
|
||||
fields: [...zonesFields],
|
||||
fieldContracts: {
|
||||
area_square_meters: { type: "number", required: false, minimum: 0 },
|
||||
description: { type: "string", required: false },
|
||||
display_name: { type: "string", required: true },
|
||||
geometry: { type: "geometry", required: true },
|
||||
geometry_kind: { type: "string", required: true, enum: ["circle", "corridor", "polygon"] },
|
||||
max_speed_kph: { type: "number", required: false, minimum: 0 },
|
||||
perimeter_meters: { type: "number", required: false, minimum: 0 },
|
||||
source_kind: { type: "string", required: true, enum: ["live_api", "versioned_snapshot"] },
|
||||
source_revision: { type: "string", required: true },
|
||||
style_color: { type: "string", required: false },
|
||||
},
|
||||
history: { mode: "none", retentionDays: 1 },
|
||||
});
|
||||
value.mappingContracts.push({
|
||||
schemaVersion: "nodedc.semantic-mapping/v1",
|
||||
id: ZONES_MAPPING_ID,
|
||||
version: GELIOS_PROVIDER_PACKAGE_VERSION,
|
||||
sourceCapabilityId: ZONES_CAPABILITY_ID,
|
||||
fieldPolicyId: ZONES_FIELD_POLICY_ID,
|
||||
target: {
|
||||
dataProductId: GELIOS_ZONES_DATA_PRODUCT_ID,
|
||||
version: GELIOS_ZONES_DATA_PRODUCT_VERSION,
|
||||
ontologyRevision: GELIOS_ZONES_ONTOLOGY_REVISION,
|
||||
semanticType: "map.zone",
|
||||
},
|
||||
derivations: {
|
||||
geometry_kind: {
|
||||
kind: "ordered_rules",
|
||||
rules: ["type_circle.circle", "type_line.corridor", "otherwise.polygon"],
|
||||
default: "polygon",
|
||||
},
|
||||
},
|
||||
fact: {
|
||||
sourceId: { strategy: "first_non_empty", paths: ["id"], coerce: "string", prefix: GELIOS_ZONE_SOURCE_ID_PREFIX },
|
||||
semanticType: { constant: "map.zone" },
|
||||
observedAt: { strategy: "first_non_empty", paths: ["updatedAt"], coerce: "unix_or_iso_timestamp", fallback: "collection_received_at" },
|
||||
geometry: {
|
||||
type: "GeoJSON",
|
||||
strategy: "gelios_geozone_v1",
|
||||
paths: ["type", "points", "line", "radius"],
|
||||
allowedTypes: ["Polygon", "MultiPolygon"],
|
||||
omitIfInvalid: false,
|
||||
},
|
||||
attributes: {
|
||||
area_square_meters: { strategy: "first_non_empty", paths: ["surfaceArea", "surface_area"], coerce: "number", minimum: 0, omitIfInvalid: true },
|
||||
description: { strategy: "first_non_empty", paths: ["description", "descr"], coerce: "string", omitIfMissing: true },
|
||||
display_name: { strategy: "first_non_empty", paths: ["name"], coerce: "string" },
|
||||
geometry_kind: { derive: "geometry_kind" },
|
||||
max_speed_kph: { strategy: "first_non_empty", paths: ["maxPermissibleSpeed", "max_permissible_speed"], coerce: "number", minimum: 0, omitIfInvalid: true },
|
||||
perimeter_meters: { strategy: "first_non_empty", paths: ["perimeter"], coerce: "number", minimum: 0, omitIfInvalid: true },
|
||||
source_kind: { constant: "live_api" },
|
||||
source_revision: { constant: "gelios-rest-v1" },
|
||||
style_color: { strategy: "first_non_empty", paths: ["color"], coerce: "string", omitIfMissing: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
value.l2Templates.push({
|
||||
schemaVersion: "nodedc.l2-template/v1",
|
||||
id: ZONES_TEMPLATE_ID,
|
||||
version: GELIOS_PROVIDER_PACKAGE_VERSION,
|
||||
runtime: "ndc_l2",
|
||||
instanceMode: "one_connection_per_workflow",
|
||||
connectionParameters: ["tenant_id", "connection_id", "collection_profile_id", "provider_credential_ref"],
|
||||
credentialBindings: structuredClone(value.l2Templates[0].credentialBindings),
|
||||
steps: [
|
||||
{ id: "collection.trigger", kind: "collection_trigger", collectionProfileDriven: true },
|
||||
{ id: "provider.fetch-geozones", kind: "provider_request", capabilityId: ZONES_CAPABILITY_ID },
|
||||
{ id: "provider.extract-geozones", kind: "extract_items", capabilityId: ZONES_CAPABILITY_ID },
|
||||
{ id: "ontology.map", kind: "semantic_mapping", mappingContractId: ZONES_MAPPING_ID },
|
||||
{ id: "data-product.publish", kind: "data_product_publish", dataProductId: GELIOS_ZONES_DATA_PRODUCT_ID, nodeType: "n8n-nodes-ndc.ndcDataProductPublish" },
|
||||
],
|
||||
invariants: structuredClone(value.l2Templates[0].invariants),
|
||||
});
|
||||
|
||||
export const geliosProviderPackageV6 = deepFreeze(value);
|
||||
|
||||
function deepFreeze(input) {
|
||||
if (!input || typeof input !== "object" || Object.isFrozen(input)) return input;
|
||||
Object.freeze(input);
|
||||
for (const child of Object.values(input)) deepFreeze(child);
|
||||
return input;
|
||||
}
|
||||
Reference in New Issue
Block a user