movie-agent 1.0.0
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/LICENSE +21 -0
- package/README.md +464 -0
- package/bin/movie-agent +209 -0
- package/dist/agent.d.ts +89 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +365 -0
- package/dist/agent.js.map +1 -0
- package/dist/cache.d.ts +75 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +133 -0
- package/dist/cache.js.map +1 -0
- package/dist/config.d.ts +17 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +31 -0
- package/dist/config.js.map +1 -0
- package/dist/discover.d.ts +38 -0
- package/dist/discover.d.ts.map +1 -0
- package/dist/discover.js +121 -0
- package/dist/discover.js.map +1 -0
- package/dist/factory.d.ts +87 -0
- package/dist/factory.d.ts.map +1 -0
- package/dist/factory.js +118 -0
- package/dist/factory.js.map +1 -0
- package/dist/filters.d.ts +61 -0
- package/dist/filters.d.ts.map +1 -0
- package/dist/filters.js +97 -0
- package/dist/filters.js.map +1 -0
- package/dist/format.d.ts +33 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +85 -0
- package/dist/format.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/mood.d.ts +7 -0
- package/dist/mood.d.ts.map +1 -0
- package/dist/mood.js +21 -0
- package/dist/mood.js.map +1 -0
- package/dist/providers.d.ts +10 -0
- package/dist/providers.d.ts.map +1 -0
- package/dist/providers.js +70 -0
- package/dist/providers.js.map +1 -0
- package/dist/ranking.d.ts +57 -0
- package/dist/ranking.d.ts.map +1 -0
- package/dist/ranking.js +198 -0
- package/dist/ranking.js.map +1 -0
- package/dist/tmdbApi.d.ts +79 -0
- package/dist/tmdbApi.d.ts.map +1 -0
- package/dist/tmdbApi.js +88 -0
- package/dist/tmdbApi.js.map +1 -0
- package/dist/types.d.ts +99 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/validate.d.ts +13 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +47 -0
- package/dist/validate.js.map +1 -0
- package/package.json +72 -0
package/dist/filters.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/filters.ts
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.filterByPlatforms = filterByPlatforms;
|
|
5
|
+
exports.filterByRuntime = filterByRuntime;
|
|
6
|
+
exports.filterByYear = filterByYear;
|
|
7
|
+
exports.applyFilters = applyFilters;
|
|
8
|
+
/**
|
|
9
|
+
* Filter a movie by checking if it's available on any of the user's platforms
|
|
10
|
+
* @param movie The movie object with available platforms
|
|
11
|
+
* @param userPlatforms Array of platform names the user has access to
|
|
12
|
+
* @returns true if the movie is available on at least one of the user's platforms
|
|
13
|
+
*/
|
|
14
|
+
function filterByPlatforms(movie, userPlatforms) {
|
|
15
|
+
if (!movie.platforms || movie.platforms.length === 0) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
if (!userPlatforms || userPlatforms.length === 0) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
// Check if any of the movie's platforms match any of the user's platforms
|
|
22
|
+
return movie.platforms.some(platform => userPlatforms.includes(platform));
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Filter a movie by runtime constraints
|
|
26
|
+
* @param movie The movie object with runtime information
|
|
27
|
+
* @param constraints Runtime constraints (min and/or max)
|
|
28
|
+
* @returns true if the movie's runtime satisfies the constraints
|
|
29
|
+
*/
|
|
30
|
+
function filterByRuntime(movie, constraints) {
|
|
31
|
+
// If runtime is not specified, we can't filter
|
|
32
|
+
if (movie.runtime === undefined || movie.runtime === null) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
const { min, max } = constraints;
|
|
36
|
+
// Check minimum runtime constraint
|
|
37
|
+
if (min !== undefined && movie.runtime < min) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
// Check maximum runtime constraint
|
|
41
|
+
if (max !== undefined && movie.runtime > max) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Filter a movie by release year
|
|
48
|
+
* @param movie The movie object with release_date
|
|
49
|
+
* @param year Either a specific year (number) or a year range object
|
|
50
|
+
* @returns true if the movie's release year matches the criteria
|
|
51
|
+
*/
|
|
52
|
+
function filterByYear(movie, year) {
|
|
53
|
+
if (!movie.release_date) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
// Extract year from release_date (format: YYYY-MM-DD)
|
|
57
|
+
const releaseYear = parseInt(movie.release_date.split('-')[0], 10);
|
|
58
|
+
if (isNaN(releaseYear)) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
// If year is a number, check for exact match
|
|
62
|
+
if (typeof year === 'number') {
|
|
63
|
+
return releaseYear === year;
|
|
64
|
+
}
|
|
65
|
+
// If year is a range, check if release year falls within range
|
|
66
|
+
const { from, to } = year;
|
|
67
|
+
if (from !== undefined && releaseYear < from) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
if (to !== undefined && releaseYear > to) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Apply multiple filters to an array of movies
|
|
77
|
+
* @param movies Array of movies to filter
|
|
78
|
+
* @param options Filter options to apply
|
|
79
|
+
* @returns Filtered array of movies that pass all specified filters
|
|
80
|
+
*/
|
|
81
|
+
function applyFilters(movies, options) {
|
|
82
|
+
let filtered = movies;
|
|
83
|
+
// Apply platform filter if specified
|
|
84
|
+
if (options.platforms && options.platforms.length > 0) {
|
|
85
|
+
filtered = filtered.filter(movie => filterByPlatforms(movie, options.platforms));
|
|
86
|
+
}
|
|
87
|
+
// Apply runtime filter if specified
|
|
88
|
+
if (options.runtime) {
|
|
89
|
+
filtered = filtered.filter(movie => filterByRuntime(movie, options.runtime));
|
|
90
|
+
}
|
|
91
|
+
// Apply year filter if specified
|
|
92
|
+
if (options.year !== undefined) {
|
|
93
|
+
filtered = filtered.filter(movie => filterByYear(movie, options.year));
|
|
94
|
+
}
|
|
95
|
+
return filtered;
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=filters.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filters.js","sourceRoot":"","sources":["../src/filters.ts"],"names":[],"mappings":"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"}
|
package/dist/format.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { MovieRecommendation, StreamingPlatform, AgentResponse, ResponseMetadata } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Builds a concise, non-spoiler description (50-100 words)
|
|
4
|
+
* @param overview - The movie overview from TMDb
|
|
5
|
+
* @returns Formatted description within word count constraints
|
|
6
|
+
*/
|
|
7
|
+
export declare function buildDescription(overview: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* Formats a movie with streaming providers into a MovieRecommendation
|
|
10
|
+
* @param movie - TMDb movie object with id, title, release_date, runtime, overview, genres
|
|
11
|
+
* @param providers - Streaming provider information
|
|
12
|
+
* @param reason - Why this movie was recommended
|
|
13
|
+
* @returns Formatted MovieRecommendation
|
|
14
|
+
*/
|
|
15
|
+
export declare function toRecommendation(movie: {
|
|
16
|
+
id: number;
|
|
17
|
+
title: string;
|
|
18
|
+
release_date: string;
|
|
19
|
+
runtime: number;
|
|
20
|
+
overview: string;
|
|
21
|
+
genres: Array<{
|
|
22
|
+
id: number;
|
|
23
|
+
name: string;
|
|
24
|
+
}>;
|
|
25
|
+
}, providers: StreamingPlatform[], reason: string): MovieRecommendation;
|
|
26
|
+
/**
|
|
27
|
+
* Formats the complete agent response with recommendations and metadata
|
|
28
|
+
* @param recommendations - Array of MovieRecommendation objects (3-5 items)
|
|
29
|
+
* @param metadata - Request metadata including input parameters
|
|
30
|
+
* @returns Complete AgentResponse object
|
|
31
|
+
*/
|
|
32
|
+
export declare function formatResponse(recommendations: MovieRecommendation[], metadata: Omit<ResponseMetadata, 'requestTimestamp' | 'totalResults'>): AgentResponse;
|
|
33
|
+
//# sourceMappingURL=format.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,aAAa,EACb,gBAAgB,EACjB,MAAM,SAAS,CAAC;AAEjB;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAuCzD;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE;IACL,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC7C,EACD,SAAS,EAAE,iBAAiB,EAAE,EAC9B,MAAM,EAAE,MAAM,GACb,mBAAmB,CAcrB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,eAAe,EAAE,mBAAmB,EAAE,EACtC,QAAQ,EAAE,IAAI,CAAC,gBAAgB,EAAE,kBAAkB,GAAG,cAAc,CAAC,GACpE,aAAa,CAkBf"}
|
package/dist/format.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildDescription = buildDescription;
|
|
4
|
+
exports.toRecommendation = toRecommendation;
|
|
5
|
+
exports.formatResponse = formatResponse;
|
|
6
|
+
/**
|
|
7
|
+
* Builds a concise, non-spoiler description (50-100 words)
|
|
8
|
+
* @param overview - The movie overview from TMDb
|
|
9
|
+
* @returns Formatted description within word count constraints
|
|
10
|
+
*/
|
|
11
|
+
function buildDescription(overview) {
|
|
12
|
+
// TODO: Implement intelligent description builder
|
|
13
|
+
// - Truncate to 50-100 words
|
|
14
|
+
// - Avoid spoilers (could use AI or heuristics)
|
|
15
|
+
// - Ensure complete sentences
|
|
16
|
+
if (!overview) {
|
|
17
|
+
return 'No description available.';
|
|
18
|
+
}
|
|
19
|
+
const words = overview.split(/\s+/);
|
|
20
|
+
const minWords = 50;
|
|
21
|
+
const maxWords = 100;
|
|
22
|
+
if (words.length <= maxWords) {
|
|
23
|
+
return overview;
|
|
24
|
+
}
|
|
25
|
+
// Truncate to max words and try to end at a sentence boundary
|
|
26
|
+
const truncated = words.slice(0, maxWords).join(' ');
|
|
27
|
+
// Find the last sentence-ending punctuation within our limit
|
|
28
|
+
const lastPeriod = truncated.lastIndexOf('.');
|
|
29
|
+
const lastExclamation = truncated.lastIndexOf('!');
|
|
30
|
+
const lastQuestion = truncated.lastIndexOf('?');
|
|
31
|
+
const lastSentenceEnd = Math.max(lastPeriod, lastExclamation, lastQuestion);
|
|
32
|
+
if (lastSentenceEnd > 0) {
|
|
33
|
+
// Check if we have enough words before this point
|
|
34
|
+
const beforeSentenceEnd = truncated.substring(0, lastSentenceEnd + 1);
|
|
35
|
+
const beforeWords = beforeSentenceEnd.split(/\s+/).length;
|
|
36
|
+
if (beforeWords >= minWords) {
|
|
37
|
+
return beforeSentenceEnd;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// If we can't find a good sentence boundary, add ellipsis
|
|
41
|
+
return truncated + '...';
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Formats a movie with streaming providers into a MovieRecommendation
|
|
45
|
+
* @param movie - TMDb movie object with id, title, release_date, runtime, overview, genres
|
|
46
|
+
* @param providers - Streaming provider information
|
|
47
|
+
* @param reason - Why this movie was recommended
|
|
48
|
+
* @returns Formatted MovieRecommendation
|
|
49
|
+
*/
|
|
50
|
+
function toRecommendation(movie, providers, reason) {
|
|
51
|
+
const releaseYear = new Date(movie.release_date).getFullYear();
|
|
52
|
+
const description = buildDescription(movie.overview);
|
|
53
|
+
const genres = movie.genres.map(g => g.name);
|
|
54
|
+
return {
|
|
55
|
+
title: movie.title,
|
|
56
|
+
releaseYear,
|
|
57
|
+
runtime: movie.runtime,
|
|
58
|
+
description,
|
|
59
|
+
genres,
|
|
60
|
+
streamingPlatforms: providers,
|
|
61
|
+
matchReason: reason,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Formats the complete agent response with recommendations and metadata
|
|
66
|
+
* @param recommendations - Array of MovieRecommendation objects (3-5 items)
|
|
67
|
+
* @param metadata - Request metadata including input parameters
|
|
68
|
+
* @returns Complete AgentResponse object
|
|
69
|
+
*/
|
|
70
|
+
function formatResponse(recommendations, metadata) {
|
|
71
|
+
// Enforce 3-5 recommendations
|
|
72
|
+
if (recommendations.length < 3 || recommendations.length > 5) {
|
|
73
|
+
throw new Error(`Expected 3-5 recommendations, got ${recommendations.length}`);
|
|
74
|
+
}
|
|
75
|
+
const response = {
|
|
76
|
+
recommendations,
|
|
77
|
+
metadata: {
|
|
78
|
+
requestTimestamp: new Date().toISOString(),
|
|
79
|
+
totalResults: recommendations.length,
|
|
80
|
+
inputParameters: metadata.inputParameters,
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
return response;
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":";;AAYA,4CAuCC;AASD,4CAyBC;AAQD,wCAqBC;AA3GD;;;;GAIG;AACH,SAAgB,gBAAgB,CAAC,QAAgB;IAC/C,kDAAkD;IAClD,6BAA6B;IAC7B,gDAAgD;IAChD,8BAA8B;IAE9B,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,2BAA2B,CAAC;IACrC,CAAC;IAED,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACpC,MAAM,QAAQ,GAAG,EAAE,CAAC;IACpB,MAAM,QAAQ,GAAG,GAAG,CAAC;IAErB,IAAI,KAAK,CAAC,MAAM,IAAI,QAAQ,EAAE,CAAC;QAC7B,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,8DAA8D;IAC9D,MAAM,SAAS,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAErD,6DAA6D;IAC7D,MAAM,UAAU,GAAG,SAAS,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAC9C,MAAM,eAAe,GAAG,SAAS,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACnD,MAAM,YAAY,GAAG,SAAS,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAChD,MAAM,eAAe,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,eAAe,EAAE,YAAY,CAAC,CAAC;IAE5E,IAAI,eAAe,GAAG,CAAC,EAAE,CAAC;QACxB,kDAAkD;QAClD,MAAM,iBAAiB,GAAG,SAAS,CAAC,SAAS,CAAC,CAAC,EAAE,eAAe,GAAG,CAAC,CAAC,CAAC;QACtE,MAAM,WAAW,GAAG,iBAAiB,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC;QAE1D,IAAI,WAAW,IAAI,QAAQ,EAAE,CAAC;YAC5B,OAAO,iBAAiB,CAAC;QAC3B,CAAC;IACH,CAAC;IAED,0DAA0D;IAC1D,OAAO,SAAS,GAAG,KAAK,CAAC;AAC3B,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,gBAAgB,CAC9B,KAOC,EACD,SAA8B,EAC9B,MAAc;IAEd,MAAM,WAAW,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,WAAW,EAAE,CAAC;IAC/D,MAAM,WAAW,GAAG,gBAAgB,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IACrD,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAE7C,OAAO;QACL,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,WAAW;QACX,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,WAAW;QACX,MAAM;QACN,kBAAkB,EAAE,SAAS;QAC7B,WAAW,EAAE,MAAM;KACpB,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,SAAgB,cAAc,CAC5B,eAAsC,EACtC,QAAqE;IAErE,8BAA8B;IAC9B,IAAI,eAAe,CAAC,MAAM,GAAG,CAAC,IAAI,eAAe,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC7D,MAAM,IAAI,KAAK,CACb,qCAAqC,eAAe,CAAC,MAAM,EAAE,CAC9D,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAkB;QAC9B,eAAe;QACf,QAAQ,EAAE;YACR,gBAAgB,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YAC1C,YAAY,EAAE,eAAe,CAAC,MAAM;YACpC,eAAe,EAAE,QAAQ,CAAC,eAAe;SAC1C;KACF,CAAC;IAEF,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { MovieAgent } from './agent';
|
|
2
|
+
export { MovieAgentFactory, MovieAgentConfig } from './factory';
|
|
3
|
+
export { UserInput, AgentResponse, MovieRecommendation, StreamingPlatform, ErrorResponse, } from './types';
|
|
4
|
+
export { default as TmdbApiClient } from './tmdbApi';
|
|
5
|
+
export { MovieAgentFactory as default } from './factory';
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAGrC,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAGhE,OAAO,EACL,SAAS,EACT,aAAa,EACb,mBAAmB,EACnB,iBAAiB,EACjB,aAAa,GACd,MAAM,SAAS,CAAC;AAGjB,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,WAAW,CAAC;AAGrD,OAAO,EAAE,iBAAiB,IAAI,OAAO,EAAE,MAAM,WAAW,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/index.ts - Main entry point for the movie-agent package
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.default = exports.TmdbApiClient = exports.MovieAgentFactory = exports.MovieAgent = void 0;
|
|
8
|
+
// Export the main MovieAgent class
|
|
9
|
+
var agent_1 = require("./agent");
|
|
10
|
+
Object.defineProperty(exports, "MovieAgent", { enumerable: true, get: function () { return agent_1.MovieAgent; } });
|
|
11
|
+
// Export the factory for easy configuration
|
|
12
|
+
var factory_1 = require("./factory");
|
|
13
|
+
Object.defineProperty(exports, "MovieAgentFactory", { enumerable: true, get: function () { return factory_1.MovieAgentFactory; } });
|
|
14
|
+
// Export TMDb API client for advanced use cases
|
|
15
|
+
var tmdbApi_1 = require("./tmdbApi");
|
|
16
|
+
Object.defineProperty(exports, "TmdbApiClient", { enumerable: true, get: function () { return __importDefault(tmdbApi_1).default; } });
|
|
17
|
+
// Default export is the factory for convenience
|
|
18
|
+
var factory_2 = require("./factory");
|
|
19
|
+
Object.defineProperty(exports, "default", { enumerable: true, get: function () { return factory_2.MovieAgentFactory; } });
|
|
20
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA,8DAA8D;;;;;;AAE9D,mCAAmC;AACnC,iCAAqC;AAA5B,mGAAA,UAAU,OAAA;AAEnB,4CAA4C;AAC5C,qCAAgE;AAAvD,4GAAA,iBAAiB,OAAA;AAW1B,gDAAgD;AAChD,qCAAqD;AAA5C,yHAAA,OAAO,OAAiB;AAEjC,gDAAgD;AAChD,qCAAyD;AAAhD,kGAAA,iBAAiB,OAAW"}
|
package/dist/mood.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mood.d.ts","sourceRoot":"","sources":["../src/mood.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAWnD"}
|
package/dist/mood.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.moodToGenres = moodToGenres;
|
|
4
|
+
/**
|
|
5
|
+
* Maps a mood to an array of movie genres.
|
|
6
|
+
* @param mood - The mood to map.
|
|
7
|
+
* @returns Array of genres, or [] if unknown mood.
|
|
8
|
+
*/
|
|
9
|
+
function moodToGenres(mood) {
|
|
10
|
+
const mapping = {
|
|
11
|
+
happy: ['Comedy', 'Family', 'Musical'],
|
|
12
|
+
thoughtful: ['Drama', 'Documentary', 'Biography'],
|
|
13
|
+
excited: ['Action', 'Adventure', 'Thriller'],
|
|
14
|
+
relaxed: ['Romance', 'Comedy', 'Animation'],
|
|
15
|
+
scared: ['Horror', 'Thriller', 'Mystery'],
|
|
16
|
+
};
|
|
17
|
+
const genres = mapping[mood.toLowerCase()] || [];
|
|
18
|
+
// Ensure uniqueness
|
|
19
|
+
return Array.from(new Set(genres));
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=mood.js.map
|
package/dist/mood.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mood.js","sourceRoot":"","sources":["../src/mood.ts"],"names":[],"mappings":";;AAKA,oCAWC;AAhBD;;;;GAIG;AACH,SAAgB,YAAY,CAAC,IAAY;IACvC,MAAM,OAAO,GAA6B;QACxC,KAAK,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,CAAC;QACtC,UAAU,EAAE,CAAC,OAAO,EAAE,aAAa,EAAE,WAAW,CAAC;QACjD,OAAO,EAAE,CAAC,QAAQ,EAAE,WAAW,EAAE,UAAU,CAAC;QAC5C,OAAO,EAAE,CAAC,SAAS,EAAE,QAAQ,EAAE,WAAW,CAAC;QAC3C,MAAM,EAAE,CAAC,QAAQ,EAAE,UAAU,EAAE,SAAS,CAAC;KAC1C,CAAC;IACF,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC;IACjD,oBAAoB;IACpB,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;AACrC,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import TmdbApiClient from './tmdbApi';
|
|
2
|
+
/**
|
|
3
|
+
* Fetch and normalize Canadian watch providers for a movie.
|
|
4
|
+
* @param movieId TMDb movie ID
|
|
5
|
+
* @param region Region code (default: "CA")
|
|
6
|
+
* @param client Optional TmdbApiClient for testing
|
|
7
|
+
* @returns Array of normalized platform names
|
|
8
|
+
*/
|
|
9
|
+
export declare function getCanadianProviders(movieId: number | string, region?: string, client?: TmdbApiClient): Promise<string[]>;
|
|
10
|
+
//# sourceMappingURL=providers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"providers.d.ts","sourceRoot":"","sources":["../src/providers.ts"],"names":[],"mappings":"AACA,OAAO,aAAa,MAAM,WAAW,CAAC;AAmBtC;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,MAAM,GAAG,MAAM,EACxB,MAAM,SAAO,EACb,MAAM,CAAC,EAAE,aAAa,GACrB,OAAO,CAAC,MAAM,EAAE,CAAC,CAwCnB"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getCanadianProviders = getCanadianProviders;
|
|
7
|
+
// src/providers.ts
|
|
8
|
+
const tmdbApi_1 = __importDefault(require("./tmdbApi"));
|
|
9
|
+
const cache_1 = require("./cache");
|
|
10
|
+
const config_1 = __importDefault(require("./config"));
|
|
11
|
+
// Allowed platforms mapping (add more as needed)
|
|
12
|
+
const PLATFORM_MAP = {
|
|
13
|
+
Netflix: 'Netflix',
|
|
14
|
+
'Amazon Prime Video': 'Prime Video',
|
|
15
|
+
'Disney Plus': 'Disney+',
|
|
16
|
+
Crave: 'Crave',
|
|
17
|
+
'Apple TV Plus': 'Apple TV+',
|
|
18
|
+
'Paramount Plus': 'Paramount+',
|
|
19
|
+
'CBC Gem': 'CBC Gem',
|
|
20
|
+
MUBI: 'MUBI',
|
|
21
|
+
hayu: 'hayu',
|
|
22
|
+
fuboTV: 'fuboTV',
|
|
23
|
+
// Add more mappings as needed
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Fetch and normalize Canadian watch providers for a movie.
|
|
27
|
+
* @param movieId TMDb movie ID
|
|
28
|
+
* @param region Region code (default: "CA")
|
|
29
|
+
* @param client Optional TmdbApiClient for testing
|
|
30
|
+
* @returns Array of normalized platform names
|
|
31
|
+
*/
|
|
32
|
+
async function getCanadianProviders(movieId, region = 'CA', client) {
|
|
33
|
+
try {
|
|
34
|
+
// Check cache first
|
|
35
|
+
const cache = (0, cache_1.getCache)(config_1.default.CACHE_TTL);
|
|
36
|
+
const cacheKey = (0, cache_1.generateProvidersCacheKey)(movieId, region);
|
|
37
|
+
const cachedResult = cache.get(cacheKey);
|
|
38
|
+
if (cachedResult) {
|
|
39
|
+
return cachedResult;
|
|
40
|
+
}
|
|
41
|
+
// Cache miss - fetch from API
|
|
42
|
+
const apiClient = client ?? new tmdbApi_1.default();
|
|
43
|
+
const data = await apiClient.getWatchProviders(Number(movieId));
|
|
44
|
+
if (!data || !data.results || !data.results[region]) {
|
|
45
|
+
// Cache empty result to avoid repeated API calls
|
|
46
|
+
cache.set(cacheKey, []);
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
const regionData = data.results[region];
|
|
50
|
+
// Only consider 'flatrate' (subscription) providers
|
|
51
|
+
const flatrate = regionData.flatrate || [];
|
|
52
|
+
if (!Array.isArray(flatrate) || flatrate.length === 0) {
|
|
53
|
+
cache.set(cacheKey, []);
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
// Map provider names to allowed platforms
|
|
57
|
+
const platforms = flatrate
|
|
58
|
+
.map((p) => PLATFORM_MAP[p.provider_name])
|
|
59
|
+
.filter(Boolean);
|
|
60
|
+
// Remove duplicates
|
|
61
|
+
const result = Array.from(new Set(platforms));
|
|
62
|
+
// Store in cache
|
|
63
|
+
cache.set(cacheKey, result);
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=providers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"providers.js","sourceRoot":"","sources":["../src/providers.ts"],"names":[],"mappings":";;;;;AA2BA,oDA4CC;AAvED,mBAAmB;AACnB,wDAAsC;AACtC,mCAA8D;AAC9D,sDAA8B;AAE9B,iDAAiD;AACjD,MAAM,YAAY,GAA2B;IAC3C,OAAO,EAAE,SAAS;IAClB,oBAAoB,EAAE,aAAa;IACnC,aAAa,EAAE,SAAS;IACxB,KAAK,EAAE,OAAO;IACd,eAAe,EAAE,WAAW;IAC5B,gBAAgB,EAAE,YAAY;IAC9B,SAAS,EAAE,SAAS;IACpB,IAAI,EAAE,MAAM;IACZ,IAAI,EAAE,MAAM;IACZ,MAAM,EAAE,QAAQ;IAChB,8BAA8B;CAC/B,CAAC;AAEF;;;;;;GAMG;AACI,KAAK,UAAU,oBAAoB,CACxC,OAAwB,EACxB,MAAM,GAAG,IAAI,EACb,MAAsB;IAEtB,IAAI,CAAC;QACH,oBAAoB;QACpB,MAAM,KAAK,GAAG,IAAA,gBAAQ,EAAC,gBAAM,CAAC,SAAS,CAAC,CAAC;QACzC,MAAM,QAAQ,GAAG,IAAA,iCAAyB,EAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC5D,MAAM,YAAY,GAAG,KAAK,CAAC,GAAG,CAAW,QAAQ,CAAC,CAAC;QAEnD,IAAI,YAAY,EAAE,CAAC;YACjB,OAAO,YAAY,CAAC;QACtB,CAAC;QAED,8BAA8B;QAC9B,MAAM,SAAS,GAAG,MAAM,IAAI,IAAI,iBAAa,EAAE,CAAC;QAChD,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,iBAAiB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;QAChE,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YACpD,iDAAiD;YACjD,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;YACxB,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACxC,oDAAoD;QACpD,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,IAAI,EAAE,CAAC;QAC3C,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACtD,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;YACxB,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,0CAA0C;QAC1C,MAAM,SAAS,GAAG,QAAQ;aACvB,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC;aAC9C,MAAM,CAAC,OAAO,CAAC,CAAC;QACnB,oBAAoB;QACpB,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC;QAE9C,iBAAiB;QACjB,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAE5B,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { MovieDetails } from './tmdbApi';
|
|
2
|
+
/**
|
|
3
|
+
* User input for ranking context
|
|
4
|
+
*/
|
|
5
|
+
export interface RankingInput {
|
|
6
|
+
/** User's mood to match against genres */
|
|
7
|
+
mood?: string;
|
|
8
|
+
/** Explicit genre preferences */
|
|
9
|
+
genres?: string[];
|
|
10
|
+
/** User's available streaming platforms */
|
|
11
|
+
platforms?: string[];
|
|
12
|
+
/** Runtime constraints */
|
|
13
|
+
runtime?: {
|
|
14
|
+
min?: number;
|
|
15
|
+
max?: number;
|
|
16
|
+
};
|
|
17
|
+
/** Release year preferences */
|
|
18
|
+
year?: {
|
|
19
|
+
preferred?: number;
|
|
20
|
+
from?: number;
|
|
21
|
+
to?: number;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Extended movie type with platforms information
|
|
26
|
+
*/
|
|
27
|
+
export interface RankableMovie extends MovieDetails {
|
|
28
|
+
platforms?: string[];
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Scoring context derived from input
|
|
32
|
+
*/
|
|
33
|
+
export interface ScoringContext {
|
|
34
|
+
targetGenres: string[];
|
|
35
|
+
userPlatforms: string[];
|
|
36
|
+
runtimeMin?: number;
|
|
37
|
+
runtimeMax?: number;
|
|
38
|
+
preferredYear?: number;
|
|
39
|
+
yearFrom?: number;
|
|
40
|
+
yearTo?: number;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Calculate overall score for a movie
|
|
44
|
+
* @param movie - Movie to score
|
|
45
|
+
* @param input - User input for ranking
|
|
46
|
+
* @param context - Optional pre-built scoring context
|
|
47
|
+
* @returns Overall score (0-100)
|
|
48
|
+
*/
|
|
49
|
+
export declare function scoreMovie(movie: RankableMovie, input: RankingInput, context?: ScoringContext): number;
|
|
50
|
+
/**
|
|
51
|
+
* Rank movies by score
|
|
52
|
+
* @param movies - Array of movies to rank
|
|
53
|
+
* @param input - User input for ranking
|
|
54
|
+
* @returns Sorted array of movies (highest score first)
|
|
55
|
+
*/
|
|
56
|
+
export declare function rankMovies(movies: RankableMovie[], input: RankingInput): RankableMovie[];
|
|
57
|
+
//# sourceMappingURL=ranking.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ranking.d.ts","sourceRoot":"","sources":["../src/ranking.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAGzC;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,0CAA0C;IAC1C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iCAAiC;IACjC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,0BAA0B;IAC1B,OAAO,CAAC,EAAE;QACR,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,GAAG,CAAC,EAAE,MAAM,CAAC;KACd,CAAC;IACF,+BAA+B;IAC/B,IAAI,CAAC,EAAE;QACL,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,EAAE,CAAC,EAAE,MAAM,CAAC;KACb,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,aAAc,SAAQ,YAAY;IACjD,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAoMD;;;;;;GAMG;AACH,wBAAgB,UAAU,CACxB,KAAK,EAAE,aAAa,EACpB,KAAK,EAAE,YAAY,EACnB,OAAO,CAAC,EAAE,cAAc,GACvB,MAAM,CAkBR;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,aAAa,EAAE,EACvB,KAAK,EAAE,YAAY,GAClB,aAAa,EAAE,CAejB"}
|
package/dist/ranking.js
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.scoreMovie = scoreMovie;
|
|
4
|
+
exports.rankMovies = rankMovies;
|
|
5
|
+
const mood_1 = require("./mood");
|
|
6
|
+
/**
|
|
7
|
+
* Scoring weights for different criteria
|
|
8
|
+
*/
|
|
9
|
+
const WEIGHTS = {
|
|
10
|
+
GENRE_MATCH: 40, // Mood/genre alignment
|
|
11
|
+
PLATFORM_MATCH: 30, // Platform availability
|
|
12
|
+
RUNTIME_MATCH: 15, // Runtime compatibility
|
|
13
|
+
YEAR_MATCH: 10, // Release year preference
|
|
14
|
+
POPULARITY: 5, // Popularity as tiebreaker
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Build scoring context from user input
|
|
18
|
+
* @param input - User ranking input
|
|
19
|
+
* @returns Scoring context
|
|
20
|
+
*/
|
|
21
|
+
function buildScoringContext(input) {
|
|
22
|
+
// Determine target genres from mood or explicit genres
|
|
23
|
+
let targetGenres = [];
|
|
24
|
+
if (input.genres && input.genres.length > 0) {
|
|
25
|
+
targetGenres = input.genres;
|
|
26
|
+
}
|
|
27
|
+
else if (input.mood) {
|
|
28
|
+
targetGenres = (0, mood_1.moodToGenres)(input.mood);
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
targetGenres,
|
|
32
|
+
userPlatforms: input.platforms || [],
|
|
33
|
+
runtimeMin: input.runtime?.min,
|
|
34
|
+
runtimeMax: input.runtime?.max,
|
|
35
|
+
preferredYear: input.year?.preferred,
|
|
36
|
+
yearFrom: input.year?.from,
|
|
37
|
+
yearTo: input.year?.to,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Calculate genre match score (0-1)
|
|
42
|
+
* @param movie - Movie to score
|
|
43
|
+
* @param context - Scoring context
|
|
44
|
+
* @returns Score between 0 and 1
|
|
45
|
+
*/
|
|
46
|
+
function scoreGenreMatch(movie, context) {
|
|
47
|
+
if (context.targetGenres.length === 0) {
|
|
48
|
+
return 0.5; // Neutral score if no genre preference
|
|
49
|
+
}
|
|
50
|
+
if (!movie.genres || movie.genres.length === 0) {
|
|
51
|
+
return 0;
|
|
52
|
+
}
|
|
53
|
+
const movieGenreNames = movie.genres.map(g => g.name);
|
|
54
|
+
const matchCount = context.targetGenres.filter(targetGenre => movieGenreNames.includes(targetGenre)).length;
|
|
55
|
+
// Score is the ratio of matched genres to target genres
|
|
56
|
+
return matchCount / context.targetGenres.length;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Calculate platform availability score (0-1)
|
|
60
|
+
* @param movie - Movie to score
|
|
61
|
+
* @param context - Scoring context
|
|
62
|
+
* @returns Score between 0 and 1
|
|
63
|
+
*/
|
|
64
|
+
function scorePlatformMatch(movie, context) {
|
|
65
|
+
if (context.userPlatforms.length === 0) {
|
|
66
|
+
return 0.5; // Neutral score if no platform preference
|
|
67
|
+
}
|
|
68
|
+
if (!movie.platforms || movie.platforms.length === 0) {
|
|
69
|
+
return 0; // Not available on any platform
|
|
70
|
+
}
|
|
71
|
+
// Check if movie is available on any user platform
|
|
72
|
+
const isAvailable = movie.platforms.some(platform => context.userPlatforms.includes(platform));
|
|
73
|
+
return isAvailable ? 1 : 0;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Calculate runtime compatibility score (0-1)
|
|
77
|
+
* @param movie - Movie to score
|
|
78
|
+
* @param context - Scoring context
|
|
79
|
+
* @returns Score between 0 and 1
|
|
80
|
+
*/
|
|
81
|
+
function scoreRuntimeMatch(movie, context) {
|
|
82
|
+
if (context.runtimeMin === undefined && context.runtimeMax === undefined) {
|
|
83
|
+
return 0.5; // Neutral score if no runtime preference
|
|
84
|
+
}
|
|
85
|
+
if (movie.runtime === undefined || movie.runtime === null) {
|
|
86
|
+
return 0; // Can't score without runtime info
|
|
87
|
+
}
|
|
88
|
+
const { runtimeMin, runtimeMax } = context;
|
|
89
|
+
// Perfect match if within range
|
|
90
|
+
if ((runtimeMin === undefined || movie.runtime >= runtimeMin) &&
|
|
91
|
+
(runtimeMax === undefined || movie.runtime <= runtimeMax)) {
|
|
92
|
+
return 1;
|
|
93
|
+
}
|
|
94
|
+
// Calculate how far outside the range
|
|
95
|
+
let distance = 0;
|
|
96
|
+
if (runtimeMin !== undefined && movie.runtime < runtimeMin) {
|
|
97
|
+
distance = runtimeMin - movie.runtime;
|
|
98
|
+
}
|
|
99
|
+
else if (runtimeMax !== undefined && movie.runtime > runtimeMax) {
|
|
100
|
+
distance = movie.runtime - runtimeMax;
|
|
101
|
+
}
|
|
102
|
+
// Penalize based on distance (exponential decay)
|
|
103
|
+
// Movies 30+ minutes outside range get very low scores
|
|
104
|
+
return Math.max(0, Math.exp(-distance / 30));
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Calculate release year preference score (0-1)
|
|
108
|
+
* @param movie - Movie to score
|
|
109
|
+
* @param context - Scoring context
|
|
110
|
+
* @returns Score between 0 and 1
|
|
111
|
+
*/
|
|
112
|
+
function scoreYearMatch(movie, context) {
|
|
113
|
+
if (context.preferredYear === undefined &&
|
|
114
|
+
context.yearFrom === undefined &&
|
|
115
|
+
context.yearTo === undefined) {
|
|
116
|
+
return 0.5; // Neutral score if no year preference
|
|
117
|
+
}
|
|
118
|
+
if (!movie.release_date) {
|
|
119
|
+
return 0; // Can't score without release date
|
|
120
|
+
}
|
|
121
|
+
const releaseYear = parseInt(movie.release_date.split('-')[0], 10);
|
|
122
|
+
if (isNaN(releaseYear)) {
|
|
123
|
+
return 0;
|
|
124
|
+
}
|
|
125
|
+
// Perfect match for preferred year
|
|
126
|
+
if (context.preferredYear !== undefined) {
|
|
127
|
+
if (releaseYear === context.preferredYear) {
|
|
128
|
+
return 1;
|
|
129
|
+
}
|
|
130
|
+
// Decay score based on distance from preferred year
|
|
131
|
+
const distance = Math.abs(releaseYear - context.preferredYear);
|
|
132
|
+
return Math.max(0, Math.exp(-distance / 5));
|
|
133
|
+
}
|
|
134
|
+
// Check if within acceptable range
|
|
135
|
+
if ((context.yearFrom === undefined || releaseYear >= context.yearFrom) &&
|
|
136
|
+
(context.yearTo === undefined || releaseYear <= context.yearTo)) {
|
|
137
|
+
return 1;
|
|
138
|
+
}
|
|
139
|
+
// Outside range
|
|
140
|
+
return 0;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Normalize popularity score (0-1)
|
|
144
|
+
* @param movie - Movie to score
|
|
145
|
+
* @returns Normalized score between 0 and 1
|
|
146
|
+
*/
|
|
147
|
+
function scorePopularity(movie) {
|
|
148
|
+
if (movie.popularity === undefined || movie.popularity === null) {
|
|
149
|
+
return 0;
|
|
150
|
+
}
|
|
151
|
+
// TMDb popularity typically ranges from 0-1000+
|
|
152
|
+
// Use logarithmic scale to normalize
|
|
153
|
+
const maxPopularity = 1000;
|
|
154
|
+
const normalized = Math.min(movie.popularity, maxPopularity) / maxPopularity;
|
|
155
|
+
return normalized;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Calculate overall score for a movie
|
|
159
|
+
* @param movie - Movie to score
|
|
160
|
+
* @param input - User input for ranking
|
|
161
|
+
* @param context - Optional pre-built scoring context
|
|
162
|
+
* @returns Overall score (0-100)
|
|
163
|
+
*/
|
|
164
|
+
function scoreMovie(movie, input, context) {
|
|
165
|
+
const ctx = context || buildScoringContext(input);
|
|
166
|
+
const genreScore = scoreGenreMatch(movie, ctx);
|
|
167
|
+
const platformScore = scorePlatformMatch(movie, ctx);
|
|
168
|
+
const runtimeScore = scoreRuntimeMatch(movie, ctx);
|
|
169
|
+
const yearScore = scoreYearMatch(movie, ctx);
|
|
170
|
+
const popularityScore = scorePopularity(movie);
|
|
171
|
+
// Calculate weighted total
|
|
172
|
+
const totalScore = genreScore * WEIGHTS.GENRE_MATCH +
|
|
173
|
+
platformScore * WEIGHTS.PLATFORM_MATCH +
|
|
174
|
+
runtimeScore * WEIGHTS.RUNTIME_MATCH +
|
|
175
|
+
yearScore * WEIGHTS.YEAR_MATCH +
|
|
176
|
+
popularityScore * WEIGHTS.POPULARITY;
|
|
177
|
+
return totalScore;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Rank movies by score
|
|
181
|
+
* @param movies - Array of movies to rank
|
|
182
|
+
* @param input - User input for ranking
|
|
183
|
+
* @returns Sorted array of movies (highest score first)
|
|
184
|
+
*/
|
|
185
|
+
function rankMovies(movies, input) {
|
|
186
|
+
// Build context once for efficiency
|
|
187
|
+
const context = buildScoringContext(input);
|
|
188
|
+
// Score all movies
|
|
189
|
+
const scoredMovies = movies.map(movie => ({
|
|
190
|
+
movie,
|
|
191
|
+
score: scoreMovie(movie, input, context),
|
|
192
|
+
}));
|
|
193
|
+
// Sort by score (descending)
|
|
194
|
+
scoredMovies.sort((a, b) => b.score - a.score);
|
|
195
|
+
// Return sorted movies
|
|
196
|
+
return scoredMovies.map(item => item.movie);
|
|
197
|
+
}
|
|
198
|
+
//# sourceMappingURL=ranking.js.map
|