education-playlists 0.1.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.
@@ -0,0 +1,131 @@
1
+ /**
2
+ * @fileoverview The data model: a college-style catalog (category → major →
3
+ * program) of playlists, each an ordered list of self-paced items (videos and
4
+ * courseware links) that carry a time estimate and where they came from.
5
+ *
6
+ * Provenance is on every item, not on the catalog, because a playlist mixes
7
+ * sources: an official MIT record, a community-curated link and a result an
8
+ * LLM search found are not equally fresh or authoritative, and the UI should
9
+ * be able to say which one it is showing.
10
+ */
11
+ /** Who publishes the material an item links to. */
12
+ export type EducationProvider = 'mit_ocw' | 'edx' | 'coursera' | 'khan' | 'openstax' | 'youtube' | 'web';
13
+ /** How an item's record entered the catalog. */
14
+ export type ProvenanceMethod =
15
+ /** Hand-picked into the bundled seed; the URL is the course's canonical page. */
16
+ 'curated_seed'
17
+ /** Pulled from a provider's own API (e.g. MIT Learn via `ocw_oer_export`). */
18
+ | 'official_api'
19
+ /** Pulled from a third-party dataset snapshot (e.g. a Kaggle export). */
20
+ | 'snapshot_dataset'
21
+ /** Parsed from a community awesome-list. */
22
+ | 'community_list'
23
+ /** Found by the planner's web search for a custom playlist. */
24
+ | 'llm_search'
25
+ /** Added by hand by a playlist's owner. */
26
+ | 'user';
27
+ export interface Provenance {
28
+ provider: EducationProvider;
29
+ method: ProvenanceMethod;
30
+ /** The page or dataset the record was read from, when it differs from the item URL. */
31
+ sourceUrl?: string;
32
+ /** ISO date the record was last read from its source. */
33
+ fetchedAt?: string;
34
+ /**
35
+ * Whether the URL itself was checked against the provider. Seed records are
36
+ * canonical course pages; deep links (individual videos, PDFs) are not
37
+ * marked verified until an ingestion run has confirmed them.
38
+ */
39
+ verified: boolean;
40
+ note?: string;
41
+ }
42
+ /** What following an item's link gets you. */
43
+ export type PlaylistItemKind = 'video_playlist' | 'video' | 'courseware' | 'lecture_notes' | 'assignment' | 'reading' | 'textbook';
44
+ export type CourseLevel = 'introductory' | 'intermediate' | 'advanced';
45
+ /** One self-paced step in a playlist. */
46
+ export interface PlaylistItem {
47
+ /** Stable within the catalog; progress is keyed by it. */
48
+ id: string;
49
+ title: string;
50
+ url: string;
51
+ kind: PlaylistItemKind;
52
+ /** Expected time to finish, in minutes. */
53
+ minutes: number;
54
+ /**
55
+ * `exact` when the provider states a duration (a video's length); `rough`
56
+ * when it is derived, e.g. lecture count × typical lecture length.
57
+ */
58
+ estimate: 'exact' | 'rough';
59
+ /** How the estimate was reached, for a `rough` one. */
60
+ estimateBasis?: string;
61
+ /** Course number at the institution, e.g. "6.0001". */
62
+ courseNumber?: string;
63
+ institution?: string;
64
+ level?: CourseLevel;
65
+ description?: string;
66
+ provenance: Provenance;
67
+ }
68
+ /**
69
+ * - `preset`: ships with the catalog, read-only, everyone sees it.
70
+ * - `public`: someone's playlist, readable by anyone with the link.
71
+ * - `private`: readable only by its owner and the members they invited.
72
+ */
73
+ export type PlaylistVisibility = 'preset' | 'public' | 'private';
74
+ export type PlaylistRole = 'owner' | 'editor' | 'viewer';
75
+ export interface PlaylistMember {
76
+ /** Set once the invite is accepted. */
77
+ userId?: string;
78
+ /** Who the invite was addressed to. */
79
+ email?: string;
80
+ role: PlaylistRole;
81
+ status: 'invited' | 'active';
82
+ /** Opaque token an invite link carries; cleared when accepted. */
83
+ inviteToken?: string;
84
+ }
85
+ export interface Playlist {
86
+ id: string;
87
+ title: string;
88
+ description?: string;
89
+ categoryId: string;
90
+ majorId: string;
91
+ programId?: string;
92
+ level?: CourseLevel;
93
+ visibility: PlaylistVisibility;
94
+ ownerId?: string;
95
+ members?: PlaylistMember[];
96
+ items: PlaylistItem[];
97
+ /** For a planner-built playlist: the goal and answers it was planned from. */
98
+ plannedFrom?: {
99
+ goal: string;
100
+ answers: PlanAnswer[];
101
+ };
102
+ createdAt?: string;
103
+ updatedAt?: string;
104
+ }
105
+ /** One follow-up question the planner asked, and what the learner said. */
106
+ export interface PlanAnswer {
107
+ question: string;
108
+ answer: string;
109
+ }
110
+ /** A track inside a major, the way a department lists concentrations. */
111
+ export interface Program {
112
+ id: string;
113
+ name: string;
114
+ description?: string;
115
+ }
116
+ export interface Major {
117
+ id: string;
118
+ name: string;
119
+ /** Department number at MIT, when the major maps to one (e.g. "6", "18"). */
120
+ department?: string;
121
+ programs: Program[];
122
+ }
123
+ export interface Category {
124
+ id: string;
125
+ name: string;
126
+ majors: Major[];
127
+ }
128
+ export interface EducationCatalog {
129
+ categories: Category[];
130
+ playlists: Playlist[];
131
+ }
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "education-playlists",
3
+ "version": "0.1.0",
4
+ "description": "Self-paced education playlists organized like a college catalog (category, major, program), seeded from MIT OpenCourseWare, with progress checkmarks, time estimates, sharing, and an LLM planner that asks follow-up questions and builds a custom playlist",
5
+ "keywords": [
6
+ "react",
7
+ "education",
8
+ "playlists",
9
+ "mit-ocw",
10
+ "opencourseware",
11
+ "self-paced",
12
+ "learning-path",
13
+ "component-library"
14
+ ],
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/OpenSourceAGI/qwksearch-research-agent.git",
18
+ "directory": "packages/education-playlists"
19
+ },
20
+ "type": "module",
21
+ "main": "./dist/index.js",
22
+ "module": "./dist/index.js",
23
+ "types": "./dist/index.d.ts",
24
+ "exports": {
25
+ ".": {
26
+ "types": "./dist/index.d.ts",
27
+ "import": "./dist/index.js",
28
+ "require": "./dist/index.cjs"
29
+ },
30
+ "./server": {
31
+ "types": "./dist/server/index.d.ts",
32
+ "import": "./dist/server/index.js",
33
+ "require": "./dist/server/index.cjs"
34
+ }
35
+ },
36
+ "files": [
37
+ "dist",
38
+ "README.md"
39
+ ],
40
+ "sideEffects": false,
41
+ "peerDependencies": {
42
+ "react": "^18 || ^19",
43
+ "react-dom": "^18 || ^19"
44
+ },
45
+ "devDependencies": {
46
+ "@testing-library/react": "^16.3.3",
47
+ "@types/react": "^19.2.18",
48
+ "@types/react-dom": "^19.2.7",
49
+ "@vitejs/plugin-react": "^6.1.1",
50
+ "@vitest/coverage-v8": "^5.0.0",
51
+ "jsdom": "^30.0.1",
52
+ "react": "^19.2.8",
53
+ "react-dom": "^19.2.8",
54
+ "tsup": "^8.5.1",
55
+ "typescript": "^7.0.2",
56
+ "vitest": "^5.0.0"
57
+ },
58
+ "scripts": {
59
+ "build": "tsup && tsc --project tsconfig.build.json",
60
+ "typecheck": "tsc --noEmit",
61
+ "test": "vitest run",
62
+ "test:coverage": "vitest run --coverage"
63
+ }
64
+ }