import type { InteractionUsage } from './gemini-interactions.js'; import { type JudgeVerdict } from './match-highlights-verify.js'; import type { EventPlacement } from './match-highlights-place.js'; export declare const MATCH_HIGHLIGHTS_SCHEMA_VERSION: 1; /** Segment length bounds, in seconds. Below 60 s the per-call overhead dominates; above 1800 s the call outruns any sane wait. */ export declare const MIN_SEGMENT_SECONDS = 60; export declare const MAX_SEGMENT_SECONDS = 1800; export declare const DEFAULT_SEGMENT_SECONDS = 900; /** * Parallelism bounds. FOUR concurrent 175 MB upload buffers got the live run * killed for memory on a 16 GB machine, which is why the DEFAULT is 2; 4 stays * the accepted ceiling for a machine with the headroom, so `--parallel 4` is the * operator taking that risk knowingly rather than a value we hand out. */ export declare const MIN_PARALLEL = 1; export declare const MAX_PARALLEL = 4; export declare const DEFAULT_PARALLEL = 2; /** Output cap for each segment call. Thinking tokens count against it, per model invocation. */ export declare const MATCH_HIGHLIGHTS_MAX_OUTPUT_TOKENS = 32768; /** Low temperature: this is a timestamp-extraction task, not a creative one. */ export declare const MATCH_HIGHLIGHTS_TEMPERATURE = 0.1; /** Files API objects expire after 48 h; re-upload before a cached `uri` can go stale mid-run. */ export declare const UPLOAD_CACHE_TTL_HOURS = 47; /** Total per-call cap, in seconds. Matches the reference harness's `curl --max-time 2400`. */ export declare const DEFAULT_TIMEOUT_SECONDS: number; export declare const MIN_TIMEOUT_SECONDS = 60; export declare const MAX_TIMEOUT_SECONDS = 7200; /** * The built-in listing prompt — the EXACT wording that produced 120 usable * deliveries on the live run. It now lives in the sport table * (`SPORT_PROMPTS.cricket.listing`) so another sport is a data addition; * `--prompt-file` still overrides it wholesale. */ export declare const DEFAULT_MATCH_PROMPT: string; /** Event types the built-in prompt emits; `--types` is validated against nothing (a custom prompt may use its own vocabulary). */ export declare const DEFAULT_HIGHLIGHT_TYPES: readonly string[]; export interface MatchSegment { index: number; /** Basename of the segment file, as ffmpeg wrote it into the segment list. */ file: string; /** Absolute offset of this segment inside the source, in seconds. */ start: number; end: number; } export type MatchSegmentStatus = 'planned' | 'analyzed' | 'cached' | 'failed'; export interface MatchSegmentReport extends MatchSegment { status: MatchSegmentStatus; uploadSeconds?: number; analysisSeconds?: number; /** Events parsed out of this segment's response (0 on failure). */ events: number; usage?: InteractionUsage; error?: string; } export interface MatchEvent { /** Absolute start second inside the source. */ s: number; /** Absolute end second inside the source. */ e: number; /** Event type, e.g. `four` / `wicket` / `dot`. */ t?: string; /** Short operator note. */ n?: string; /** The segment file this event came from. */ segment: string; /** * Set only by the `--place` pass: `model` when it timed this window, `unplaced` * when it was asked and could not. Absent everywhere else. */ placement?: EventPlacement; /** * Seconds of lead the placement pass gave this event's candidate window, chosen by * its type. Recorded so a run can be reproduced and so an `unplaced` event says how * much room it was actually given. */ lead?: number; } export interface MatchReel { name: string; path: string; windows: number; seconds: number; types?: string[]; /** Set when the reel was skipped (e.g. `--types` matched nothing). */ skipped?: string; } /** One verification pass's cost, reported separately so the passes can be compared. */ export interface MatchPassReport { /** Interactions calls this pass made (a failed call still counts: it was billed). */ calls: number; /** Wall-clock seconds the pass took, uploads included. */ seconds: number; usage?: InteractionUsage; } export interface MatchPassReports { listing: MatchPassReport; gap?: MatchPassReport; judge?: MatchPassReport; judge2?: MatchPassReport; /** The `--place` pass: the only pass that spends in `--events-file` mode. */ place?: MatchPassReport; } export interface MatchHighlightsArtifact { schemaVersion: typeof MATCH_HIGHLIGHTS_SCHEMA_VERSION; source: string; generatedAt: string; model: string; sport: string; segmentSeconds: number; segments: MatchSegmentReport[]; events: MatchEvent[]; reels: MatchReel[]; /** Gap-pass stretches cut and scanned. */ gapsScanned?: number; /** Deliveries the gap pass found that the listing had missed. */ recovered?: number; /** Duplicate rows collapsed at the 4-second rule. */ deduped?: number; /** Every judge verdict, kept whether or not it survived into a reel. */ judged?: JudgeVerdict[]; /** `--place`: candidate events the model timed. */ placed?: number; /** `--place`: candidate events it was asked about and could not time; their original window stands. */ unplaced?: number; /** Per-pass cost and wall time. */ passes?: MatchPassReports; usage?: InteractionUsage; providerCalls: number; /** Non-fatal observations: dropped rows, skipped reels, failed segments. */ notes?: string[]; } /** * Nominal segment plan for `--dry-run`, where nothing has been cut yet so the * real `-segment_list` CSV does not exist. Boundaries are `i * segmentSeconds` * and are explicitly NOT measured — the real run reads the CSV because ffmpeg * lands each cut on the next keyframe. */ export declare function planSegments(input: { durationSeconds: number; segmentSeconds: number; prefix?: string; }): MatchSegment[]; /** * ffmpeg argv (everything AFTER `ffmpeg -y`, matching {@link runFfmpeg}) that * splits `source` into stream-copied segments plus a CSV segment list. * * `-c copy` keeps this near-instant and lossless; `-reset_timestamps 1` makes * every segment start at 0 so the model's timestamps are segment-relative. Audio * is mapped only when the source actually has an audio stream — `-map 0:a:0` on * a silent source fails with "matches no streams". */ export declare function buildSegmentArgs(input: { source: string; outDir: string; segmentSeconds: number; prefix?: string; hasAudio?: boolean; }): string[]; /** * Parse ffmpeg's `-segment_list_type csv` output: `,,` per * row. These are the REAL offsets (ffmpeg cuts on keyframes, so they drift from * the nominal `i * segmentSeconds` by a fraction of a second and, on a sparse * GOP, by much more). */ export declare function parseSegmentListCsv(text: string): MatchSegment[]; /** * The segment length an existing cut was made with, read back from its CSV. * * A `--out` produced before the plan sidecar existed has a `segments.csv` and no * record of the `--segment-seconds` that made it. Consecutive starts are that * length apart (give or take the keyframe drift that moved each cut), so the * first interval recovers it. Returns undefined for a single-segment cut, where * the source was shorter than one segment and nothing can be inferred. */ export declare function inferSegmentSeconds(segments: readonly MatchSegment[]): number | undefined; /** True when a flag value disagrees with an existing cut by more than keyframe drift. */ export declare function segmentSecondsConflict(requested: number, existing: number | undefined, toleranceSeconds?: number): boolean; /** Strip a ```json fence (or bare backticks) from a model answer. */ export declare function stripJsonFences(text: string): string; export interface ParsedEventRows { rows: Array<{ s: number; e: number; t?: string; n?: string; }>; /** Rows the model emitted that had a non-numeric `s`/`e` and were dropped. */ dropped: number; } export declare function parseEventRows(text: string): ParsedEventRows; /** One decimal place, matching the live run's merged artifact. */ export declare function round1(value: number): number; /** * Shift one segment's segment-relative rows onto the source timeline. * This is the step that makes per-segment analysis add up to a whole match. */ export declare function offsetEventRows(rows: ParsedEventRows['rows'], segment: Pick): MatchEvent[]; /** Merge every segment's events onto one timeline, ordered by start then end. */ export declare function mergeSegmentEvents(perSegment: readonly MatchEvent[][]): MatchEvent[]; /** Sum the numeric usage fields across segments. `byModality` is per-call detail and is not summed. */ export declare function sumInteractionUsage(usages: ReadonlyArray): InteractionUsage | undefined; /** `--types four,six,wicket` → `['four','six','wicket']`; empty/absent → the default trio. */ export declare function resolveHighlightTypes(flag: string | undefined): string[]; /** * Fingerprint of the pinned Gemini key. The raw key is NEVER written to disk — * the sidecar only has to prove that a cached `uri` belongs to a key still in * the pool, because a Files API object is visible only to its uploader. */ export declare function fingerprintKey(key: string): string; /** Identity of the bytes a cached upload was made from. */ export interface UploadedFileIdentity { sizeBytes: number; mtimeMs: number; } export interface MatchUploadCache { schemaVersion: typeof MATCH_HIGHLIGHTS_SCHEMA_VERSION; uri: string; mimeType: string; sizeBytes: number; /** * Modification time of the file that was uploaded. * * The cache is keyed by PATH, and the pass reels reuse fixed paths * (`gaps-0.mp4`, `candidates.mp4`, `disputed.mp4`). A second run whose gap * stretches or candidate set changed rebuilds those paths with DIFFERENT * content, so without this the stale `uri` would be handed to a new window * table and every recovered timestamp would be silently wrong — after paid * calls. Segment files are written once and never change, so they stay cached. */ mtimeMs?: number; /** sha256 prefix of the uploading key — resolved back against the pool on re-use. */ keyFingerprint: string; uploadedAt: string; uploadSeconds: number; } /** * True when a cached upload can still be used: the pinned key is one of the * pool's current keys AND the object has not aged past the Files API lifetime. * A stale/foreign cache must be discarded and re-uploaded — reusing it pairs the * `uri` with the wrong key, and every segment then fails with no retry. */ export declare function isUploadCacheUsable(cache: MatchUploadCache, input: { poolFingerprints: ReadonlySet; now?: Date; ttlHours?: number; /** Current bytes at the cached path. A mismatch means the file was rebuilt. */ file?: UploadedFileIdentity; }): boolean; /** * A completed segment's answer, cached beside the segment so a re-run after one * failed segment does not re-spend the calls that already succeeded. Only the * parsed result is kept: the raw payload's `signature` blobs are opaque and * must never be written into an artifact. */ export interface MatchResultCache { schemaVersion: typeof MATCH_HIGHLIGHTS_SCHEMA_VERSION; model: string; status: string; text: string; usage?: InteractionUsage; analysisSeconds: number; completedAt: string; /** * The CACHE KEY: the hash of the prompt the caller asked under. A changed * prompt invalidates the entry. * * On a tool-overflow retry this is deliberately the ORIGINAL prompt's hash * even though `text` came from the `[retry]` variant, so the next run's first * attempt finds the answer instead of paying, overflowing and only then * hitting the cache. The two prompts ask the same question of the same reel * and the same windows; the retry only adds a "inspect fewer, larger windows" * preamble, so the answer is interchangeable. */ promptHash: string; } export declare function hashPrompt(prompt: string): string; /** A cached result is reusable only when it completed AND came from the same prompt and model. */ export declare function isResultCacheUsable(cache: MatchResultCache, input: { promptHash: string; model: string; }): boolean; export declare function buildMatchHighlightsArtifact(input: { source: string; generatedAt: string; model: string; sport?: string; segmentSeconds: number; segments: MatchSegmentReport[]; events: MatchEvent[]; reels: MatchReel[]; gapsScanned?: number; recovered?: number; deduped?: number; judged?: JudgeVerdict[]; placed?: number; unplaced?: number; passes?: MatchPassReports; providerCalls: number; notes?: string[]; }): MatchHighlightsArtifact; export * from './match-highlights-cut.js'; export * from './match-highlights-verify.js'; export * from './match-highlights-place.js'; //# sourceMappingURL=match-highlights.d.ts.map