pediatric-ai-scribe-v3/migrations/1781500000000_resource-jobs.js
Daniel ef574eddcb
Some checks failed
Forgejo Docker Build / Build Docker image (push) Blocked by required conditions
Forgejo Docker Build / End-to-end (browser) (push) Blocked by required conditions
Forgejo Docker Build / Root app tests (push) Has been cancelled
feat: a resource is generated as a job, and DeepSeek writes it without thinking
Writing a resource held the request open for as long as it took: a library
search, one or two long model calls, a review, then figures. Measured end to
end that was six minutes on 2026-09-16 (00:40 to 00:46:16, resource 53), and
Firefox abandons a request that has said nothing for five — the browser
reported "NetworkError when attempting to fetch resource" while the server
carried on and saved the deck anyway, so a generation that worked looked
like a failure and left no status line.

The request now records what was asked and answers 202; the work runs on the
server as a job; the page lists what is being written, what landed and what
failed, polls while anything is in flight, and reloads the library when one
lands. Several can run at once, a reload loses nothing, and a boot pass marks
jobs stranded by a restart as failed rather than spinning for ever.

The same generation also ran with DeepSeek's thinking on, which is what made
it take minutes rather than seconds: the 16,000-token write spent the whole
budget reasoning and returned an empty reply (completion_tokens=16000,
reasoning_chars=51573), which fired the automatic retry at four times the
budget, and the 2,000-token reviews of that deck starved the same way four
times over. Thinking is now off for the writing, the review of it and a
revision — DeepSeek's own field, sent by the model wrapper. Other clinical
routes are deliberately untouched and keep the provider default.

The review inherits the writer's rule rather than hard-coding it, so a task
that wants reasoning can still ask. Migration 1781500000000_resource-jobs.js
adds user_resource_jobs; the container entrypoint applies it before the app
starts.
2026-09-16 03:53:57 +02:00

30 lines
1.3 KiB
JavaScript

// Generating a resource is a job, not a request. Writing a deck takes minutes
// — a library search, one or two long model calls, figures — and a browser
// holding a request open that long gives up on its own (Firefox at five
// minutes, exactly the "NetworkError when attempting to fetch resource" the
// user saw while the server carried on and saved the deck anyway). The request
// now records what was asked and returns at once; the work runs on the server,
// the page polls, and the library updates when a job lands. Several can run at
// once, and a reload loses nothing.
exports.up = pgm => pgm.sql(`
CREATE TABLE IF NOT EXISTS user_resource_jobs (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
topic TEXT NOT NULL,
kind TEXT NOT NULL,
request JSONB NOT NULL DEFAULT '{}'::jsonb,
status TEXT NOT NULL DEFAULT 'queued',
result JSONB,
error TEXT,
resource_id INTEGER REFERENCES user_resources(id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
started_at TIMESTAMPTZ,
finished_at TIMESTAMPTZ
);
CREATE INDEX IF NOT EXISTS idx_resource_jobs_user_created ON user_resource_jobs (user_id, created_at DESC);
`);
exports.down = pgm => pgm.sql(`
DROP TABLE IF EXISTS user_resource_jobs;
`);