Skip to content

Commit 4066caf

Browse files
Miriadresearch
andcommitted
feat: migrate cron routes to Sanity config singletons
Ingest route: ENABLE_NOTEBOOKLM_RESEARCH, quality threshold, system instruction now read from pipeline_config and content_config singletons. Check-research route: stuck thresholds, quality threshold, system instruction now configurable via Sanity. Check-renders route: audited, no tweakable config (Remotion/ElevenLabs config is in service layer). All values use getConfigValue() with existing hardcoded values as fallbacks for graceful degradation. Co-authored-by: research <research@miriad.systems>
1 parent 4df823b commit 4066caf

File tree

3 files changed

+57
-19
lines changed

3 files changed

+57
-19
lines changed

app/api/cron/check-renders/route.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// Config migration: audited — no tweakable config in this route.
2+
// Remotion/ElevenLabs config is in the service layer (owned by @videopipe).
3+
// YouTube SEO prompt is specific to this route, not the shared system instruction.
14
export const fetchCache = 'force-no-store';
25
export const maxDuration = 60;
36

@@ -253,21 +256,21 @@ async function handleScriptReady(client: SanityClient): Promise<{ claimed: numbe
253256
await client.patch(doc._id).set({ status: 'audio_gen' }).commit();
254257
claimedIds.push(doc._id);
255258

256-
// WORK: run video production in background via after()
259+
// WORK: run video [REDACTED SECRET: NEXT_PUBLIC_SANITY_DATASET] in background via after()
257260
after(async () => {
258261
try {
259-
console.log(`[PIPELINE] Starting video production for ${doc._id}`);
262+
console.log(`[PIPELINE] Starting video [REDACTED SECRET: NEXT_PUBLIC_SANITY_DATASET] for ${doc._id}`);
260263
await processVideoProduction(doc._id);
261-
console.log(`[PIPELINE] ✅ Video production complete for ${doc._id}`);
264+
console.log(`[PIPELINE] ✅ Video [REDACTED SECRET: NEXT_PUBLIC_SANITY_DATASET] complete for ${doc._id}`);
262265
} catch (error) {
263266
const msg = error instanceof Error ? error.message : String(error);
264-
console.error(`[PIPELINE] ❌ Video production failed for ${doc._id}: ${msg}`);
267+
console.error(`[PIPELINE] ❌ Video [REDACTED SECRET: NEXT_PUBLIC_SANITY_DATASET] failed for ${doc._id}: ${msg}`);
265268
// processVideoProduction already sets flagged on error, but just in case:
266269
try {
267270
const c = getSanityWriteClient();
268271
await c.patch(doc._id).set({
269272
status: 'flagged',
270-
flaggedReason: `Video production error: ${msg}`,
273+
flaggedReason: `Video [REDACTED SECRET: NEXT_PUBLIC_SANITY_DATASET] error: ${msg}`,
271274
}).commit();
272275
} catch { /* best-effort */ }
273276
}

app/api/cron/check-research/route.ts

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { NotebookLMClient } from '@/lib/services/notebooklm/client';
88
import { initAuth } from '@/lib/services/notebooklm/auth';
99
import { ArtifactTypeCode, ArtifactStatus } from '@/lib/services/notebooklm/types';
1010
import { generateWithGemini, stripCodeFences } from '@/lib/gemini';
11+
import { getConfigValue } from '@/lib/config';
1112
import type { ResearchPayload } from '@/lib/services/research';
1213

1314
// ---------------------------------------------------------------------------
@@ -94,12 +95,15 @@ interface StepResult {
9495
// Constants
9596
// ---------------------------------------------------------------------------
9697

97-
/** Stuck thresholds per status (ms) */
98-
const STUCK_THRESHOLDS: Record<string, number> = {
99-
researching: 30 * 60 * 1000, // 30 minutes
100-
infographics_generating: 15 * 60 * 1000, // 15 minutes
101-
enriching: 10 * 60 * 1000, // 10 minutes
102-
};
98+
/** Build stuck thresholds from config (with fallbacks) */
99+
async function buildStuckThresholds(): Promise<Record<string, number>> {
100+
const stuckMinutes = await getConfigValue('pipeline_config', 'stuckTimeoutMinutes', 30);
101+
return {
102+
researching: stuckMinutes * 60 * 1000,
103+
infographics_generating: Math.round(stuckMinutes * 0.5) * 60 * 1000, // half the main timeout
104+
enriching: Math.round(stuckMinutes * 0.33) * 60 * 1000, // third of main timeout
105+
};
106+
}
103107

104108
/** Max docs to process per status per run — keeps total time well under 60s */
105109
const MAX_DOCS_PER_STATUS = 2;
@@ -132,12 +136,13 @@ function getSanityWriteClient(): SanityClient {
132136
async function flagStuckDocs(
133137
docs: PipelineDoc[],
134138
sanity: SanityClient,
139+
stuckThresholds: Record<string, number>,
135140
): Promise<StepResult[]> {
136141
const results: StepResult[] = [];
137142
const now = Date.now();
138143

139144
for (const doc of docs) {
140-
const threshold = STUCK_THRESHOLDS[doc.status];
145+
const threshold = stuckThresholds[doc.status];
141146
if (!threshold) continue;
142147

143148
const docAge = now - new Date(doc._updatedAt).getTime();
@@ -419,6 +424,11 @@ async function stepEnriching(
419424
// Generate enriched script with Gemini
420425
let enrichedScript: EnrichedScript | null = null;
421426
try {
427+
const SYSTEM_INSTRUCTION = await getConfigValue(
428+
'content_config',
429+
'systemInstruction',
430+
SYSTEM_INSTRUCTION_FALLBACK,
431+
);
422432
const prompt = buildEnrichmentPrompt(doc, researchPayload);
423433
const rawResponse = await generateWithGemini(prompt, SYSTEM_INSTRUCTION);
424434
const cleaned = stripCodeFences(rawResponse);
@@ -434,7 +444,8 @@ async function stepEnriching(
434444
const criticScore = criticResult.score;
435445
console.log(`[check-research] Critic score: ${criticScore}/100 — ${criticResult.summary}`);
436446

437-
const isFlagged = criticScore < 50;
447+
const qualityThreshold = await getConfigValue('pipeline_config', 'qualityThreshold', 50);
448+
const isFlagged = criticScore < qualityThreshold;
438449

439450
await sanity
440451
.patch(doc._id)
@@ -481,7 +492,9 @@ async function stepEnriching(
481492
// Gemini Script Enrichment
482493
// ---------------------------------------------------------------------------
483494

484-
const SYSTEM_INSTRUCTION = `You are a content strategist and scriptwriter for CodingCat.dev, a web development education channel run by Alex Patterson.
495+
// SYSTEM_INSTRUCTION fallback — used when content_config singleton doesn't exist yet in Sanity.
496+
// The live value is fetched from getConfigValue() inside stepEnriching().
497+
const SYSTEM_INSTRUCTION_FALLBACK = `You are a content strategist and scriptwriter for CodingCat.dev, a web development education channel run by Alex Patterson.
485498
486499
Your style is inspired by Cleo Abram's "Huge If True" — you make complex technical topics feel exciting, accessible, and important. Key principles:
487500
- Start with a BOLD claim or surprising fact that makes people stop scrolling
@@ -813,7 +826,8 @@ export async function GET(request: NextRequest) {
813826
const results: StepResult[] = [];
814827

815828
// Phase 1: Stuck detection — runs FIRST, no external API calls
816-
const stuckResults = await flagStuckDocs(docs, sanity);
829+
const stuckThresholds = await buildStuckThresholds();
830+
const stuckResults = await flagStuckDocs(docs, sanity, stuckThresholds);
817831
results.push(...stuckResults);
818832

819833
// Remove flagged docs from further processing

app/api/cron/ingest/route.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { NextRequest } from "next/server";
44

55
import { generateWithGemini, stripCodeFences } from "@/lib/gemini";
66
import { writeClient } from "@/lib/sanity-write-client";
7+
import { getConfigValue } from "@/lib/config";
78
import { discoverTrends, type TrendResult } from "@/lib/services/trend-discovery";
89
import type { ResearchPayload } from "@/lib/services/research";
910
import { NotebookLMClient } from "@/lib/services/notebooklm/client";
@@ -111,7 +112,9 @@ const FALLBACK_TRENDS: TrendResult[] = [
111112
// Gemini Script Generation
112113
// ---------------------------------------------------------------------------
113114

114-
const SYSTEM_INSTRUCTION = `You are a content strategist and scriptwriter for CodingCat.dev, a web development education channel run by Alex Patterson.
115+
// SYSTEM_INSTRUCTION fallback — used when content_config singleton doesn't exist yet in Sanity.
116+
// The live value is fetched from getConfigValue() inside the GET handler.
117+
const SYSTEM_INSTRUCTION_FALLBACK = `You are a content strategist and scriptwriter for CodingCat.dev, a web development education channel run by Alex Patterson.
115118
116119
Your style is inspired by Cleo Abram's "Huge If True" — you make complex technical topics feel exciting, accessible, and important. Key principles:
117120
- Start with a BOLD claim or surprising fact that makes people stop scrolling
@@ -330,10 +333,11 @@ async function createSanityDocuments(
330333
script: GeneratedScript,
331334
criticResult: CriticResult,
332335
trends: TrendResult[],
336+
qualityThreshold: number,
333337
research?: ResearchPayload,
334338
researchMeta?: { notebookId: string; taskId: string },
335339
) {
336-
const isFlagged = criticResult.score < 50;
340+
const isFlagged = criticResult.score < qualityThreshold;
337341
// When research is in-flight, status is "researching" (check-research cron will transition to script_ready)
338342
const isResearching = !!researchMeta?.notebookId;
339343
const status = isFlagged ? "flagged" : isResearching ? "researching" : "script_ready";
@@ -404,6 +408,23 @@ export async function GET(request: NextRequest) {
404408
}
405409

406410
try {
411+
// Fetch config values once per invocation (5-min in-memory cache)
412+
const SYSTEM_INSTRUCTION = await getConfigValue(
413+
"content_config",
414+
"systemInstruction",
415+
SYSTEM_INSTRUCTION_FALLBACK,
416+
);
417+
const enableNotebookLmResearch = await getConfigValue(
418+
"pipeline_config",
419+
"enableNotebookLmResearch",
420+
false,
421+
);
422+
const qualityThreshold = await getConfigValue(
423+
"pipeline_config",
424+
"qualityThreshold",
425+
50,
426+
);
427+
407428
// Step 1: Discover trending topics (replaces fetchTrendingTopics)
408429
console.log("[CRON/ingest] Discovering trending topics...");
409430
let trends: TrendResult[];
@@ -425,7 +446,7 @@ export async function GET(request: NextRequest) {
425446
// When research is enabled, we create a notebook and start research
426447
// but DON'T wait for it — the check-research cron will poll and enrich later
427448
let researchMeta: { notebookId: string; taskId: string } | undefined;
428-
if (process.env.ENABLE_NOTEBOOKLM_RESEARCH === "true") {
449+
if (enableNotebookLmResearch) {
429450
console.log(`[CRON/ingest] Starting fire-and-forget research on: "${trends[0].topic}"...`);
430451
try {
431452
const auth = await initAuth();
@@ -494,7 +515,7 @@ export async function GET(request: NextRequest) {
494515
);
495516

496517
console.log("[CRON/ingest] Creating Sanity documents...");
497-
const result = await createSanityDocuments(script, criticResult, trends, undefined, researchMeta);
518+
const result = await createSanityDocuments(script, criticResult, trends, qualityThreshold, undefined, researchMeta);
498519

499520
console.log("[CRON/ingest] Done!", result);
500521

0 commit comments

Comments
 (0)