diff --git a/apps/api/src/recommendation/routes/stellar/engine.ts b/apps/api/src/recommendation/routes/stellar/engine.ts new file mode 100644 index 0000000..4793644 --- /dev/null +++ b/apps/api/src/recommendation/routes/stellar/engine.ts @@ -0,0 +1,23 @@ +import { StellarRoute, ScoredRoute, RouteRecommendationOptions } from "./types"; +import { scoreRoutes } from "./scorer"; + +export class StellarRouteRecommendationEngine { + recommend( + routes: StellarRoute[], + options?: RouteRecommendationOptions + ): ScoredRoute[] { + if (!routes.length) return []; + + const scored = scoreRoutes(routes, options); + + return scored.sort((a, b) => b.score - a.score); + } + + getBestRoute( + routes: StellarRoute[], + options?: RouteRecommendationOptions + ): ScoredRoute | null { + const ranked = this.recommend(routes, options); + return ranked[0] || null; + } +} \ No newline at end of file diff --git a/apps/api/src/recommendation/routes/stellar/index.ts b/apps/api/src/recommendation/routes/stellar/index.ts new file mode 100644 index 0000000..01441e6 --- /dev/null +++ b/apps/api/src/recommendation/routes/stellar/index.ts @@ -0,0 +1,3 @@ +export * from "./types"; +export * from "./engine"; +export * from "./scorer"; \ No newline at end of file diff --git a/apps/api/src/recommendation/routes/stellar/scorer.ts b/apps/api/src/recommendation/routes/stellar/scorer.ts new file mode 100644 index 0000000..57b7562 --- /dev/null +++ b/apps/api/src/recommendation/routes/stellar/scorer.ts @@ -0,0 +1,40 @@ +import { StellarRoute, RouteRecommendationOptions } from "./types"; +import { defaultWeights } from "./weights"; + +function normalize(value: number, min: number, max: number) { + if (max === min) return 0; + return (value - min) / (max - min); +} + +export function scoreRoutes( + routes: StellarRoute[], + options: RouteRecommendationOptions = {} +) { + const weights = { ...defaultWeights, ...options }; + + const costs = routes.map(r => r.estimatedCost); + const times = routes.map(r => r.estimatedTimeMs); + const reliabilities = routes.map(r => r.reliability ?? 0.5); + + const minCost = Math.min(...costs); + const maxCost = Math.max(...costs); + + const minTime = Math.min(...times); + const maxTime = Math.max(...times); + + return routes.map(route => { + const costScore = 1 - normalize(route.estimatedCost, minCost, maxCost); + const speedScore = 1 - normalize(route.estimatedTimeMs, minTime, maxTime); + const reliabilityScore = route.reliability ?? 0.5; + + const score = + costScore * weights.weightCost + + speedScore * weights.weightSpeed + + reliabilityScore * weights.weightReliability; + + return { + ...route, + score, + }; + }); +} \ No newline at end of file diff --git a/apps/api/src/recommendation/routes/stellar/types.ts b/apps/api/src/recommendation/routes/stellar/types.ts new file mode 100644 index 0000000..dff61f5 --- /dev/null +++ b/apps/api/src/recommendation/routes/stellar/types.ts @@ -0,0 +1,17 @@ +export type StellarRoute = { + id: string; + path: string[]; // hops between bridges/nodes + estimatedTimeMs: number; + estimatedCost: number; // fees or slippage + reliability?: number; // optional 0-1 score +}; + +export type ScoredRoute = StellarRoute & { + score: number; +}; + +export type RouteRecommendationOptions = { + weightCost?: number; + weightSpeed?: number; + weightReliability?: number; +}; \ No newline at end of file diff --git a/apps/api/src/recommendation/routes/stellar/weights.ts b/apps/api/src/recommendation/routes/stellar/weights.ts new file mode 100644 index 0000000..630aaa2 --- /dev/null +++ b/apps/api/src/recommendation/routes/stellar/weights.ts @@ -0,0 +1,7 @@ +import { RouteRecommendationOptions } from "./types"; + +export const defaultWeights: Required = { + weightCost: 0.5, + weightSpeed: 0.4, + weightReliability: 0.1, +}; \ No newline at end of file