Add randomized landing network story
Build and deploy Docker image / build (push) Successful in 1m59s

This commit is contained in:
Ruslan Bakiev
2026-08-22 19:00:25 +07:00
parent 7da13c895d
commit a09a67797d
2 changed files with 113 additions and 1 deletions
+112
View File
@@ -112,6 +112,26 @@ export const typeDefs = `#graphql
distanceKm: Float distanceKm: Float
} }
enum LandingNetworkKind {
HUB
SUPPLIER
}
type LandingNetworkPoint {
uuid: String!
kind: LandingNetworkKind!
name: String!
country: String
latitude: Float!
longitude: Float!
distanceKm: Float
}
type LandingNetworkStory {
focus: LandingNetworkPoint!
neighbors: [LandingNetworkPoint!]!
}
type OfferNode { type OfferNode {
uuid: String uuid: String
productUuid: String productUuid: String
@@ -177,6 +197,7 @@ export const typeDefs = `#graphql
nearestHubs(lat: Float!, lon: Float!, radius: Float, productUuid: String, limit: Int): [Node!]! nearestHubs(lat: Float!, lon: Float!, radius: Float, productUuid: String, limit: Int): [Node!]!
nearestOffers(lat: Float!, lon: Float!, radius: Float, productUuid: String, hubUuid: String, limit: Int): [OfferWithRoute!]! nearestOffers(lat: Float!, lon: Float!, radius: Float, productUuid: String, hubUuid: String, limit: Int): [OfferWithRoute!]!
nearestSuppliers(lat: Float!, lon: Float!, radius: Float, productUuid: String, limit: Int): [Supplier!]! nearestSuppliers(lat: Float!, lon: Float!, radius: Float, productUuid: String, limit: Int): [Supplier!]!
landingNetworkStory(focusKind: LandingNetworkKind!, neighborLimit: Int): LandingNetworkStory!
routeToCoordinate(offerUuid: String!, lat: Float!, lon: Float!): ProductRouteOption routeToCoordinate(offerUuid: String!, lat: Float!, lon: Float!): ProductRouteOption
hubsList(limit: Int, offset: Int, country: String, transportType: String, west: Float, south: Float, east: Float, north: Float): [Node!]! hubsList(limit: Int, offset: Int, country: String, transportType: String, west: Float, south: Float, east: Float, north: Float): [Node!]!
suppliersList(limit: Int, offset: Int, country: String, west: Float, south: Float, east: Float, north: Float): [Supplier!]! suppliersList(limit: Int, offset: Int, country: String, west: Float, south: Float, east: Float, north: Float): [Supplier!]!
@@ -237,6 +258,93 @@ function mapOffer(doc: ArangoDoc, catalog = new Map<string, ProductCatalogItem>(
} }
} }
type LandingNetworkKind = 'HUB' | 'SUPPLIER'
function mapLandingNetworkPoint(doc: ArangoDoc, kind: LandingNetworkKind) {
return {
uuid: doc._key,
kind,
name: doc.name,
country: doc.country ?? null,
latitude: doc.latitude,
longitude: doc.longitude,
distanceKm: doc.distance_km ?? null,
}
}
async function resolveLandingNetworkStory(focusKind: LandingNetworkKind, neighborLimit?: number) {
const db = getDb()
const limit = Math.max(1, Math.min(neighborLimit ?? 3, 6))
const focusCursor = await db.query(focusKind === 'HUB' ? `
FOR hub IN nodes
FILTER hub.node_type == 'logistics' OR hub.node_type == null
FILTER hub.product_uuid == null
FILTER hub.latitude != null AND hub.longitude != null
SORT RAND()
LIMIT 1
RETURN hub
` : `
FOR supplier IN nodes
FILTER supplier.node_type == 'supplier'
FILTER supplier.latitude != null AND supplier.longitude != null
SORT RAND()
LIMIT 1
RETURN supplier
`)
const focus = await focusCursor.next() as ArangoDoc | undefined
if (!focus) throw new Error(`No ${focusKind.toLowerCase()} is available for the landing network story`)
if (focusKind === 'SUPPLIER') {
const neighborsCursor = await db.query(`
FOR hub IN nodes
FILTER hub.node_type == 'logistics' OR hub.node_type == null
FILTER hub.product_uuid == null
FILTER hub.latitude != null AND hub.longitude != null
LET dist = DISTANCE(hub.latitude, hub.longitude, @lat, @lon) / 1000
SORT dist ASC
LIMIT @limit
RETURN MERGE(hub, {distance_km: dist})
`, { lat: focus.latitude, lon: focus.longitude, limit })
const neighbors = await neighborsCursor.all()
return {
focus: mapLandingNetworkPoint(focus, 'SUPPLIER'),
neighbors: neighbors.map((neighbor: ArangoDoc) => mapLandingNetworkPoint(neighbor, 'HUB')),
}
}
const neighborsCursor = await db.query(`
FOR offer IN nodes
FILTER offer.node_type == 'offer'
FILTER offer.supplier_uuid != null
FILTER offer.latitude != null AND offer.longitude != null
LET dist = DISTANCE(offer.latitude, offer.longitude, @lat, @lon) / 1000
COLLECT supplier_uuid = offer.supplier_uuid INTO offers
LET first_offer = FIRST(offers).offer
LET supplier_dist = DISTANCE(first_offer.latitude, first_offer.longitude, @lat, @lon) / 1000
LET supplier_node = FIRST(
FOR supplier IN nodes
FILTER supplier._key == supplier_uuid
FILTER supplier.node_type == 'supplier'
RETURN supplier
)
SORT supplier_dist ASC
LIMIT @limit
RETURN {
_key: supplier_uuid,
name: supplier_node != null ? supplier_node.name : first_offer.supplier_name,
country: supplier_node != null ? supplier_node.country : first_offer.country,
latitude: supplier_node != null ? supplier_node.latitude : first_offer.latitude,
longitude: supplier_node != null ? supplier_node.longitude : first_offer.longitude,
distance_km: supplier_dist
}
`, { lat: focus.latitude, lon: focus.longitude, limit })
const neighbors = await neighborsCursor.all()
return {
focus: mapLandingNetworkPoint(focus, 'HUB'),
neighbors: neighbors.map((neighbor: ArangoDoc) => mapLandingNetworkPoint(neighbor, 'SUPPLIER')),
}
}
async function loadProductCatalog() { async function loadProductCatalog() {
const res = await fetch(EXCHANGE_GRAPHQL_URL, { const res = await fetch(EXCHANGE_GRAPHQL_URL, {
method: 'POST', method: 'POST',
@@ -460,6 +568,10 @@ export const resolvers = {
}, },
Query: { Query: {
landingNetworkStory: async (_: unknown, args: { focusKind: LandingNetworkKind; neighborLimit?: number }) => {
return resolveLandingNetworkStory(args.focusKind, args.neighborLimit)
},
node: async (_: unknown, args: { uuid: string }) => { node: async (_: unknown, args: { uuid: string }) => {
const db = getDb() const db = getDb()
const nodesCol = db.collection('nodes') const nodesCol = db.collection('nodes')