File size: 2,011 Bytes
ad19202
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
657bb2e
ad19202
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
import {
	assignBadges,
	buildActivitySnapshot,
	buildSlides,
	deriveArchetype,
} from "../domain/aggregate";
import type { GenerateWrappedInput, WrappedResult } from "../domain/types";
import {
	readCachedWrapped,
	writeWrappedSnapshot,
} from "../infrastructure/dataset-client";
import { fetchHubActivity } from "../infrastructure/hub-client";

const FREEZE_DATE = new Date("2026-01-01T00:00:00.000Z");

export async function generateWrapped(
	input: GenerateWrappedInput,
): Promise<WrappedResult> {
	const year = input.year ?? new Date().getUTCFullYear();
	const normalized: GenerateWrappedInput = {
		handle: input.handle.trim(),
		year,
		subjectType: input.subjectType ?? "auto",
		allowRefresh: input.allowRefresh ?? false,
	};

	if (!normalized.allowRefresh) {
		const cached = await readCachedWrapped(normalized);
		if (cached) {
			return { ...cached, cached: true, source: "cache" };
		}
	}

	if (!isRefreshAllowed(normalized.year) && normalized.allowRefresh) {
		throw new Error("Refresh window is closed for this year");
	}

	const activity = await fetchHubActivity(
		normalized.handle,
		normalized.subjectType === "auto"
			? "user"
			: (normalized.subjectType ?? "user"),
		normalized.year,
	);

	const snapshot = buildActivitySnapshot(
		activity.models,
		activity.datasets,
		activity.spaces,
		activity.papers,
	);
	const archetype = deriveArchetype(snapshot);
	const badges = assignBadges(snapshot);
	const slides = buildSlides({
		profile: activity.profile,
		year: normalized.year,
		activity: snapshot,
		archetype,
		badges,
	});

	const result: WrappedResult = {
		profile: activity.profile,
		year: normalized.year,
		activity: snapshot,
		archetype,
		badges,
		slides,
		cached: false,
		generatedAt: new Date().toISOString(),
		source: "live",
	};

	await writeWrappedSnapshot(result);

	return result;
}

function isRefreshAllowed(year: number): boolean {
	const now = new Date();
	if (year < now.getUTCFullYear()) {
		return now < FREEZE_DATE;
	}
	return now < FREEZE_DATE;
}