Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/src/packages/util/db-schema/purchases.ts
Views: 687
/*1Purchases23NOTES:456- cost is by definition how much the thing costs the customer, e.g., -10 means a credit of $10.7- amount is by definition the negative of cost.89We typically *show* user the amount, but we do absolutely all internal accounting10and storage with cost. Why? Because I wrote all the code and tests that way, and it was11too late to change t use amount internally. That's the only reason.12*/1314import { PurchaseInfo } from "@cocalc/util/licenses/purchase/types";15import * as computeServers from "./compute-servers";16import { CREATED_BY, ID } from "./crm";17import { SCHEMA as schema } from "./index";18import { LanguageServiceCore } from "./llm-utils";19import type { CourseInfo } from "./projects";20import { Table } from "./types";2122export type Reason =23| "duplicate"24| "fraudulent"25| "requested_by_customer"26| "other";2728// The general categories of services we offer. These must29// be at most 127 characters, and users can set an individual30// monthly quota on each one in purchase-quotas.31// The service names for openai are of the form "openai-[model name]"3233export type ComputeService =34| "credit"35| "refund"36| "project-upgrade"37| "compute-server"38| "compute-server-network-usage"39| "compute-server-storage"40| "license"41| "voucher"42| "edit-license";4344export type Service = LanguageServiceCore | ComputeService;4546export interface LLMDescription {47type: LanguageServiceCore;48prompt_tokens: number;49completion_tokens: number;50amount?: number; // appears in purchses/close.ts51last_updated?: number; // also in purchases/close.ts, a timestamp (Date.valueOf())52}5354export interface ProjectUpgrade {55type: "project-upgrade";56project_id: string;57start: number; // ms since epoch58stop?: number; // ms since epoch59quota: {60cost: number; // dollars per hour61cores?: number;62memory?: number;63network?: number;64mintime?: number;65cpu_shares?: number;66disk_quota?: number;67member_host?: number;68always_running?: number;69memory_request?: number;70};71}7273export interface ComputeServer {74type: "compute-server";75state: computeServers.State;76compute_server_id: number;77configuration: computeServers.Configuration;78}7980export interface ComputeServerNetworkUsage {81type: "compute-server-network-usage";82cost?: number;83compute_server_id: number;84amount: number; // amount of data used in GB85last_updated?: number;86}8788// describes how the charges for GCS for a period time break down89// into components. Of course there is much more detail than this90// in billing data, e.g., exactly how much of each kind of network.91// But at least this breakdown is probably helpful as a start to92// better understand charges.93export interface GoogleCloudStorageBucketCost {94network: number;95storage: number;96classA: number;97classB: number;98autoclass: number;99other: number;100}101102// This is used to support cloud file systems; however, it's generic103// enough it could be for any bucket storage.104export interface ComputeServerStorage {105type: "compute-server-storage";106cloud: "google-cloud"; // only google-cloud currently supported107bucket: string; // SUPER important -- the name of the bucket108cloud_filesystem_id: number;109// once the purchase is done and finalized, we put the final cost here:110cost?: number;111// this is a breakdown of the cost, which is cloud-specific112cost_breakdown?: GoogleCloudStorageBucketCost;113// filesystem the bucket is used for.114// an estimated cost for the given period of time -- we try to make this115// based on collected metrics, and it may or may not be close to the116// actual cost.117estimated_cost?: { min: number; max: number };118// when the estimated cost was set.119last_updated?: number;120}121122export interface License {123type: "license";124info: PurchaseInfo;125license_id: string;126item?; // item in shopping cart127course?: CourseInfo;128}129130export interface Voucher {131type: "voucher";132quantity: number;133cost: number; // per voucher134title: string;135voucher_id: number;136}137138export interface EditLicense {139type: "edit-license";140license_id: string;141origInfo: PurchaseInfo;142modifiedInfo: PurchaseInfo;143note: string; // not explaining the cost144}145146export interface Credit {147type: "credit";148voucher_code?: string; // if credit is the result of redeeming a voucher code149}150151export interface Refund {152type: "refund";153purchase_id: number; // id of entry in purchases table of the credit that this is refunding back from154refund_id?: string; // stripe Refund object id for the refund155reason: Reason;156notes: string;157}158159export type Description =160| LLMDescription161| ProjectUpgrade162| ComputeServer163| ComputeServerNetworkUsage164| ComputeServerStorage165| Credit166| Refund167| License168| Voucher169| EditLicense;170171// max number of purchases a user can get in one query.172export const MAX_API_LIMIT = 500;173174// maximum for any single purchase ever. Any frontend175// ui or api should use this constant to define a check.176export const MAX_COST = 99999;177178export function getAmountStyle(amount: number) {179return {180fontWeight: "bold",181color: amount >= 0 ? "#126bc5" : "#414042",182} as const;183}184185export interface Purchase {186id: number;187time: Date;188account_id: string;189cost?: number;190cost_per_hour?: number; // for purchases with a specific rate (e.g., an upgrade)191cost_so_far?: number; // for purchases that accumulate (e.g., data transfer)192period_start?: Date;193period_end?: Date;194pending?: boolean;195service: Service;196description: Description;197invoice_id?: string;198project_id?: string;199tag?: string;200day_statement_id?: number;201month_statement_id?: number;202notes?: string;203}204205Table({206name: "purchases",207fields: {208id: ID,209time: { type: "timestamp", desc: "When this purchase was logged." },210account_id: CREATED_BY,211cost: {212title: "Cost ($)",213desc: "The cost in US dollars. Not set if the purchase isn't finished, e.g., when upgrading a project this is only set when project stops or purchase is finalized. This takes precedence over the cost_per_hour times the length of the period when active.",214type: "number",215pg_type: "real",216},217pending: {218type: "boolean",219desc: "If true, then this transaction is considered pending, which means that for a few days it doesn't count against the user's quotas for the purposes of deciding whether or not a purchase is allowed. This is needed so we can charge a user for their subscriptions, then collect the money from them, without all of the running pay-as-you-go project upgrades suddenly breaking (etc.).",220},221cost_per_hour: {222title: "Cost Per Hour",223desc: "The cost in US dollars per hour. This is used to compute the cost so far for metered purchases when the cost field isn't set yet. The cost so far is the number of hours since period_start times the cost_per_hour. The description field may also contain redundant cost per hour information, but this cost_per_hour field is the definitive source of truth. Once the cost field is set, this cost_per_hour is just useful for display purposes.",224type: "number",225pg_type: "real",226},227cost_so_far: {228title: "Cost So Far",229desc: "The cost so far in US dollars for a metered purchase that accumulates. This is used, e.g., for data transfer charges.",230type: "number",231pg_type: "real",232},233period_start: {234title: "Period Start",235type: "timestamp",236desc: "When the purchase starts being active (e.g., a 1 week license starts and ends on specific days; for metered purchases it is when the purchased started charging)",237},238period_end: {239title: "Period End",240type: "timestamp",241desc: "When the purchase stops being active. For metered purchases, it's when the purchase finished being charged, in which case the cost field should be equal to the length of the period times the cost_per_hour.",242},243invoice_id: {244title: "Invoice Id",245desc: "The id of the stripe invoice that was sent that included this item. Legacy: if paid via a payment intent, this will be the id of a payment intent instead, and it will start with pi_.",246type: "string",247},248project_id: {249title: "Project Id",250desc: "The id of the project where this purchase happened. Not all purchases necessarily involve a project.",251type: "uuid",252render: { type: "project_link" },253},254service: {255title: "Service Category",256desc: "The service being charged for, e.g., openai-gpt-4, etc.",257type: "string",258pg_type: "varchar(127)",259},260description: {261title: "Description",262desc: "An object that provides additional details about what was purchased and can have an arbitrary format. This is mainly used to provide extra insight when rendering this purchase for users, and its content should not be relied on for queries.",263type: "map",264pg_type: "jsonb",265},266tag: {267type: "string",268pg_type: "varchar(127)",269desc: "Optional string that can be useful in analytics to understand where and how this purchase was made.",270},271day_statement_id: {272type: "integer",273desc: "id of the daily statement that includes this purchase",274},275month_statement_id: {276type: "integer",277desc: "id of the monthly statement that includes this purchase",278},279notes: {280type: "string",281desc: "Non-private notes about this purchase. The user CAN see but not edit them.",282render: {283type: "markdown",284editable: true,285},286},287},288rules: {289desc: "Purchase Log",290primary_key: "id",291pg_indexes: ["account_id", "time", "project_id"],292user_query: {293get: {294pg_where: [{ "account_id = $::UUID": "account_id" }],295fields: {296id: null,297time: null,298period_start: null,299period_end: null,300account_id: null,301cost: null,302pending: null,303cost_per_hour: null,304cost_so_far: null,305service: null,306description: null,307invoice_id: null,308project_id: null,309tag: null,310notes: null,311},312},313},314},315});316317Table({318name: "crm_purchases",319rules: {320virtual: "purchases",321primary_key: "id",322user_query: {323get: {324pg_where: [],325admin: true,326fields: {327id: null,328time: null,329period_start: null,330period_end: null,331account_id: null,332cost: null,333pending: null,334cost_per_hour: null,335cost_so_far: null,336service: null,337description: null,338invoice_id: null,339project_id: null,340tag: null,341notes: null,342},343},344set: {345admin: true,346fields: {347id: true,348tag: true,349notes: true,350},351},352},353},354fields: schema.purchases.fields,355});356357358