330 lines
7.9 KiB
TypeScript
330 lines
7.9 KiB
TypeScript
import { aql } from 'arangojs'
|
|
import { ensureGraph, getDb } from '../dist/db.js'
|
|
|
|
type GeoNode = {
|
|
_key: string
|
|
node_type: string
|
|
name: string
|
|
latitude: number
|
|
longitude: number
|
|
country: string
|
|
country_code: string
|
|
transport_types?: string[]
|
|
product_uuid?: string
|
|
product_name?: string
|
|
supplier_uuid?: string
|
|
supplier_name?: string
|
|
price_per_unit?: string
|
|
currency?: string
|
|
quantity?: string
|
|
unit?: string
|
|
synced_at: string
|
|
}
|
|
|
|
type DemoHub = GeoNode & {
|
|
_key: string
|
|
latitude: number
|
|
longitude: number
|
|
}
|
|
|
|
type DemoProduct = {
|
|
uuid: string
|
|
name: string
|
|
unit: string
|
|
}
|
|
|
|
type DemoSupplier = {
|
|
uuid: string
|
|
name: string
|
|
latitude: number
|
|
longitude: number
|
|
country: string
|
|
country_code: string
|
|
hubKey: string
|
|
}
|
|
|
|
async function upsertNode(doc: GeoNode) {
|
|
const { _key, ...patch } = doc
|
|
await getDb().query(aql`
|
|
UPSERT { _key: ${_key} }
|
|
INSERT ${doc}
|
|
UPDATE ${patch}
|
|
IN ${getDb().collection('nodes')}
|
|
`)
|
|
}
|
|
|
|
async function upsertEdge(doc: Record<string, unknown>) {
|
|
const key = String(doc._key)
|
|
const { _key, ...patch } = doc
|
|
await getDb().query(aql`
|
|
UPSERT { _key: ${key} }
|
|
INSERT ${doc}
|
|
UPDATE ${patch}
|
|
IN ${getDb().collection('edges')}
|
|
`)
|
|
}
|
|
|
|
await ensureGraph()
|
|
|
|
const now = new Date().toISOString()
|
|
|
|
const products: DemoProduct[] = [
|
|
{
|
|
uuid: '55555555-1111-4111-8111-111111111111',
|
|
name: 'Solar panels',
|
|
unit: 'pallet',
|
|
},
|
|
{
|
|
uuid: '55555555-1111-4111-8111-222222222222',
|
|
name: 'Industrial pumps',
|
|
unit: 'unit',
|
|
},
|
|
{
|
|
uuid: '55555555-1111-4111-8111-333333333333',
|
|
name: 'Soybean meal',
|
|
unit: 'ton',
|
|
},
|
|
]
|
|
|
|
const hubs: DemoHub[] = [
|
|
{
|
|
_key: 'logistics-demo-hub-1',
|
|
node_type: 'logistics',
|
|
name: 'Guangzhou Rail Terminal',
|
|
latitude: 23.1291,
|
|
longitude: 113.2644,
|
|
country: 'China',
|
|
country_code: 'CN',
|
|
transport_types: ['auto', 'rail'],
|
|
synced_at: now,
|
|
},
|
|
{
|
|
_key: 'logistics-demo-hub-2',
|
|
node_type: 'logistics',
|
|
name: 'Shenzhen Yantian Port',
|
|
latitude: 22.5942,
|
|
longitude: 114.2743,
|
|
country: 'China',
|
|
country_code: 'CN',
|
|
transport_types: ['auto', 'sea'],
|
|
synced_at: now,
|
|
},
|
|
{
|
|
_key: 'logistics-demo-hub-3',
|
|
node_type: 'logistics',
|
|
name: 'Harbin Rail Hub',
|
|
latitude: 45.8038,
|
|
longitude: 126.5349,
|
|
country: 'China',
|
|
country_code: 'CN',
|
|
transport_types: ['auto', 'rail'],
|
|
synced_at: now,
|
|
},
|
|
{
|
|
_key: 'logistics-demo-hub-4',
|
|
node_type: 'logistics',
|
|
name: 'Vladivostok Commercial Port',
|
|
latitude: 43.1155,
|
|
longitude: 131.8855,
|
|
country: 'Russia',
|
|
country_code: 'RU',
|
|
transport_types: ['sea', 'rail'],
|
|
synced_at: now,
|
|
},
|
|
{
|
|
_key: 'logistics-demo-hub-5',
|
|
node_type: 'logistics',
|
|
name: 'Moscow Rail Hub',
|
|
latitude: 55.7558,
|
|
longitude: 37.6173,
|
|
country: 'Russia',
|
|
country_code: 'RU',
|
|
transport_types: ['auto', 'rail'],
|
|
synced_at: now,
|
|
},
|
|
]
|
|
|
|
const suppliers: DemoSupplier[] = [
|
|
{
|
|
uuid: '66666666-2222-4111-8111-111111111111',
|
|
name: 'Guangzhou Solar Factory',
|
|
latitude: 23.0462,
|
|
longitude: 113.3831,
|
|
country: 'China',
|
|
country_code: 'CN',
|
|
hubKey: 'logistics-demo-hub-1',
|
|
},
|
|
{
|
|
uuid: '66666666-2222-4111-8111-222222222222',
|
|
name: 'Shenzhen Pump Works',
|
|
latitude: 22.5431,
|
|
longitude: 114.0579,
|
|
country: 'China',
|
|
country_code: 'CN',
|
|
hubKey: 'logistics-demo-hub-2',
|
|
},
|
|
{
|
|
uuid: '66666666-2222-4111-8111-333333333333',
|
|
name: 'Harbin Agro Export',
|
|
latitude: 45.7424,
|
|
longitude: 126.6617,
|
|
country: 'China',
|
|
country_code: 'CN',
|
|
hubKey: 'logistics-demo-hub-3',
|
|
},
|
|
{
|
|
uuid: '66666666-2222-4111-8111-444444444444',
|
|
name: 'Vladivostok Timber Terminal',
|
|
latitude: 43.1056,
|
|
longitude: 131.8735,
|
|
country: 'Russia',
|
|
country_code: 'RU',
|
|
hubKey: 'logistics-demo-hub-4',
|
|
},
|
|
]
|
|
|
|
function quoteUuid(index: number) {
|
|
return `77777777-3333-4${String(index).padStart(3, '0')}-8${String(index).padStart(3, '0')}-777777${String(index).padStart(6, '0')}`
|
|
}
|
|
|
|
function hubByKey(key: string) {
|
|
const hub = hubs.find(item => item._key === key)
|
|
if (!hub) throw new Error(`Unknown demo hub: ${key}`)
|
|
return hub
|
|
}
|
|
|
|
const supplierNodes: GeoNode[] = suppliers.map(supplier => ({
|
|
_key: supplier.uuid,
|
|
node_type: 'supplier',
|
|
name: supplier.name,
|
|
latitude: supplier.latitude,
|
|
longitude: supplier.longitude,
|
|
country: supplier.country,
|
|
country_code: supplier.country_code,
|
|
synced_at: now,
|
|
}))
|
|
|
|
const offers: GeoNode[] = Array.from({ length: 24 }, (_, index) => {
|
|
const product = products[index % products.length]
|
|
const supplier = suppliers[Math.floor(index / products.length) % suppliers.length]
|
|
const localIndex = Math.floor(index / suppliers.length)
|
|
const latitudeOffset = ((localIndex % 3) - 1) * 0.08
|
|
const longitudeOffset = (Math.floor(localIndex / 3) - 1) * 0.1
|
|
|
|
return {
|
|
_key: quoteUuid(index),
|
|
node_type: 'offer',
|
|
name: `${supplier.name} ${product.name}`,
|
|
latitude: supplier.latitude + latitudeOffset,
|
|
longitude: supplier.longitude + longitudeOffset,
|
|
country: supplier.country,
|
|
country_code: supplier.country_code,
|
|
product_uuid: product.uuid,
|
|
product_name: product.name,
|
|
supplier_uuid: supplier.uuid,
|
|
supplier_name: supplier.name,
|
|
price_per_unit: String(900 + (index % 8) * 55),
|
|
currency: 'USD',
|
|
quantity: String(500 + index * 25),
|
|
unit: product.unit,
|
|
synced_at: now,
|
|
}
|
|
})
|
|
|
|
for (const node of [...hubs, ...offers, ...supplierNodes]) {
|
|
await upsertNode(node)
|
|
}
|
|
|
|
const hubLinks = [
|
|
{
|
|
from: 'logistics-demo-hub-1',
|
|
to: 'logistics-demo-hub-3',
|
|
distanceKm: 3280,
|
|
travelTimeSeconds: 691200,
|
|
transportType: 'rail',
|
|
},
|
|
{
|
|
from: 'logistics-demo-hub-3',
|
|
to: 'logistics-demo-hub-1',
|
|
distanceKm: 3280,
|
|
travelTimeSeconds: 691200,
|
|
transportType: 'rail',
|
|
},
|
|
{
|
|
from: 'logistics-demo-hub-2',
|
|
to: 'logistics-demo-hub-4',
|
|
distanceKm: 2850,
|
|
travelTimeSeconds: 432000,
|
|
transportType: 'sea',
|
|
},
|
|
{
|
|
from: 'logistics-demo-hub-4',
|
|
to: 'logistics-demo-hub-2',
|
|
distanceKm: 2850,
|
|
travelTimeSeconds: 432000,
|
|
transportType: 'sea',
|
|
},
|
|
{
|
|
from: 'logistics-demo-hub-3',
|
|
to: 'logistics-demo-hub-4',
|
|
distanceKm: 650,
|
|
travelTimeSeconds: 129600,
|
|
transportType: 'rail',
|
|
},
|
|
{
|
|
from: 'logistics-demo-hub-4',
|
|
to: 'logistics-demo-hub-3',
|
|
distanceKm: 650,
|
|
travelTimeSeconds: 129600,
|
|
transportType: 'rail',
|
|
},
|
|
{
|
|
from: 'logistics-demo-hub-4',
|
|
to: 'logistics-demo-hub-5',
|
|
distanceKm: 9250,
|
|
travelTimeSeconds: 1036800,
|
|
transportType: 'rail',
|
|
},
|
|
{
|
|
from: 'logistics-demo-hub-5',
|
|
to: 'logistics-demo-hub-4',
|
|
distanceKm: 9250,
|
|
travelTimeSeconds: 1036800,
|
|
transportType: 'rail',
|
|
},
|
|
]
|
|
|
|
for (const [index, link] of hubLinks.entries()) {
|
|
const toHub = hubByKey(link.to)
|
|
await upsertEdge({
|
|
_key: `geo-demo-hub-edge-${index + 1}`,
|
|
_from: `nodes/${link.from}`,
|
|
_to: `nodes/${link.to}`,
|
|
to_uuid: link.to,
|
|
to_name: toHub.name,
|
|
to_latitude: toHub.latitude,
|
|
to_longitude: toHub.longitude,
|
|
distance_km: link.distanceKm,
|
|
travel_time_seconds: link.travelTimeSeconds,
|
|
transport_type: link.transportType,
|
|
})
|
|
}
|
|
|
|
for (const [index, offer] of offers.entries()) {
|
|
const supplier = suppliers[Math.floor(index / products.length) % suppliers.length]
|
|
await upsertEdge({
|
|
_key: `geo-demo-offer-edge-${index + 1}`,
|
|
_from: `nodes/${supplier.hubKey}`,
|
|
_to: `nodes/${offer._key}`,
|
|
to_uuid: offer._key,
|
|
to_name: offer.name,
|
|
to_latitude: offer.latitude,
|
|
to_longitude: offer.longitude,
|
|
distance_km: Math.max(1, Math.round(Math.hypot((offer.latitude - supplier.latitude) * 111, (offer.longitude - supplier.longitude) * 75))),
|
|
travel_time_seconds: 3600 + index * 120,
|
|
transport_type: 'offer',
|
|
})
|
|
}
|
|
|
|
console.log(`Seeded geo demo data: hubs ${hubs.length}, offers ${offers.length}, suppliers ${suppliers.length}, edges ${hubLinks.length + offers.length}`)
|