Skip to content

Commit

Permalink
merge main
Browse files Browse the repository at this point in the history
  • Loading branch information
at2005 committed Oct 1, 2024
2 parents 982cf78 + 283bc58 commit 5f5b4e9
Show file tree
Hide file tree
Showing 12 changed files with 611 additions and 249 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,5 @@
"peerDependencies": {
"typescript": "^5.6.2"
},
"version": "0.0.38"
"version": "0.0.48"
}
4 changes: 4 additions & 0 deletions src/helpers/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,7 @@ export function failedToConnect(): never {
"Failed to connect to the server. Please check your internet connection and try again.",
);
}

export function unreachable(): never {
throw new Error("unreachable code");
}
28 changes: 15 additions & 13 deletions src/helpers/price.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
export function pricePerGPUHourToTotalPrice(
pricePerGPUHourInCenticents: number,
import type { Cents } from "./units";

export function pricePerGPUHourToTotalPriceCents(
pricePerGPUHourCents: Cents,
durationSeconds: number,
nodes: number,
gpusPerNode: number,
) {
return Math.ceil(
((pricePerGPUHourInCenticents * durationSeconds) / 3600) *
nodes *
gpusPerNode,
);
): Cents {
const totalGPUs = nodes * gpusPerNode;
const totalHours = durationSeconds / 3600;

return Math.ceil(pricePerGPUHourCents * totalGPUs * totalHours);
}

export function totalPriceToPricePerGPUHour(
totalPriceInCenticents: number,
priceCents: number,
durationSeconds: number,
nodes: number,
gpusPerNode: number,
) {
return (
totalPriceInCenticents / nodes / gpusPerNode / (durationSeconds / 3600)
);
): Cents {
const totalGPUs = nodes * gpusPerNode;
const totalHours = durationSeconds / 3600;

return priceCents / totalGPUs / totalHours;
}
92 changes: 41 additions & 51 deletions src/helpers/test/units.test.ts
Original file line number Diff line number Diff line change
@@ -1,102 +1,92 @@
import { describe, expect, test } from "bun:test";
import {
type Centicents,
centicentsToDollarsFormatted,
priceWholeToCenticents,
type Cents,
centsToDollarsFormatted,
priceWholeToCents,
} from "../units";

describe("units", () => {
test("price whole to centicents", () => {
test("price whole to cents", () => {
const inputToExpectedValids = [
// formatted as USD
["$0", 0],
["$1", 10_000],
["$10", 100_000],
["$100", 1_000_000],
["$1", 100],
["$10", 10_00],
["$100", 100_00],

["$0.0", 0],
["$0.00", 0],
["$0.000", 0],

["$1.0", 10_000],
["$1.00", 10_000],
["$1.000", 10_000],
["$1.0", 100],
["$1.00", 100],
["$1.000", 100],

["$1.23", 12_300],
["$1.234", 12_340],
["$1.2345", 12_345],
["$$1.2345", 12_345],
["$1.23", 123],
["$1.234", 123.4],

// formatted as numbers
["0", 0],
["1", 10_000],
["10", 100_000],
["100", 1_000_000],
["1", 100],
["10", 10_00],
["100", 100_00],

["1.23", 12_300],
["1.234", 12_340],
["1.2345", 12_345],
["1.23", 123],
["1.234", 123.4],

// nested quotes (double)
['"$0"', 0],
['"$1"', 10_000],
['"$10"', 100_000],
['"$1"', 100],
['"$10"', 10_00],
['"0"', 0],
['"1"', 10_000],
['"10"', 100_000],
['"1"', 100],
['"10"', 10_00],

// nested quotes (single)
["'$0'", 0],
["'$1'", 10_000],
["'$10'", 100_000],
["'$1'", 100],
["'$10'", 10_00],
["'$0'", 0],
["'$1'", 10_000],
["'$10'", 100_000],
["'$1'", 100],
["'$10'", 10_00],
];

for (const [input, centicentsExpected] of inputToExpectedValids) {
const { centicents, invalid } = priceWholeToCenticents(input);
for (const [input, centsExpected] of inputToExpectedValids) {
const { cents, invalid } = priceWholeToCents(input);

expect(centicents).not.toBeNull();
expect(centicents).toEqual(centicentsExpected as number);
expect(cents).not.toBeNull();
expect(cents).toEqual(centsExpected as number);
expect(invalid).toBe(false);
}

const invalidPrices = [null, undefined, [], {}];
for (const input of invalidPrices) {
const { centicents, invalid } = priceWholeToCenticents(input as any);
const { cents, invalid } = priceWholeToCents(input as any);

expect(centicents).toBeNull();
expect(cents).toBeNull();
expect(invalid).toBeTrue();
}
});

test("centicents to dollars formatted", () => {
test("cents to dollars formatted", () => {
const inputToExpectedValids = [
// whole
[0, "$0.00"],
[10_000, "$1.00"],
[100_000, "$10.00"],
[1_000_000, "$100.00"],
[100, "$1.00"],
[10_00, "$10.00"],
[100_00, "$100.00"],

[99_910, "$9.99"],
[9_99, "$9.99"],

// with cents
[100, "$0.01"],
[200, "$0.02"],
[1_000, "$0.10"],
[9000, "$0.90"],

// rounding
[1, "$0.00"],
[49, "$0.00"],
[50, "$0.01"],
[99, "$0.01"],
[100, "$0.01"],
[1, "$0.01"],
[2, "$0.02"],
[10, "$0.10"],
[90, "$0.90"],
];

for (const [input, expected] of inputToExpectedValids) {
const result = centicentsToDollarsFormatted(input as Centicents);
const result = centsToDollarsFormatted(input as Cents);

expect(result).toEqual(expected as string);
}
Expand Down
33 changes: 20 additions & 13 deletions src/helpers/units.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,45 +39,52 @@ function roundEpochUpToHour(epoch: number): number {
// -- currency

export type Cents = number;
export type Centicents = number;

interface PriceWholeToCenticentsReturn {
centicents: Nullable<Centicents>;
interface PriceWholeToCentsReturn {
cents: Nullable<Cents>;
invalid: boolean;
}
export function priceWholeToCenticents(
export function priceWholeToCents(
price: string | number,
): PriceWholeToCenticentsReturn {
): PriceWholeToCentsReturn {
if (
price === null ||
price === undefined ||
(typeof price !== "number" && typeof price !== "string")
) {
return { centicents: null, invalid: true };
return { cents: null, invalid: true };
}

if (typeof price === "number") {
if (price < 0) {
return { centicents: null, invalid: true };
return { cents: null, invalid: true };
}

return { centicents: price * 10_000, invalid: false };
return { cents: price * 100, invalid: false };
} else if (typeof price === "string") {
// remove any whitespace, dollar signs, negative signs, single and double quotes
const priceCleaned = price.replace(/[\s\$\-\'\"]/g, "");
if (priceCleaned === "") {
return { centicents: null, invalid: true };
return { cents: null, invalid: true };
}

const parsedPrice = Number.parseFloat(priceCleaned);

return { centicents: parsedPrice * 10_000, invalid: false };
return { cents: parsedPrice * 100, invalid: false };
}

// default invalid
return { centicents: null, invalid: true };
return { cents: null, invalid: true };
}

export function centicentsToDollarsFormatted(centicents: Centicents): string {
return `$${(centicents / 10_000).toFixed(2)}`;
export function centsToDollarsFormatted(cents: Cents): string {
return `$${centsToDollars(cents).toFixed(2)}`;
}

export function centsToDollars(cents: Cents): number {
return cents / 100;
}

export function dollarsToCents(dollars: number): Cents {
return Math.ceil(dollars * 100);
}
40 changes: 18 additions & 22 deletions src/lib/balance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
logLoginMessageAndQuit,
logSessionTokenExpiredAndQuit,
} from "../helpers/errors";
import type { Centicents } from "../helpers/units";
import type { Cents } from "../helpers/units";

const usdFormatter = new Intl.NumberFormat("en-US", {
style: "currency",
Expand All @@ -22,19 +22,19 @@ export function registerBalance(program: Command) {
.option("--json", "Output in JSON format")
.action(async (options) => {
const {
available: { whole: availableWhole, centicents: availableCenticents },
reserved: { whole: reservedWhole, centicents: reservedCenticents },
available: { whole: availableWhole, cents: availableCents },
reserved: { whole: reservedWhole, cents: reservedCents },
} = await getBalance();

if (options.json) {
const jsonOutput = {
available: {
whole: availableWhole,
centicents: availableCenticents,
cents: availableCents,
},
reserved: {
whole: reservedWhole,
centicents: reservedCenticents,
cents: reservedCents,
},
};
console.log(JSON.stringify(jsonOutput, null, 2));
Expand All @@ -43,24 +43,20 @@ export function registerBalance(program: Command) {
const formattedReserved = usdFormatter.format(reservedWhole);

const table = new Table({
head: [
chalk.gray("Type"),
chalk.gray("Amount"),
chalk.gray("Centicents (1/100th of a cent)"),
],
head: [chalk.gray("Type"), chalk.gray("Amount"), chalk.gray("Cents")],
colWidths: [15, 15, 35],
});

table.push(
[
"Available",
chalk.green(formattedAvailable),
chalk.green(availableCenticents.toLocaleString()),
chalk.green(availableCents.toLocaleString()),
],
[
"Reserved",
chalk.gray(formattedReserved),
chalk.gray(reservedCenticents.toLocaleString()),
chalk.gray(reservedCents.toLocaleString()),
],
);

Expand All @@ -71,18 +67,18 @@ export function registerBalance(program: Command) {
});
}

export type BalanceUsdCenticents = {
available: { centicents: Centicents; whole: number };
reserved: { centicents: Centicents; whole: number };
export type BalanceUsdCents = {
available: { cents: Cents; whole: number };
reserved: { cents: Cents; whole: number };
};
export async function getBalance(): Promise<BalanceUsdCenticents> {
export async function getBalance(): Promise<BalanceUsdCents> {
const loggedIn = await isLoggedIn();
if (!loggedIn) {
logLoginMessageAndQuit();

return {
available: { centicents: 0, whole: 0 },
reserved: { centicents: 0, whole: 0 },
available: { cents: 0, whole: 0 },
reserved: { cents: 0, whole: 0 },
};
}
const client = await apiClient();
Expand Down Expand Up @@ -126,12 +122,12 @@ export async function getBalance(): Promise<BalanceUsdCenticents> {

return {
available: {
centicents: available,
whole: available / 10_000,
cents: available,
whole: available / 100,
},
reserved: {
centicents: reserved,
whole: reserved / 10_000,
cents: reserved,
whole: reserved / 100,
},
};
}
Loading

0 comments on commit 5f5b4e9

Please sign in to comment.