@ansango/lastfm-api 3.2.0 → 3.4.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/CHANGELOG.md +61 -0
- package/README.md +66 -22
- package/dist/client.d.ts +2 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +3 -1
- package/dist/client.js.map +1 -1
- package/dist/entrypoints/insights.d.ts +4 -0
- package/dist/entrypoints/insights.d.ts.map +1 -0
- package/dist/entrypoints/insights.js +3 -0
- package/dist/entrypoints/insights.js.map +1 -0
- package/dist/entrypoints/insights.schemas.d.ts +2 -0
- package/dist/entrypoints/insights.schemas.d.ts.map +1 -0
- package/dist/entrypoints/insights.schemas.js +3 -0
- package/dist/entrypoints/insights.schemas.js.map +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/services/album.d.ts +2 -4
- package/dist/services/album.d.ts.map +1 -1
- package/dist/services/album.js.map +1 -1
- package/dist/services/artist.d.ts +2 -4
- package/dist/services/artist.d.ts.map +1 -1
- package/dist/services/artist.js.map +1 -1
- package/dist/services/auth.d.ts +1 -38
- package/dist/services/auth.d.ts.map +1 -1
- package/dist/services/auth.js +1 -19
- package/dist/services/auth.js.map +1 -1
- package/dist/services/auth.schemas.d.ts +0 -13
- package/dist/services/auth.schemas.d.ts.map +1 -1
- package/dist/services/auth.schemas.js +0 -7
- package/dist/services/auth.schemas.js.map +1 -1
- package/dist/services/index.d.ts +2 -0
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +2 -0
- package/dist/services/index.js.map +1 -1
- package/dist/services/insights/binges.d.ts +8 -0
- package/dist/services/insights/binges.d.ts.map +1 -0
- package/dist/services/insights/binges.js +53 -0
- package/dist/services/insights/binges.js.map +1 -0
- package/dist/services/insights/compare.d.ts +8 -0
- package/dist/services/insights/compare.d.ts.map +1 -0
- package/dist/services/insights/compare.js +39 -0
- package/dist/services/insights/compare.js.map +1 -0
- package/dist/services/insights/discoveries.d.ts +8 -0
- package/dist/services/insights/discoveries.d.ts.map +1 -0
- package/dist/services/insights/discoveries.js +53 -0
- package/dist/services/insights/discoveries.js.map +1 -0
- package/dist/services/insights/hours.d.ts +8 -0
- package/dist/services/insights/hours.d.ts.map +1 -0
- package/dist/services/insights/hours.js +43 -0
- package/dist/services/insights/hours.js.map +1 -0
- package/dist/services/insights/lib/binges.d.ts +31 -0
- package/dist/services/insights/lib/binges.d.ts.map +1 -0
- package/dist/services/insights/lib/binges.js +61 -0
- package/dist/services/insights/lib/binges.js.map +1 -0
- package/dist/services/insights/lib/compare.d.ts +31 -0
- package/dist/services/insights/lib/compare.d.ts.map +1 -0
- package/dist/services/insights/lib/compare.js +68 -0
- package/dist/services/insights/lib/compare.js.map +1 -0
- package/dist/services/insights/lib/discoveries.d.ts +17 -0
- package/dist/services/insights/lib/discoveries.d.ts.map +1 -0
- package/dist/services/insights/lib/discoveries.js +24 -0
- package/dist/services/insights/lib/discoveries.js.map +1 -0
- package/dist/services/insights/lib/diversity.d.ts +28 -0
- package/dist/services/insights/lib/diversity.d.ts.map +1 -0
- package/dist/services/insights/lib/diversity.js +39 -0
- package/dist/services/insights/lib/diversity.js.map +1 -0
- package/dist/services/insights/lib/hours.d.ts +39 -0
- package/dist/services/insights/lib/hours.d.ts.map +1 -0
- package/dist/services/insights/lib/hours.js +66 -0
- package/dist/services/insights/lib/hours.js.map +1 -0
- package/dist/services/insights/lib/mood.d.ts +22 -0
- package/dist/services/insights/lib/mood.d.ts.map +1 -0
- package/dist/services/insights/lib/mood.js +187 -0
- package/dist/services/insights/lib/mood.js.map +1 -0
- package/dist/services/insights/lib/periods.d.ts +17 -0
- package/dist/services/insights/lib/periods.d.ts.map +1 -0
- package/dist/services/insights/lib/periods.js +66 -0
- package/dist/services/insights/lib/periods.js.map +1 -0
- package/dist/services/insights/lib/personality.d.ts +30 -0
- package/dist/services/insights/lib/personality.d.ts.map +1 -0
- package/dist/services/insights/lib/personality.js +131 -0
- package/dist/services/insights/lib/personality.js.map +1 -0
- package/dist/services/insights/lib/trends.d.ts +30 -0
- package/dist/services/insights/lib/trends.d.ts.map +1 -0
- package/dist/services/insights/lib/trends.js +67 -0
- package/dist/services/insights/lib/trends.js.map +1 -0
- package/dist/services/insights/mood.d.ts +8 -0
- package/dist/services/insights/mood.d.ts.map +1 -0
- package/dist/services/insights/mood.js +53 -0
- package/dist/services/insights/mood.js.map +1 -0
- package/dist/services/insights/now-playing.d.ts +20 -0
- package/dist/services/insights/now-playing.d.ts.map +1 -0
- package/dist/services/insights/now-playing.js +128 -0
- package/dist/services/insights/now-playing.js.map +1 -0
- package/dist/services/insights/personality.d.ts +8 -0
- package/dist/services/insights/personality.d.ts.map +1 -0
- package/dist/services/insights/personality.js +42 -0
- package/dist/services/insights/personality.js.map +1 -0
- package/dist/services/insights/summary.d.ts +8 -0
- package/dist/services/insights/summary.d.ts.map +1 -0
- package/dist/services/insights/summary.js +117 -0
- package/dist/services/insights/summary.js.map +1 -0
- package/dist/services/insights/trends.d.ts +8 -0
- package/dist/services/insights/trends.d.ts.map +1 -0
- package/dist/services/insights/trends.js +96 -0
- package/dist/services/insights/trends.js.map +1 -0
- package/dist/services/insights.d.ts +96 -0
- package/dist/services/insights.d.ts.map +1 -0
- package/dist/services/insights.js +26 -0
- package/dist/services/insights.js.map +1 -0
- package/dist/services/insights.schemas.d.ts +710 -0
- package/dist/services/insights.schemas.d.ts.map +1 -0
- package/dist/services/insights.schemas.js +307 -0
- package/dist/services/insights.schemas.js.map +1 -0
- package/dist/services/track.d.ts +6 -12
- package/dist/services/track.d.ts.map +1 -1
- package/dist/services/track.js.map +1 -1
- package/dist/utils.d.ts +1 -1
- package/package.json +12 -4
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Computes the Shannon diversity index over a map of item counts.
|
|
3
|
+
*/
|
|
4
|
+
export function computeDiversity(counts) {
|
|
5
|
+
const entries = Object.entries(counts).filter(([, c]) => c > 0);
|
|
6
|
+
const total = entries.reduce((acc, [, c]) => acc + c, 0);
|
|
7
|
+
const uniqueCount = entries.length;
|
|
8
|
+
if (total === 0 || uniqueCount === 0) {
|
|
9
|
+
return { total: 0, uniqueCount: 0, shannon: 0, normalized: 0 };
|
|
10
|
+
}
|
|
11
|
+
if (uniqueCount === 1) {
|
|
12
|
+
return { total, uniqueCount: 1, shannon: 0, normalized: 0 };
|
|
13
|
+
}
|
|
14
|
+
let h = 0;
|
|
15
|
+
for (const [, c] of entries) {
|
|
16
|
+
const p = c / total;
|
|
17
|
+
h -= p * Math.log(p);
|
|
18
|
+
}
|
|
19
|
+
const normalized = h / Math.log(uniqueCount);
|
|
20
|
+
return { total, uniqueCount, shannon: h, normalized };
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Fraction of total plays consumed by the top-N entries.
|
|
24
|
+
* - N larger than uniqueCount returns 1.
|
|
25
|
+
* - Empty map returns 0.
|
|
26
|
+
*/
|
|
27
|
+
export function topNShare(counts, n) {
|
|
28
|
+
const sorted = Object.values(counts)
|
|
29
|
+
.filter((c) => c > 0)
|
|
30
|
+
.sort((a, b) => b - a);
|
|
31
|
+
if (sorted.length === 0)
|
|
32
|
+
return 0;
|
|
33
|
+
const total = sorted.reduce((acc, c) => acc + c, 0);
|
|
34
|
+
if (total === 0)
|
|
35
|
+
return 0;
|
|
36
|
+
const top = sorted.slice(0, Math.max(0, n)).reduce((acc, c) => acc + c, 0);
|
|
37
|
+
return top / total;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=diversity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diversity.js","sourceRoot":"","sources":["../../../../src/services/insights/lib/diversity.ts"],"names":[],"mappings":"AAmBA;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAgB;IAChD,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;IAC/D,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,EAAE,CAAC,CAAC,CAAA;IACxD,MAAM,WAAW,GAAG,OAAO,CAAC,MAAM,CAAA;IAClC,IAAI,KAAK,KAAK,CAAC,IAAI,WAAW,KAAK,CAAC,EAAE,CAAC;QACtC,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,CAAA;IAC/D,CAAC;IACD,IAAI,WAAW,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,CAAA;IAC5D,CAAC;IACD,IAAI,CAAC,GAAG,CAAC,CAAA;IACT,KAAK,MAAM,CAAC,EAAE,CAAC,CAAC,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAA;QACnB,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;IACrB,CAAC;IACD,MAAM,UAAU,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,CAAA;IAC5C,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,EAAE,UAAU,EAAE,CAAA;AACtD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,MAAgB,EAAE,CAAS;IACpD,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC;SAClC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC;SACpB,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;IACvB,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAA;IACjC,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,EAAE,CAAC,CAAC,CAAA;IACnD,IAAI,KAAK,KAAK,CAAC;QAAE,OAAO,CAAC,CAAA;IACzB,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,EAAE,CAAC,CAAC,CAAA;IAC1E,OAAO,GAAG,GAAG,KAAK,CAAA;AACnB,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hours.ts — bucket timestamps by hour-of-day (0..23) and weekday (0..6).
|
|
3
|
+
*
|
|
4
|
+
* Weekday indexing follows ISO convention: Mon=0 .. Sun=6.
|
|
5
|
+
* All bucketing is in UTC for deterministic behavior.
|
|
6
|
+
*/
|
|
7
|
+
export declare const WEEKDAY_LABELS_EN: readonly ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
|
|
8
|
+
export declare const WEEKDAY_LABELS_ES: readonly ["lunes", "martes", "miércoles", "jueves", "viernes", "sábado", "domingo"];
|
|
9
|
+
export interface HourBucket {
|
|
10
|
+
/** Hour of the day in 24h format, 0–23 (UTC). */
|
|
11
|
+
readonly hour: number;
|
|
12
|
+
/** Day of the week, 0 = Monday … 6 = Sunday. */
|
|
13
|
+
readonly weekday: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Convert a UNIX-seconds timestamp to (hour, weekday).
|
|
17
|
+
*/
|
|
18
|
+
export declare function bucketTimestamp(unixSeconds: number): HourBucket;
|
|
19
|
+
export interface HourHistogram {
|
|
20
|
+
readonly total: number;
|
|
21
|
+
/** Counts per hour, index = hour (0..23). Length always 24. */
|
|
22
|
+
readonly byHour: number[];
|
|
23
|
+
/** Counts per weekday, index = Mon(0)..Sun(6). Length always 7. */
|
|
24
|
+
readonly byWeekday: number[];
|
|
25
|
+
/** Hour with the highest count, or null when total === 0. */
|
|
26
|
+
readonly peakHour: number | null;
|
|
27
|
+
readonly peakHourCount: number;
|
|
28
|
+
readonly peakWeekday: number | null;
|
|
29
|
+
readonly peakWeekdayCount: number;
|
|
30
|
+
readonly peakWeekdayLabel: string | null;
|
|
31
|
+
/** Time-of-day shares [0..1] */
|
|
32
|
+
readonly nightShare: number;
|
|
33
|
+
readonly morningShare: number;
|
|
34
|
+
readonly afternoonShare: number;
|
|
35
|
+
readonly eveningShare: number;
|
|
36
|
+
readonly weekendShare: number;
|
|
37
|
+
}
|
|
38
|
+
export declare function buildHourHistogram(timestamps: readonly number[]): HourHistogram;
|
|
39
|
+
//# sourceMappingURL=hours.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hours.d.ts","sourceRoot":"","sources":["../../../../src/services/insights/lib/hours.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,4DAA6D,CAAA;AAC3F,eAAO,MAAM,iBAAiB,qFAAsF,CAAA;AAEpH,MAAM,WAAW,UAAU;IAC1B,iDAAiD;IACjD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,gDAAgD;IAChD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CACxB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,UAAU,CAO/D;AAED,MAAM,WAAW,aAAa;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,+DAA+D;IAC/D,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,CAAA;IACzB,mEAAmE;IACnE,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,CAAA;IAC5B,6DAA6D;IAC7D,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IAChC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IACnC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAA;IACjC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAA;IACxC,gCAAgC;IAChC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;IAC/B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;CAC7B;AAgBD,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,GAAG,aAAa,CAmC/E"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hours.ts — bucket timestamps by hour-of-day (0..23) and weekday (0..6).
|
|
3
|
+
*
|
|
4
|
+
* Weekday indexing follows ISO convention: Mon=0 .. Sun=6.
|
|
5
|
+
* All bucketing is in UTC for deterministic behavior.
|
|
6
|
+
*/
|
|
7
|
+
export const WEEKDAY_LABELS_EN = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
|
|
8
|
+
export const WEEKDAY_LABELS_ES = ['lunes', 'martes', 'miércoles', 'jueves', 'viernes', 'sábado', 'domingo'];
|
|
9
|
+
/**
|
|
10
|
+
* Convert a UNIX-seconds timestamp to (hour, weekday).
|
|
11
|
+
*/
|
|
12
|
+
export function bucketTimestamp(unixSeconds) {
|
|
13
|
+
const date = new Date(unixSeconds * 1000);
|
|
14
|
+
const hour = date.getUTCHours();
|
|
15
|
+
// Map JS getUTCDay() (Sun=0..Sat=6) to Mon=0..Sun=6.
|
|
16
|
+
const jsDow = date.getUTCDay();
|
|
17
|
+
const weekday = (jsDow + 6) % 7;
|
|
18
|
+
return { hour, weekday };
|
|
19
|
+
}
|
|
20
|
+
function indexOfMax(arr) {
|
|
21
|
+
if (arr.length === 0)
|
|
22
|
+
return null;
|
|
23
|
+
let maxIdx = 0;
|
|
24
|
+
let maxVal = arr[0] ?? 0;
|
|
25
|
+
for (let i = 1; i < arr.length; i++) {
|
|
26
|
+
const val = arr[i] ?? 0;
|
|
27
|
+
if (val > maxVal) {
|
|
28
|
+
maxVal = val;
|
|
29
|
+
maxIdx = i;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return maxVal > 0 ? maxIdx : null;
|
|
33
|
+
}
|
|
34
|
+
export function buildHourHistogram(timestamps) {
|
|
35
|
+
const byHour = new Array(24).fill(0);
|
|
36
|
+
const byWeekday = new Array(7).fill(0);
|
|
37
|
+
for (const ts of timestamps) {
|
|
38
|
+
const b = bucketTimestamp(ts);
|
|
39
|
+
byHour[b.hour] = (byHour[b.hour] ?? 0) + 1;
|
|
40
|
+
byWeekday[b.weekday] = (byWeekday[b.weekday] ?? 0) + 1;
|
|
41
|
+
}
|
|
42
|
+
const total = timestamps.length;
|
|
43
|
+
const peakHourIdx = indexOfMax(byHour);
|
|
44
|
+
const peakWeekdayIdx = indexOfMax(byWeekday);
|
|
45
|
+
const nightCount = byHour.slice(0, 6).reduce((a, b) => a + b, 0);
|
|
46
|
+
const morningCount = byHour.slice(6, 12).reduce((a, b) => a + b, 0);
|
|
47
|
+
const afternoonCount = byHour.slice(12, 18).reduce((a, b) => a + b, 0);
|
|
48
|
+
const eveningCount = byHour.slice(18, 24).reduce((a, b) => a + b, 0);
|
|
49
|
+
const weekendCount = (byWeekday[5] ?? 0) + (byWeekday[6] ?? 0);
|
|
50
|
+
return {
|
|
51
|
+
total,
|
|
52
|
+
byHour,
|
|
53
|
+
byWeekday,
|
|
54
|
+
peakHour: peakHourIdx,
|
|
55
|
+
peakHourCount: peakHourIdx !== null ? (byHour[peakHourIdx] ?? 0) : 0,
|
|
56
|
+
peakWeekday: peakWeekdayIdx,
|
|
57
|
+
peakWeekdayCount: peakWeekdayIdx !== null ? (byWeekday[peakWeekdayIdx] ?? 0) : 0,
|
|
58
|
+
peakWeekdayLabel: peakWeekdayIdx !== null ? (WEEKDAY_LABELS_EN[peakWeekdayIdx] ?? null) : null,
|
|
59
|
+
nightShare: total > 0 ? nightCount / total : 0,
|
|
60
|
+
morningShare: total > 0 ? morningCount / total : 0,
|
|
61
|
+
afternoonShare: total > 0 ? afternoonCount / total : 0,
|
|
62
|
+
eveningShare: total > 0 ? eveningCount / total : 0,
|
|
63
|
+
weekendShare: total > 0 ? weekendCount / total : 0,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=hours.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hours.js","sourceRoot":"","sources":["../../../../src/services/insights/lib/hours.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mood.ts — derive a 2D mood profile from Last.fm tags.
|
|
3
|
+
*
|
|
4
|
+
* Axes:
|
|
5
|
+
* energy ∈ [-1, +1] -1 = ambient/sleep, +1 = hardcore/thrash
|
|
6
|
+
* valence ∈ [-1, +1] -1 = doom/sad, +1 = euphoric/uplifting
|
|
7
|
+
*/
|
|
8
|
+
export interface MoodAxes {
|
|
9
|
+
readonly energy: number;
|
|
10
|
+
readonly valence: number;
|
|
11
|
+
}
|
|
12
|
+
export interface MoodProfile {
|
|
13
|
+
readonly axes: MoodAxes;
|
|
14
|
+
readonly label: string;
|
|
15
|
+
readonly categories: readonly string[];
|
|
16
|
+
readonly confidence: number;
|
|
17
|
+
}
|
|
18
|
+
export declare function axesFromTags(tags: readonly string[]): MoodAxes;
|
|
19
|
+
export declare function categoriesFromTags(tags: readonly string[]): readonly string[];
|
|
20
|
+
export declare function labelFromAxes({ energy, valence }: MoodAxes): string;
|
|
21
|
+
export declare function classifyMood(tags: readonly string[]): MoodProfile;
|
|
22
|
+
//# sourceMappingURL=mood.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mood.d.ts","sourceRoot":"","sources":["../../../../src/services/insights/lib/mood.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAuHH,MAAM,WAAW,QAAQ;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,WAAW,WAAW;IAC3B,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAA;IACtC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;CAC3B;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,QAAQ,CAU9D;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,MAAM,EAAE,CAY7E;AAED,wBAAgB,aAAa,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,QAAQ,GAAG,MAAM,CAiBnE;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,WAAW,CAoBjE"}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mood.ts — derive a 2D mood profile from Last.fm tags.
|
|
3
|
+
*
|
|
4
|
+
* Axes:
|
|
5
|
+
* energy ∈ [-1, +1] -1 = ambient/sleep, +1 = hardcore/thrash
|
|
6
|
+
* valence ∈ [-1, +1] -1 = doom/sad, +1 = euphoric/uplifting
|
|
7
|
+
*/
|
|
8
|
+
const ENERGY = {
|
|
9
|
+
// high energy
|
|
10
|
+
hardcore: 1,
|
|
11
|
+
punk: 0.8,
|
|
12
|
+
'punk rock': 0.8,
|
|
13
|
+
'hard rock': 0.7,
|
|
14
|
+
'thrash metal': 1,
|
|
15
|
+
'speed metal': 1,
|
|
16
|
+
grindcore: 1,
|
|
17
|
+
drum: 0.7,
|
|
18
|
+
'drum and bass': 0.8,
|
|
19
|
+
dnb: 0.8,
|
|
20
|
+
techno: 0.6,
|
|
21
|
+
gabber: 1,
|
|
22
|
+
'happy hardcore': 1,
|
|
23
|
+
rave: 0.9,
|
|
24
|
+
thrash: 0.9,
|
|
25
|
+
metal: 0.6,
|
|
26
|
+
'heavy metal': 0.7,
|
|
27
|
+
'death metal': 0.8,
|
|
28
|
+
'black metal': 0.6,
|
|
29
|
+
rock: 0.5,
|
|
30
|
+
'indie rock': 0.4,
|
|
31
|
+
pop: 0.3,
|
|
32
|
+
dance: 0.6,
|
|
33
|
+
hip: 0.5,
|
|
34
|
+
rap: 0.4,
|
|
35
|
+
hiphop: 0.5,
|
|
36
|
+
// low energy
|
|
37
|
+
ambient: -1,
|
|
38
|
+
drone: -0.9,
|
|
39
|
+
sleep: -1,
|
|
40
|
+
'new age': -0.8,
|
|
41
|
+
meditation: -0.9,
|
|
42
|
+
downtempo: -0.6,
|
|
43
|
+
chillout: -0.6,
|
|
44
|
+
chill: -0.6,
|
|
45
|
+
lounge: -0.5,
|
|
46
|
+
'trip-hop': -0.4,
|
|
47
|
+
'slow core': -0.5,
|
|
48
|
+
slowcore: -0.5,
|
|
49
|
+
ballad: -0.4,
|
|
50
|
+
jazz: -0.2,
|
|
51
|
+
'smooth jazz': -0.4,
|
|
52
|
+
'bossa nova': -0.3,
|
|
53
|
+
};
|
|
54
|
+
const VALENCE = {
|
|
55
|
+
// high valence (euphoric / happy)
|
|
56
|
+
euphoric: 1,
|
|
57
|
+
happy: 1,
|
|
58
|
+
uplifting: 0.9,
|
|
59
|
+
summer: 0.5,
|
|
60
|
+
sunshine: 0.6,
|
|
61
|
+
'feel good': 0.8,
|
|
62
|
+
feelgood: 0.8,
|
|
63
|
+
dance: 0.6,
|
|
64
|
+
party: 0.7,
|
|
65
|
+
pop: 0.4,
|
|
66
|
+
'power pop': 0.6,
|
|
67
|
+
'bubblegum pop': 0.5,
|
|
68
|
+
'k-pop': 0.4,
|
|
69
|
+
// low valence (sad / dark)
|
|
70
|
+
doom: -1,
|
|
71
|
+
sad: -1,
|
|
72
|
+
melancholic: -0.9,
|
|
73
|
+
melancholy: -0.9,
|
|
74
|
+
depressive: -1,
|
|
75
|
+
black: -0.7,
|
|
76
|
+
'black metal': -0.7,
|
|
77
|
+
'dark ambient': -0.8,
|
|
78
|
+
dark: -0.6,
|
|
79
|
+
funeral: -1,
|
|
80
|
+
'funeral doom': -1,
|
|
81
|
+
'doom metal': -0.9,
|
|
82
|
+
goth: -0.7,
|
|
83
|
+
gothic: -0.7,
|
|
84
|
+
'gothic rock': -0.6,
|
|
85
|
+
'post-punk': -0.4,
|
|
86
|
+
coldwave: -0.6,
|
|
87
|
+
// neutral-ish
|
|
88
|
+
indie: 0,
|
|
89
|
+
'indie rock': 0,
|
|
90
|
+
rock: 0,
|
|
91
|
+
electronic: 0,
|
|
92
|
+
experimental: 0,
|
|
93
|
+
};
|
|
94
|
+
const CATEGORIES = {
|
|
95
|
+
rock: ['rock', 'indie rock', 'post-rock', 'garage rock', 'hard rock'],
|
|
96
|
+
electronic: ['electronic', 'techno', 'house', 'ambient', 'idm', 'dnb', 'drum and bass', 'dubstep', 'synth'],
|
|
97
|
+
'hip-hop': ['hip-hop', 'hip hop', 'rap', 'trap'],
|
|
98
|
+
jazz: ['jazz', 'bebop', 'bossa nova', 'smooth jazz', 'nu jazz', 'free jazz'],
|
|
99
|
+
folk: ['folk', 'singer-songwriter', 'acoustic', 'indie folk', 'folk rock'],
|
|
100
|
+
classical: ['classical', 'baroque', 'romantic', 'modern classical', 'opera', 'symphony'],
|
|
101
|
+
ambient: ['ambient', 'drone', 'new age', 'meditation', 'dark ambient'],
|
|
102
|
+
experimental: ['experimental', 'noise', 'avant-garde', 'free improvisation'],
|
|
103
|
+
metal: ['metal', 'thrash metal', 'doom metal', 'death metal', 'black metal', 'heavy metal', 'sludge'],
|
|
104
|
+
pop: ['pop', 'synth-pop', 'electropop', 'k-pop', 'indie pop', 'dream pop'],
|
|
105
|
+
world: ['world', 'afrobeat', 'latin', 'flamenco', 'brazilian', 'reggae'],
|
|
106
|
+
punk: ['punk', 'post-punk', 'hardcore', 'anarcho-punk'],
|
|
107
|
+
country: ['country', 'bluegrass', 'outlaw country', 'country rock'],
|
|
108
|
+
soul: ['soul', 'r&b', 'funk', 'motown', 'neo-soul'],
|
|
109
|
+
};
|
|
110
|
+
function normalize(tag) {
|
|
111
|
+
return tag.toLowerCase().trim();
|
|
112
|
+
}
|
|
113
|
+
function lookup(map, tag) {
|
|
114
|
+
return map[tag] ?? 0;
|
|
115
|
+
}
|
|
116
|
+
function round2(n) {
|
|
117
|
+
return Math.round(n * 100) / 100;
|
|
118
|
+
}
|
|
119
|
+
export function axesFromTags(tags) {
|
|
120
|
+
if (tags.length === 0)
|
|
121
|
+
return { energy: 0, valence: 0 };
|
|
122
|
+
let energy = 0;
|
|
123
|
+
let valence = 0;
|
|
124
|
+
for (const t of tags) {
|
|
125
|
+
energy += lookup(ENERGY, normalize(t));
|
|
126
|
+
valence += lookup(VALENCE, normalize(t));
|
|
127
|
+
}
|
|
128
|
+
const avg = (n) => Math.max(-1, Math.min(1, n / tags.length));
|
|
129
|
+
return { energy: round2(avg(energy)), valence: round2(avg(valence)) };
|
|
130
|
+
}
|
|
131
|
+
export function categoriesFromTags(tags) {
|
|
132
|
+
if (tags.length === 0)
|
|
133
|
+
return [];
|
|
134
|
+
const counts = new Map();
|
|
135
|
+
for (const tag of tags) {
|
|
136
|
+
const norm = normalize(tag);
|
|
137
|
+
for (const [cat, members] of Object.entries(CATEGORIES)) {
|
|
138
|
+
if (members.includes(norm)) {
|
|
139
|
+
counts.set(cat, (counts.get(cat) ?? 0) + 1);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return [...counts.entries()].sort((a, b) => b[1] - a[1]).map(([cat]) => cat);
|
|
144
|
+
}
|
|
145
|
+
export function labelFromAxes({ energy, valence }) {
|
|
146
|
+
if (energy === 0 && valence === 0)
|
|
147
|
+
return 'neutral / undefined';
|
|
148
|
+
const eHigh = energy > 0.2;
|
|
149
|
+
const eLow = energy < -0.2;
|
|
150
|
+
const vHigh = valence > 0.2;
|
|
151
|
+
const vLow = valence < -0.2;
|
|
152
|
+
if (eHigh && vHigh)
|
|
153
|
+
return 'euphoric & energetic';
|
|
154
|
+
if (eHigh && vLow)
|
|
155
|
+
return 'intense & dark';
|
|
156
|
+
if (eLow && vHigh)
|
|
157
|
+
return 'chill & uplifting';
|
|
158
|
+
if (eLow && vLow)
|
|
159
|
+
return 'melancholic & calm';
|
|
160
|
+
if (eHigh && !vHigh && !vLow)
|
|
161
|
+
return 'high energy';
|
|
162
|
+
if (eLow && !vHigh && !vLow)
|
|
163
|
+
return 'low energy / ambient';
|
|
164
|
+
if (vHigh && !eHigh && !eLow)
|
|
165
|
+
return 'positive / bright';
|
|
166
|
+
if (vLow && !eHigh && !eLow)
|
|
167
|
+
return 'somber / melancholic';
|
|
168
|
+
return 'neutral / undefined';
|
|
169
|
+
}
|
|
170
|
+
export function classifyMood(tags) {
|
|
171
|
+
const axes = axesFromTags(tags);
|
|
172
|
+
const categories = categoriesFromTags(tags);
|
|
173
|
+
const recognized = tags.filter((t) => {
|
|
174
|
+
const norm = normalize(t);
|
|
175
|
+
return (ENERGY[norm] !== undefined ||
|
|
176
|
+
VALENCE[norm] !== undefined ||
|
|
177
|
+
Object.values(CATEGORIES).some((arr) => arr.includes(norm)));
|
|
178
|
+
}).length;
|
|
179
|
+
const confidence = tags.length === 0 ? 0 : round2(recognized / tags.length);
|
|
180
|
+
return {
|
|
181
|
+
axes,
|
|
182
|
+
label: labelFromAxes(axes),
|
|
183
|
+
categories,
|
|
184
|
+
confidence,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=mood.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mood.js","sourceRoot":"","sources":["../../../../src/services/insights/lib/mood.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Period as CanonicalPeriod } from '../../schemas/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Supported period inputs for insights (canonical tokens + human aliases)
|
|
4
|
+
*/
|
|
5
|
+
export type InsightsPeriodInput = CanonicalPeriod | 'daily' | 'weekly' | 'monthly' | 'yearly';
|
|
6
|
+
export interface ResolvedPeriod {
|
|
7
|
+
readonly lastfm: CanonicalPeriod;
|
|
8
|
+
readonly from?: number;
|
|
9
|
+
readonly to: number;
|
|
10
|
+
readonly label: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function periodToLastfm(p: string): CanonicalPeriod;
|
|
13
|
+
/**
|
|
14
|
+
* Resolves an insight period into a canonical Last.fm period token and time range
|
|
15
|
+
*/
|
|
16
|
+
export declare function resolvePeriod(p?: string, clock?: () => number): ResolvedPeriod;
|
|
17
|
+
//# sourceMappingURL=periods.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"periods.d.ts","sourceRoot":"","sources":["../../../../src/services/insights/lib/periods.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,IAAI,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAEvE;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG,eAAe,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAA;AAE7F,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAA;IAChC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CACtB;AA6CD,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,GAAG,eAAe,CAEzD;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,CAAC,GAAE,MAAe,EAAE,KAAK,GAAE,MAAM,MAAiB,GAAG,cAAc,CAqBhG"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
const SECOND = 1;
|
|
2
|
+
const MINUTE = 60 * SECOND;
|
|
3
|
+
const HOUR = 60 * MINUTE;
|
|
4
|
+
const DAY = 24 * HOUR;
|
|
5
|
+
const LASTFM_BY_PERIOD = {
|
|
6
|
+
daily: '7day',
|
|
7
|
+
weekly: '7day',
|
|
8
|
+
monthly: '1month',
|
|
9
|
+
yearly: '12month',
|
|
10
|
+
'7day': '7day',
|
|
11
|
+
'1month': '1month',
|
|
12
|
+
'3month': '3month',
|
|
13
|
+
'6month': '6month',
|
|
14
|
+
'12month': '12month',
|
|
15
|
+
overall: 'overall',
|
|
16
|
+
};
|
|
17
|
+
const LABEL_BY_PERIOD = {
|
|
18
|
+
daily: 'last 24 hours',
|
|
19
|
+
weekly: 'this week',
|
|
20
|
+
monthly: 'this month',
|
|
21
|
+
yearly: 'this year',
|
|
22
|
+
'7day': 'last 7 days',
|
|
23
|
+
'1month': 'last 30 days',
|
|
24
|
+
'3month': 'last 90 days',
|
|
25
|
+
'6month': 'last 180 days',
|
|
26
|
+
'12month': 'last 365 days',
|
|
27
|
+
overall: 'all time',
|
|
28
|
+
};
|
|
29
|
+
const WINDOW_SECONDS = {
|
|
30
|
+
daily: 1 * DAY,
|
|
31
|
+
weekly: 7 * DAY,
|
|
32
|
+
monthly: 30 * DAY,
|
|
33
|
+
yearly: 365 * DAY,
|
|
34
|
+
'7day': 7 * DAY,
|
|
35
|
+
'1month': 30 * DAY,
|
|
36
|
+
'3month': 90 * DAY,
|
|
37
|
+
'6month': 180 * DAY,
|
|
38
|
+
'12month': 365 * DAY,
|
|
39
|
+
};
|
|
40
|
+
export function periodToLastfm(p) {
|
|
41
|
+
return LASTFM_BY_PERIOD[p] ?? '7day';
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Resolves an insight period into a canonical Last.fm period token and time range
|
|
45
|
+
*/
|
|
46
|
+
export function resolvePeriod(p = '7day', clock = Date.now) {
|
|
47
|
+
const lastfm = periodToLastfm(p);
|
|
48
|
+
const label = LABEL_BY_PERIOD[p] ?? LABEL_BY_PERIOD[lastfm] ?? p;
|
|
49
|
+
const to = Math.floor(clock() / 1000);
|
|
50
|
+
if (p === 'overall' || lastfm === 'overall') {
|
|
51
|
+
return {
|
|
52
|
+
lastfm: 'overall',
|
|
53
|
+
to,
|
|
54
|
+
label: 'all time',
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
const span = WINDOW_SECONDS[p] ?? WINDOW_SECONDS[lastfm] ?? 7 * DAY;
|
|
58
|
+
const from = to - span;
|
|
59
|
+
return {
|
|
60
|
+
lastfm,
|
|
61
|
+
from,
|
|
62
|
+
to,
|
|
63
|
+
label,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=periods.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"periods.js","sourceRoot":"","sources":["../../../../src/services/insights/lib/periods.ts"],"names":[],"mappings":"AAcA,MAAM,MAAM,GAAG,CAAC,CAAA;AAChB,MAAM,MAAM,GAAG,EAAE,GAAG,MAAM,CAAA;AAC1B,MAAM,IAAI,GAAG,EAAE,GAAG,MAAM,CAAA;AACxB,MAAM,GAAG,GAAG,EAAE,GAAG,IAAI,CAAA;AAErB,MAAM,gBAAgB,GAAoC;IACzD,KAAK,EAAE,MAAM;IACb,MAAM,EAAE,MAAM;IACd,OAAO,EAAE,QAAQ;IACjB,MAAM,EAAE,SAAS;IACjB,MAAM,EAAE,MAAM;IACd,QAAQ,EAAE,QAAQ;IAClB,QAAQ,EAAE,QAAQ;IAClB,QAAQ,EAAE,QAAQ;IAClB,SAAS,EAAE,SAAS;IACpB,OAAO,EAAE,SAAS;CAClB,CAAA;AAED,MAAM,eAAe,GAA2B;IAC/C,KAAK,EAAE,eAAe;IACtB,MAAM,EAAE,WAAW;IACnB,OAAO,EAAE,YAAY;IACrB,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,aAAa;IACrB,QAAQ,EAAE,cAAc;IACxB,QAAQ,EAAE,cAAc;IACxB,QAAQ,EAAE,eAAe;IACzB,SAAS,EAAE,eAAe;IAC1B,OAAO,EAAE,UAAU;CACnB,CAAA;AAED,MAAM,cAAc,GAA2B;IAC9C,KAAK,EAAE,CAAC,GAAG,GAAG;IACd,MAAM,EAAE,CAAC,GAAG,GAAG;IACf,OAAO,EAAE,EAAE,GAAG,GAAG;IACjB,MAAM,EAAE,GAAG,GAAG,GAAG;IACjB,MAAM,EAAE,CAAC,GAAG,GAAG;IACf,QAAQ,EAAE,EAAE,GAAG,GAAG;IAClB,QAAQ,EAAE,EAAE,GAAG,GAAG;IAClB,QAAQ,EAAE,GAAG,GAAG,GAAG;IACnB,SAAS,EAAE,GAAG,GAAG,GAAG;CACpB,CAAA;AAED,MAAM,UAAU,cAAc,CAAC,CAAS;IACvC,OAAO,gBAAgB,CAAC,CAAC,CAAC,IAAI,MAAM,CAAA;AACrC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY,MAAM,EAAE,QAAsB,IAAI,CAAC,GAAG;IAC/E,MAAM,MAAM,GAAG,cAAc,CAAC,CAAC,CAAC,CAAA;IAChC,MAAM,KAAK,GAAG,eAAe,CAAC,CAAC,CAAC,IAAI,eAAe,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IAChE,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC,CAAA;IAErC,IAAI,CAAC,KAAK,SAAS,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QAC7C,OAAO;YACN,MAAM,EAAE,SAAS;YACjB,EAAE;YACF,KAAK,EAAE,UAAU;SACjB,CAAA;IACF,CAAC;IAED,MAAM,IAAI,GAAG,cAAc,CAAC,CAAC,CAAC,IAAI,cAAc,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,GAAG,CAAA;IACnE,MAAM,IAAI,GAAG,EAAE,GAAG,IAAI,CAAA;IACtB,OAAO;QACN,MAAM;QACN,IAAI;QACJ,EAAE;QACF,KAAK;KACL,CAAA;AACF,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* personality.ts — classify the user into a listening personality archetype.
|
|
3
|
+
*/
|
|
4
|
+
export type ArchetypeId = 'Devotee' | 'Explorer' | 'Drifter' | 'DJ' | 'Nocturnal' | 'Archivist';
|
|
5
|
+
export declare const ARCHETYPE_ORDER: readonly ArchetypeId[];
|
|
6
|
+
export interface PersonalityFeatures {
|
|
7
|
+
readonly totalScrobbles: number;
|
|
8
|
+
readonly uniqueArtists: number;
|
|
9
|
+
readonly top1Share: number;
|
|
10
|
+
readonly top3Share: number;
|
|
11
|
+
readonly top5Share: number;
|
|
12
|
+
readonly normalizedDiversity: number;
|
|
13
|
+
readonly newArtistsLast30d: number;
|
|
14
|
+
readonly totalArtistsLast30d: number;
|
|
15
|
+
readonly nightHourShare: number;
|
|
16
|
+
readonly morningHourShare: number;
|
|
17
|
+
readonly weekdayShare: number;
|
|
18
|
+
}
|
|
19
|
+
export interface PersonalityResult {
|
|
20
|
+
readonly scores: Record<ArchetypeId, number>;
|
|
21
|
+
readonly winner: ArchetypeId;
|
|
22
|
+
readonly reasons: readonly string[];
|
|
23
|
+
}
|
|
24
|
+
export declare const ARCHETYPE_INFO: Record<ArchetypeId, {
|
|
25
|
+
name: string;
|
|
26
|
+
emoji: string;
|
|
27
|
+
blurb: string;
|
|
28
|
+
}>;
|
|
29
|
+
export declare function scoreArchetypes(features: PersonalityFeatures): PersonalityResult;
|
|
30
|
+
//# sourceMappingURL=personality.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"personality.d.ts","sourceRoot":"","sources":["../../../../src/services/insights/lib/personality.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,GAAG,IAAI,GAAG,WAAW,GAAG,WAAW,CAAA;AAE/F,eAAO,MAAM,eAAe,EAAE,SAAS,WAAW,EAOjD,CAAA;AAED,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;IAC/B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAA;IACpC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAA;IAClC,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAA;IACpC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;IAC/B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAA;IACjC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;CAC7B;AAED,MAAM,WAAW,iBAAiB;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAAA;IAC5C,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;IAC5B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;CACnC;AAMD,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,WAAW,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CA+B9F,CAAA;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,mBAAmB,GAAG,iBAAiB,CA2FhF"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* personality.ts — classify the user into a listening personality archetype.
|
|
3
|
+
*/
|
|
4
|
+
export const ARCHETYPE_ORDER = [
|
|
5
|
+
'Devotee',
|
|
6
|
+
'Explorer',
|
|
7
|
+
'Drifter',
|
|
8
|
+
'DJ',
|
|
9
|
+
'Nocturnal',
|
|
10
|
+
'Archivist',
|
|
11
|
+
];
|
|
12
|
+
function clamp01(n) {
|
|
13
|
+
return Math.max(0, Math.min(1, n));
|
|
14
|
+
}
|
|
15
|
+
export const ARCHETYPE_INFO = {
|
|
16
|
+
Devotee: {
|
|
17
|
+
name: 'The Devotee',
|
|
18
|
+
emoji: '🛐',
|
|
19
|
+
blurb: 'Extreme loyalty to your favorite artists. High concentration, low dispersion.',
|
|
20
|
+
},
|
|
21
|
+
Explorer: {
|
|
22
|
+
name: 'The Explorer',
|
|
23
|
+
emoji: '🧭',
|
|
24
|
+
blurb: 'Always discovering new music. Your musical radar is constantly searching.',
|
|
25
|
+
},
|
|
26
|
+
Drifter: {
|
|
27
|
+
name: 'The Drifter',
|
|
28
|
+
emoji: '🍂',
|
|
29
|
+
blurb: 'Balanced listening across varied genres without strong fixations.',
|
|
30
|
+
},
|
|
31
|
+
DJ: {
|
|
32
|
+
name: 'The DJ',
|
|
33
|
+
emoji: '🎧',
|
|
34
|
+
blurb: 'High listening velocity across a broad and rotating selection of artists.',
|
|
35
|
+
},
|
|
36
|
+
Nocturnal: {
|
|
37
|
+
name: 'The Nocturnal',
|
|
38
|
+
emoji: '🌙',
|
|
39
|
+
blurb: 'Your music comes alive at night. The late hours are your peak listening zone.',
|
|
40
|
+
},
|
|
41
|
+
Archivist: {
|
|
42
|
+
name: 'The Archivist',
|
|
43
|
+
emoji: '📚',
|
|
44
|
+
blurb: 'A massive, eclectic, and deep personal music library with balanced play counts.',
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
export function scoreArchetypes(features) {
|
|
48
|
+
const scores = {
|
|
49
|
+
Devotee: 0,
|
|
50
|
+
Explorer: 0,
|
|
51
|
+
Drifter: 0,
|
|
52
|
+
DJ: 0,
|
|
53
|
+
Nocturnal: 0,
|
|
54
|
+
Archivist: 0,
|
|
55
|
+
};
|
|
56
|
+
const reasons = {
|
|
57
|
+
Devotee: [],
|
|
58
|
+
Explorer: [],
|
|
59
|
+
Drifter: [],
|
|
60
|
+
DJ: [],
|
|
61
|
+
Nocturnal: [],
|
|
62
|
+
Archivist: [],
|
|
63
|
+
};
|
|
64
|
+
// Devotee: high top1Share + low diversity + small unique roster
|
|
65
|
+
{
|
|
66
|
+
const focus = clamp01((features.top1Share - 0.15) / 0.5);
|
|
67
|
+
const monotony = clamp01((0.6 - features.normalizedDiversity) / 0.6);
|
|
68
|
+
const smallness = clamp01((20 - features.uniqueArtists) / 20);
|
|
69
|
+
scores.Devotee = Math.round((0.5 * focus + 0.3 * monotony + 0.2 * smallness) * 100) / 100;
|
|
70
|
+
if (focus > 0.5)
|
|
71
|
+
reasons.Devotee.push(`Top artist accounts for ${(features.top1Share * 100).toFixed(0)}% of scrobbles.`);
|
|
72
|
+
if (monotony > 0.5)
|
|
73
|
+
reasons.Devotee.push(`Low diversity score (${features.normalizedDiversity.toFixed(2)}).`);
|
|
74
|
+
}
|
|
75
|
+
// Explorer: high diversity + many new artists relative to total roster
|
|
76
|
+
{
|
|
77
|
+
const diversity = features.normalizedDiversity;
|
|
78
|
+
const newRatio = features.totalArtistsLast30d > 0 ? clamp01(features.newArtistsLast30d / features.totalArtistsLast30d) : 0;
|
|
79
|
+
scores.Explorer = Math.round((0.5 * diversity + 0.5 * newRatio) * 100) / 100;
|
|
80
|
+
if (diversity > 0.7)
|
|
81
|
+
reasons.Explorer.push(`High diversity score (${diversity.toFixed(2)}).`);
|
|
82
|
+
if (newRatio > 0.2)
|
|
83
|
+
reasons.Explorer.push(`${features.newArtistsLast30d} new artists discovered this month out of ${features.totalArtistsLast30d} played.`);
|
|
84
|
+
}
|
|
85
|
+
// Drifter: middle of the road on diversity and low novelty
|
|
86
|
+
{
|
|
87
|
+
const midDiversity = 1 - Math.abs(features.normalizedDiversity - 0.65) / 0.35;
|
|
88
|
+
const lowNovelty = clamp01((0.15 - (features.totalArtistsLast30d > 0 ? features.newArtistsLast30d / features.totalArtistsLast30d : 0)) /
|
|
89
|
+
0.15);
|
|
90
|
+
const lowFocus = clamp01((0.4 - features.top3Share) / 0.4);
|
|
91
|
+
scores.Drifter = Math.round((0.4 * midDiversity + 0.3 * lowNovelty + 0.3 * lowFocus) * 100) / 100;
|
|
92
|
+
if (midDiversity > 0.7)
|
|
93
|
+
reasons.Drifter.push(`Moderate diversity (${features.normalizedDiversity.toFixed(2)}).`);
|
|
94
|
+
}
|
|
95
|
+
// DJ: high velocity + many artists
|
|
96
|
+
{
|
|
97
|
+
const velocity = clamp01((features.totalScrobbles - 200) / 1800);
|
|
98
|
+
const breadth = clamp01((features.uniqueArtists - 20) / 80);
|
|
99
|
+
scores.DJ = Math.round((0.5 * velocity + 0.3 * breadth + 0.2 * features.normalizedDiversity) * 100) / 100;
|
|
100
|
+
if (velocity > 0.5)
|
|
101
|
+
reasons.DJ.push(`${features.totalScrobbles} total scrobbles in the window.`);
|
|
102
|
+
}
|
|
103
|
+
// Nocturnal: nightHourShare dominant
|
|
104
|
+
{
|
|
105
|
+
const nightness = clamp01((features.nightHourShare - 0.4) / 0.2);
|
|
106
|
+
const antiDaytime = clamp01((0.3 - features.morningHourShare) / 0.3);
|
|
107
|
+
scores.Nocturnal = Math.round((0.7 * nightness + 0.3 * antiDaytime) * 100) / 100;
|
|
108
|
+
if (nightness > 0.5)
|
|
109
|
+
reasons.Nocturnal.push(`${(features.nightHourShare * 100).toFixed(0)}% of scrobbles occurred during night hours.`);
|
|
110
|
+
}
|
|
111
|
+
// Archivist: massive library, spread across many artists
|
|
112
|
+
{
|
|
113
|
+
const massive = clamp01((features.totalScrobbles - 1500) / 3500);
|
|
114
|
+
const hugeRoster = clamp01((features.uniqueArtists - 100) / 200);
|
|
115
|
+
const spread = clamp01((1 - features.top5Share) / 0.5);
|
|
116
|
+
scores.Archivist = Math.round((0.4 * massive + 0.4 * hugeRoster + 0.2 * spread) * 100) / 100;
|
|
117
|
+
if (hugeRoster > 0.5)
|
|
118
|
+
reasons.Archivist.push(`${features.uniqueArtists} unique artists in window.`);
|
|
119
|
+
}
|
|
120
|
+
let winner = 'Devotee';
|
|
121
|
+
let best = -Number.POSITIVE_INFINITY;
|
|
122
|
+
for (const id of ARCHETYPE_ORDER) {
|
|
123
|
+
const s = scores[id];
|
|
124
|
+
if (s > best) {
|
|
125
|
+
best = s;
|
|
126
|
+
winner = id;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return { scores, winner, reasons: reasons[winner] ?? [] };
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=personality.js.map
|