mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-08-30 00:15:27 -07:00
fix(stats): stop counting duplicate typeset subtitle lines (#191)
This commit is contained in:
@@ -5,6 +5,7 @@ import {
|
||||
buildSentenceSearchOptions,
|
||||
enrichSessionsWithKnownWordMetrics,
|
||||
parseBooleanQuery,
|
||||
parseDuplicateLineCleanupBody,
|
||||
parseExcludedWordsBody,
|
||||
parseIntQuery,
|
||||
} from './route-support.js';
|
||||
@@ -40,6 +41,19 @@ export function registerStatsLibraryRoutes(
|
||||
return c.json(statsJson('setExcludedWords', { ok: true }));
|
||||
});
|
||||
|
||||
// Collapse animation bursts older versions recorded frame by frame. `dryRun` measures
|
||||
// the same scan without writing, so the confirmation the user sees is the real cost.
|
||||
app.post('/api/stats/maintenance/duplicate-lines', async (c) => {
|
||||
const contentType = c.req.header('content-type')?.split(';', 1)[0]?.trim().toLowerCase();
|
||||
if (contentType !== 'application/json') return c.body(null, 415);
|
||||
const body = await c.req.json().catch(() => null);
|
||||
const options = parseDuplicateLineCleanupBody(body);
|
||||
if (!options) return c.body(null, 400);
|
||||
const { dryRun, lookbackDays } = options;
|
||||
const result = await tracker.cleanupDuplicateSubtitleLines({ dryRun, lookbackDays });
|
||||
return c.json(statsJson('duplicateLineCleanup', result));
|
||||
});
|
||||
|
||||
app.get('/api/stats/vocabulary/occurrences', async (c) => {
|
||||
const headword = (c.req.query('headword') ?? '').trim();
|
||||
const word = (c.req.query('word') ?? '').trim();
|
||||
|
||||
@@ -88,6 +88,35 @@ export function parseExcludedWordsBody(body: unknown): StatsExcludedWord[] | nul
|
||||
return words;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a duplicate-line cleanup request. An explicit object with no lookback scans all
|
||||
* history. Invalid bodies and invalid windows are rejected instead of broadening scope.
|
||||
*/
|
||||
export function parseDuplicateLineCleanupBody(body: unknown): {
|
||||
dryRun: boolean;
|
||||
lookbackDays: number | null;
|
||||
} | null {
|
||||
if (!body || typeof body !== 'object' || Array.isArray(body)) {
|
||||
return null;
|
||||
}
|
||||
const source = body as Record<string, unknown>;
|
||||
if (source.dryRun !== undefined && typeof source.dryRun !== 'boolean') {
|
||||
return null;
|
||||
}
|
||||
const rawLookback = source.lookbackDays;
|
||||
if (
|
||||
rawLookback !== undefined &&
|
||||
rawLookback !== null &&
|
||||
(typeof rawLookback !== 'number' || !Number.isFinite(rawLookback) || rawLookback < 1)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
dryRun: source.dryRun === true,
|
||||
lookbackDays: typeof rawLookback === 'number' ? Math.floor(rawLookback) : null,
|
||||
};
|
||||
}
|
||||
|
||||
export function loadKnownWordsSet(cachePath: string | undefined): Set<string> | null {
|
||||
if (!cachePath || !existsSync(cachePath)) return null;
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user