The code cited its plan instead of saying why: `E28 review J4`, `(R6 S3b)`, `S1 justified…`,
`deep-review round 5`. That plan lives in the private ops repo, so for every reader of this one the
citation was noise, and a sentence whose subject was the plan step ("round 2 showed…") hid its
lesson behind an id. Each one is gone and the "why" kept: a bare citation is deleted, a sentence
carrying a lesson is reworded around it ("a later attempt showed…").
Measured (`scripts/audit_metrics.py`, product code): 652 lines in 270 files carried an epic code by
the definition the next commit holds (507 / 241 by the old, two-digit-only one) -> 0. Across all
code, tests, scripts and configs: ~1100 epic-code lines plus ~320 sprint/round ids.
Behaviour unchanged, checked mechanically: every changed .py file has the same AST once docstrings
are blanked; css/sh/toml/yml/json files are identical once comments are stripped; the only
non-comment changes in TS/JS are test titles, one description string in a contract table, and five
ESLint messages, whose text is all the gates snapshot diff holds. Not touched: Markdown (docs and
the changelog keep their history), Alembic migrations, TV episode notation (`S2 · E5`), DESIGN.md
ids (`C1`, `B4`, `S3h`), tracker ids (`SEC-16`, `DX-17`).
One stale claim fixed on the way: test_hls_reaper's docstring said the player checkpoints "every
10 s whether or not it is playing", which plex/stream.py calls wrong; it now says what the code does.
109 lines
5.3 KiB
TypeScript
109 lines
5.3 KiB
TypeScript
import type { TFunction } from "i18next";
|
|
import type { JobResult } from "./api";
|
|
import { formatNumber } from "./format";
|
|
import { vocabKey } from "./vocab";
|
|
|
|
/** A notice from before the result rode along as data — and every audit row — holds only the server's
|
|
* one-liner: `_summarize`'s "new=3, failed=0" (backend/app/scheduler.py). A flat one reads back into a
|
|
* result, and `enrich`'s bare count ("50") into `{enriched: 50}` as `_result_data` names it; a nested
|
|
* phase (a Python dict repr) or anything else does not, and stays as it was. */
|
|
export function parseSummary(summary: string | null | undefined): JobResult | null {
|
|
if (!summary || /[{}[\]]/.test(summary)) return null;
|
|
if (/^\d+$/.test(summary)) return { enriched: Number(summary) };
|
|
const out: JobResult = {};
|
|
for (const part of summary.split(", ")) {
|
|
const m = /^(\w+)=(.+)$/.exec(part);
|
|
if (!m) return null;
|
|
const [, key, raw] = m;
|
|
out[key!] = /^-?\d+$/.test(raw!) ? Number(raw) : raw!;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
/** A scheduler job's result in the reader's language: "új videó: 3 · sikertelen: 0". The server
|
|
* stored "new=3, failed=0" — English keys, a Python dict repr for a nested phase. A count's key is
|
|
* named from `scheduler.result`, a `skipped` / `reason` word from `scheduler.skip`; an unnamed key or
|
|
* word shows as it is, and an `error` stays the upstream's own text (it is diagnostic). */
|
|
export function jobResultText(t: TFunction, result: JobResult, lang: string): string {
|
|
return Object.entries(result)
|
|
.map(([key, value]) => {
|
|
const label = t(`scheduler.result.${key}`, { defaultValue: key.replace(/_/g, " ") });
|
|
if (typeof value === "number") return `${label}: ${formatNumber(value, lang)}`;
|
|
if (typeof value === "string")
|
|
return key === "error"
|
|
? `${label}: ${value}`
|
|
: `${label}: ${t(`scheduler.skip.${vocabKey(value)}`, { defaultValue: value })}`;
|
|
return `${label} (${jobResultText(t, value, lang)})`;
|
|
})
|
|
.join(" · ");
|
|
}
|
|
|
|
/** A failure the job named (the server's `progress.JobFailed`: `{failure: <reason>, …params}`) in the
|
|
* reader's language; null for any other failure, which keeps the upstream's own text. */
|
|
export function jobFailureText(t: TFunction, result: unknown, lang: string): string | null {
|
|
if (!result || typeof result !== "object") return null;
|
|
const { failure, ...params } = result as Record<string, unknown>;
|
|
if (typeof failure !== "string") return null;
|
|
const values: Record<string, string> = {};
|
|
for (const [k, v] of Object.entries(params))
|
|
values[k] = typeof v === "number" ? formatNumber(v, lang) : String(v);
|
|
if (typeof params.cause === "string") values.cause = rssCause(t, params.cause);
|
|
// A failure counted over `total` reads as a plural pair: i18next picks the form from a NUMERIC
|
|
// count, and the formatted values above are strings ("1 399" would pick none).
|
|
const count = typeof params.total === "number" ? params.total : undefined;
|
|
return t(`scheduler.failure.${failure}`, { ...values, count, defaultValue: "" }) || null;
|
|
}
|
|
|
|
/** Why a feed did not answer, from the poll's reason code (`http_404`, `transport`, …). */
|
|
function rssCause(t: TFunction, cause: string): string {
|
|
const http = /^http_(\d+)$/.exec(cause);
|
|
if (http) return t("scheduler.rssCause.http", { status: http[1] });
|
|
return t(`scheduler.rssCause.${cause}`, { defaultValue: cause });
|
|
}
|
|
|
|
/** What a job run said, as the inbox notice and the audit row both show it: a title naming the job
|
|
* and how the run ended, and its result in the reader's language — from the data when it rode along,
|
|
* else read back from the English one-liner. A skipped run names only why; a failed run says the
|
|
* failure the job named (`jobFailureText`), else keeps the upstream's own text (diagnostic). */
|
|
export function jobRunText(
|
|
t: TFunction,
|
|
run: { job_id: string; status?: unknown; summary?: unknown; result?: unknown },
|
|
lang: string,
|
|
): { title: string; body: string | null } {
|
|
const job = t(`scheduler.jobs.${run.job_id}`, run.job_id);
|
|
const summary = typeof run.summary === "string" && run.summary ? run.summary : null;
|
|
const titleKey =
|
|
run.status === "error"
|
|
? "inbox.jobDone.titleError"
|
|
: run.status === "skipped"
|
|
? "inbox.jobDone.titleSkipped"
|
|
: "inbox.jobDone.titleOk";
|
|
let result: JobResult | null = null;
|
|
if (run.status !== "error") {
|
|
result =
|
|
run.result && typeof run.result === "object"
|
|
? (run.result as JobResult)
|
|
: (parseSummary(summary) ??
|
|
(run.status === "skipped" && summary ? { skipped: summary } : null));
|
|
}
|
|
// A skipped run's title already says "skipped": its body is only the reason, not "skipped: <reason>".
|
|
const reason = run.status === "skipped" ? onlySkipReason(result) : null;
|
|
const failure = run.status === "error" ? jobFailureText(t, run.result, lang) : null;
|
|
return {
|
|
title: t(titleKey, { job }),
|
|
body: failure
|
|
? failure
|
|
: reason !== null
|
|
? t(`scheduler.skip.${vocabKey(reason)}`, { defaultValue: reason })
|
|
: result
|
|
? jobResultText(t, result, lang)
|
|
: summary,
|
|
};
|
|
}
|
|
|
|
function onlySkipReason(result: JobResult | null): string | null {
|
|
const keys = result ? Object.keys(result) : [];
|
|
const word = result?.skipped;
|
|
return keys.length === 1 && typeof word === "string" ? word : null;
|
|
}
|