diff --git a/packages/core/db/snapshots/milestone.ts b/packages/core/db/snapshots/milestone.ts index 5bad4e8..57eaf3d 100644 --- a/packages/core/db/snapshots/milestone.ts +++ b/packages/core/db/snapshots/milestone.ts @@ -1,10 +1,16 @@ +import { dbMain } from "@core/drizzle"; +import { eta as etaTable } from "@core/drizzle/main/schema"; +import { eq } from "drizzle-orm"; import { MINUTE, HOUR, getClosetMilestone } from "@core/lib"; import { getLatestSnapshot, getClosestSnapshot } from "@core/db"; -export const getMilestoneETA = async (aid: number, targetViews?: number): Promise => { +export const getGroundTruthMilestoneETA = async ( + aid: number, + targetViews?: number +): Promise => { const DELTA = 1e-5; let minETAHours = Infinity; - const timeIntervals = [3 * HOUR, 24 * HOUR, 96 * HOUR]; + const timeIntervals = [3 * MINUTE, 20 * MINUTE, HOUR, 3 * HOUR, 6 * HOUR, 72 * HOUR]; const currentTimestamp = new Date().getTime(); const latestSnapshot = await getLatestSnapshot(aid); const latestSnapshotTime = new Date(latestSnapshot.time).getTime(); @@ -26,3 +32,11 @@ export const getMilestoneETA = async (aid: number, targetViews?: number): Promis } return minETAHours; }; + +export const getMilestoneETA = async (aid: number) => { + const data = await dbMain.select().from(etaTable).where(eq(etaTable.aid, aid)).limit(1); + if (data.length > 0) { + return data[0].eta; + } + return getGroundTruthMilestoneETA(aid); +}; diff --git a/packages/core/drizzle/main/relations.ts b/packages/core/drizzle/main/relations.ts index 0ed80c7..80768e2 100644 --- a/packages/core/drizzle/main/relations.ts +++ b/packages/core/drizzle/main/relations.ts @@ -1,2 +1,3 @@ import { relations } from "drizzle-orm/relations"; -import {} from "./schema"; +import { } from "./schema"; + diff --git a/packages/core/drizzle/main/schema.ts b/packages/core/drizzle/main/schema.ts index 41efdc1..1a1c9c3 100644 --- a/packages/core/drizzle/main/schema.ts +++ b/packages/core/drizzle/main/schema.ts @@ -1,334 +1,202 @@ -import { - pgTable, - index, - uniqueIndex, - bigserial, - bigint, - text, - timestamp, - integer, - unique, - serial, - smallint, - boolean, - varchar, - jsonb, - pgSequence -} from "drizzle-orm/pg-core"; -import { sql } from "drizzle-orm"; +import { pgTable, uniqueIndex, index, bigint, real, integer, timestamp, bigserial, text, unique, serial, smallint, boolean, varchar, jsonb, pgSequence } from "drizzle-orm/pg-core" +import { sql } from "drizzle-orm" -export const viewsIncrementRateIdSeq = pgSequence("views_increment_rate_id_seq", { - startWith: "1", - increment: "1", - minValue: "1", - maxValue: "9223372036854775807", - cache: "1", - cycle: false -}); -export const allDataIdSeq = pgSequence("all_data_id_seq", { - startWith: "1", - increment: "1", - minValue: "1", - maxValue: "2147483647", - cache: "1", - cycle: false -}); -export const labelingResultIdSeq = pgSequence("labeling_result_id_seq", { - startWith: "1", - increment: "1", - minValue: "1", - maxValue: "2147483647", - cache: "1", - cycle: false -}); -export const songsIdSeq = pgSequence("songs_id_seq", { - startWith: "1", - increment: "1", - minValue: "1", - maxValue: "2147483647", - cache: "1", - cycle: false -}); -export const videoSnapshotIdSeq = pgSequence("video_snapshot_id_seq", { - startWith: "1", - increment: "1", - minValue: "1", - maxValue: "2147483647", - cache: "1", - cycle: false -}); -export const snapshotSchedule = pgTable( - "snapshot_schedule", - { - id: bigserial({ mode: "bigint" }).notNull(), - // You can use { mode: "bigint" } if numbers are exceeding js number limitations - aid: bigint({ mode: "number" }).notNull(), - type: text(), - createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }) - .default(sql`CURRENT_TIMESTAMP`) - .notNull(), - startedAt: timestamp("started_at", { withTimezone: true, mode: "string" }), - finishedAt: timestamp("finished_at", { withTimezone: true, mode: "string" }), - status: text().default("pending").notNull() - }, - (table) => [ - index("idx_snapshot_schedule_aid").using("btree", table.aid.asc().nullsLast().op("int8_ops")), - index("idx_snapshot_schedule_started_at").using( - "btree", - table.startedAt.asc().nullsLast().op("timestamptz_ops") - ), - index("idx_snapshot_schedule_status").using("btree", table.status.asc().nullsLast().op("text_ops")), - index("idx_snapshot_schedule_type").using("btree", table.type.asc().nullsLast().op("text_ops")), - uniqueIndex("snapshot_schedule_pkey").using("btree", table.id.asc().nullsLast().op("int8_ops")) - ] -); +export const viewsIncrementRateIdSeq = pgSequence("views_increment_rate_id_seq", { startWith: "1", increment: "1", minValue: "1", maxValue: "9223372036854775807", cache: "1", cycle: false }) +export const allDataIdSeq = pgSequence("all_data_id_seq", { startWith: "1", increment: "1", minValue: "1", maxValue: "2147483647", cache: "1", cycle: false }) +export const labelingResultIdSeq = pgSequence("labeling_result_id_seq", { startWith: "1", increment: "1", minValue: "1", maxValue: "2147483647", cache: "1", cycle: false }) +export const songsIdSeq = pgSequence("songs_id_seq", { startWith: "1", increment: "1", minValue: "1", maxValue: "2147483647", cache: "1", cycle: false }) +export const videoSnapshotIdSeq = pgSequence("video_snapshot_id_seq", { startWith: "1", increment: "1", minValue: "1", maxValue: "2147483647", cache: "1", cycle: false }) +export const captchaDifficultySettingsIdSeq = pgSequence("captcha_difficulty_settings_id_seq", { startWith: "1", increment: "1", minValue: "1", maxValue: "2147483647", cache: "1", cycle: false }) +export const usersIdSeq = pgSequence("users_id_seq", { startWith: "1", increment: "1", minValue: "1", maxValue: "2147483647", cache: "1", cycle: false }) -export const videoSnapshot = pgTable( - "video_snapshot", - { - id: integer() - .default(sql`nextval('video_snapshot_id_seq'::regclass)`) - .notNull(), - createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }) - .default(sql`CURRENT_TIMESTAMP`) - .notNull(), - views: integer().notNull(), - coins: integer().notNull(), - likes: integer().notNull(), - favorites: integer().notNull(), - shares: integer().notNull(), - danmakus: integer().notNull(), - // You can use { mode: "bigint" } if numbers are exceeding js number limitations - aid: bigint({ mode: "number" }).notNull(), - replies: integer().notNull() - }, - (table) => [ - index("idx_vid_snapshot_aid").using("btree", table.aid.asc().nullsLast().op("int8_ops")), - index("idx_vid_snapshot_aid_created_at").using( - "btree", - table.aid.asc().nullsLast().op("int8_ops"), - table.createdAt.asc().nullsLast().op("int8_ops") - ), - index("idx_vid_snapshot_time").using("btree", table.createdAt.asc().nullsLast().op("timestamptz_ops")), - index("idx_vid_snapshot_views").using("btree", table.views.asc().nullsLast().op("int4_ops")), - uniqueIndex("video_snapshot_pkey").using("btree", table.id.asc().nullsLast().op("int4_ops")) - ] -); +export const eta = pgTable("eta", { + // You can use { mode: "bigint" } if numbers are exceeding js number limitations + aid: bigint({ mode: "number" }).notNull(), + eta: real().notNull(), + speed: real().notNull(), + currentViews: integer("current_views").notNull(), + updatedAt: timestamp("updated_at", { withTimezone: true, mode: 'string' }).defaultNow().notNull(), +}, (table) => [ + uniqueIndex("eta_pkey").using("btree", table.aid.asc().nullsLast().op("int8_ops")), + index("idx_eta_eta").using("btree", table.eta.asc().nullsLast().op("float4_ops")), +]); -export const bilibiliUser = pgTable( - "bilibili_user", - { - id: serial().primaryKey().notNull(), - // You can use { mode: "bigint" } if numbers are exceeding js number limitations - uid: bigint({ mode: "number" }).notNull(), - username: text().notNull(), - desc: text().notNull(), - fans: integer().notNull(), - createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }) - .default(sql`CURRENT_TIMESTAMP`) - .notNull(), - updatedAt: timestamp("updated_at", { withTimezone: true, mode: "string" }) - .default(sql`CURRENT_TIMESTAMP`) - .notNull() - }, - (table) => [ - index("idx_bili-user_uid").using("btree", table.uid.asc().nullsLast().op("int8_ops")), - unique("unq_bili-user_uid").on(table.uid) - ] -); +export const snapshotSchedule = pgTable("snapshot_schedule", { + id: bigserial({ mode: "bigint" }).notNull(), + // You can use { mode: "bigint" } if numbers are exceeding js number limitations + aid: bigint({ mode: "number" }).notNull(), + type: text(), + createdAt: timestamp("created_at", { withTimezone: true, mode: 'string' }).default(sql`CURRENT_TIMESTAMP`).notNull(), + startedAt: timestamp("started_at", { withTimezone: true, mode: 'string' }), + finishedAt: timestamp("finished_at", { withTimezone: true, mode: 'string' }), + status: text().default('pending').notNull(), +}, (table) => [ + index("idx_snapshot_schedule_aid").using("btree", table.aid.asc().nullsLast().op("int8_ops")), + index("idx_snapshot_schedule_started_at").using("btree", table.startedAt.asc().nullsLast().op("timestamptz_ops")), + index("idx_snapshot_schedule_status").using("btree", table.status.asc().nullsLast().op("text_ops")), + index("idx_snapshot_schedule_type").using("btree", table.type.asc().nullsLast().op("text_ops")), + uniqueIndex("snapshot_schedule_pkey").using("btree", table.id.asc().nullsLast().op("int8_ops")), +]); -export const relations = pgTable( - "relations", - { - id: serial().primaryKey().notNull(), - // You can use { mode: "bigint" } if numbers are exceeding js number limitations - sourceId: bigint("source_id", { mode: "number" }).notNull(), - sourceType: text("source_type").notNull(), - // You can use { mode: "bigint" } if numbers are exceeding js number limitations - targetId: bigint("target_id", { mode: "number" }).notNull(), - targetType: text("target_type").notNull(), - relation: text().notNull(), - createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }) - .default(sql`CURRENT_TIMESTAMP`) - .notNull(), - updatedAt: timestamp("updated_at", { withTimezone: true, mode: "string" }) - .default(sql`CURRENT_TIMESTAMP`) - .notNull() - }, - (table) => [ - index("idx_relations_source_id_source_type_relation").using( - "btree", - table.sourceId.asc().nullsLast().op("int8_ops"), - table.sourceType.asc().nullsLast().op("int8_ops"), - table.relation.asc().nullsLast().op("text_ops") - ), - index("idx_relations_target_id_target_type_relation").using( - "btree", - table.targetId.asc().nullsLast().op("text_ops"), - table.targetType.asc().nullsLast().op("text_ops"), - table.relation.asc().nullsLast().op("text_ops") - ), - unique("unq_relations").on(table.sourceId, table.sourceType, table.targetId, table.targetType, table.relation) - ] -); +export const videoSnapshot = pgTable("video_snapshot", { + id: integer().default(sql`nextval('video_snapshot_id_seq'::regclass)`).notNull(), + createdAt: timestamp("created_at", { withTimezone: true, mode: 'string' }).default(sql`CURRENT_TIMESTAMP`).notNull(), + views: integer().notNull(), + coins: integer().notNull(), + likes: integer().notNull(), + favorites: integer().notNull(), + shares: integer().notNull(), + danmakus: integer().notNull(), + // You can use { mode: "bigint" } if numbers are exceeding js number limitations + aid: bigint({ mode: "number" }).notNull(), + replies: integer().notNull(), +}, (table) => [ + index("idx_vid_snapshot_aid").using("btree", table.aid.asc().nullsLast().op("int8_ops")), + index("idx_vid_snapshot_aid_created_at").using("btree", table.aid.asc().nullsLast().op("int8_ops"), table.createdAt.asc().nullsLast().op("int8_ops")), + index("idx_vid_snapshot_time").using("btree", table.createdAt.asc().nullsLast().op("timestamptz_ops")), + index("idx_vid_snapshot_views").using("btree", table.views.asc().nullsLast().op("int4_ops")), + uniqueIndex("video_snapshot_pkey").using("btree", table.id.asc().nullsLast().op("int4_ops")), +]); -export const songs = pgTable( - "songs", - { - id: integer() - .default(sql`nextval('songs_id_seq'::regclass)`) - .notNull(), - name: text(), - // You can use { mode: "bigint" } if numbers are exceeding js number limitations - aid: bigint({ mode: "number" }), - publishedAt: timestamp("published_at", { withTimezone: true, mode: "string" }), - duration: integer(), - type: smallint(), - // You can use { mode: "bigint" } if numbers are exceeding js number limitations - neteaseId: bigint("netease_id", { mode: "number" }), - createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }) - .default(sql`CURRENT_TIMESTAMP`) - .notNull(), - updatedAt: timestamp("updated_at", { withTimezone: true, mode: "string" }) - .default(sql`CURRENT_TIMESTAMP`) - .notNull(), - deleted: boolean().default(false).notNull(), - image: text(), - producer: text() - }, - (table) => [ - index("idx_aid").using("btree", table.aid.asc().nullsLast().op("int8_ops")), - index("idx_hash_songs_aid").using("hash", table.aid.asc().nullsLast().op("int8_ops")), - index("idx_netease_id").using("btree", table.neteaseId.asc().nullsLast().op("int8_ops")), - index("idx_published_at").using("btree", table.publishedAt.asc().nullsLast().op("timestamptz_ops")), - index("idx_type").using("btree", table.type.asc().nullsLast().op("int2_ops")), - uniqueIndex("songs_pkey").using("btree", table.id.asc().nullsLast().op("int4_ops")), - uniqueIndex("unq_songs_aid").using("btree", table.aid.asc().nullsLast().op("int8_ops")), - uniqueIndex("unq_songs_netease_id").using("btree", table.neteaseId.asc().nullsLast().op("int8_ops")) - ] -); +export const bilibiliUser = pgTable("bilibili_user", { + id: serial().primaryKey().notNull(), + // You can use { mode: "bigint" } if numbers are exceeding js number limitations + uid: bigint({ mode: "number" }).notNull(), + username: text().notNull(), + desc: text().notNull(), + fans: integer().notNull(), + createdAt: timestamp("created_at", { withTimezone: true, mode: 'string' }).default(sql`CURRENT_TIMESTAMP`).notNull(), + updatedAt: timestamp("updated_at", { withTimezone: true, mode: 'string' }).default(sql`CURRENT_TIMESTAMP`).notNull(), +}, (table) => [ + index("idx_bili-user_uid").using("btree", table.uid.asc().nullsLast().op("int8_ops")), + unique("unq_bili-user_uid").on(table.uid), +]); + +export const relations = pgTable("relations", { + id: serial().primaryKey().notNull(), + // You can use { mode: "bigint" } if numbers are exceeding js number limitations + sourceId: bigint("source_id", { mode: "number" }).notNull(), + sourceType: text("source_type").notNull(), + // You can use { mode: "bigint" } if numbers are exceeding js number limitations + targetId: bigint("target_id", { mode: "number" }).notNull(), + targetType: text("target_type").notNull(), + relation: text().notNull(), + createdAt: timestamp("created_at", { withTimezone: true, mode: 'string' }).default(sql`CURRENT_TIMESTAMP`).notNull(), + updatedAt: timestamp("updated_at", { withTimezone: true, mode: 'string' }).default(sql`CURRENT_TIMESTAMP`).notNull(), +}, (table) => [ + index("idx_relations_source_id_source_type_relation").using("btree", table.sourceId.asc().nullsLast().op("int8_ops"), table.sourceType.asc().nullsLast().op("int8_ops"), table.relation.asc().nullsLast().op("text_ops")), + index("idx_relations_target_id_target_type_relation").using("btree", table.targetId.asc().nullsLast().op("text_ops"), table.targetType.asc().nullsLast().op("text_ops"), table.relation.asc().nullsLast().op("text_ops")), + unique("unq_relations").on(table.sourceId, table.sourceType, table.targetId, table.targetType, table.relation), +]); + +export const songs = pgTable("songs", { + id: integer().default(sql`nextval('songs_id_seq'::regclass)`).notNull(), + name: text(), + // You can use { mode: "bigint" } if numbers are exceeding js number limitations + aid: bigint({ mode: "number" }), + publishedAt: timestamp("published_at", { withTimezone: true, mode: 'string' }), + duration: integer(), + type: smallint(), + // You can use { mode: "bigint" } if numbers are exceeding js number limitations + neteaseId: bigint("netease_id", { mode: "number" }), + createdAt: timestamp("created_at", { withTimezone: true, mode: 'string' }).default(sql`CURRENT_TIMESTAMP`).notNull(), + updatedAt: timestamp("updated_at", { withTimezone: true, mode: 'string' }).default(sql`CURRENT_TIMESTAMP`).notNull(), + deleted: boolean().default(false).notNull(), + image: text(), + producer: text(), +}, (table) => [ + index("idx_aid").using("btree", table.aid.asc().nullsLast().op("int8_ops")), + index("idx_hash_songs_aid").using("hash", table.aid.asc().nullsLast().op("int8_ops")), + index("idx_netease_id").using("btree", table.neteaseId.asc().nullsLast().op("int8_ops")), + index("idx_published_at").using("btree", table.publishedAt.asc().nullsLast().op("timestamptz_ops")), + index("idx_type").using("btree", table.type.asc().nullsLast().op("int2_ops")), + uniqueIndex("songs_pkey").using("btree", table.id.asc().nullsLast().op("int4_ops")), + uniqueIndex("unq_songs_aid").using("btree", table.aid.asc().nullsLast().op("int8_ops")), + uniqueIndex("unq_songs_netease_id").using("btree", table.neteaseId.asc().nullsLast().op("int8_ops")), +]); export const singer = pgTable("singer", { id: serial().primaryKey().notNull(), - name: text().notNull() + name: text().notNull(), }); -export const bilibiliMetadata = pgTable( - "bilibili_metadata", - { - id: integer() - .default(sql`nextval('all_data_id_seq'::regclass)`) - .notNull(), - // You can use { mode: "bigint" } if numbers are exceeding js number limitations - aid: bigint({ mode: "number" }).notNull(), - bvid: varchar({ length: 12 }), - description: text(), - // You can use { mode: "bigint" } if numbers are exceeding js number limitations - uid: bigint({ mode: "number" }), - tags: text(), - title: text(), - publishedAt: timestamp("published_at", { withTimezone: true, mode: "string" }), - duration: integer(), - createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`), - status: integer().default(0).notNull(), - coverUrl: text("cover_url") - }, - (table) => [ - uniqueIndex("all_data_pkey").using("btree", table.id.asc().nullsLast().op("int4_ops")), - index("idx_all-data_aid").using("btree", table.aid.asc().nullsLast().op("int8_ops")), - index("idx_all-data_bvid").using("btree", table.bvid.asc().nullsLast().op("text_ops")), - index("idx_all-data_uid").using("btree", table.uid.asc().nullsLast().op("int8_ops")), - index("idx_bili-meta_status").using("btree", table.status.asc().nullsLast().op("int4_ops")), - uniqueIndex("unq_all-data_aid").using("btree", table.aid.asc().nullsLast().op("int8_ops")) - ] -); +export const bilibiliMetadata = pgTable("bilibili_metadata", { + id: integer().default(sql`nextval('all_data_id_seq'::regclass)`).notNull(), + // You can use { mode: "bigint" } if numbers are exceeding js number limitations + aid: bigint({ mode: "number" }).notNull(), + bvid: varchar({ length: 12 }), + description: text(), + // You can use { mode: "bigint" } if numbers are exceeding js number limitations + uid: bigint({ mode: "number" }), + tags: text(), + title: text(), + publishedAt: timestamp("published_at", { withTimezone: true, mode: 'string' }), + duration: integer(), + createdAt: timestamp("created_at", { withTimezone: true, mode: 'string' }).default(sql`CURRENT_TIMESTAMP`), + status: integer().default(0).notNull(), + coverUrl: text("cover_url"), +}, (table) => [ + uniqueIndex("all_data_pkey").using("btree", table.id.asc().nullsLast().op("int4_ops")), + index("idx_all-data_aid").using("btree", table.aid.asc().nullsLast().op("int8_ops")), + index("idx_all-data_bvid").using("btree", table.bvid.asc().nullsLast().op("text_ops")), + index("idx_all-data_uid").using("btree", table.uid.asc().nullsLast().op("int8_ops")), + index("idx_bili-meta_status").using("btree", table.status.asc().nullsLast().op("int4_ops")), + uniqueIndex("unq_all-data_aid").using("btree", table.aid.asc().nullsLast().op("int8_ops")), +]); -export const humanClassifiedLables = pgTable( - "human_classified_lables", - { - id: serial().notNull(), - // You can use { mode: "bigint" } if numbers are exceeding js number limitations - aid: bigint({ mode: "number" }).notNull(), - uid: integer().notNull(), - label: smallint().notNull(), - createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }) - .default(sql`CURRENT_TIMESTAMP`) - .notNull() - }, - (table) => [ - index("idx_classified-labels-human_aid").using("btree", table.aid.asc().nullsLast().op("int8_ops")), - index("idx_classified-labels-human_author").using("btree", table.uid.asc().nullsLast().op("int4_ops")), - index("idx_classified-labels-human_created-at").using( - "btree", - table.createdAt.asc().nullsLast().op("timestamptz_ops") - ), - index("idx_classified-labels-human_label").using("btree", table.label.asc().nullsLast().op("int2_ops")) - ] -); +export const humanClassifiedLables = pgTable("human_classified_lables", { + id: serial().notNull(), + // You can use { mode: "bigint" } if numbers are exceeding js number limitations + aid: bigint({ mode: "number" }).notNull(), + uid: integer().notNull(), + label: smallint().notNull(), + createdAt: timestamp("created_at", { withTimezone: true, mode: 'string' }).default(sql`CURRENT_TIMESTAMP`).notNull(), +}, (table) => [ + index("idx_classified-labels-human_aid").using("btree", table.aid.asc().nullsLast().op("int8_ops")), + index("idx_classified-labels-human_author").using("btree", table.uid.asc().nullsLast().op("int4_ops")), + index("idx_classified-labels-human_created-at").using("btree", table.createdAt.asc().nullsLast().op("timestamptz_ops")), + index("idx_classified-labels-human_label").using("btree", table.label.asc().nullsLast().op("int2_ops")), +]); export const history = pgTable("history", { id: serial().primaryKey().notNull(), // You can use { mode: "bigint" } if numbers are exceeding js number limitations objectId: bigint("object_id", { mode: "number" }).notNull(), changeType: text("change_type").notNull(), - changedAt: timestamp("changed_at", { withTimezone: true, mode: "string" }).notNull(), + changedAt: timestamp("changed_at", { withTimezone: true, mode: 'string' }).notNull(), changedBy: integer("changed_by").notNull(), - data: jsonb().notNull() + data: jsonb().notNull(), }); -export const labellingResult = pgTable( - "labelling_result", - { - id: integer() - .default(sql`nextval('labeling_result_id_seq'::regclass)`) - .notNull(), - // You can use { mode: "bigint" } if numbers are exceeding js number limitations - aid: bigint({ mode: "number" }).notNull(), - label: smallint().notNull(), - modelVersion: text("model_version").notNull(), - createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }) - .default(sql`CURRENT_TIMESTAMP`) - .notNull(), - logits: smallint().array() - }, - (table) => [ - index("idx_labeling_label_model-version").using( - "btree", - table.label.asc().nullsLast().op("int2_ops"), - table.modelVersion.asc().nullsLast().op("int2_ops") - ), - index("idx_labeling_model-version").using("btree", table.modelVersion.asc().nullsLast().op("text_ops")), - index("idx_labelling_aid-label").using( - "btree", - table.aid.asc().nullsLast().op("int2_ops"), - table.label.asc().nullsLast().op("int2_ops") - ), - uniqueIndex("labeling_result_pkey").using("btree", table.id.asc().nullsLast().op("int4_ops")), - uniqueIndex("unq_labelling-result_aid_model-version").using( - "btree", - table.aid.asc().nullsLast().op("int8_ops"), - table.modelVersion.asc().nullsLast().op("int8_ops") - ) - ] -); +export const labellingResult = pgTable("labelling_result", { + id: integer().default(sql`nextval('labeling_result_id_seq'::regclass)`).notNull(), + // You can use { mode: "bigint" } if numbers are exceeding js number limitations + aid: bigint({ mode: "number" }).notNull(), + label: smallint().notNull(), + modelVersion: text("model_version").notNull(), + createdAt: timestamp("created_at", { withTimezone: true, mode: 'string' }).default(sql`CURRENT_TIMESTAMP`).notNull(), + logits: smallint().array(), +}, (table) => [ + index("idx_labeling_label_model-version").using("btree", table.label.asc().nullsLast().op("int2_ops"), table.modelVersion.asc().nullsLast().op("int2_ops")), + index("idx_labeling_model-version").using("btree", table.modelVersion.asc().nullsLast().op("text_ops")), + index("idx_labelling_aid-label").using("btree", table.aid.asc().nullsLast().op("int2_ops"), table.label.asc().nullsLast().op("int2_ops")), + uniqueIndex("labeling_result_pkey").using("btree", table.id.asc().nullsLast().op("int4_ops")), + uniqueIndex("unq_labelling-result_aid_model-version").using("btree", table.aid.asc().nullsLast().op("int8_ops"), table.modelVersion.asc().nullsLast().op("int8_ops")), +]); -export const latestVideoSnapshot = pgTable( - "latest_video_snapshot", - { - // You can use { mode: "bigint" } if numbers are exceeding js number limitations - aid: bigint({ mode: "number" }).primaryKey().notNull(), - time: timestamp({ withTimezone: true, mode: "string" }).notNull(), - views: integer().notNull(), - coins: integer().notNull(), - likes: integer().notNull(), - favorites: integer().notNull(), - replies: integer().notNull(), - danmakus: integer().notNull(), - shares: integer().notNull() - }, - (table) => [ - index("idx_latest-video-snapshot_time").using("btree", table.time.asc().nullsLast().op("timestamptz_ops")), - index("idx_latest-video-snapshot_views").using("btree", table.views.asc().nullsLast().op("int4_ops")) - ] -); +export const latestVideoSnapshot = pgTable("latest_video_snapshot", { + // You can use { mode: "bigint" } if numbers are exceeding js number limitations + aid: bigint({ mode: "number" }).primaryKey().notNull(), + time: timestamp({ withTimezone: true, mode: 'string' }).notNull(), + views: integer().notNull(), + coins: integer().notNull(), + likes: integer().notNull(), + favorites: integer().notNull(), + replies: integer().notNull(), + danmakus: integer().notNull(), + shares: integer().notNull(), +}, (table) => [ + index("idx_latest-video-snapshot_time").using("btree", table.time.asc().nullsLast().op("timestamptz_ops")), + index("idx_latest-video-snapshot_views").using("btree", table.views.asc().nullsLast().op("int4_ops")), +]); diff --git a/packages/elysia/routes/song/milestone.ts b/packages/elysia/routes/song/milestone.ts index 849cec9..0125a85 100644 --- a/packages/elysia/routes/song/milestone.ts +++ b/packages/elysia/routes/song/milestone.ts @@ -1,51 +1,36 @@ import { Elysia, t } from "elysia"; import { dbMain } from "@core/drizzle"; -import { bilibiliMetadata, latestVideoSnapshot } from "@core/drizzle/main/schema"; +import { bilibiliMetadata, eta, latestVideoSnapshot } from "@core/drizzle/main/schema"; import { eq, and, gte, lt, desc } from "drizzle-orm"; -import { getMilestoneETA } from "@core/db"; import serverTiming from "@elysia/middlewares/timing"; type MileStoneType = "dendou" | "densetsu" | "shinwa"; const range = { - dendou: [90000, 99999, 100000], - densetsu: [900000, 999999, 1000000], - shinwa: [5000000, 9999999, 10000000] + dendou: [90000, 99999, 2160], + densetsu: [900000, 999999, 8760], + shinwa: [5000000, 9999999, 87600] }; -export const closeMileStoneHandler = new Elysia({ prefix: "/song" }).use(serverTiming()).get( +export const closeMileStoneHandler = new Elysia({ prefix: "/songs" }).use(serverTiming()).get( "/close-milestone/:type", async ({ params, timeLog }) => { - timeLog.startTime("retrieveCandidates") + timeLog.startTime("retrieveCandidates"); const type = params.type; const min = range[type as MileStoneType][0]; const max = range[type as MileStoneType][1]; - const data = await dbMain + return dbMain .select() - .from(bilibiliMetadata) - .innerJoin(latestVideoSnapshot, eq(latestVideoSnapshot.aid, bilibiliMetadata.aid)) - .where(and(gte(latestVideoSnapshot.views, min), lt(latestVideoSnapshot.views, max))) - .orderBy(desc(latestVideoSnapshot.views)); - type Row = (typeof data)[number]; - type Result = Row & { - eta: number; - }; - const result: Result[] = []; - timeLog.endTime("retrieveCandidates") - - timeLog.startTime("calculateETA"); - for (let i = 0; i < data.length; i++) { - const aid = data[i].bilibili_metadata.aid; - const eta = await getMilestoneETA(aid, range[type as MileStoneType][2]); - result.push({ - ...data[i], - eta - }); - } - timeLog.endTime("calculateETA"); - - result.sort((a, b) => a.eta - b.eta); - return result; + .from(eta) + .innerJoin(bilibiliMetadata, eq(bilibiliMetadata.aid, eta.aid)) + .where( + and( + gte(eta.currentViews, min), + lt(eta.currentViews, max), + lt(eta.eta, range[type as MileStoneType][2]) + ) + ) + .orderBy(eta.eta); }, { response: {