Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
ALTER TABLE events ADD COLUMN breaking_raw_score REAL;
ALTER TABLE events ADD COLUMN breaking_percentile REAL;
ALTER TABLE events ADD COLUMN breaking_calibrated_score REAL;
ALTER TABLE events ADD COLUMN breaking_adversarial_flags TEXT DEFAULT '{}';

UPDATE events
SET
breaking_raw_score = COALESCE(breaking_raw_score, breaking_score),
breaking_percentile = COALESCE(breaking_percentile, breaking_score),
breaking_calibrated_score = COALESCE(breaking_calibrated_score, breaking_score),
breaking_score_version = COALESCE(breaking_score_version, 'breaking-v2.0')
WHERE breaking_score IS NOT NULL;

DROP INDEX IF EXISTS idx_events_public_breaking;
CREATE INDEX IF NOT EXISTS idx_events_public_breaking
ON events(
pipeline_stage,
breaking_calibrated_score DESC,
breaking_score DESC,
published_at DESC
);
11 changes: 10 additions & 1 deletion frontend/cloudflare/db/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,15 @@ CREATE TABLE IF NOT EXISTS events (
discussion_count INTEGER,
classification TEXT DEFAULT '{}',
extra TEXT DEFAULT '{}',
breaking_raw_score REAL,
breaking_percentile REAL,
breaking_calibrated_score REAL,
breaking_score REAL,
breaking_label TEXT,
breaking_reason TEXT,
breaking_confidence INTEGER,
breaking_dimensions TEXT DEFAULT '{}',
breaking_adversarial_flags TEXT DEFAULT '{}',
breaking_score_version TEXT,
target_timezone TEXT DEFAULT 'UTC',
published_at_local TEXT,
Expand All @@ -56,7 +60,12 @@ CREATE INDEX IF NOT EXISTS idx_events_pipeline_stage ON events(pipeline_stage);
CREATE INDEX IF NOT EXISTS idx_events_source_id ON events(source_id);
CREATE INDEX IF NOT EXISTS idx_events_value_label ON events(value_label);
CREATE INDEX IF NOT EXISTS idx_events_public_featured ON events(pipeline_stage, value_score DESC, published_at DESC);
CREATE INDEX IF NOT EXISTS idx_events_public_breaking ON events(pipeline_stage, breaking_score DESC, published_at DESC);
CREATE INDEX IF NOT EXISTS idx_events_public_breaking ON events(
pipeline_stage,
breaking_calibrated_score DESC,
breaking_score DESC,
published_at DESC
);

CREATE TABLE IF NOT EXISTS event_localizations (
event_id TEXT NOT NULL,
Expand Down
6 changes: 4 additions & 2 deletions frontend/cloudflare/workers/api/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
publicNewsOrderBy,
publicNewsLocaleJoin,
publicNewsSelectColumnsForLocale,
pollAfterMsForPublicNews,
rowToPublicNewsItem,
} from "../lib/public-news-query";
import {
Expand Down Expand Up @@ -177,11 +178,12 @@ export async function handleBootstrap(

// 构建 news
const newsRows = (newsResult.results || []) as NewsRow[];
const newsItems = newsRows.map((r) => rowToPublicNewsItem(r));
const newsFeed: PublicNewsFeedResponse = {
items: newsRows.map((r) => rowToPublicNewsItem(r)),
items: newsItems,
latestCursor: null,
nextCursor: null,
pollAfterMs: featured ? 30000 : 60000,
pollAfterMs: pollAfterMsForPublicNews(featured, newsItems),
hasNewer: false,
total: newsCountResult?.total ?? newsRows.length,
};
Expand Down
14 changes: 8 additions & 6 deletions frontend/cloudflare/workers/api/news.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
publicNewsOrderBy,
publicNewsLocaleJoin,
publicNewsSelectColumnsForLocale,
pollAfterMsForPublicNews,
rowToPublicNewsItem,
} from "../lib/public-news-query";
import {
Expand Down Expand Up @@ -46,6 +47,7 @@ interface CursorRow {
published_at: string;
value_score: number | null;
breaking_score: number | null;
breaking_calibrated_score: number | null;
}

function withLocaleHeaders(response: Response, locale: string): Response {
Expand All @@ -69,22 +71,22 @@ async function buildCursorFilter(
if (!eventId) return { sql: "", bindings: [] };

const cursor = await db
.prepare("SELECT event_id, published_at, value_score, breaking_score FROM events WHERE event_id = ?")
.prepare("SELECT event_id, published_at, value_score, breaking_score, breaking_calibrated_score FROM events WHERE event_id = ?")
.bind(eventId)
.first<CursorRow>();
if (!cursor) return { sql: "", bindings: [] };

if (featured) {
const score = cursor.breaking_score ?? cursor.value_score ?? -1;
const score = cursor.breaking_calibrated_score ?? cursor.breaking_score ?? cursor.value_score ?? -1;
const scoreOp = mode === "before" ? "<" : ">";
const timeOp = mode === "before" ? "<" : ">";
const idOp = mode === "before" ? "<" : ">";
return {
sql: `
AND (
COALESCE(breaking_score, value_score, -1) ${scoreOp} ?
OR (COALESCE(breaking_score, value_score, -1) = ? AND events.published_at ${timeOp} ?)
OR (COALESCE(breaking_score, value_score, -1) = ? AND events.published_at = ? AND events.event_id ${idOp} ?)
COALESCE(breaking_calibrated_score, breaking_score, value_score, -1) ${scoreOp} ?
OR (COALESCE(breaking_calibrated_score, breaking_score, value_score, -1) = ? AND events.published_at ${timeOp} ?)
OR (COALESCE(breaking_calibrated_score, breaking_score, value_score, -1) = ? AND events.published_at = ? AND events.event_id ${idOp} ?)
)
`,
bindings: [score, score, cursor.published_at, score, cursor.published_at, cursor.event_id],
Expand Down Expand Up @@ -216,7 +218,7 @@ export async function handleNewsFeed(
items,
latestCursor,
nextCursor,
pollAfterMs: featured ? 30000 : 60000,
pollAfterMs: pollAfterMsForPublicNews(featured, items),
hasNewer: Boolean(sinceCursor && items.length > 0),
total: totalResult?.total ?? rows.length,
};
Expand Down
36 changes: 33 additions & 3 deletions frontend/cloudflare/workers/api/webhook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import type { WebhookResponse, ImportResponse, ImportEventItem } from "../lib/contracts";
import { internalError } from "../lib/errors";
import { refreshBreakingScoreStats } from "../lib/breaking-calibration";
import { refreshPublicReadSnapshots } from "../lib/public-read-snapshots";

type ImportEventWithId = ImportEventItem & {
Expand Down Expand Up @@ -66,11 +67,15 @@ function hasProjectionUpdate(item: ImportEventWithId): boolean {
jsonArrayText(item.image_urls) ||
jsonArrayText(item.entities) ||
jsonObjectText(item.classification) ||
typeof item.breaking_raw_score === "number" ||
typeof item.breaking_percentile === "number" ||
typeof item.breaking_calibrated_score === "number" ||
typeof item.breaking_score === "number" ||
nonEmptyText(item.breaking_label) ||
nonEmptyText(item.breaking_reason) ||
typeof item.breaking_confidence === "number" ||
jsonObjectText(item.breaking_dimensions) ||
jsonObjectText(item.breaking_adversarial_flags) ||
nonEmptyText(item.target_timezone) ||
nonEmptyText(item.published_at_local) ||
(Array.isArray(item.localizations) && item.localizations.length > 0),
Expand Down Expand Up @@ -204,11 +209,15 @@ export async function importEventsToD1(
value_score = COALESCE(?, value_score),
china_relevance_label = COALESCE(NULLIF(?, ''), china_relevance_label),
classification = COALESCE(?, classification),
breaking_raw_score = COALESCE(?, breaking_raw_score),
breaking_percentile = COALESCE(?, breaking_percentile),
breaking_calibrated_score = COALESCE(?, breaking_calibrated_score),
breaking_score = COALESCE(?, breaking_score),
breaking_label = COALESCE(NULLIF(?, ''), breaking_label),
breaking_reason = COALESCE(NULLIF(?, ''), breaking_reason),
breaking_confidence = COALESCE(?, breaking_confidence),
breaking_dimensions = COALESCE(?, breaking_dimensions),
breaking_adversarial_flags = COALESCE(?, breaking_adversarial_flags),
breaking_score_version = COALESCE(NULLIF(?, ''), breaking_score_version),
target_timezone = COALESCE(NULLIF(?, ''), target_timezone),
published_at_local = COALESCE(NULLIF(?, ''), published_at_local),
Expand All @@ -231,11 +240,15 @@ export async function importEventsToD1(
typeof item.value_score === "number" ? item.value_score : null,
nonEmptyText(item.china_relevance_label),
jsonObjectText(item.classification),
typeof item.breaking_raw_score === "number" ? item.breaking_raw_score : null,
typeof item.breaking_percentile === "number" ? item.breaking_percentile : null,
typeof item.breaking_calibrated_score === "number" ? item.breaking_calibrated_score : null,
typeof item.breaking_score === "number" ? item.breaking_score : null,
nonEmptyText(item.breaking_label),
nonEmptyText(item.breaking_reason),
typeof item.breaking_confidence === "number" ? item.breaking_confidence : null,
jsonObjectText(item.breaking_dimensions),
jsonObjectText(item.breaking_adversarial_flags),
nonEmptyText(item.breaking_score_version),
nonEmptyText(item.target_timezone),
nonEmptyText(item.published_at_local),
Expand All @@ -260,9 +273,11 @@ export async function importEventsToD1(
image_urls, tags, issue_tags, related_tags, region_tags, entities,
language, pipeline_stage,
value_label, value_score, china_relevance_label, classification
, breaking_score, breaking_label, breaking_reason, breaking_confidence,
breaking_dimensions, breaking_score_version, target_timezone, published_at_local
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
, breaking_raw_score, breaking_percentile, breaking_calibrated_score,
breaking_score, breaking_label, breaking_reason, breaking_confidence,
breaking_dimensions, breaking_adversarial_flags, breaking_score_version,
target_timezone, published_at_local
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(event_id) DO UPDATE SET
target_id=excluded.target_id,
target_label=excluded.target_label,
Expand Down Expand Up @@ -297,11 +312,21 @@ export async function importEventsToD1(
events.china_relevance_label
),
classification=COALESCE(NULLIF(excluded.classification, '{}'), events.classification),
breaking_raw_score=COALESCE(excluded.breaking_raw_score, events.breaking_raw_score),
breaking_percentile=COALESCE(excluded.breaking_percentile, events.breaking_percentile),
breaking_calibrated_score=COALESCE(
excluded.breaking_calibrated_score,
events.breaking_calibrated_score
),
breaking_score=COALESCE(excluded.breaking_score, events.breaking_score),
breaking_label=COALESCE(NULLIF(excluded.breaking_label, ''), events.breaking_label),
breaking_reason=COALESCE(NULLIF(excluded.breaking_reason, ''), events.breaking_reason),
breaking_confidence=COALESCE(excluded.breaking_confidence, events.breaking_confidence),
breaking_dimensions=COALESCE(NULLIF(excluded.breaking_dimensions, '{}'), events.breaking_dimensions),
breaking_adversarial_flags=COALESCE(
NULLIF(excluded.breaking_adversarial_flags, '{}'),
events.breaking_adversarial_flags
),
breaking_score_version=COALESCE(
NULLIF(excluded.breaking_score_version, ''),
events.breaking_score_version
Expand Down Expand Up @@ -342,11 +367,15 @@ export async function importEventsToD1(
item.value_score ?? null,
item.china_relevance_label || "未知",
jsonText(item.classification, {}),
item.breaking_raw_score ?? item.breaking_score ?? item.value_score ?? null,
item.breaking_percentile ?? item.breaking_score ?? item.value_score ?? null,
item.breaking_calibrated_score ?? item.breaking_score ?? item.value_score ?? null,
item.breaking_score ?? item.value_score ?? null,
item.breaking_label || null,
item.breaking_reason || null,
item.breaking_confidence ?? null,
jsonText(item.breaking_dimensions, {}),
jsonText(item.breaking_adversarial_flags, {}),
item.breaking_score_version || null,
item.target_timezone || "UTC",
item.published_at_local || null,
Expand Down Expand Up @@ -407,6 +436,7 @@ export async function handleImport(
const body = await importEventsToD1(db, events);
if (body.imported > 0 || body.updated > 0) {
try {
await refreshBreakingScoreStats(db);
await refreshPublicReadSnapshots(db);
} catch (error) {
console.warn("public snapshot refresh after import failed:", error);
Expand Down
Loading
Loading