feat(identity): web metadata enrichment for titles missing on TMDB (META-1)
- metadata.enrich (propose, low, replay safe): normalized-title cache (kv_cache
region enrich, 7 days) -> daily budget -> web.search (title + 短剧 / 电视剧 平台,
second query limited to whitelist via site:) -> web.fetch of up to 3 relevant
whitelisted pages (registered site domains never read, redirects out of the
whitelist dropped) -> upstream model extraction with rule fallback -> server-side
evidence check (rule_confirmed / multi_evidence / needs_review). Suggestion only.
- POST /identity-pool/{id}/enrich (suggest) and /enrich/apply (console session):
provisional entity or same-title merge, season/episode units, field-level
metadata with source web/agent + evidence, aliases, release assignment.
Poster proxy GET /img/enrich/{id} (og:image from whitelisted pages only).
- Auto-enrich once per pool entry when subscription/rule lookups find nothing on
TMDB (identity.autoEnrich, off by default); identity.enrichDailyBudget,
identity.enrichDomains settings; web.fetch now returns og:image.
- Pool sheet "元数据补全" section; entity detail shows evidence links.
- Migration 0049_enrichment (metadata_enrichment, metadata_field.evidence, source
web). Requires image rebuild / api + worker restart to take effect.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
18a5cf1de7
commit
f4666a4671
36 files changed
+2514
-25
No files matched your search
@@ -136,7 +136,7 @@ export type ExternalIdentityVerifyStatus = "unverified" | "ok" | "not_found" | "
|
||||
export type FeedSourcePurpose = "movie" | "series" | "variety" | "promotion" | "search" | "generic";
|
||||
export type FeedSourceKind = "rss" | "atom" | "list" | "query";
|
||||
export type FeedSourceHealthStatus = "unknown" | "healthy" | "degraded" | "failing" | "circuit_open" | "disabled";
|
||||
export type FieldOverrideSource = "tmdb" | "user" | "agent" | "nfo" | "site" | "import";
|
||||
export type FieldOverrideSource = "tmdb" | "user" | "agent" | "nfo" | "site" | "import" | "web";
|
||||
export type FieldOverrideActorType = "user" | "agent" | "system" | "api_key";
|
||||
export type IdentityCandidateStatus = "pending" | "resolved" | "dismissed" | "archived";
|
||||
export type IdentityCandidateReason = "ambiguous" | "no_match" | "proposed" | "lookup_failed" | "unmatched";
|
||||
@@ -160,7 +160,9 @@ export type MediaEntityKind = "movie" | "series" | "variety" | "collection" | "o
|
||||
export type MediaEntityStatus = "provisional" | "active" | "merged" | "archived";
|
||||
export type MediaServerInstanceKind = "emby" | "jellyfin";
|
||||
export type MediaServerInstanceHealthStatus = "unknown" | "healthy" | "degraded" | "offline";
|
||||
export type MetadataFieldSource = "tmdb" | "user" | "agent" | "nfo" | "site" | "import";
|
||||
export type MetadataEnrichmentStatus = "suggested" | "applied" | "failed";
|
||||
export type MetadataEnrichmentTrigger = "manual" | "auto" | "agent";
|
||||
export type MetadataFieldSource = "tmdb" | "user" | "agent" | "nfo" | "site" | "import" | "web";
|
||||
export type ObservationStatus = "new" | "linked" | "ignored" | "error";
|
||||
export type OrganizationFileResultRole = "main" | "subtitle" | "audio" | "bluray_dir" | "nfo" | "image" | "other";
|
||||
export type OrganizationFileResultMode =
|
||||
@@ -924,6 +926,24 @@ export interface MediaServerInstanceTable {
|
||||
updated_at: GeneratedTimestamptz;
|
||||
}
|
||||
|
||||
export interface MetadataEnrichmentTable {
|
||||
id: Generated<string>;
|
||||
identity_candidate_id: string | null;
|
||||
normalized_title: string;
|
||||
query: Json;
|
||||
status: Generated<MetadataEnrichmentStatus>;
|
||||
trigger: Generated<MetadataEnrichmentTrigger>;
|
||||
suggestion: Json | null;
|
||||
error: string | null;
|
||||
usage: Generated<Json>;
|
||||
media_entity_id: string | null;
|
||||
applied_at: Timestamptz | null;
|
||||
applied_by: string | null;
|
||||
created_by: string | null;
|
||||
created_at: GeneratedTimestamptz;
|
||||
updated_at: GeneratedTimestamptz;
|
||||
}
|
||||
|
||||
export interface MetadataFieldTable {
|
||||
id: Generated<string>;
|
||||
media_entity_id: string | null;
|
||||
@@ -936,6 +956,7 @@ export interface MetadataFieldTable {
|
||||
fetched_at: GeneratedTimestamptz;
|
||||
created_at: GeneratedTimestamptz;
|
||||
updated_at: GeneratedTimestamptz;
|
||||
evidence: Json | null;
|
||||
}
|
||||
|
||||
export interface NamingTemplateVersionTable {
|
||||
@@ -1671,6 +1692,7 @@ export interface DB {
|
||||
media_asset: MediaAssetTable;
|
||||
media_entity: MediaEntityTable;
|
||||
media_server_instance: MediaServerInstanceTable;
|
||||
metadata_enrichment: MetadataEnrichmentTable;
|
||||
metadata_field: MetadataFieldTable;
|
||||
naming_template_version: NamingTemplateVersionTable;
|
||||
observation: ObservationTable;
|
||||
@@ -1832,6 +1854,9 @@ export type MediaEntityUpdate = Updateable<MediaEntityTable>;
|
||||
export type MediaServerInstanceRow = Selectable<MediaServerInstanceTable>;
|
||||
export type NewMediaServerInstance = Insertable<MediaServerInstanceTable>;
|
||||
export type MediaServerInstanceUpdate = Updateable<MediaServerInstanceTable>;
|
||||
export type MetadataEnrichmentRow = Selectable<MetadataEnrichmentTable>;
|
||||
export type NewMetadataEnrichment = Insertable<MetadataEnrichmentTable>;
|
||||
export type MetadataEnrichmentUpdate = Updateable<MetadataEnrichmentTable>;
|
||||
export type MetadataFieldRow = Selectable<MetadataFieldTable>;
|
||||
export type NewMetadataField = Insertable<MetadataFieldTable>;
|
||||
export type MetadataFieldUpdate = Updateable<MetadataFieldTable>;
|
||||
|
||||
Reference in new issue
Block a user