@media-engine/core 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -268
- package/README.ru.md +54 -0
- package/dist/cache/memory.d.ts +5 -0
- package/dist/cache/memory.js +40 -2
- package/dist/cache/memory.js.map +1 -1
- package/dist/cache/types.d.ts +2 -0
- package/dist/engine/availability.d.ts +5 -0
- package/dist/engine/availability.js +129 -0
- package/dist/engine/availability.js.map +1 -0
- package/dist/engine/circuit-breaker.d.ts +36 -0
- package/dist/engine/circuit-breaker.js +163 -0
- package/dist/engine/circuit-breaker.js.map +1 -0
- package/dist/engine/concurrency-limiter.d.ts +13 -0
- package/dist/engine/concurrency-limiter.js +109 -0
- package/dist/engine/concurrency-limiter.js.map +1 -0
- package/dist/engine/engine.d.ts +7 -2
- package/dist/engine/engine.js +335 -848
- package/dist/engine/engine.js.map +1 -1
- package/dist/engine/in-flight.d.ts +4 -0
- package/dist/engine/in-flight.js +25 -0
- package/dist/engine/in-flight.js.map +1 -0
- package/dist/engine/provider-calls.d.ts +42 -0
- package/dist/engine/provider-calls.js +212 -0
- package/dist/engine/provider-calls.js.map +1 -0
- package/dist/engine/query.d.ts +21 -0
- package/dist/engine/query.js +241 -0
- package/dist/engine/query.js.map +1 -0
- package/dist/engine/response-meta.d.ts +13 -0
- package/dist/engine/response-meta.js +26 -0
- package/dist/engine/response-meta.js.map +1 -0
- package/dist/engine/runtime.d.ts +3 -0
- package/dist/engine/runtime.js +29 -0
- package/dist/engine/runtime.js.map +1 -0
- package/dist/engine/search-enrichment.d.ts +28 -0
- package/dist/engine/search-enrichment.js +90 -0
- package/dist/engine/search-enrichment.js.map +1 -0
- package/dist/engine/stale-fallback.d.ts +11 -0
- package/dist/engine/stale-fallback.js +60 -0
- package/dist/engine/stale-fallback.js.map +1 -0
- package/dist/engine/timeout-budget.d.ts +7 -0
- package/dist/engine/timeout-budget.js +31 -0
- package/dist/engine/timeout-budget.js.map +1 -0
- package/dist/engine/types.d.ts +30 -0
- package/dist/merge/details-identity.d.ts +5 -0
- package/dist/merge/details-identity.js +63 -0
- package/dist/merge/details-identity.js.map +1 -0
- package/dist/merge/fields.d.ts +25 -0
- package/dist/merge/fields.js +364 -0
- package/dist/merge/fields.js.map +1 -0
- package/dist/merge/grouping.d.ts +3 -0
- package/dist/merge/grouping.js +143 -0
- package/dist/merge/grouping.js.map +1 -0
- package/dist/merge/identity.d.ts +5 -0
- package/dist/merge/identity.js +21 -0
- package/dist/merge/identity.js.map +1 -0
- package/dist/merge/internal.d.ts +22 -0
- package/dist/merge/internal.js +15 -0
- package/dist/merge/internal.js.map +1 -0
- package/dist/merge/media-type.d.ts +5 -0
- package/dist/merge/media-type.js +15 -0
- package/dist/merge/media-type.js.map +1 -0
- package/dist/merge/priority.d.ts +9 -0
- package/dist/merge/priority.js +61 -0
- package/dist/merge/priority.js.map +1 -0
- package/dist/merge/scoring.d.ts +5 -0
- package/dist/merge/scoring.js +260 -0
- package/dist/merge/scoring.js.map +1 -0
- package/dist/merge/strategy.js +14 -943
- package/dist/merge/strategy.js.map +1 -1
- package/dist/merge/title.d.ts +4 -0
- package/dist/merge/title.js +22 -0
- package/dist/merge/title.js.map +1 -0
- package/dist/providers/types.d.ts +1 -0
- package/dist/response/types.d.ts +1 -0
- package/package.json +7 -3
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ExternalIds } from "../media/index.js";
|
|
2
|
+
import { STRONG_ID_KEYS } from "./internal.js";
|
|
3
|
+
export declare function hasSharedStrongId(left: ExternalIds | undefined, right: ExternalIds | undefined): boolean;
|
|
4
|
+
export declare function hasStrongIdConflict(left: ExternalIds | undefined, right: ExternalIds | undefined): boolean;
|
|
5
|
+
export declare function strongIdConflicts(left: ExternalIds | undefined, right: ExternalIds | undefined): Array<(typeof STRONG_ID_KEYS)[number]>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { STRONG_ID_KEYS } from "./internal.js";
|
|
2
|
+
// Checks whether two ID maps share at least one strong external ID.
|
|
3
|
+
// Проверяет, есть ли у двух карт ID хотя бы один общий сильный внешний ID.
|
|
4
|
+
export function hasSharedStrongId(left, right) {
|
|
5
|
+
if (!left || !right) {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
return STRONG_ID_KEYS.some((key) => Boolean(left[key] && right[key] && left[key] === right[key]));
|
|
9
|
+
}
|
|
10
|
+
// Checks whether two ID maps contain conflicting strong external IDs.
|
|
11
|
+
// Проверяет, содержат ли две карты ID конфликтующие сильные внешние ID.
|
|
12
|
+
export function hasStrongIdConflict(left, right) {
|
|
13
|
+
return strongIdConflicts(left, right).length > 0;
|
|
14
|
+
}
|
|
15
|
+
export function strongIdConflicts(left, right) {
|
|
16
|
+
if (!left || !right) {
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
return STRONG_ID_KEYS.filter((key) => Boolean(left[key] && right[key] && left[key] !== right[key]));
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=identity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity.js","sourceRoot":"","sources":["../../src/merge/identity.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C,oEAAoE;AACpE,2EAA2E;AAC3E,MAAM,UAAU,iBAAiB,CAC/B,IAA6B,EAC7B,KAA8B;IAE9B,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QACpB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,cAAc,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AACpG,CAAC;AAED,sEAAsE;AACtE,wEAAwE;AACxE,MAAM,UAAU,mBAAmB,CACjC,IAA6B,EAC7B,KAA8B;IAE9B,OAAO,iBAAiB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;AACnD,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,IAA6B,EAC7B,KAA8B;IAE9B,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QACpB,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,OAAO,cAAc,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CACnC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,GAAG,CAAC,CAAC,CAC7D,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ExternalIds } from "../media/index.js";
|
|
2
|
+
import type { ProviderDetailsResult, ProviderSearchResult } from "../providers/index.js";
|
|
3
|
+
import type { MatchStrength } from "./types.js";
|
|
4
|
+
export type ExternalIdKey = keyof ExternalIds;
|
|
5
|
+
export interface SearchEntry {
|
|
6
|
+
result: ProviderSearchResult;
|
|
7
|
+
index: number;
|
|
8
|
+
}
|
|
9
|
+
export interface DetailsEntry {
|
|
10
|
+
result: ProviderDetailsResult;
|
|
11
|
+
index: number;
|
|
12
|
+
}
|
|
13
|
+
export interface SearchGroup {
|
|
14
|
+
entries: SearchEntry[];
|
|
15
|
+
matchStrength: MatchStrength;
|
|
16
|
+
}
|
|
17
|
+
export interface SelectedExternalId {
|
|
18
|
+
value: string;
|
|
19
|
+
provider: string;
|
|
20
|
+
}
|
|
21
|
+
export declare const STRONG_ID_KEYS: readonly ["imdb", "tmdb", "kinopoisk", "shikimori", "myAnimeList"];
|
|
22
|
+
export declare const EXTERNAL_ID_KEYS: readonly ["imdb", "tmdb", "kinopoisk", "shikimori", "myAnimeList", "aniList", "worldArt"];
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Strong IDs that can safely prove two results describe the same media.
|
|
2
|
+
// Сильные ID, которые безопасно доказывают, что два результата описывают одно медиа.
|
|
3
|
+
export const STRONG_ID_KEYS = ["imdb", "tmdb", "kinopoisk", "shikimori", "myAnimeList"];
|
|
4
|
+
// All external ID fields copied into a merged media item.
|
|
5
|
+
// Все поля внешних ID, которые копируются в объединенный media item.
|
|
6
|
+
export const EXTERNAL_ID_KEYS = [
|
|
7
|
+
"imdb",
|
|
8
|
+
"tmdb",
|
|
9
|
+
"kinopoisk",
|
|
10
|
+
"shikimori",
|
|
11
|
+
"myAnimeList",
|
|
12
|
+
"aniList",
|
|
13
|
+
"worldArt",
|
|
14
|
+
];
|
|
15
|
+
//# sourceMappingURL=internal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"internal.js","sourceRoot":"","sources":["../../src/merge/internal.ts"],"names":[],"mappings":"AAoCA,wEAAwE;AACxE,qFAAqF;AACrF,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,WAAW,EAAE,aAAa,CAAU,CAAC;AAEjG,0DAA0D;AAC1D,qEAAqE;AACrE,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,MAAM;IACN,MAAM;IACN,WAAW;IACX,WAAW;IACX,aAAa;IACb,SAAS;IACT,UAAU;CACF,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { MediaItem, MediaType } from "../media/index.js";
|
|
2
|
+
import type { SearchEntry } from "./internal.js";
|
|
3
|
+
export declare function selectMergedSearchType(entries: SearchEntry[]): MediaType | undefined;
|
|
4
|
+
export declare function selectMetadataPriorityType(items: MediaItem[]): MediaType | undefined;
|
|
5
|
+
export declare function selectMergedMediaType(items: MediaItem[]): MediaType | undefined;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Preserves anime semantics when a generic series catalog describes the same animated title.
|
|
2
|
+
// Сохраняет anime-семантику, когда generic series-каталог описывает тот же анимационный тайтл.
|
|
3
|
+
export function selectMergedSearchType(entries) {
|
|
4
|
+
return selectMergedMediaType(entries.map((entry) => entry.result.item));
|
|
5
|
+
}
|
|
6
|
+
// Uses general-series metadata priority for mixed anime/catalog groups without losing anime type.
|
|
7
|
+
// Использует приоритет metadata сериалов для смешанных anime/catalog групп без потери anime-типа.
|
|
8
|
+
export function selectMetadataPriorityType(items) {
|
|
9
|
+
const types = new Set(items.map((item) => item.type));
|
|
10
|
+
return types.has("anime") && types.has("series") ? "series" : items[0]?.type;
|
|
11
|
+
}
|
|
12
|
+
export function selectMergedMediaType(items) {
|
|
13
|
+
return items.some((item) => item.type === "anime") ? "anime" : items[0]?.type;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=media-type.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-type.js","sourceRoot":"","sources":["../../src/merge/media-type.ts"],"names":[],"mappings":"AAGA,6FAA6F;AAC7F,+FAA+F;AAC/F,MAAM,UAAU,sBAAsB,CAAC,OAAsB;IAC3D,OAAO,qBAAqB,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;AAC1E,CAAC;AAED,kGAAkG;AAClG,kGAAkG;AAClG,MAAM,UAAU,0BAA0B,CAAC,KAAkB;IAC3D,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IACtD,OAAO,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC;AAC/E,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,KAAkB;IACtD,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC;AAChF,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { MediaType } from "../media/index.js";
|
|
2
|
+
import type { MergeContext } from "./types.js";
|
|
3
|
+
export declare const SEARCH_RESULT_PRIORITY: string[];
|
|
4
|
+
export declare function sortEntriesByPriority<T extends {
|
|
5
|
+
result: {
|
|
6
|
+
provider: string;
|
|
7
|
+
};
|
|
8
|
+
}>(entries: T[], context: MergeContext, mediaType?: MediaType): T[];
|
|
9
|
+
export declare function providerRank(provider: string, priority: string[]): number;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Default provider priority for movies and series.
|
|
2
|
+
// Приоритет провайдеров по умолчанию для фильмов и сериалов.
|
|
3
|
+
const DEFAULT_PRIORITY = ["tmdb", "kinobd", "cinemeta", "imdb", "wikidata", "kinopoisk"];
|
|
4
|
+
// Default provider priority for anime results.
|
|
5
|
+
// Приоритет провайдеров по умолчанию для аниме.
|
|
6
|
+
const ANIME_PRIORITY = [
|
|
7
|
+
"shikimori",
|
|
8
|
+
"anilist",
|
|
9
|
+
"tmdb",
|
|
10
|
+
"kinobd",
|
|
11
|
+
"cinemeta",
|
|
12
|
+
"imdb",
|
|
13
|
+
"wikidata",
|
|
14
|
+
"kinopoisk",
|
|
15
|
+
];
|
|
16
|
+
// Search-level tie-break priority used when results from different media types have equal scores.
|
|
17
|
+
// Search-level priority для tie-break, когда результаты разных media types имеют одинаковый score.
|
|
18
|
+
export const SEARCH_RESULT_PRIORITY = [
|
|
19
|
+
"tmdb",
|
|
20
|
+
"kinobd",
|
|
21
|
+
"cinemeta",
|
|
22
|
+
"imdb",
|
|
23
|
+
"wikidata",
|
|
24
|
+
"kinopoisk",
|
|
25
|
+
"shikimori",
|
|
26
|
+
"anilist",
|
|
27
|
+
];
|
|
28
|
+
// Sorts entries by explicit provider priority and then original input order.
|
|
29
|
+
// Сортирует entries по заданному приоритету провайдеров и исходному порядку.
|
|
30
|
+
export function sortEntriesByPriority(entries, context, mediaType) {
|
|
31
|
+
const priority = context.providerPriority ?? defaultPriority(mediaType);
|
|
32
|
+
return [...entries].sort((left, right) => {
|
|
33
|
+
const priorityDiff = providerRank(left.result.provider, priority) - providerRank(right.result.provider, priority);
|
|
34
|
+
if (priorityDiff !== 0) {
|
|
35
|
+
return priorityDiff;
|
|
36
|
+
}
|
|
37
|
+
return getEntryIndex(left) - getEntryIndex(right);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
// Calculates provider priority rank for deterministic sorting.
|
|
41
|
+
// Вычисляет ранг провайдера для детерминированной сортировки.
|
|
42
|
+
export function providerRank(provider, priority) {
|
|
43
|
+
const index = priority.indexOf(provider);
|
|
44
|
+
return index === -1 ? priority.length : index;
|
|
45
|
+
}
|
|
46
|
+
// Returns the default provider priority for a media type.
|
|
47
|
+
// Возвращает приоритет провайдеров по умолчанию для типа медиа.
|
|
48
|
+
function defaultPriority(mediaType) {
|
|
49
|
+
return mediaType === "anime" ? ANIME_PRIORITY : DEFAULT_PRIORITY;
|
|
50
|
+
}
|
|
51
|
+
// Reads an entry index from generic sorted objects.
|
|
52
|
+
// Читает index из обобщенных объектов для сортировки.
|
|
53
|
+
function getEntryIndex(entry) {
|
|
54
|
+
return typeof entry === "object" &&
|
|
55
|
+
entry !== null &&
|
|
56
|
+
"index" in entry &&
|
|
57
|
+
typeof entry.index === "number"
|
|
58
|
+
? entry.index
|
|
59
|
+
: 0;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=priority.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"priority.js","sourceRoot":"","sources":["../../src/merge/priority.ts"],"names":[],"mappings":"AAGA,mDAAmD;AACnD,6DAA6D;AAC7D,MAAM,gBAAgB,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,WAAW,CAAC,CAAC;AAEzF,+CAA+C;AAC/C,gDAAgD;AAChD,MAAM,cAAc,GAAG;IACrB,WAAW;IACX,SAAS;IACT,MAAM;IACN,QAAQ;IACR,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;CACZ,CAAC;AAEF,kGAAkG;AAClG,mGAAmG;AACnG,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACpC,MAAM;IACN,QAAQ;IACR,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,WAAW;IACX,SAAS;CACV,CAAC;AAEF,6EAA6E;AAC7E,6EAA6E;AAC7E,MAAM,UAAU,qBAAqB,CACnC,OAAY,EACZ,OAAqB,EACrB,SAAqB;IAErB,MAAM,QAAQ,GAAG,OAAO,CAAC,gBAAgB,IAAI,eAAe,CAAC,SAAS,CAAC,CAAC;IAExE,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;QACvC,MAAM,YAAY,GAChB,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,QAAQ,CAAC,GAAG,YAAY,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAE/F,IAAI,YAAY,KAAK,CAAC,EAAE,CAAC;YACvB,OAAO,YAAY,CAAC;QACtB,CAAC;QAED,OAAO,aAAa,CAAC,IAAI,CAAC,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;AACL,CAAC;AAED,+DAA+D;AAC/D,8DAA8D;AAC9D,MAAM,UAAU,YAAY,CAAC,QAAgB,EAAE,QAAkB;IAC/D,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAEzC,OAAO,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC;AAChD,CAAC;AAED,0DAA0D;AAC1D,gEAAgE;AAChE,SAAS,eAAe,CAAC,SAAqB;IAC5C,OAAO,SAAS,KAAK,OAAO,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,gBAAgB,CAAC;AACnE,CAAC;AAED,oDAAoD;AACpD,sDAAsD;AACtD,SAAS,aAAa,CAAC,KAAc;IACnC,OAAO,OAAO,KAAK,KAAK,QAAQ;QAC9B,KAAK,KAAK,IAAI;QACd,OAAO,IAAI,KAAK;QAChB,OAAO,KAAK,CAAC,KAAK,KAAK,QAAQ;QAC/B,CAAC,CAAC,KAAK,CAAC,KAAK;QACb,CAAC,CAAC,CAAC,CAAC;AACR,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { SearchEntry, SearchGroup } from "./internal.js";
|
|
2
|
+
import type { MergeContext } from "./types.js";
|
|
3
|
+
export declare function scoreGroup(group: SearchGroup, entries: SearchEntry[], context: MergeContext): number;
|
|
4
|
+
export declare function hasExactPrimaryTitle(entries: SearchEntry[], queryTitle: string): boolean;
|
|
5
|
+
export declare function titleRelevanceScore(entries: SearchEntry[], queryTitle: string): number;
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { hasSharedStrongId } from "./identity.js";
|
|
2
|
+
import { STRONG_ID_KEYS } from "./internal.js";
|
|
3
|
+
import { normalizeTitle, titleCandidates } from "./title.js";
|
|
4
|
+
// Calculates a public score from match strength, query relevance, and popularity signals.
|
|
5
|
+
// Вычисляет публичную оценку по силе совпадения, релевантности запросу и популярности.
|
|
6
|
+
export function scoreGroup(group, entries, context) {
|
|
7
|
+
const query = context.query;
|
|
8
|
+
const queryIds = "ids" in (query ?? {}) ? query?.ids : undefined;
|
|
9
|
+
const queryTitle = "title" in (query ?? {}) ? query?.title : undefined;
|
|
10
|
+
if (queryIds && entries.some((entry) => hasSharedStrongId(queryIds, entry.result.item.ids))) {
|
|
11
|
+
return 1;
|
|
12
|
+
}
|
|
13
|
+
if (!queryTitle?.trim()) {
|
|
14
|
+
return baseGroupScore(group, entries);
|
|
15
|
+
}
|
|
16
|
+
const baseScore = baseTextSearchScore(group, entries);
|
|
17
|
+
const titleScore = titleRelevanceScore(entries, queryTitle);
|
|
18
|
+
const exactPrimaryTitleScore = hasExactPrimaryTitle(entries, queryTitle) ? 1 : 0;
|
|
19
|
+
const popularityScore = ratingVotesScore(entries);
|
|
20
|
+
const ratingScore = normalizedRatingScore(entries);
|
|
21
|
+
const idScore = externalIdCompletenessScore(entries);
|
|
22
|
+
const sourceScore = sourceCoverageScore(entries);
|
|
23
|
+
const authorityScore = sourceAuthorityScore(entries);
|
|
24
|
+
return boundedTextScore(baseScore +
|
|
25
|
+
titleScore * 0.2 +
|
|
26
|
+
// Prefer exact primary/original intent over prefix and incidental-alias noise while
|
|
27
|
+
// still allowing overwhelmingly corroborated canonical results for broad short queries.
|
|
28
|
+
exactPrimaryTitleScore * 0.3 +
|
|
29
|
+
popularityScore * 0.15 +
|
|
30
|
+
ratingScore * 0.05 +
|
|
31
|
+
idScore * 0.01 +
|
|
32
|
+
sourceScore * 0.02 +
|
|
33
|
+
authorityScore * 0.15);
|
|
34
|
+
}
|
|
35
|
+
// Distinguishes exact primary/original titles from incidental alternative aliases.
|
|
36
|
+
// Отличает точные основные/оригинальные названия от случайных alternative aliases.
|
|
37
|
+
export function hasExactPrimaryTitle(entries, queryTitle) {
|
|
38
|
+
const normalizedQuery = normalizeTitle(queryTitle);
|
|
39
|
+
return entries.some((entry) => [entry.result.item.title, entry.result.item.originalTitle]
|
|
40
|
+
.filter((title) => Boolean(title))
|
|
41
|
+
.some((title) => normalizeTitle(title) === normalizedQuery));
|
|
42
|
+
}
|
|
43
|
+
// Scores how well result titles match the user's text query.
|
|
44
|
+
// Оценивает, насколько названия результатов совпадают с текстовым запросом пользователя.
|
|
45
|
+
export function titleRelevanceScore(entries, queryTitle) {
|
|
46
|
+
const normalizedQuery = normalizeTitle(queryTitle);
|
|
47
|
+
if (!normalizedQuery) {
|
|
48
|
+
return 0;
|
|
49
|
+
}
|
|
50
|
+
return Math.max(...entries.flatMap((entry) => titleCandidates(entry.result.item).map((title) => scoreNormalizedTitle(normalizeTitle(title), normalizedQuery))));
|
|
51
|
+
}
|
|
52
|
+
// Keeps legacy scores for non-title searches where no relevance ranking is possible.
|
|
53
|
+
// Сохраняет прежние оценки для поиска без title, где нельзя посчитать релевантность.
|
|
54
|
+
function baseGroupScore(group, entries) {
|
|
55
|
+
switch (group.matchStrength) {
|
|
56
|
+
case "exact_id":
|
|
57
|
+
return 1;
|
|
58
|
+
case "exact_title_year_type":
|
|
59
|
+
return 0.9;
|
|
60
|
+
case "normalized_title_year_type":
|
|
61
|
+
return 0.8;
|
|
62
|
+
case "weak":
|
|
63
|
+
return 0.4;
|
|
64
|
+
case "none":
|
|
65
|
+
return clampScore(entries[0]?.result.confidence ?? 0.5);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// Starts text-search scoring below 1 so popularity and relevance can break exact-ID ties.
|
|
69
|
+
// Начинает оценку текстового поиска ниже 1, чтобы популярность и релевантность разбивали tie по ID.
|
|
70
|
+
function baseTextSearchScore(group, entries) {
|
|
71
|
+
switch (group.matchStrength) {
|
|
72
|
+
case "exact_id":
|
|
73
|
+
return 0.44 + bestProviderConfidence(entries) * 0.18;
|
|
74
|
+
case "exact_title_year_type":
|
|
75
|
+
return 0.62;
|
|
76
|
+
case "normalized_title_year_type":
|
|
77
|
+
return 0.52;
|
|
78
|
+
case "weak":
|
|
79
|
+
return 0.25;
|
|
80
|
+
case "none":
|
|
81
|
+
return bestProviderConfidence(entries) * 0.45;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// Uses the strongest provider confidence inside a merged group.
|
|
85
|
+
// Использует самый сильный confidence провайдера внутри объединенной группы.
|
|
86
|
+
function bestProviderConfidence(entries) {
|
|
87
|
+
return Math.max(...entries.map((entry) => clampScore(entry.result.confidence ?? 0.5)));
|
|
88
|
+
}
|
|
89
|
+
// Scores one normalized title against one normalized query.
|
|
90
|
+
// Оценивает одно нормализованное название против одного нормализованного запроса.
|
|
91
|
+
function scoreNormalizedTitle(title, query) {
|
|
92
|
+
if (!title) {
|
|
93
|
+
return 0;
|
|
94
|
+
}
|
|
95
|
+
if (title === query) {
|
|
96
|
+
return 1;
|
|
97
|
+
}
|
|
98
|
+
if (title.replace(/\s+/g, "") === query.replace(/\s+/g, "")) {
|
|
99
|
+
return 0.98;
|
|
100
|
+
}
|
|
101
|
+
if (title.startsWith(`${query} `)) {
|
|
102
|
+
return 0.92;
|
|
103
|
+
}
|
|
104
|
+
if (title.includes(` ${query} `) || title.endsWith(` ${query}`)) {
|
|
105
|
+
return 0.75;
|
|
106
|
+
}
|
|
107
|
+
const queryTokens = query.split(" ").filter(Boolean);
|
|
108
|
+
if (queryTokens.length > 0 && queryTokens.every((token) => title.includes(token))) {
|
|
109
|
+
return 0.55;
|
|
110
|
+
}
|
|
111
|
+
const titleTokens = title.split(" ").filter(Boolean);
|
|
112
|
+
const fuzzyTokenScores = queryTokens.map((queryToken) => Math.max(...titleTokens.map((titleToken) => fuzzyTokenSimilarity(queryToken, titleToken)), 0));
|
|
113
|
+
const minimumFuzzyScore = queryTokens.length >= 3 ? 0.7 : 0.75;
|
|
114
|
+
if (fuzzyTokenScores.length > 0 &&
|
|
115
|
+
fuzzyTokenScores.every((score) => score >= minimumFuzzyScore)) {
|
|
116
|
+
return ((fuzzyTokenScores.reduce((sum, score) => sum + score, 0) / fuzzyTokenScores.length) * 0.7);
|
|
117
|
+
}
|
|
118
|
+
return 0;
|
|
119
|
+
}
|
|
120
|
+
// Allows one small typo in meaningful words while keeping short tokens exact.
|
|
121
|
+
// Допускает одну небольшую опечатку в значимых словах, сохраняя короткие токены точными.
|
|
122
|
+
function fuzzyTokenSimilarity(left, right) {
|
|
123
|
+
if (left === right) {
|
|
124
|
+
return 1;
|
|
125
|
+
}
|
|
126
|
+
if (Math.min(left.length, right.length) < 4) {
|
|
127
|
+
return 0;
|
|
128
|
+
}
|
|
129
|
+
if (isSingleAdjacentTransposition(left, right)) {
|
|
130
|
+
return 1 - 1 / Math.max(left.length, right.length);
|
|
131
|
+
}
|
|
132
|
+
const variants = [right, ...(right.endsWith("s") ? [right.slice(0, -1)] : [])];
|
|
133
|
+
const distance = Math.min(...variants
|
|
134
|
+
.filter((variant) => Math.abs(left.length - variant.length) <= 1)
|
|
135
|
+
.map((variant) => levenshteinDistance(left, variant, 1)));
|
|
136
|
+
if (distance <= 1) {
|
|
137
|
+
return 1 - distance / Math.max(left.length, right.length);
|
|
138
|
+
}
|
|
139
|
+
if (Math.min(left.length, right.length) >= 6 && isTranspositionPlusOneEdit(left, right)) {
|
|
140
|
+
return 1 - 2 / Math.max(left.length, right.length);
|
|
141
|
+
}
|
|
142
|
+
return 0;
|
|
143
|
+
}
|
|
144
|
+
// Allows one adjacent swap plus one insertion, deletion, or substitution in long words.
|
|
145
|
+
// Допускает перестановку соседних символов и еще одну правку в длинных словах.
|
|
146
|
+
function isTranspositionPlusOneEdit(left, right) {
|
|
147
|
+
for (let index = 0; index < right.length - 1; index += 1) {
|
|
148
|
+
const swapped = right.slice(0, index) + right[index + 1] + right[index] + right.slice(index + 2);
|
|
149
|
+
if (Math.abs(left.length - swapped.length) <= 1 && levenshteinDistance(left, swapped, 1) <= 1) {
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
// Recognizes a single swapped adjacent character, a common typing error Levenshtein counts as two.
|
|
156
|
+
// Распознает перестановку соседних символов, которую Levenshtein считает двумя ошибками.
|
|
157
|
+
function isSingleAdjacentTransposition(left, right) {
|
|
158
|
+
if (left.length !== right.length) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
let firstDifference = -1;
|
|
162
|
+
for (let index = 0; index < left.length; index += 1) {
|
|
163
|
+
if (left[index] === right[index]) {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (firstDifference !== -1) {
|
|
167
|
+
return (index === firstDifference + 1 &&
|
|
168
|
+
left[firstDifference] === right[index] &&
|
|
169
|
+
left[index] === right[firstDifference] &&
|
|
170
|
+
left.slice(index + 1) === right.slice(index + 1));
|
|
171
|
+
}
|
|
172
|
+
firstDifference = index;
|
|
173
|
+
}
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
function levenshteinDistance(left, right, maxDistance) {
|
|
177
|
+
let previous = Array.from({ length: right.length + 1 }, (_, index) => index);
|
|
178
|
+
for (let leftIndex = 1; leftIndex <= left.length; leftIndex += 1) {
|
|
179
|
+
const current = [leftIndex];
|
|
180
|
+
let rowMinimum = leftIndex;
|
|
181
|
+
for (let rightIndex = 1; rightIndex <= right.length; rightIndex += 1) {
|
|
182
|
+
const substitutionCost = left[leftIndex - 1] === right[rightIndex - 1] ? 0 : 1;
|
|
183
|
+
const value = Math.min(current[rightIndex - 1] + 1, previous[rightIndex] + 1, previous[rightIndex - 1] + substitutionCost);
|
|
184
|
+
current.push(value);
|
|
185
|
+
rowMinimum = Math.min(rowMinimum, value);
|
|
186
|
+
}
|
|
187
|
+
if (rowMinimum > maxDistance) {
|
|
188
|
+
return maxDistance + 1;
|
|
189
|
+
}
|
|
190
|
+
previous = current;
|
|
191
|
+
}
|
|
192
|
+
return previous[right.length] ?? maxDistance + 1;
|
|
193
|
+
}
|
|
194
|
+
// Scores popularity from the largest available vote count.
|
|
195
|
+
// Оценивает популярность по самому большому доступному числу голосов.
|
|
196
|
+
function ratingVotesScore(entries) {
|
|
197
|
+
const maxVotes = Math.max(0, ...entries.flatMap((entry) => entry.result.item.ratings?.map((rating) => rating.votes ?? 0) ?? []));
|
|
198
|
+
return Math.min(1, Math.log10(maxVotes + 1) / 7);
|
|
199
|
+
}
|
|
200
|
+
// Scores normalized rating values across providers.
|
|
201
|
+
// Оценивает нормализованные значения рейтингов от провайдеров.
|
|
202
|
+
function normalizedRatingScore(entries) {
|
|
203
|
+
const values = entries.flatMap((entry) => entry.result.item.ratings
|
|
204
|
+
?.map((rating) => rating.value / rating.max)
|
|
205
|
+
.filter((value) => Number.isFinite(value)) ?? []);
|
|
206
|
+
return values.length === 0 ? 0 : Math.max(...values.map((value) => clampScore(value)));
|
|
207
|
+
}
|
|
208
|
+
// Rewards results that carry strong external IDs for better follow-up details lookup.
|
|
209
|
+
// Поощряет результаты с сильными внешними ID для более надежной загрузки деталей.
|
|
210
|
+
function externalIdCompletenessScore(entries) {
|
|
211
|
+
const idCount = Math.max(0, ...entries.map((entry) => STRONG_ID_KEYS.filter((key) => Boolean(entry.result.item.ids?.[key])).length));
|
|
212
|
+
return Math.min(1, idCount / 3);
|
|
213
|
+
}
|
|
214
|
+
// Rewards results confirmed by multiple providers.
|
|
215
|
+
// Поощряет результаты, подтвержденные несколькими провайдерами.
|
|
216
|
+
function sourceCoverageScore(entries) {
|
|
217
|
+
return Math.min(1, new Set(entries.map((entry) => entry.result.provider)).size / 3);
|
|
218
|
+
}
|
|
219
|
+
// Adds a small authority signal for sources that usually imply broader popularity.
|
|
220
|
+
// Добавляет небольшой сигнал авторитетности для источников, которые обычно отражают популярность.
|
|
221
|
+
function sourceAuthorityScore(entries) {
|
|
222
|
+
const providers = [...new Set(entries.map((entry) => entry.result.provider))];
|
|
223
|
+
return (providers.reduce((total, provider) => total + providerAuthority(provider), 0) /
|
|
224
|
+
Math.max(1, providers.length));
|
|
225
|
+
}
|
|
226
|
+
// Scores provider authority for broad text search ranking.
|
|
227
|
+
// Оценивает авторитетность провайдера для ранжирования широкого текстового поиска.
|
|
228
|
+
function providerAuthority(provider) {
|
|
229
|
+
switch (provider) {
|
|
230
|
+
case "wikidata":
|
|
231
|
+
return 0.7;
|
|
232
|
+
case "tmdb":
|
|
233
|
+
return 0.95;
|
|
234
|
+
case "cinemeta":
|
|
235
|
+
return 0.75;
|
|
236
|
+
case "kinobd":
|
|
237
|
+
return 0.75;
|
|
238
|
+
case "imdb":
|
|
239
|
+
return 0.65;
|
|
240
|
+
case "kinopoisk":
|
|
241
|
+
return 0.6;
|
|
242
|
+
case "shikimori":
|
|
243
|
+
return 0.9;
|
|
244
|
+
case "anilist":
|
|
245
|
+
return 0.9;
|
|
246
|
+
default:
|
|
247
|
+
return 0.3;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
// Keeps text-search scores comparable without flattening many strong matches to exactly 1.
|
|
251
|
+
// Сохраняет сравнимость text-search score без схлопывания сильных совпадений ровно в 1.
|
|
252
|
+
function boundedTextScore(score) {
|
|
253
|
+
return 0.5 + clampScore(score / (score + 1)) * 0.5;
|
|
254
|
+
}
|
|
255
|
+
// Restricts a score value to the public 0..1 range.
|
|
256
|
+
// Ограничивает значение score публичным диапазоном 0..1.
|
|
257
|
+
function clampScore(score) {
|
|
258
|
+
return Math.max(0, Math.min(1, score));
|
|
259
|
+
}
|
|
260
|
+
//# sourceMappingURL=scoring.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scoring.js","sourceRoot":"","sources":["../../src/merge/scoring.ts"],"names":[],"mappings":"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"}
|