major scoring implementation, major frontend work

This commit is contained in:
2024-12-14 01:05:26 -06:00
parent 6f0b2050ad
commit 7fc5980e3b
15 changed files with 541 additions and 75 deletions

View File

@@ -1,15 +1,198 @@
package api package api
import (
"fmt"
"math"
"go.uber.org/zap"
)
type ScoreReason struct {
Reason string
Offset float64
Value interface{}
IsMultiplier bool
}
type ScoredOffer struct { type ScoredOffer struct {
Offer Offer Offer Offer
Score float64 Score float64
Reasons []ScoreReason
}
var (
sugar *zap.SugaredLogger
)
func init() {
logger, err := zap.NewDevelopment()
sugar = logger.Sugar()
if err != nil {
panic(err)
}
}
func AddReason(reasonList []ScoreReason, reason string, offset float64, multiplier bool, value interface{}) []ScoreReason {
return append(reasonList, ScoreReason{Reason: reason, Offset: offset, IsMultiplier: multiplier, Value: fmt.Sprintf("%v", value)})
} }
func ScoreOffers(offers []Offer) []ScoredOffer { func ScoreOffers(offers []Offer) []ScoredOffer {
var scoredOffers = make([]ScoredOffer, 0, len(offers)) var scoredOffers = make([]ScoredOffer, 0, len(offers))
for _, offer := range offers { for _, offer := range offers {
score := 100.0 reasons := make([]ScoreReason, 0, 16)
scoredOffers = append(scoredOffers, ScoredOffer{Offer: offer, Score: score})
// Judge cost
targetCost := 0.42 // $/hour
costMultiplier := targetCost / offer.Search.TotalHour // e.x $0.42 / $0.50 = x0.84
// sugar.Debugw("Cost", "offer", offer.ID, "costMultiplier", costMultiplier, "targetCost", targetCost, "actualCost", offer.Search.TotalHour)
if math.Abs(costMultiplier-1.0) > 0.05 {
reasons = AddReason(reasons, "Cost Multiplier", costMultiplier, true, offer.Search.TotalHour)
}
// Judge DLPerf
targetDLPerf := 85.0
dlPerfMultiplier := offer.DLPerf / targetDLPerf // e.x 100 / 85 = x1.18
if math.Abs(dlPerfMultiplier-1.0) > 0.03 {
if dlPerfMultiplier > 1.0 {
dlPerfMultiplier = math.Sqrt(dlPerfMultiplier)
} else {
dlPerfMultiplier = math.Pow(dlPerfMultiplier, 2.0)
}
reasons = AddReason(reasons, "DLPerf Multiplier", dlPerfMultiplier, true, offer.DLPerf)
}
// Judge Internet Download Speed
if offer.InetDown < 150.0 {
reasons = AddReason(reasons, "Very Poor Internet Download Speed", -7.0, false, offer.InetDown)
} else if offer.InetDown < 300.0 {
reasons = AddReason(reasons, "Poor Internet Download Speed", -3.0, false, offer.InetDown)
} else if offer.InetDown < 500.0 {
reasons = AddReason(reasons, "Decent Internet Download Speed", 1.0, false, offer.InetDown)
} else if offer.InetDown < 1000.0 {
reasons = AddReason(reasons, "Good Internet Download Speed", 3.0, false, offer.InetDown)
} else if offer.InetDown >= 2000.0 {
reasons = AddReason(reasons, "Great Internet Download Speed", 4.0, false, offer.InetDown)
}
// Judge Internet Upload Speed
if offer.InetUp < 50.0 {
reasons = AddReason(reasons, "Extremely Poor Internet Upload Speed", -9.0, false, offer.InetUp)
} else if offer.InetUp < 100.0 {
reasons = AddReason(reasons, "Very Poor Internet Upload Speed", -5.0, false, offer.InetUp)
} else if offer.InetUp < 200.0 {
reasons = AddReason(reasons, "Poor Internet Upload Speed", -3.0, false, offer.InetUp)
} else if offer.InetUp < 400.0 {
reasons = AddReason(reasons, "Decent Internet Upload Speed", 1.0, false, offer.InetUp)
} else if offer.InetUp >= 800.0 {
reasons = AddReason(reasons, "Great Internet Upload Speed", 3.0, false, offer.InetUp)
} else if offer.InetUp >= 1000.0 {
reasons = AddReason(reasons, "Amazing Internet Upload Speed", 4.0, false, offer.InetUp)
}
// Judge verification
if offer.Verification == "verified" {
reasons = AddReason(reasons, "Verified", 2.0, false, offer.Verification)
} else {
reasons = AddReason(reasons, "Not Verified", -5.0, false, offer.Verification)
}
// Judge direct port count
if offer.DirectPortCount < 8 {
reasons = AddReason(reasons, "Very low direct port count", -2.0, false, offer.DirectPortCount)
} else if offer.DirectPortCount >= 32 {
reasons = AddReason(reasons, "Decent port count", 0.5, false, offer.DirectPortCount)
} else if offer.DirectPortCount >= 100 {
reasons = AddReason(reasons, "High port count", 1.0, false, offer.DirectPortCount)
}
// Judge CPU memory
if offer.CPURam < 16*1024 {
reasons = AddReason(reasons, "Low CPU memory", -4.0, false, offer.CPURam)
} else if offer.CPURam >= 31*1024 {
reasons = AddReason(reasons, "High CPU memory", 2.0, false, offer.CPURam)
} else if offer.CPURam >= 63*1024 {
reasons = AddReason(reasons, "Very High CPU memory", 3.0, false, offer.CPURam)
}
// Judge GPU count
if offer.NumGPUs < 1 {
reasons = AddReason(reasons, "No GPUs", -20.0, false, offer.NumGPUs)
} else if offer.NumGPUs == 2 {
reasons = AddReason(reasons, "Dual GPU", 1.0, false, offer.NumGPUs)
} else if offer.NumGPUs >= 3 {
reasons = AddReason(reasons, "Multi GPU", 2.0, false, offer.NumGPUs)
}
// Judge CUDA version
if offer.CudaMaxGood < 11.0 {
reasons = AddReason(reasons, "CUDA version very outdated", -7.0, false, offer.CudaMaxGood)
} else if offer.CudaMaxGood < 12.0 {
reasons = AddReason(reasons, "CUDA version outdated", -5.0, false, offer.CudaMaxGood)
} else if offer.CudaMaxGood >= 12.5 {
reasons = AddReason(reasons, "CUDA version high", 5.0, false, offer.CudaMaxGood)
} else if offer.CudaMaxGood >= 12.0 {
reasons = AddReason(reasons, "CUDA version decent", 1.5, false, offer.CudaMaxGood)
}
// Judge effective core count
if offer.CPUCoresEffective < 4.0 {
reasons = AddReason(reasons, "Low core count", -5.0, false, offer.CPUCoresEffective)
} else if offer.CPUCoresEffective >= 8.0 {
reasons = AddReason(reasons, "High core count", 5.0, false, offer.CPUCoresEffective)
} else if offer.CPUCoresEffective >= 6.0 {
reasons = AddReason(reasons, "Decent core count", 2.0, false, offer.CPUCoresEffective)
}
// Judge disk space available
if offer.DiskSpace < 100.0 {
reasons = AddReason(reasons, "Low disk space", -5.0, false, offer.DiskSpace)
} else if offer.DiskSpace >= 750.0 {
reasons = AddReason(reasons, "Reasonable disk space", 1.0, false, offer.DiskSpace)
} else if offer.DiskSpace >= 250.0 {
reasons = AddReason(reasons, "Concerning disk space", -1.0, false, offer.DiskSpace)
} else if offer.DiskSpace >= 2500.0 {
reasons = AddReason(reasons, "High disk space", 2.0, false, offer.DiskSpace)
}
// Judge GPU architecture
if offer.GPUArch == "nvidia" {
reasons = AddReason(reasons, "Nvidia Preference", 1.0, false, offer.GPUArch)
} else {
reasons = AddReason(reasons, "Unknown/Incompatible GPU Architecture", -10.0, false, offer.GPUArch)
}
// Judge reliability
if offer.Reliability2 < 0.98 {
reasons = AddReason(reasons, "Low reliability", -5.0, false, offer.Reliability2)
} else if offer.Reliability2 >= 0.999 {
reasons = AddReason(reasons, "Very high reliability", 5.0, false, offer.Reliability2)
} else if offer.Reliability2 >= 0.995 {
reasons = AddReason(reasons, "High reliability", 2.0, false, offer.Reliability2)
} else if offer.Reliability2 >= 0.99 {
reasons = AddReason(reasons, "Decent reliability", 1.0, false, offer.Reliability2)
}
// Calculate base score
score := 0.0
for _, reason := range reasons {
if !reason.IsMultiplier {
score += reason.Offset
}
}
// Apply multipliers
multiplier := 1.0
for _, reason := range reasons {
if reason.IsMultiplier {
multiplier *= reason.Offset
}
}
newScore := score * multiplier
// sugar.Infow("Multiplier Applied", "offer", offer.ID, "baseScore", score, "score", newScore, "multiplier", multiplier)
score = newScore
scoredOffers = append(scoredOffers, ScoredOffer{Offer: offer, Score: score, Reasons: reasons})
} }
return scoredOffers return scoredOffers
} }

View File

@@ -6,6 +6,8 @@ import (
) )
type AdvancedSearch struct { type AdvancedSearch struct {
Limit int `json:"limit,omitempty"`
AllocatedStorage float64 `json:"allocated_storage,omitempty"`
Verified *bool `json:"verified,omitempty"` Verified *bool `json:"verified,omitempty"`
ComputeCap *ComparableInteger `json:"compute_cap,omitempty"` ComputeCap *ComparableInteger `json:"compute_cap,omitempty"`
DiskSpace *ComparableInteger `json:"disk_space,omitempty"` DiskSpace *ComparableInteger `json:"disk_space,omitempty"`
@@ -50,6 +52,7 @@ type AdvancedSearch struct {
func NewSearch() *AdvancedSearch { func NewSearch() *AdvancedSearch {
return &AdvancedSearch{ return &AdvancedSearch{
Rented: Pointer(false), Rented: Pointer(false),
Limit: 500,
} }
} }

View File

@@ -129,6 +129,7 @@ type Offer struct {
DPHBase float64 `json:"dph_base"` DPHBase float64 `json:"dph_base"`
DPHTotal float64 `json:"dph_total"` DPHTotal float64 `json:"dph_total"`
GPUName string `json:"gpu_name"` GPUName string `json:"gpu_name"`
GPUArch string `json:"gpu_arch"`
GPURam int `json:"gpu_ram"` GPURam int `json:"gpu_ram"`
GPUDisplayActive bool `json:"gpu_display_active"` GPUDisplayActive bool `json:"gpu_display_active"`
GPUMemBw float64 `json:"gpu_mem_bw"` GPUMemBw float64 `json:"gpu_mem_bw"`
@@ -162,6 +163,16 @@ type Offer struct {
Rented bool `json:"rented"` Rented bool `json:"rented"`
BundledResults int `json:"bundled_results"` BundledResults int `json:"bundled_results"`
PendingCount int `json:"pending_count"` PendingCount int `json:"pending_count"`
Search ExtendedOfferDetails `json:"search"`
Instance ExtendedOfferDetails `json:"instance"`
}
type ExtendedOfferDetails struct {
GPUCostPerHour float64 `json:"gpuCostPerHour"`
DiskHour float64 `json:"diskHour"`
TotalHour float64 `json:"totalHour"`
DiscountTotalHour float64 `json:"discountTotalHour"`
DiscountedTotalPerHour float64 `json:"discountPerHour"`
} }
func (o *Offer) String() string { func (o *Offer) String() string {

3
app.go
View File

@@ -52,6 +52,9 @@ func (a *App) Search() []api.ScoredOffer {
// Create search // Create search
search := api.NewSearch() search := api.NewSearch()
search.AllocatedStorage = 39.94657756485159
search.Limit = 1000
// search.Rentable = api.Pointer(true)
// search.CPUCores = api.Ge(8) // search.CPUCores = api.Ge(8)
// Perform search // Perform search

View File

@@ -8,8 +8,11 @@
"name": "frontend", "name": "frontend",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"clsx": "^2.1.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0" "react-dom": "^18.2.0",
"react-tooltip": "^5.28.0",
"tailwind-merge": "^2.5.5"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^18.0.17", "@types/react": "^18.0.17",
@@ -394,6 +397,28 @@
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/@floating-ui/core": {
"version": "1.6.8",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.8.tgz",
"integrity": "sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==",
"dependencies": {
"@floating-ui/utils": "^0.2.8"
}
},
"node_modules/@floating-ui/dom": {
"version": "1.6.12",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.12.tgz",
"integrity": "sha512-NP83c0HjokcGVEMeoStg317VD9W7eDlGK7457dMBANbKA6GJZdc7rjujdgqzTaz93jkGgc5P/jeWbaCHnMNc+w==",
"dependencies": {
"@floating-ui/core": "^1.6.0",
"@floating-ui/utils": "^0.2.8"
}
},
"node_modules/@floating-ui/utils": {
"version": "0.2.8",
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.8.tgz",
"integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig=="
},
"node_modules/@isaacs/cliui": { "node_modules/@isaacs/cliui": {
"version": "8.0.2", "version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
@@ -772,6 +797,19 @@
"node": ">= 6" "node": ">= 6"
} }
}, },
"node_modules/classnames": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
"integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow=="
},
"node_modules/clsx": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
"engines": {
"node": ">=6"
}
},
"node_modules/color-convert": { "node_modules/color-convert": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -1985,6 +2023,19 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/react-tooltip": {
"version": "5.28.0",
"resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-5.28.0.tgz",
"integrity": "sha512-R5cO3JPPXk6FRbBHMO0rI9nkUG/JKfalBSQfZedZYzmqaZQgq7GLzF8vcCWx6IhUCKg0yPqJhXIzmIO5ff15xg==",
"dependencies": {
"@floating-ui/dom": "^1.6.1",
"classnames": "^2.3.0"
},
"peerDependencies": {
"react": ">=16.14.0",
"react-dom": ">=16.14.0"
}
},
"node_modules/read-cache": { "node_modules/read-cache": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@@ -2267,6 +2318,15 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/tailwind-merge": {
"version": "2.5.5",
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.5.tgz",
"integrity": "sha512-0LXunzzAZzo0tEPxV3I297ffKZPlKDrjj7NXphC8V5ak9yHC5zRmxnOe2m/Rd/7ivsOMJe3JZ2JVocoDdQTRBA==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/dcastil"
}
},
"node_modules/tailwindcss": { "node_modules/tailwindcss": {
"version": "3.4.16", "version": "3.4.16",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.16.tgz", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.16.tgz",

View File

@@ -9,8 +9,11 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"clsx": "^2.1.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0" "react-dom": "^18.2.0",
"react-tooltip": "^5.28.0",
"tailwind-merge": "^2.5.5"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^18.0.17", "@types/react": "^18.0.17",

View File

@@ -1 +1 @@
05b5c44c752e5fa7c8860e80963ac683 1a36b753b2917c4d0b27ca4c30b27a95

View File

@@ -9,6 +9,7 @@ function App() {
async function invoke() { async function invoke() {
const offers = await Search(); const offers = await Search();
console.log({ offer: offers[0] }); console.log({ offer: offers[0] });
offers.sort((a, b) => b.Score - a.Score);
setState(offers); setState(offers);
} }
@@ -18,12 +19,14 @@ function App() {
return ( return (
<div id="App"> <div id="App">
<div className="p-4" onClick={invoke}> <div className="p-4">
<div className="space-y-3 flex flex-col justify-items-center">
{state?.map((offer) => ( {state?.map((offer) => (
<Offer offer={offer} /> <Offer offer={offer} />
))} ))}
</div> </div>
</div> </div>
</div>
); );
} }

View File

@@ -1,9 +1,102 @@
import { api } from "@wails/go/models"; import { api } from "@wails/go/models";
import { useState } from "react";
import { cn } from "@src/utils";
import { Tooltip } from "react-tooltip";
export default function Offer({ export default function Offer({
offer: scoredOffer, offer: { Offer: offer, Score: score, Reasons: reasons },
}: { }: {
offer: api.ScoredOffer; offer: api.ScoredOffer;
}) { }) {
return <div className="p-4">{scoredOffer.Score}</div>; const copy = (text: string) => navigator.clipboard.writeText(text);
const [showDetails, setShowDetails] = useState(false);
const mb_to_gb = (mb: number) => Math.round(mb / 1024);
return (
<div
className={cn({
"flex [&>*]:px-2 flex-col relative bg-zinc-700/90 rounded max-w-md overflow-hidden":
true,
"h-24": !showDetails,
"min-h-24 max-h-48": showDetails,
})}
>
<div className="flex">
<span className="text-4xl font-bold pr-2">
{score >= 10 ? Math.round(score) : score.toFixed(1)}
</span>
<span className="relative text-xl top-2.5">
{offer.num_gpus}x {offer.gpu_name}{" "}
<span className="text-sm">{mb_to_gb(offer.gpu_ram)} GB</span>
</span>
</div>
<div>
<span>${offer.search.totalHour.toFixed(2)}/hr</span>
<span className="pl-3 text-sm">
<span className="text-xs select-none">mem</span>{" "}
{mb_to_gb(offer.cpu_ram)}/{mb_to_gb(offer.cpu_ram / offer.gpu_frac)}GB
</span>
</div>
<div className="select-none [>button]:select-auto [>button]:text-blue-500 w-full left-1 text-xs space-x-1">
<button onClick={() => copy(offer.machine_id.toString())}>
m{offer.machine_id}
</button>
<button onClick={() => copy(offer.host_id.toString())}>
h{offer.host_id}
</button>
<span>{Math.round(offer.duration / 60 / 60 / 24)} days</span>
<span
className={
offer.verification != "verified" ? "text-orange-400/90" : ""
}
>
{offer.verification}
</span>
</div>
<div
onClick={() => setShowDetails(!showDetails)}
className={cn({
"px-0 w-full bg-zinc-900/70 border-t cursor-pointer border-zinc-600/80 text-center":
true,
"select-none h-3 leading-[0.2rem] text-zinc-100 absolute bottom-0":
!showDetails,
"h-40 overflow-y-auto text-sm": showDetails,
})}
>
{showDetails ? (
<>
<Tooltip id="reason" />
{reasons
.sort((a, _) => (a.IsMultiplier ? 1 : -1))
.map((reason, i) => (
<div
data-tooltip-id="reason"
data-tooltip-content={reason.Value}
key={i}
className={cn(
"space-x-2",
(reason.IsMultiplier && reason.Offset < 1) ||
(!reason.IsMultiplier && reason.Offset < 0)
? ""
: ""
)}
>
{reason.IsMultiplier ? (
<span>x{reason.Offset.toFixed(2)}</span>
) : (
<span>
{reason.Offset > 0 ? "+" : null}
{reason.Offset}
</span>
)}
<span>{reason.Reason}</span>
</div>
))}
</>
) : (
"..."
)}
</div>
</div>
);
} }

View File

@@ -5,3 +5,37 @@
html { html {
@apply bg-zinc-800 p-0 m-0 h-screen w-screen text-zinc-200 overflow-x-hidden; @apply bg-zinc-800 p-0 m-0 h-screen w-screen text-zinc-200 overflow-x-hidden;
} }
@layer base {
* {
/* @apply border-border; */
}
body {
/* @apply bg-background text-foreground; */
}
ul,
ol {
list-style: revert;
}
/* NEW CODE */
/* width */
::-webkit-scrollbar {
@apply w-2 rounded-lg;
}
/* Track */
::-webkit-scrollbar-track {
@apply bg-zinc-700 rounded-lg;
}
/* Handle */
::-webkit-scrollbar-thumb {
@apply bg-zinc-500 rounded-xl;
}
/* Handle on hover */
::-webkit-scrollbar-thumb:hover {
@apply bg-zinc-400 rounded-lg;
}
}

View File

@@ -1,5 +1,6 @@
import React from "react"; import React from "react";
import { createRoot } from "react-dom/client"; import { createRoot } from "react-dom/client";
import "react-tooltip/dist/react-tooltip.css";
import "./main.css"; import "./main.css";
import App from "./App"; import App from "./App";

6
frontend/src/utils.ts Executable file
View File

@@ -0,0 +1,6 @@
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}

View File

@@ -18,7 +18,8 @@
"baseUrl": "./", "baseUrl": "./",
"paths": { "paths": {
"@components/*": ["src/components/*"], "@components/*": ["src/components/*"],
"@wails/*": ["./wailsjs/*"] "@wails/*": ["./wailsjs/*"],
"@src/*": ["src/*"]
} }
}, },
"include": ["src"], "include": ["src"],

View File

@@ -11,6 +11,7 @@ export default defineConfig({
alias: { alias: {
"@components": rootPath + "src/components", "@components": rootPath + "src/components",
"@wails": rootPath + "wailsjs", "@wails": rootPath + "wailsjs",
"@src": rootPath + "src",
}, },
}, },
}); });

View File

@@ -1,5 +1,25 @@
export namespace api { export namespace api {
export class ExtendedOfferDetails {
gpuCostPerHour: number;
diskHour: number;
totalHour: number;
discountTotalHour: number;
discountPerHour: number;
static createFrom(source: any = {}) {
return new ExtendedOfferDetails(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.gpuCostPerHour = source["gpuCostPerHour"];
this.diskHour = source["diskHour"];
this.totalHour = source["totalHour"];
this.discountTotalHour = source["discountTotalHour"];
this.discountPerHour = source["discountPerHour"];
}
}
export class Offer { export class Offer {
is_bid: boolean; is_bid: boolean;
inet_up_billed?: number; inet_up_billed?: number;
@@ -29,6 +49,7 @@ export namespace api {
dph_base: number; dph_base: number;
dph_total: number; dph_total: number;
gpu_name: string; gpu_name: string;
gpu_arch: string;
gpu_ram: number; gpu_ram: number;
gpu_display_active: boolean; gpu_display_active: boolean;
gpu_mem_bw: number; gpu_mem_bw: number;
@@ -62,6 +83,8 @@ export namespace api {
rented: boolean; rented: boolean;
bundled_results: number; bundled_results: number;
pending_count: number; pending_count: number;
search: ExtendedOfferDetails;
instance: ExtendedOfferDetails;
static createFrom(source: any = {}) { static createFrom(source: any = {}) {
return new Offer(source); return new Offer(source);
@@ -97,6 +120,7 @@ export namespace api {
this.dph_base = source["dph_base"]; this.dph_base = source["dph_base"];
this.dph_total = source["dph_total"]; this.dph_total = source["dph_total"];
this.gpu_name = source["gpu_name"]; this.gpu_name = source["gpu_name"];
this.gpu_arch = source["gpu_arch"];
this.gpu_ram = source["gpu_ram"]; this.gpu_ram = source["gpu_ram"];
this.gpu_display_active = source["gpu_display_active"]; this.gpu_display_active = source["gpu_display_active"];
this.gpu_mem_bw = source["gpu_mem_bw"]; this.gpu_mem_bw = source["gpu_mem_bw"];
@@ -130,11 +154,50 @@ export namespace api {
this.rented = source["rented"]; this.rented = source["rented"];
this.bundled_results = source["bundled_results"]; this.bundled_results = source["bundled_results"];
this.pending_count = source["pending_count"]; this.pending_count = source["pending_count"];
this.search = this.convertValues(source["search"], ExtendedOfferDetails);
this.instance = this.convertValues(source["instance"], ExtendedOfferDetails);
}
convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice && a.map) {
return (a as any[]).map(elem => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
}
export class ScoreReason {
Reason: string;
Offset: number;
Value: any;
IsMultiplier: boolean;
static createFrom(source: any = {}) {
return new ScoreReason(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.Reason = source["Reason"];
this.Offset = source["Offset"];
this.Value = source["Value"];
this.IsMultiplier = source["IsMultiplier"];
} }
} }
export class ScoredOffer { export class ScoredOffer {
Offer: Offer; Offer: Offer;
Score: number; Score: number;
Reasons: ScoreReason[];
static createFrom(source: any = {}) { static createFrom(source: any = {}) {
return new ScoredOffer(source); return new ScoredOffer(source);
@@ -144,6 +207,7 @@ export namespace api {
if ('string' === typeof source) source = JSON.parse(source); if ('string' === typeof source) source = JSON.parse(source);
this.Offer = this.convertValues(source["Offer"], Offer); this.Offer = this.convertValues(source["Offer"], Offer);
this.Score = source["Score"]; this.Score = source["Score"];
this.Reasons = this.convertValues(source["Reasons"], ScoreReason);
} }
convertValues(a: any, classs: any, asMap: boolean = false): any { convertValues(a: any, classs: any, asMap: boolean = false): any {