Skip to content
Closed
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { singleton } from "tsyringe";

import type { BidScreeningRequest, BidScreeningResponse } from "@src/provider/http-schemas/bid-screening.schema";
import { BidScreeningService } from "@src/provider/services/bid-screening/bid-screening.service";

@singleton()
export class BidScreeningController {
constructor(private readonly bidScreeningService: BidScreeningService) {}

async screen(input: BidScreeningRequest["data"]): Promise<BidScreeningResponse> {
return { data: await this.bidScreeningService.findMatchingProviders(input) };
}
}
75 changes: 75 additions & 0 deletions apps/api/src/provider/http-schemas/bid-screening.schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { z } from "zod";

const GpuAttributesSchema = z.object({
vendor: z.string(),
model: z.string().optional(),
interface: z.string().optional(),
memorySize: z.string().optional()
});

const ResourceUnitSchema = z
.object({
cpu: z.number().int().positive(),
memory: z.number().int().positive(),
gpu: z.number().int().min(0),
gpuAttributes: GpuAttributesSchema.optional(),
ephemeralStorage: z.number().int().positive(),
persistentStorage: z.number().int().positive().optional(),
persistentStorageClass: z.enum(["beta1", "beta2", "beta3"]).optional(),
count: z.number().int().positive()
})
.refine(data => data.gpu === 0 || data.gpuAttributes !== undefined, {
message: "gpuAttributes is required when gpu > 0",
path: ["gpuAttributes"]
});

const PlacementRequirementsSchema = z.object({
attributes: z
.array(z.object({ key: z.string(), value: z.string() }))
.max(20)
.default([]),
signedBy: z
.object({
allOf: z.array(z.string()).max(10).default([]),
anyOf: z.array(z.string()).max(10).default([])
})
.default({})
});

export const BidScreeningRequestSchema = z.object({
data: z.object({
resources: z.array(ResourceUnitSchema).min(1).max(20),
requirements: PlacementRequirementsSchema.default({}),
limit: z.number().int().min(1).max(200).default(50)
})
});

export type BidScreeningRequest = z.infer<typeof BidScreeningRequestSchema>;

const ProviderMatchSchema = z.object({
owner: z.string(),
hostUri: z.string(),
leaseCount: z.number(),
availableCpu: z.number(),
availableMemory: z.number(),
availableGpu: z.number(),
availableEphemeralStorage: z.number(),
availablePersistentStorage: z.number()
});

const ConstraintSchema = z.object({
name: z.string(),
count: z.number(),
actionableFeedback: z.string()
});

export const BidScreeningResponseSchema = z.object({
data: z.object({
providers: z.array(ProviderMatchSchema),
total: z.number(),
queryTimeMs: z.number(),
constraints: z.array(ConstraintSchema).optional()
})
});

export type BidScreeningResponse = z.infer<typeof BidScreeningResponseSchema>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { container } from "tsyringe";

import { createRoute } from "@src/core/lib/create-route/create-route";
import { OpenApiHonoHandler } from "@src/core/services/open-api-hono-handler/open-api-hono-handler";
import { SECURITY_NONE } from "@src/core/services/openapi-docs/openapi-security";
import { BidScreeningController } from "@src/provider/controllers/bid-screening/bid-screening.controller";
import { BidScreeningRequestSchema, BidScreeningResponseSchema } from "@src/provider/http-schemas/bid-screening.schema";

export const bidScreeningRouter = new OpenApiHonoHandler();

const postRoute = createRoute({
method: "post",
path: "/v1/bid-screening",
summary: "Pre-filter providers by resource capacity (Stage 1 bid screening)",
tags: ["Providers"],
security: SECURITY_NONE,
request: {
body: {
content: {
"application/json": {
schema: BidScreeningRequestSchema
}
}
}
},
responses: {
200: {
description: "Matching providers ranked by lease count and available resources",
content: {
"application/json": {
schema: BidScreeningResponseSchema
}
}
}
}
});

bidScreeningRouter.openapi(postRoute, async function routeBidScreening(c) {
const { data } = c.req.valid("json");
const result = await container.resolve(BidScreeningController).screen(data);
return c.json(result, 200);
});
1 change: 1 addition & 0 deletions apps/api/src/provider/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export * from "@src/provider/routes/provider-versions/provider-versions.router";
export * from "@src/provider/routes/provider-graph-data/provider-graph-data.router";
export * from "@src/provider/routes/provider-deployments/provider-deployments.router";
export * from "@src/provider/routes/jwt-token/jwt-token.router";
export * from "@src/provider/routes/bid-screening/bid-screening.router";
Loading
Loading