Add specifications for gamification, generation, and R42 chat services

- Introduced gamification service spec detailing responsibilities, API surface, XP calculation, levels, streaks, badges, milestone cards, and heatmap data.
- Added generation service spec outlining the process for generating micro learning content, including API endpoints, AI call configuration, prompt strategies, and error handling.
- Created R42 chat service spec covering chatbot interactions, retrieval pipeline, prompt construction, response generation, and stateless design principles.
This commit is contained in:
RaymondVerhoef
2026-05-23 18:13:08 +02:00
parent dda20612e9
commit 472685f0d7
62 changed files with 11552 additions and 21 deletions

View File

@@ -0,0 +1,139 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((db) => {
const collection = new Collection({
"id": "0lr0nf3rzh5tdq3",
"created": "2026-05-23 13:57:33.625Z",
"updated": "2026-05-23 13:57:33.625Z",
"name": "source_documents",
"type": "base",
"system": false,
"schema": [
{
"system": false,
"id": "ju0ymfsy",
"name": "filename",
"type": "text",
"required": true,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "7q9zkqtk",
"name": "file",
"type": "file",
"required": false,
"presentable": false,
"unique": false,
"options": {
"mimeTypes": [
"application/pdf",
"text/markdown",
"text/x-markdown",
"text/plain"
],
"thumbs": [],
"maxSelect": 1,
"maxSize": 52428800,
"protected": false
}
},
{
"system": false,
"id": "ny7sztwr",
"name": "format",
"type": "select",
"required": true,
"presentable": false,
"unique": false,
"options": {
"maxSelect": 1,
"values": [
"pdf",
"md",
"txt"
]
}
},
{
"system": false,
"id": "sitx0eng",
"name": "status",
"type": "select",
"required": true,
"presentable": false,
"unique": false,
"options": {
"maxSelect": 1,
"values": [
"processing",
"processed",
"failed"
]
}
},
{
"system": false,
"id": "p7qduuyn",
"name": "ingested_at",
"type": "date",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": "",
"max": ""
}
},
{
"system": false,
"id": "tahewcck",
"name": "chunk_count",
"type": "number",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"noDecimal": false
}
},
{
"system": false,
"id": "d0x4m7ow",
"name": "created_by",
"type": "relation",
"required": false,
"presentable": false,
"unique": false,
"options": {
"collectionId": "_pb_users_auth_",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": 1,
"displayFields": null
}
}
],
"indexes": [],
"listRule": null,
"viewRule": null,
"createRule": null,
"updateRule": null,
"deleteRule": null,
"options": {}
});
return Dao(db).saveCollection(collection);
}, (db) => {
const dao = new Dao(db);
const collection = dao.findCollectionByNameOrId("0lr0nf3rzh5tdq3");
return dao.deleteCollection(collection);
})

View File

@@ -0,0 +1,116 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((db) => {
const collection = new Collection({
"id": "bm117d8jhn68xqr",
"created": "2026-05-23 13:57:33.634Z",
"updated": "2026-05-23 13:57:33.634Z",
"name": "themes",
"type": "base",
"system": false,
"schema": [
{
"system": false,
"id": "zl4nz5yw",
"name": "title",
"type": "text",
"required": true,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "hvkfrx3i",
"name": "description",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "mfk9eat0",
"name": "status",
"type": "select",
"required": true,
"presentable": false,
"unique": false,
"options": {
"maxSelect": 1,
"values": [
"draft",
"published"
]
}
},
{
"system": false,
"id": "n8fksm3u",
"name": "source_documents",
"type": "relation",
"required": false,
"presentable": false,
"unique": false,
"options": {
"collectionId": "0lr0nf3rzh5tdq3",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": null,
"displayFields": null
}
},
{
"system": false,
"id": "998ievsr",
"name": "approved_by",
"type": "relation",
"required": false,
"presentable": false,
"unique": false,
"options": {
"collectionId": "_pb_users_auth_",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": 1,
"displayFields": null
}
},
{
"system": false,
"id": "4ilud3ts",
"name": "approved_at",
"type": "date",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": "",
"max": ""
}
}
],
"indexes": [],
"listRule": null,
"viewRule": null,
"createRule": null,
"updateRule": null,
"deleteRule": null,
"options": {}
});
return Dao(db).saveCollection(collection);
}, (db) => {
const dao = new Dao(db);
const collection = dao.findCollectionByNameOrId("bm117d8jhn68xqr");
return dao.deleteCollection(collection);
})

View File

@@ -0,0 +1,52 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((db) => {
const dao = new Dao(db)
const collection = dao.findCollectionByNameOrId("_pb_users_auth_")
// add
collection.schema.addField(new SchemaField({
"system": false,
"id": "mx3g38jd",
"name": "role",
"type": "select",
"required": true,
"presentable": false,
"unique": false,
"options": {
"maxSelect": 1,
"values": [
"admin",
"employee"
]
}
}))
// add
collection.schema.addField(new SchemaField({
"system": false,
"id": "pgfyrf71",
"name": "display_name",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
}))
return dao.saveCollection(collection)
}, (db) => {
const dao = new Dao(db)
const collection = dao.findCollectionByNameOrId("_pb_users_auth_")
// remove
collection.schema.removeField("mx3g38jd")
// remove
collection.schema.removeField("pgfyrf71")
return dao.saveCollection(collection)
})

View File

@@ -0,0 +1,55 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((db) => {
const collection = new Collection({
"id": "1ixwljuo2xqxcqj",
"created": "2026-05-23 13:58:56.098Z",
"updated": "2026-05-23 13:58:56.098Z",
"name": "topics",
"type": "base",
"system": false,
"schema": [
{
"system": false,
"id": "kkjhfk5h",
"name": "theme",
"type": "relation",
"required": true,
"presentable": false,
"unique": false,
"options": {
"collectionId": "bm117d8jhn68xqr",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": 1,
"displayFields": null
}
},
{
"system": false,
"id": "ygnod47v",
"name": "body",
"type": "editor",
"required": false,
"presentable": false,
"unique": false,
"options": {
"convertUrls": false
}
}
],
"indexes": [],
"listRule": null,
"viewRule": null,
"createRule": null,
"updateRule": null,
"deleteRule": null,
"options": {}
});
return Dao(db).saveCollection(collection);
}, (db) => {
const dao = new Dao(db);
const collection = dao.findCollectionByNameOrId("1ixwljuo2xqxcqj");
return dao.deleteCollection(collection);
})

View File

@@ -0,0 +1,102 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((db) => {
const collection = new Collection({
"id": "5gl7qj7aky6wjl9",
"created": "2026-05-23 14:00:42.375Z",
"updated": "2026-05-23 14:00:42.375Z",
"name": "badges",
"type": "base",
"system": false,
"schema": [
{
"system": false,
"id": "edwrmvav",
"name": "key",
"type": "text",
"required": true,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "2wpmab8o",
"name": "tier",
"type": "select",
"required": true,
"presentable": false,
"unique": false,
"options": {
"maxSelect": 1,
"values": [
"bronze",
"silver",
"gold",
"legendary",
"content"
]
}
},
{
"system": false,
"id": "hknbw5l3",
"name": "label",
"type": "text",
"required": true,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "mq6imou8",
"name": "description",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "l8vkthft",
"name": "icon",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
}
],
"indexes": [],
"listRule": null,
"viewRule": null,
"createRule": null,
"updateRule": null,
"deleteRule": null,
"options": {}
});
return Dao(db).saveCollection(collection);
}, (db) => {
const dao = new Dao(db);
const collection = dao.findCollectionByNameOrId("5gl7qj7aky6wjl9");
return dao.deleteCollection(collection);
})

View File

@@ -0,0 +1,114 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((db) => {
const collection = new Collection({
"id": "1cbepol4z1jxb20",
"created": "2026-05-23 14:00:42.280Z",
"updated": "2026-05-23 14:00:42.280Z",
"name": "curriculum_versions",
"type": "base",
"system": false,
"schema": [
{
"system": false,
"id": "x70h1e1v",
"name": "version",
"type": "number",
"required": true,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"noDecimal": false
}
},
{
"system": false,
"id": "0rbdmeyi",
"name": "status",
"type": "select",
"required": true,
"presentable": false,
"unique": false,
"options": {
"maxSelect": 1,
"values": [
"draft",
"active",
"superseded"
]
}
},
{
"system": false,
"id": "s8btpnp7",
"name": "generated_at",
"type": "date",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": "",
"max": ""
}
},
{
"system": false,
"id": "d297xuk1",
"name": "approved_by",
"type": "relation",
"required": false,
"presentable": false,
"unique": false,
"options": {
"collectionId": "_pb_users_auth_",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": 1,
"displayFields": null
}
},
{
"system": false,
"id": "zmzzcv6z",
"name": "approved_at",
"type": "date",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": "",
"max": ""
}
},
{
"system": false,
"id": "fsn8cgni",
"name": "generation_notes",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
}
],
"indexes": [],
"listRule": null,
"viewRule": null,
"createRule": null,
"updateRule": null,
"deleteRule": null,
"options": {}
});
return Dao(db).saveCollection(collection);
}, (db) => {
const dao = new Dao(db);
const collection = dao.findCollectionByNameOrId("1cbepol4z1jxb20");
return dao.deleteCollection(collection);
})

View File

@@ -0,0 +1,129 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((db) => {
const collection = new Collection({
"id": "9ivwlfc584lp63w",
"created": "2026-05-23 14:00:42.294Z",
"updated": "2026-05-23 14:00:42.294Z",
"name": "curriculum_weeks",
"type": "base",
"system": false,
"schema": [
{
"system": false,
"id": "ano3xevy",
"name": "curriculum_version",
"type": "relation",
"required": true,
"presentable": false,
"unique": false,
"options": {
"collectionId": "1cbepol4z1jxb20",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": 1,
"displayFields": null
}
},
{
"system": false,
"id": "p7odhccm",
"name": "week_number",
"type": "number",
"required": true,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"noDecimal": false
}
},
{
"system": false,
"id": "l0w7ienh",
"name": "theme",
"type": "relation",
"required": true,
"presentable": false,
"unique": false,
"options": {
"collectionId": "bm117d8jhn68xqr",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": 1,
"displayFields": null
}
},
{
"system": false,
"id": "37yncqmv",
"name": "topics",
"type": "relation",
"required": false,
"presentable": false,
"unique": false,
"options": {
"collectionId": "1ixwljuo2xqxcqj",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": null,
"displayFields": null
}
},
{
"system": false,
"id": "ut2ilwjx",
"name": "topic_order",
"type": "json",
"required": false,
"presentable": false,
"unique": false,
"options": {
"maxSize": 2097152
}
},
{
"system": false,
"id": "wdleuro6",
"name": "estimated_duration_minutes",
"type": "number",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"noDecimal": false
}
},
{
"system": false,
"id": "omddyhqe",
"name": "admin_notes",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
}
],
"indexes": [],
"listRule": null,
"viewRule": null,
"createRule": null,
"updateRule": null,
"deleteRule": null,
"options": {}
});
return Dao(db).saveCollection(collection);
}, (db) => {
const dao = new Dao(db);
const collection = dao.findCollectionByNameOrId("9ivwlfc584lp63w");
return dao.deleteCollection(collection);
})

View File

@@ -0,0 +1,86 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((db) => {
const collection = new Collection({
"id": "52xqp11w4t0y68u",
"created": "2026-05-23 14:00:42.405Z",
"updated": "2026-05-23 14:00:42.405Z",
"name": "employee_badges",
"type": "base",
"system": false,
"schema": [
{
"system": false,
"id": "uyiom4ry",
"name": "user",
"type": "relation",
"required": true,
"presentable": false,
"unique": false,
"options": {
"collectionId": "_pb_users_auth_",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": 1,
"displayFields": null
}
},
{
"system": false,
"id": "hmafhq29",
"name": "badge",
"type": "relation",
"required": true,
"presentable": false,
"unique": false,
"options": {
"collectionId": "5gl7qj7aky6wjl9",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": 1,
"displayFields": null
}
},
{
"system": false,
"id": "5a11syeq",
"name": "earned_at",
"type": "date",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": "",
"max": ""
}
},
{
"system": false,
"id": "ekcq8kkw",
"name": "cycle",
"type": "number",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"noDecimal": false
}
}
],
"indexes": [],
"listRule": null,
"viewRule": null,
"createRule": null,
"updateRule": null,
"deleteRule": null,
"options": {}
});
return Dao(db).saveCollection(collection);
}, (db) => {
const dao = new Dao(db);
const collection = dao.findCollectionByNameOrId("52xqp11w4t0y68u");
return dao.deleteCollection(collection);
})

View File

@@ -0,0 +1,100 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((db) => {
const collection = new Collection({
"id": "516qnoxsmeomkte",
"created": "2026-05-23 14:00:42.311Z",
"updated": "2026-05-23 14:00:42.311Z",
"name": "employee_curriculum_state",
"type": "base",
"system": false,
"schema": [
{
"system": false,
"id": "4v8ps3fa",
"name": "user",
"type": "relation",
"required": true,
"presentable": false,
"unique": false,
"options": {
"collectionId": "_pb_users_auth_",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": 1,
"displayFields": null
}
},
{
"system": false,
"id": "trhrgfoz",
"name": "current_cycle",
"type": "number",
"required": true,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"noDecimal": false
}
},
{
"system": false,
"id": "g39vexbc",
"name": "current_week",
"type": "number",
"required": true,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"noDecimal": false
}
},
{
"system": false,
"id": "ca7taibg",
"name": "start_date",
"type": "date",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": "",
"max": ""
}
},
{
"system": false,
"id": "lpkyfwga",
"name": "active_version",
"type": "relation",
"required": false,
"presentable": false,
"unique": false,
"options": {
"collectionId": "1cbepol4z1jxb20",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": 1,
"displayFields": null
}
}
],
"indexes": [],
"listRule": null,
"viewRule": null,
"createRule": null,
"updateRule": null,
"deleteRule": null,
"options": {}
});
return Dao(db).saveCollection(collection);
}, (db) => {
const dao = new Dao(db);
const collection = dao.findCollectionByNameOrId("516qnoxsmeomkte");
return dao.deleteCollection(collection);
})

View File

@@ -0,0 +1,131 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((db) => {
const collection = new Collection({
"id": "o9gcrudod9g510u",
"created": "2026-05-23 14:00:42.352Z",
"updated": "2026-05-23 14:00:42.352Z",
"name": "gamification_profiles",
"type": "base",
"system": false,
"schema": [
{
"system": false,
"id": "szwphybr",
"name": "user",
"type": "relation",
"required": true,
"presentable": false,
"unique": false,
"options": {
"collectionId": "_pb_users_auth_",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": 1,
"displayFields": null
}
},
{
"system": false,
"id": "ifgzeuez",
"name": "total_commits",
"type": "number",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"noDecimal": false
}
},
{
"system": false,
"id": "bbife1f5",
"name": "current_level",
"type": "select",
"required": false,
"presentable": false,
"unique": false,
"options": {
"maxSelect": 1,
"values": [
"intern",
"junior",
"medior",
"senior",
"staff",
"principal"
]
}
},
{
"system": false,
"id": "f2aujqrw",
"name": "current_streak_weeks",
"type": "number",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"noDecimal": false
}
},
{
"system": false,
"id": "vlcxsuv6",
"name": "longest_streak_weeks",
"type": "number",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"noDecimal": false
}
},
{
"system": false,
"id": "eolnad0g",
"name": "types_used",
"type": "json",
"required": false,
"presentable": false,
"unique": false,
"options": {
"maxSize": 2097152
}
},
{
"system": false,
"id": "e2voqddw",
"name": "last_active_week",
"type": "number",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"noDecimal": false
}
}
],
"indexes": [],
"listRule": null,
"viewRule": null,
"createRule": null,
"updateRule": null,
"deleteRule": null,
"options": {}
});
return Dao(db).saveCollection(collection);
}, (db) => {
const dao = new Dao(db);
const collection = dao.findCollectionByNameOrId("o9gcrudod9g510u");
return dao.deleteCollection(collection);
})

View File

@@ -0,0 +1,137 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((db) => {
const collection = new Collection({
"id": "li75ivkr87g3r94",
"created": "2026-05-23 14:00:42.269Z",
"updated": "2026-05-23 14:00:42.269Z",
"name": "micro_learnings",
"type": "base",
"system": false,
"schema": [
{
"system": false,
"id": "ihu0d60m",
"name": "topic",
"type": "relation",
"required": true,
"presentable": false,
"unique": false,
"options": {
"collectionId": "1ixwljuo2xqxcqj",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": 1,
"displayFields": null
}
},
{
"system": false,
"id": "wdz1hhs6",
"name": "type",
"type": "select",
"required": true,
"presentable": false,
"unique": false,
"options": {
"maxSelect": 1,
"values": [
"concept_explainer",
"scenario_quiz",
"misconceptions",
"how_to",
"comparison_card",
"reflection_prompt",
"flashcard_set",
"case_study",
"glossary_anchor",
"myth_vs_evidence"
]
}
},
{
"system": false,
"id": "1ccr13b7",
"name": "content",
"type": "json",
"required": false,
"presentable": false,
"unique": false,
"options": {
"maxSize": 2097152
}
},
{
"system": false,
"id": "vdti3gns",
"name": "status",
"type": "select",
"required": true,
"presentable": false,
"unique": false,
"options": {
"maxSelect": 1,
"values": [
"queued",
"generated",
"published",
"rejected"
]
}
},
{
"system": false,
"id": "hxxcrfw8",
"name": "generation_model",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "grwpzlhc",
"name": "generated_at",
"type": "date",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": "",
"max": ""
}
},
{
"system": false,
"id": "wexudgfp",
"name": "published_at",
"type": "date",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": "",
"max": ""
}
}
],
"indexes": [],
"listRule": null,
"viewRule": null,
"createRule": null,
"updateRule": null,
"deleteRule": null,
"options": {}
});
return Dao(db).saveCollection(collection);
}, (db) => {
const dao = new Dao(db);
const collection = dao.findCollectionByNameOrId("li75ivkr87g3r94");
return dao.deleteCollection(collection);
})

View File

@@ -0,0 +1,111 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((db) => {
const collection = new Collection({
"id": "ilkfmv9xibmekna",
"created": "2026-05-23 14:00:42.453Z",
"updated": "2026-05-23 14:00:42.453Z",
"name": "milestone_cards",
"type": "base",
"system": false,
"schema": [
{
"system": false,
"id": "31f7wcxc",
"name": "user",
"type": "relation",
"required": true,
"presentable": false,
"unique": false,
"options": {
"collectionId": "_pb_users_auth_",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": 1,
"displayFields": null
}
},
{
"system": false,
"id": "uyhilsbd",
"name": "cycle",
"type": "number",
"required": true,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"noDecimal": false
}
},
{
"system": false,
"id": "47ivigbu",
"name": "week",
"type": "number",
"required": true,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"noDecimal": false
}
},
{
"system": false,
"id": "yai47mow",
"name": "total_commits",
"type": "number",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"noDecimal": false
}
},
{
"system": false,
"id": "je3vf6wh",
"name": "streak_weeks",
"type": "number",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"noDecimal": false
}
},
{
"system": false,
"id": "xvtxl0nj",
"name": "badge_keys",
"type": "json",
"required": false,
"presentable": false,
"unique": false,
"options": {
"maxSize": 2097152
}
}
],
"indexes": [],
"listRule": null,
"viewRule": null,
"createRule": null,
"updateRule": null,
"deleteRule": null,
"options": {}
});
return Dao(db).saveCollection(collection);
}, (db) => {
const dao = new Dao(db);
const collection = dao.findCollectionByNameOrId("ilkfmv9xibmekna");
return dao.deleteCollection(collection);
})

View File

@@ -0,0 +1,116 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((db) => {
const collection = new Collection({
"id": "57kc85afcdkjlje",
"created": "2026-05-23 14:00:42.328Z",
"updated": "2026-05-23 14:00:42.328Z",
"name": "session_completions",
"type": "base",
"system": false,
"schema": [
{
"system": false,
"id": "atqsid6s",
"name": "user",
"type": "relation",
"required": true,
"presentable": false,
"unique": false,
"options": {
"collectionId": "_pb_users_auth_",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": 1,
"displayFields": null
}
},
{
"system": false,
"id": "2waq3kfq",
"name": "topic",
"type": "relation",
"required": true,
"presentable": false,
"unique": false,
"options": {
"collectionId": "1ixwljuo2xqxcqj",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": 1,
"displayFields": null
}
},
{
"system": false,
"id": "tf1bapvu",
"name": "micro_learning",
"type": "relation",
"required": true,
"presentable": false,
"unique": false,
"options": {
"collectionId": "li75ivkr87g3r94",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": 1,
"displayFields": null
}
},
{
"system": false,
"id": "dvmc5jih",
"name": "week_number",
"type": "number",
"required": true,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"noDecimal": false
}
},
{
"system": false,
"id": "wblewojy",
"name": "cycle",
"type": "number",
"required": true,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"noDecimal": false
}
},
{
"system": false,
"id": "izxhly6t",
"name": "completed_at",
"type": "date",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": "",
"max": ""
}
}
],
"indexes": [],
"listRule": null,
"viewRule": null,
"createRule": null,
"updateRule": null,
"deleteRule": null,
"options": {}
});
return Dao(db).saveCollection(collection);
}, (db) => {
const dao = new Dao(db);
const collection = dao.findCollectionByNameOrId("57kc85afcdkjlje");
return dao.deleteCollection(collection);
})

View File

@@ -0,0 +1,266 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((db) => {
const dao = new Dao(db)
const collection = dao.findCollectionByNameOrId("1ixwljuo2xqxcqj")
// remove
collection.schema.removeField("kkjhfk5h")
// remove
collection.schema.removeField("ygnod47v")
// add
collection.schema.addField(new SchemaField({
"system": false,
"id": "wo6r0pay",
"name": "theme",
"type": "relation",
"required": true,
"presentable": false,
"unique": false,
"options": {
"collectionId": "bm117d8jhn68xqr",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": 1,
"displayFields": null
}
}))
// add
collection.schema.addField(new SchemaField({
"system": false,
"id": "8bnzwbwk",
"name": "title",
"type": "text",
"required": true,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
}))
// add
collection.schema.addField(new SchemaField({
"system": false,
"id": "lwhabbnq",
"name": "body",
"type": "editor",
"required": false,
"presentable": false,
"unique": false,
"options": {
"convertUrls": false
}
}))
// add
collection.schema.addField(new SchemaField({
"system": false,
"id": "8tjttsyu",
"name": "difficulty",
"type": "select",
"required": true,
"presentable": false,
"unique": false,
"options": {
"maxSelect": 1,
"values": [
"introductory",
"intermediate",
"advanced"
]
}
}))
// add
collection.schema.addField(new SchemaField({
"system": false,
"id": "qngdg6m0",
"name": "complexity_weight",
"type": "number",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"noDecimal": false
}
}))
// add
collection.schema.addField(new SchemaField({
"system": false,
"id": "dccnzcbc",
"name": "status",
"type": "select",
"required": true,
"presentable": false,
"unique": false,
"options": {
"maxSelect": 1,
"values": [
"draft",
"published"
]
}
}))
// add
collection.schema.addField(new SchemaField({
"system": false,
"id": "3rkaaglp",
"name": "key_terms",
"type": "json",
"required": false,
"presentable": false,
"unique": false,
"options": {
"maxSize": 2097152
}
}))
// add
collection.schema.addField(new SchemaField({
"system": false,
"id": "byhuuhif",
"name": "qdrant_chunk_ids",
"type": "json",
"required": false,
"presentable": false,
"unique": false,
"options": {
"maxSize": 2097152
}
}))
// add
collection.schema.addField(new SchemaField({
"system": false,
"id": "8h7ygavu",
"name": "related_topics",
"type": "relation",
"required": false,
"presentable": false,
"unique": false,
"options": {
"collectionId": "1ixwljuo2xqxcqj",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": null,
"displayFields": null
}
}))
// add
collection.schema.addField(new SchemaField({
"system": false,
"id": "dwhl1pqg",
"name": "prerequisite_topics",
"type": "relation",
"required": false,
"presentable": false,
"unique": false,
"options": {
"collectionId": "1ixwljuo2xqxcqj",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": null,
"displayFields": null
}
}))
// add
collection.schema.addField(new SchemaField({
"system": false,
"id": "38phv8cm",
"name": "contrast_topics",
"type": "relation",
"required": false,
"presentable": false,
"unique": false,
"options": {
"collectionId": "1ixwljuo2xqxcqj",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": null,
"displayFields": null
}
}))
return dao.saveCollection(collection)
}, (db) => {
const dao = new Dao(db)
const collection = dao.findCollectionByNameOrId("1ixwljuo2xqxcqj")
// add
collection.schema.addField(new SchemaField({
"system": false,
"id": "kkjhfk5h",
"name": "theme",
"type": "relation",
"required": true,
"presentable": false,
"unique": false,
"options": {
"collectionId": "bm117d8jhn68xqr",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": 1,
"displayFields": null
}
}))
// add
collection.schema.addField(new SchemaField({
"system": false,
"id": "ygnod47v",
"name": "body",
"type": "editor",
"required": false,
"presentable": false,
"unique": false,
"options": {
"convertUrls": false
}
}))
// remove
collection.schema.removeField("wo6r0pay")
// remove
collection.schema.removeField("8bnzwbwk")
// remove
collection.schema.removeField("lwhabbnq")
// remove
collection.schema.removeField("8tjttsyu")
// remove
collection.schema.removeField("qngdg6m0")
// remove
collection.schema.removeField("dccnzcbc")
// remove
collection.schema.removeField("3rkaaglp")
// remove
collection.schema.removeField("byhuuhif")
// remove
collection.schema.removeField("8h7ygavu")
// remove
collection.schema.removeField("dwhl1pqg")
// remove
collection.schema.removeField("38phv8cm")
return dao.saveCollection(collection)
})

4
app/services/curriculum/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
node_modules/
dist/
.env
*.err

1550
app/services/curriculum/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,25 @@
{
"name": "curriculum",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"dev": "tsx watch src/index.ts",
"build": "tsc",
"start": "node dist/index.js"
},
"dependencies": {
"@anthropic-ai/sdk": "^0.24",
"fastify": "^4",
"pocketbase": "^0.21",
"uuid": "^9",
"zod": "^3"
},
"devDependencies": {
"@types/node": "^20",
"@types/uuid": "^9",
"dotenv": "^16",
"tsx": "^4",
"typescript": "^5"
}
}

View File

@@ -0,0 +1,200 @@
import { getPocketBase } from '../lib/pocketbase.js';
import { anthropic, MODELS } from '../lib/anthropic.js';
import { sequenceTopics } from './sequence.js';
import { buildCycleSystemRules, buildCycleUserPrompt } from './cycle.js';
import {
KBSnapshotSchema,
CurriculumDraftSchema,
type KBSnapshot,
type CurriculumDraft,
type CycleContext,
} from '../types.js';
const BASE_SYSTEM_PROMPT = `You are a curriculum designer. Your task is to distribute a set of learning
Themes across 26 weekly sessions to create an effective learning journey.
Output ONLY valid JSON matching the schema provided. No preamble, no
explanation, no markdown fences.
Rules:
- Every Theme must appear at least once across 26 weeks
- Themes with more Topics (higher topic count) may span multiple weeks or
appear in multiple cycles within the 26 weeks
- Sequence Themes so foundational concepts precede dependent ones
- Distribute complexity progressively: introductory Themes early, advanced
Themes in the second half
- If total Topics across all Themes exceeds what 26 weeks can cover in depth,
prioritise breadth in cycle 1 — every Theme covered, key Topics per Theme
- Assign an estimated duration in minutes per week (1545 minutes per session)
- Return exactly 26 week slots
Output schema:
{
"weeks": [
{
"weekNumber": 1,
"themeId": "string",
"topicIds": ["string"],
"estimatedDurationMinutes": 25,
"rationale": "one sentence"
}
]
}`;
export async function fetchKBSnapshot(): Promise<KBSnapshot> {
const pb = await getPocketBase();
const themeRecords = await pb.collection('themes').getFullList({
filter: 'status = "published"',
expand: 'topics',
});
const themes = themeRecords.map(theme => {
const expandedTopics = (theme['expand'] as Record<string, unknown>)?.['topics'];
const rawTopics = Array.isArray(expandedTopics) ? expandedTopics : [];
const topics = rawTopics.map((t: unknown) => {
const r = t as Record<string, unknown>;
return {
id: r['id'] as string,
title: r['title'] as string,
complexityWeight: (r['complexity_weight'] as number) ?? 1,
difficulty: (r['difficulty'] as string) ?? 'introductory',
prerequisiteTopics: (r['prerequisite_topics'] as string[]) ?? [],
relatedTopics: (r['related_topics'] as string[]) ?? [],
contrastTopics: (r['contrast_topics'] as string[]) ?? [],
};
});
return {
id: theme['id'] as string,
title: theme['title'] as string,
description: (theme['description'] as string) ?? '',
topics,
};
});
return KBSnapshotSchema.parse({ themes });
}
export function preprocessSnapshot(snapshot: KBSnapshot): KBSnapshot {
return {
themes: snapshot.themes.map(theme => ({
...theme,
topics: sequenceTopics(theme.topics),
})),
};
}
async function callAI(
systemPrompt: string,
userPrompt: string,
): Promise<CurriculumDraft> {
for (let attempt = 0; attempt < 2; attempt++) {
const message = await anthropic.messages.create({
model: MODELS.SONNET,
max_tokens: 4000,
temperature: 0,
system: systemPrompt,
messages: [{ role: 'user', content: userPrompt }],
});
const textBlock = message.content.find(b => b.type === 'text');
if (!textBlock || textBlock.type !== 'text') {
if (attempt === 0) continue;
throw new Error('No text block in AI response');
}
let parsed: unknown;
try {
parsed = JSON.parse(textBlock.text);
} catch {
if (attempt === 0) continue;
throw new Error('AI response is not valid JSON');
}
const result = CurriculumDraftSchema.safeParse(parsed);
if (!result.success) {
if (attempt === 0) continue;
throw new Error(`AI response failed Zod validation: ${result.error.message}`);
}
return result.data;
}
throw new Error('AI generation failed after 2 attempts');
}
function validateDraftAgainstSnapshot(draft: CurriculumDraft, snapshot: KBSnapshot): void {
const themeIds = new Set(snapshot.themes.map(t => t.id));
const topicIds = new Set(snapshot.themes.flatMap(t => t.topics.map(p => p.id)));
for (const week of draft.weeks) {
if (!themeIds.has(week.themeId)) {
throw new Error(`Unknown themeId in week ${week.weekNumber}: ${week.themeId}`);
}
for (const topicId of week.topicIds) {
if (!topicIds.has(topicId)) {
throw new Error(`Unknown topicId in week ${week.weekNumber}: ${topicId}`);
}
}
}
}
export async function writeDraftToPocketBase(
draft: CurriculumDraft,
reason: string,
): Promise<string> {
const pb = await getPocketBase();
// Determine next version number
const existing = await pb.collection('curriculum_versions').getFullList({
sort: '-version',
perPage: 1,
});
const latestVersion = existing[0] ? (existing[0]['version'] as number) : 0;
const versionRecord = await pb.collection('curriculum_versions').create({
version: latestVersion + 1,
status: 'draft',
generated_at: new Date().toISOString(),
generation_notes: reason,
});
for (const week of draft.weeks) {
await pb.collection('curriculum_weeks').create({
curriculum_version: versionRecord.id,
week_number: week.weekNumber,
theme: week.themeId,
topics: week.topicIds,
topic_order: week.topicIds.map((_, i) => i),
estimated_duration_minutes: week.estimatedDurationMinutes,
admin_notes: week.rationale,
});
}
return versionRecord.id;
}
export async function buildCurriculum(
reason: string,
cycleCtx?: CycleContext,
): Promise<string> {
const rawSnapshot = await fetchKBSnapshot();
const snapshot = preprocessSnapshot(rawSnapshot);
let systemPrompt = BASE_SYSTEM_PROMPT;
let userPrompt: string;
if (cycleCtx && cycleCtx.cycleNumber > 1) {
systemPrompt += buildCycleSystemRules();
userPrompt = buildCycleUserPrompt(snapshot, cycleCtx);
} else {
userPrompt = `Knowledge base snapshot:\n${JSON.stringify(snapshot)}\n\nGenerate a 26-week curriculum schedule.`;
}
const draft = await callAI(systemPrompt, userPrompt);
validateDraftAgainstSnapshot(draft, snapshot);
return writeDraftToPocketBase(draft, reason);
}

View File

@@ -0,0 +1,20 @@
import type { CycleContext, KBSnapshot } from '../types.js';
const ADDITIONAL_SYSTEM_RULES = `
- Vary the Theme sequence from the previous cycle
- Topics identified as low engagement should appear earlier in this cycle
- The rationale field should note what is different from cycle 1`;
export function buildCycleUserPrompt(snapshot: KBSnapshot, ctx: CycleContext): string {
return `Knowledge base snapshot:
${JSON.stringify(snapshot)}
Cycle context:
${JSON.stringify(ctx)}
Generate a 26-week curriculum schedule.`;
}
export function buildCycleSystemRules(): string {
return ADDITIONAL_SYSTEM_RULES;
}

View File

@@ -0,0 +1,49 @@
import type { KBTopic } from '../types.js';
export function sequenceTopics(topics: KBTopic[]): KBTopic[] {
if (topics.length === 0) return [];
const idToTopic = new Map<string, KBTopic>();
for (const t of topics) {
idToTopic.set(t.id, t);
}
// Build adjacency list: id → prerequisite ids that exist in this set
const prereqs = new Map<string, Set<string>>();
for (const t of topics) {
const localPrereqs = new Set(t.prerequisiteTopics.filter(id => idToTopic.has(id)));
prereqs.set(t.id, localPrereqs);
}
const visited = new Set<string>();
const result: KBTopic[] = [];
const onStack = new Set<string>();
let hasCycle = false;
function visit(id: string): void {
if (onStack.has(id)) {
hasCycle = true;
return;
}
if (visited.has(id)) return;
onStack.add(id);
for (const prereqId of prereqs.get(id) ?? []) {
visit(prereqId);
}
onStack.delete(id);
visited.add(id);
const topic = idToTopic.get(id);
if (topic) result.push(topic);
}
for (const t of topics) {
visit(t.id);
}
if (hasCycle) {
// Fall back to complexity_weight ascending
return [...topics].sort((a, b) => a.complexityWeight - b.complexityWeight);
}
return result;
}

View File

@@ -0,0 +1,18 @@
import 'dotenv/config';
import Fastify from 'fastify';
import { curriculumRoutes } from './routes/curriculum.js';
import { employeeRoutes } from './routes/employee.js';
const app = Fastify({ logger: true });
await app.register(curriculumRoutes);
await app.register(employeeRoutes);
const port = parseInt(process.env['CURRICULUM_PORT'] ?? '3003', 10);
try {
await app.listen({ port, host: '0.0.0.0' });
} catch (err) {
app.log.error(err);
process.exit(1);
}

View File

@@ -0,0 +1,46 @@
import { v4 as uuid } from 'uuid';
import { buildCurriculum } from '../generator/build.js';
import type { GenerationJob } from '../types.js';
const jobs = new Map<string, GenerationJob>();
export function createJob(triggeredBy: string, reason: 'new_topics' | 'manual'): GenerationJob {
const job: GenerationJob = {
id: uuid(),
triggeredBy,
reason,
status: 'queued',
versionId: null,
error: null,
createdAt: new Date(),
updatedAt: new Date(),
};
jobs.set(job.id, job);
void runPipeline(job.id);
return job;
}
export function getJob(id: string): GenerationJob | undefined {
return jobs.get(id);
}
function updateJob(id: string, updates: Partial<Omit<GenerationJob, 'id' | 'createdAt'>>): void {
const job = jobs.get(id);
if (!job) return;
jobs.set(id, { ...job, ...updates, updatedAt: new Date() });
}
async function runPipeline(jobId: string): Promise<void> {
const job = jobs.get(jobId);
if (!job) return;
updateJob(jobId, { status: 'running' });
try {
const versionId = await buildCurriculum(job.reason);
updateJob(jobId, { status: 'done', versionId });
} catch (err) {
const error = err instanceof Error ? err.message : String(err);
updateJob(jobId, { status: 'failed', error });
}
}

View File

@@ -0,0 +1,9 @@
import Anthropic from '@anthropic-ai/sdk';
export const anthropic = new Anthropic({
apiKey: process.env['ANTHROPIC_API_KEY'],
});
export const MODELS = {
SONNET: 'claude-sonnet-4-20250514',
} as const;

View File

@@ -0,0 +1,14 @@
import PocketBase from 'pocketbase';
const POCKETBASE_URL = process.env['POCKETBASE_URL'] ?? '';
const POCKETBASE_ADMIN_EMAIL = process.env['POCKETBASE_ADMIN_EMAIL'] ?? '';
const POCKETBASE_ADMIN_PASSWORD = process.env['POCKETBASE_ADMIN_PASSWORD'] ?? '';
const pb = new PocketBase(POCKETBASE_URL);
export async function getPocketBase(): Promise<PocketBase> {
if (!pb.authStore.isValid) {
await pb.admins.authWithPassword(POCKETBASE_ADMIN_EMAIL, POCKETBASE_ADMIN_PASSWORD);
}
return pb;
}

View File

@@ -0,0 +1,174 @@
import type { FastifyInstance } from 'fastify';
import { z } from 'zod';
import { createJob, getJob } from '../jobs/queue.js';
import { applyVersion } from '../versioning/apply.js';
import { getPocketBase } from '../lib/pocketbase.js';
const GenerateBodySchema = z.object({
triggeredBy: z.string(),
reason: z.enum(['new_topics', 'manual']),
});
const PatchWeekBodySchema = z.object({
theme: z.string().optional(),
topics: z.array(z.string()).optional(),
topic_order: z.array(z.number()).optional(),
admin_notes: z.string().optional(),
estimated_duration_minutes: z.number().min(15).max(45).optional(),
});
export async function curriculumRoutes(app: FastifyInstance): Promise<void> {
// POST /generate
app.post('/generate', async (request, reply) => {
const body = GenerateBodySchema.safeParse(request.body);
if (!body.success) {
return reply.status(400).send({ error: 'Invalid request body', details: body.error.issues });
}
const job = createJob(body.data.triggeredBy, body.data.reason);
return reply.status(202).send({ jobId: job.id, status: job.status });
});
// GET /status/:jobId
app.get<{ Params: { jobId: string } }>('/status/:jobId', async (request, reply) => {
const job = getJob(request.params.jobId);
if (!job) {
return reply.status(404).send({ error: 'Job not found' });
}
return reply.send(job);
});
// GET /current
app.get('/current', async (_request, reply) => {
const pb = await getPocketBase();
const versions = await pb.collection('curriculum_versions').getFullList({
filter: 'status = "active"',
});
if (versions.length === 0) {
return reply.status(404).send({ error: 'No active curriculum version' });
}
const version = versions[0]!;
const weeks = await pb.collection('curriculum_weeks').getFullList({
filter: `curriculum_version = "${version.id}"`,
sort: 'week_number',
expand: 'theme,topics',
});
return reply.send({ version, weeks });
});
// GET /preview
app.get('/preview', async (_request, reply) => {
const pb = await getPocketBase();
const versions = await pb.collection('curriculum_versions').getFullList({
filter: 'status = "draft"',
sort: '-version',
perPage: 1,
});
if (versions.length === 0) {
return reply.status(404).send({ error: 'No draft curriculum version' });
}
const version = versions[0]!;
const weeks = await pb.collection('curriculum_weeks').getFullList({
filter: `curriculum_version = "${version.id}"`,
sort: 'week_number',
expand: 'theme,topics',
});
const themeIds = new Set(weeks.map(w => w['theme'] as string));
const topicIds = new Set(weeks.flatMap(w => (w['topics'] as string[]) ?? []));
const coverageStats = {
themesTotal: themeIds.size,
themesCovered: themeIds.size,
topicsTotal: topicIds.size,
topicsCovered: topicIds.size,
};
return reply.send({
version: version['version'],
weeks: weeks.map(w => {
const expand = (w['expand'] as Record<string, unknown>) ?? {};
const theme = expand['theme'] as Record<string, unknown> | undefined;
const rawTopics = expand['topics'];
const topicList = Array.isArray(rawTopics) ? rawTopics : [];
return {
weekNumber: w['week_number'],
theme: theme ? { id: theme['id'], title: theme['title'] } : null,
topics: topicList.map((t: unknown) => {
const tr = t as Record<string, unknown>;
return {
id: tr['id'],
title: tr['title'],
complexityWeight: tr['complexity_weight'],
};
}),
estimatedDurationMinutes: w['estimated_duration_minutes'],
};
}),
coverageStats,
});
});
// PATCH /weeks/:weekId
app.patch<{ Params: { weekId: string } }>('/weeks/:weekId', async (request, reply) => {
const body = PatchWeekBodySchema.safeParse(request.body);
if (!body.success) {
return reply.status(400).send({ error: 'Invalid request body', details: body.error.issues });
}
const pb = await getPocketBase();
let week;
try {
week = await pb.collection('curriculum_weeks').getOne(request.params.weekId);
} catch {
return reply.status(404).send({ error: 'Week not found' });
}
// Only allow patching draft versions
const version = await pb.collection('curriculum_versions').getOne(week['curriculum_version'] as string);
if (version['status'] !== 'draft') {
return reply.status(409).send({ error: 'Can only edit weeks belonging to a draft version' });
}
const updates: Record<string, unknown> = {};
if (body.data.theme !== undefined) updates['theme'] = body.data.theme;
if (body.data.topics !== undefined) updates['topics'] = body.data.topics;
if (body.data.topic_order !== undefined) updates['topic_order'] = body.data.topic_order;
if (body.data.admin_notes !== undefined) updates['admin_notes'] = body.data.admin_notes;
if (body.data.estimated_duration_minutes !== undefined) {
updates['estimated_duration_minutes'] = body.data.estimated_duration_minutes;
}
const updated = await pb.collection('curriculum_weeks').update(request.params.weekId, updates);
return reply.send(updated);
});
// POST /confirm
app.post('/confirm', async (_request, reply) => {
const pb = await getPocketBase();
const drafts = await pb.collection('curriculum_versions').getFullList({
filter: 'status = "draft"',
sort: '-version',
perPage: 1,
});
if (drafts.length === 0) {
return reply.status(404).send({ error: 'No draft curriculum to confirm' });
}
const draftId = drafts[0]!.id;
await applyVersion(draftId);
return reply.send({ applied: draftId, status: 'active' });
});
}

View File

@@ -0,0 +1,140 @@
import type { FastifyInstance } from 'fastify';
import { z } from 'zod';
import { getPocketBase } from '../lib/pocketbase.js';
import type { EmployeeState } from '../types.js';
const AdvanceBodySchema = z.object({
completedWeek: z.number().min(1).max(26),
});
export async function employeeRoutes(app: FastifyInstance): Promise<void> {
// GET /state/:userId
app.get<{ Params: { userId: string } }>('/state/:userId', async (request, reply) => {
const pb = await getPocketBase();
const records = await pb.collection('employee_curriculum_state').getFullList({
filter: `user = "${request.params.userId}"`,
expand: 'active_version',
});
if (records.length === 0) {
return reply.status(404).send({ error: 'Employee curriculum state not found' });
}
const state = records[0]!;
const currentWeek = state['current_week'] as number;
const activeVersionId = state['active_version'] as string;
// Fetch the week record for next session
const nextWeekRecords = await pb.collection('curriculum_weeks').getFullList({
filter: `curriculum_version = "${activeVersionId}" && week_number = ${currentWeek}`,
expand: 'theme,topics',
});
let nextSessionTheme: EmployeeState['nextSessionTheme'] = null;
let nextSessionTopics: EmployeeState['nextSessionTopics'] = [];
const nextWeek = nextWeekRecords[0];
if (nextWeek) {
const expand = (nextWeek['expand'] as Record<string, unknown>) ?? {};
const theme = expand['theme'] as Record<string, unknown> | undefined;
if (theme) {
nextSessionTheme = { id: theme['id'] as string, title: theme['title'] as string };
}
const rawTopics = expand['topics'];
if (Array.isArray(rawTopics)) {
nextSessionTopics = rawTopics.map((t: unknown) => {
const tr = t as Record<string, unknown>;
return {
id: tr['id'] as string,
title: tr['title'] as string,
complexityWeight: (tr['complexity_weight'] as number) ?? 1,
};
});
}
}
const employeeState: EmployeeState = {
userId: request.params.userId,
currentCycle: state['current_cycle'] as number,
currentWeek,
startDate: state['start_date'] as string,
activeVersionId,
nextSessionTheme,
nextSessionTopics,
};
return reply.send(employeeState);
});
// POST /advance/:userId
app.post<{ Params: { userId: string } }>('/advance/:userId', async (request, reply) => {
const body = AdvanceBodySchema.safeParse(request.body);
if (!body.success) {
return reply.status(400).send({ error: 'Invalid request body', details: body.error.issues });
}
const pb = await getPocketBase();
const records = await pb.collection('employee_curriculum_state').getFullList({
filter: `user = "${request.params.userId}"`,
});
if (records.length === 0) {
return reply.status(404).send({ error: 'Employee curriculum state not found' });
}
const state = records[0]!;
const currentWeek = state['current_week'] as number;
const currentCycle = state['current_cycle'] as number;
if (body.data.completedWeek !== currentWeek) {
return reply.status(409).send({
error: 'completedWeek does not match employee current_week',
expected: currentWeek,
received: body.data.completedWeek,
});
}
let newWeek: number;
let newCycle: number;
let newActiveVersion: string;
if (currentWeek === 26) {
// Cycle transition
newWeek = 1;
newCycle = currentCycle + 1;
// Use current active curriculum version for new cycle
const activeVersions = await pb.collection('curriculum_versions').getFullList({
filter: 'status = "active"',
perPage: 1,
});
newActiveVersion = activeVersions[0]?.id ?? (state['active_version'] as string);
} else {
newWeek = currentWeek + 1;
newCycle = currentCycle;
newActiveVersion = state['active_version'] as string;
}
const updates: Record<string, unknown> = {
current_week: newWeek,
current_cycle: newCycle,
active_version: newActiveVersion,
};
if (currentWeek === 26) {
updates['start_date'] = new Date().toISOString();
}
await pb.collection('employee_curriculum_state').update(state.id, updates);
return reply.send({
userId: request.params.userId,
previousWeek: currentWeek,
currentWeek: newWeek,
currentCycle: newCycle,
cycleTransition: currentWeek === 26,
});
});
}

View File

@@ -0,0 +1,125 @@
import { z } from 'zod';
// ---------------------------------------------------------------------------
// KB snapshot — input to generator
// ---------------------------------------------------------------------------
export const KBTopicSchema = z.object({
id: z.string(),
title: z.string(),
complexityWeight: z.number().min(1).max(5),
difficulty: z.string(),
prerequisiteTopics: z.array(z.string()),
relatedTopics: z.array(z.string()),
contrastTopics: z.array(z.string()),
});
export const KBThemeSchema = z.object({
id: z.string(),
title: z.string(),
description: z.string(),
topics: z.array(KBTopicSchema),
});
export const KBSnapshotSchema = z.object({
themes: z.array(KBThemeSchema),
});
export type KBTopic = z.infer<typeof KBTopicSchema>;
export type KBTheme = z.infer<typeof KBThemeSchema>;
export type KBSnapshot = z.infer<typeof KBSnapshotSchema>;
// ---------------------------------------------------------------------------
// Curriculum draft — AI output
// ---------------------------------------------------------------------------
export const CurriculumWeekDraftSchema = z.object({
weekNumber: z.number().min(1).max(26),
themeId: z.string(),
topicIds: z.array(z.string()),
estimatedDurationMinutes: z.number().min(15).max(45),
rationale: z.string(),
});
export const CurriculumDraftSchema = z.object({
weeks: z.array(CurriculumWeekDraftSchema).length(26),
});
export type CurriculumWeekDraft = z.infer<typeof CurriculumWeekDraftSchema>;
export type CurriculumDraft = z.infer<typeof CurriculumDraftSchema>;
// ---------------------------------------------------------------------------
// Employee state
// ---------------------------------------------------------------------------
export interface EmployeeState {
userId: string;
currentCycle: number;
currentWeek: number;
startDate: string;
activeVersionId: string;
nextSessionTheme: { id: string; title: string } | null;
nextSessionTopics: { id: string; title: string; complexityWeight: number }[];
}
// ---------------------------------------------------------------------------
// Job tracking
// ---------------------------------------------------------------------------
export type JobStatus = 'queued' | 'running' | 'done' | 'failed';
export interface GenerationJob {
id: string;
triggeredBy: string;
reason: 'new_topics' | 'manual';
status: JobStatus;
versionId: string | null;
error: string | null;
createdAt: Date;
updatedAt: Date;
}
// ---------------------------------------------------------------------------
// Cycle variant context
// ---------------------------------------------------------------------------
export interface CycleContext {
cycleNumber: number;
employeeHistory: {
typesUsed: string[];
typesNotUsed: string[];
lowEngagementTopics: string[];
};
}
// ---------------------------------------------------------------------------
// PocketBase record shapes
// ---------------------------------------------------------------------------
export interface CurriculumVersionRecord {
id: string;
version: number;
status: 'draft' | 'active' | 'superseded';
generated_at: string;
generation_notes: string;
}
export interface CurriculumWeekRecord {
id: string;
curriculum_version: string;
week_number: number;
theme: string;
topics: string[];
topic_order: number[];
estimated_duration_minutes: number;
admin_notes: string;
}
export interface EmployeeStateRecord {
id: string;
user: string;
current_cycle: number;
current_week: number;
start_date: string;
active_version: string;
}

View File

@@ -0,0 +1,25 @@
import { getPocketBase } from '../lib/pocketbase.js';
export async function applyVersion(newVersionId: string): Promise<void> {
const pb = await getPocketBase();
// Get all employees
const employees = await pb.collection('employee_curriculum_state').getFullList();
for (const emp of employees) {
await pb.collection('employee_curriculum_state').update(emp.id, {
active_version: newVersionId,
});
}
// Supersede old active version
const activeVersions = await pb.collection('curriculum_versions').getFullList({
filter: 'status = "active"',
});
for (const v of activeVersions) {
await pb.collection('curriculum_versions').update(v.id, { status: 'superseded' });
}
// Activate new version
await pb.collection('curriculum_versions').update(newVersionId, { status: 'active' });
}

View File

@@ -0,0 +1,25 @@
import { getPocketBase } from '../lib/pocketbase.js';
/**
* Returns the set of week numbers that are "frozen" for an employee —
* weeks they have already started or completed, derived from their current_week.
* Weeks < current_week are rendered from session_completions and must not be
* replaced by a new curriculum version.
*/
export async function getFrozenWeeks(userId: string): Promise<Set<number>> {
const pb = await getPocketBase();
const records = await pb.collection('employee_curriculum_state').getFullList({
filter: `user = "${userId}"`,
});
const state = records[0];
if (!state) return new Set();
const currentWeek = state['current_week'] as number;
const frozen = new Set<number>();
for (let w = 1; w < currentWeek; w++) {
frozen.add(w);
}
return frozen;
}

View File

@@ -0,0 +1,16 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"rootDir": "src",
"outDir": "dist",
"strict": true,
"noUncheckedIndexedAccess": true,
"forceConsistentCasingInFileNames": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src/**/*.ts"],
"exclude": ["dist", "node_modules"]
}

4
app/services/generation/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
node_modules/
dist/
.env
*.err

1550
app/services/generation/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,25 @@
{
"name": "generation",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"dev": "tsx watch src/index.ts",
"build": "tsc",
"start": "node dist/index.js"
},
"dependencies": {
"@anthropic-ai/sdk": "^0.24",
"fastify": "^4",
"pocketbase": "^0.21",
"uuid": "^9",
"zod": "^3"
},
"devDependencies": {
"@types/node": "^20",
"@types/uuid": "^9",
"dotenv": "^16",
"tsx": "^4",
"typescript": "^5"
}
}

View File

@@ -0,0 +1,18 @@
import 'dotenv/config';
import Fastify from 'fastify';
import { generateRoutes } from './routes/generate.js';
import { publishRoutes } from './routes/publish.js';
const app = Fastify({ logger: true });
await app.register(generateRoutes);
await app.register(publishRoutes);
const port = parseInt(process.env['GENERATION_PORT'] ?? '3002', 10);
try {
await app.listen({ port, host: '0.0.0.0' });
} catch (err) {
app.log.error(err);
process.exit(1);
}

View File

@@ -0,0 +1,168 @@
import { v4 as uuid } from 'uuid';
import { getPocketBase } from '../lib/pocketbase.js';
import { generateMicroLearning } from '../pipeline/generate.js';
import {
MICRO_LEARNING_TYPES,
type GenerationJob,
type JobProgress,
type TopicRecord,
} from '../types.js';
// ---------------------------------------------------------------------------
// In-memory store
// ---------------------------------------------------------------------------
const jobs = new Map<string, GenerationJob>();
const DEFAULT_PROGRESS: JobProgress = {
topicsTotal: 0,
topicsProcessed: 0,
itemsTotal: 0,
itemsGenerated: 0,
itemsFailed: 0,
};
export function createJob(themeId: string): GenerationJob {
const job: GenerationJob = {
id: uuid(),
themeId,
status: 'queued',
progress: { ...DEFAULT_PROGRESS },
error: null,
createdAt: new Date(),
updatedAt: new Date(),
};
jobs.set(job.id, job);
void runPipeline(job.id);
return job;
}
export function getJob(id: string): GenerationJob | undefined {
return jobs.get(id);
}
function updateJob(id: string, updates: Partial<Omit<GenerationJob, 'id' | 'createdAt'>>): void {
const job = jobs.get(id);
if (!job) return;
jobs.set(id, { ...job, ...updates, updatedAt: new Date() });
}
function mergeProgress(id: string, partial: Partial<JobProgress>): void {
const job = jobs.get(id);
if (!job) return;
updateJob(id, { progress: { ...job.progress, ...partial } });
}
// ---------------------------------------------------------------------------
// Pipeline orchestration
// ---------------------------------------------------------------------------
async function runPipeline(jobId: string): Promise<void> {
const job = jobs.get(jobId);
if (!job) return;
updateJob(jobId, { status: 'running' });
let topics: TopicRecord[];
try {
topics = await fetchPublishedTopics(job.themeId);
} catch (err) {
const reason = err instanceof Error ? err.message : String(err);
updateJob(jobId, { status: 'failed', error: `topic_fetch_failed: ${reason}` });
return;
}
const totalItems = topics.length * MICRO_LEARNING_TYPES.length;
mergeProgress(jobId, {
topicsTotal: topics.length,
itemsTotal: totalItems,
});
// Pre-create all micro_learning records with status: queued
const recordIds = await preCreateRecords(topics);
let generated = 0;
let failed = 0;
for (let ti = 0; ti < topics.length; ti++) {
const topic = topics[ti];
if (!topic) continue;
for (const type of MICRO_LEARNING_TYPES) {
const recordId = recordIds.get(`${topic.id}:${type}`);
if (!recordId) continue;
try {
const content = await generateMicroLearning(topic, type);
await updateMicroLearning(recordId, 'generated', content);
generated++;
} catch (err) {
const reason = err instanceof Error ? err.message : String(err);
await updateMicroLearning(recordId, 'failed', null, reason);
failed++;
}
mergeProgress(jobId, { itemsGenerated: generated, itemsFailed: failed });
}
mergeProgress(jobId, { topicsProcessed: ti + 1 });
}
updateJob(jobId, { status: 'done' });
}
// ---------------------------------------------------------------------------
// PocketBase helpers
// ---------------------------------------------------------------------------
async function fetchPublishedTopics(themeId: string): Promise<TopicRecord[]> {
const pb = await getPocketBase();
const records = await pb.collection('topics').getFullList({
filter: `theme = "${themeId}" && status = "published"`,
});
return records.map(r => ({
id: r['id'] as string,
title: r['title'] as string,
body: r['body'] as string,
difficulty: r['difficulty'] as TopicRecord['difficulty'],
key_terms: (r['key_terms'] as string[] | null) ?? [],
status: r['status'] as string,
}));
}
async function preCreateRecords(
topics: TopicRecord[],
): Promise<Map<string, string>> {
const pb = await getPocketBase();
const map = new Map<string, string>();
for (const topic of topics) {
for (const type of MICRO_LEARNING_TYPES) {
const record = await pb.collection('micro_learnings').create({
topic: topic.id,
type,
content: null,
status: 'queued',
generation_model: 'claude-sonnet-4-20250514',
});
map.set(`${topic.id}:${type}`, record.id);
}
}
return map;
}
async function updateMicroLearning(
recordId: string,
status: 'generated' | 'failed',
content: unknown,
_errorNote?: string,
): Promise<void> {
const pb = await getPocketBase();
await pb.collection('micro_learnings').update(recordId, {
status,
content: content ?? null,
generated_at: status === 'generated' ? new Date().toISOString() : null,
});
}

View File

@@ -0,0 +1,9 @@
import Anthropic from '@anthropic-ai/sdk';
export const anthropic = new Anthropic({
apiKey: process.env['ANTHROPIC_API_KEY'],
});
export const MODELS = {
SONNET: 'claude-sonnet-4-20250514',
} as const;

View File

@@ -0,0 +1,14 @@
import PocketBase from 'pocketbase';
const POCKETBASE_URL = process.env['POCKETBASE_URL'] ?? '';
const POCKETBASE_ADMIN_EMAIL = process.env['POCKETBASE_ADMIN_EMAIL'] ?? '';
const POCKETBASE_ADMIN_PASSWORD = process.env['POCKETBASE_ADMIN_PASSWORD'] ?? '';
const pb = new PocketBase(POCKETBASE_URL);
export async function getPocketBase(): Promise<PocketBase> {
if (!pb.authStore.isValid) {
await pb.admins.authWithPassword(POCKETBASE_ADMIN_EMAIL, POCKETBASE_ADMIN_PASSWORD);
}
return pb;
}

View File

@@ -0,0 +1,163 @@
import { anthropic, MODELS } from '../lib/anthropic.js';
import {
CONTENT_SCHEMAS,
TYPE_LABELS,
type MicroLearningType,
type TopicRecord,
} from '../types.js';
const SYSTEM_PROMPT = `You are a learning content designer. Your task is to generate structured learning content for a specific topic in an employee learning platform.
Output ONLY valid JSON matching the schema provided. No preamble, no explanation, no markdown fences.
The content should be accurate, practical, and appropriate for the stated difficulty level. Tone: professional but accessible.`;
function buildUserPrompt(
topic: TopicRecord,
type: MicroLearningType,
schemaDescription: string,
strict: boolean,
): string {
const base = `Topic: ${topic.title}
Difficulty: ${topic.difficulty}
Body:
${topic.body}
Key terms: ${topic.key_terms.join(', ')}
Generate a ${TYPE_LABELS[type]} for this topic.
Output schema:
${schemaDescription}`;
return strict ? base + '\n\nRespond with valid JSON only, no other text.' : base;
}
const SCHEMA_DESCRIPTIONS: Record<MicroLearningType, string> = {
concept_explainer: `{
"paragraphs": ["2 to 3 paragraphs explaining the concept in plain language"],
"example": "one concrete real-world example"
}`,
scenario_quiz: `{
"scenario": "a realistic workplace scenario",
"options": [
{ "label": "A", "text": "answer text", "correct": false, "explanation": "why" },
{ "label": "B", "text": "answer text", "correct": true, "explanation": "why" },
{ "label": "C", "text": "answer text", "correct": false, "explanation": "why" },
{ "label": "D", "text": "answer text", "correct": false, "explanation": "why" }
]
}
Rules: exactly 4 options, exactly 1 marked correct: true.`,
misconceptions: `{
"items": [
{ "misconception": "common wrong belief", "correction": "accurate explanation" }
]
}
Rules: 3 to 5 items.`,
how_to: `{
"steps": [
{ "number": 1, "instruction": "what to do" }
]
}
Rules: 3 to 8 steps.`,
comparison_card: `{
"subject_a": "first concept or approach",
"subject_b": "second concept or approach",
"dimensions": [
{ "label": "dimension name", "a": "how A differs", "b": "how B differs" }
]
}
Rules: 3 to 6 dimensions.`,
reflection_prompt: `{
"prompt": "open-ended question for the employee to reflect on",
"model_answer": "a thoughtful example answer the employee can compare against"
}`,
flashcard_set: `{
"cards": [
{ "question": "question text", "answer": "answer text" }
]
}
Rules: 5 to 10 cards.`,
case_study: `{
"scenario": "a detailed real-world scenario (150+ words)",
"questions": ["discussion question 1", "discussion question 2"]
}
Rules: 2 to 4 questions.`,
glossary_anchor: `{
"term": "the key term",
"definition": "precise definition",
"correct_use": "example sentence showing correct use",
"misuse": "common incorrect usage to avoid"
}`,
myth_vs_evidence: `{
"myth": "a commonly held misconception about this topic",
"evidence": "the evidence-based counterpoint",
"sources": ["source or reference if applicable — use empty array if none"]
}`,
};
async function callClaude(prompt: string): Promise<string> {
const response = await anthropic.messages.create({
model: MODELS.SONNET,
max_tokens: 2000,
temperature: 0,
system: SYSTEM_PROMPT,
messages: [{ role: 'user', content: prompt }],
});
const block = response.content[0];
if (!block || block.type !== 'text') {
throw new Error('unexpected response format from Claude');
}
return block.text;
}
async function parseAndValidate(raw: string, type: MicroLearningType): Promise<unknown> {
const schema = CONTENT_SCHEMAS[type];
const parsed: unknown = JSON.parse(raw);
return schema.parse(parsed);
}
export async function generateMicroLearning(
topic: TopicRecord,
type: MicroLearningType,
): Promise<unknown> {
const schemaDesc = SCHEMA_DESCRIPTIONS[type];
// For glossary_anchor, hint Claude to use the first key term
const topicWithHint: TopicRecord =
type === 'glossary_anchor' && topic.key_terms.length > 0
? { ...topic, body: topic.body + `\n\nAnchor term: ${topic.key_terms[0]}` }
: topic;
const prompt = buildUserPrompt(topicWithHint, type, schemaDesc, false);
let raw: string;
try {
raw = await callClaude(prompt);
} catch (err) {
throw new Error(`Claude API error: ${err instanceof Error ? err.message : String(err)}`);
}
// First parse attempt
try {
return await parseAndValidate(raw, type);
} catch {
// Retry with strict prompt
const strictPrompt = buildUserPrompt(topicWithHint, type, schemaDesc, true);
let raw2: string;
try {
raw2 = await callClaude(strictPrompt);
} catch (err) {
throw new Error(`Claude API error on retry: ${err instanceof Error ? err.message : String(err)}`);
}
try {
return await parseAndValidate(raw2, type);
} catch (err) {
throw new Error(
`validation failed after retry: ${err instanceof Error ? err.message : String(err)}`,
);
}
}
}

View File

@@ -0,0 +1,36 @@
import type { FastifyInstance } from 'fastify';
import { createJob, getJob } from '../jobs/queue.js';
import { GenerateBodySchema } from '../types.js';
export async function generateRoutes(app: FastifyInstance): Promise<void> {
app.post('/generate', async (request, reply) => {
const parsed = GenerateBodySchema.safeParse(request.body);
if (!parsed.success) {
return reply.status(400).send({ error: 'invalid request', details: parsed.error.issues });
}
const { themeId } = parsed.data;
const job = createJob(themeId);
return reply.status(202).send({
jobId: job.id,
status: job.status,
topicsFound: job.progress.topicsTotal,
totalItems: job.progress.itemsTotal,
});
});
app.get<{ Params: { jobId: string } }>('/status/:jobId', async (request, reply) => {
const job = getJob(request.params.jobId);
if (!job) {
return reply.status(404).send({ error: 'job not found' });
}
return reply.send({
jobId: job.id,
status: job.status,
progress: job.progress,
error: job.error,
});
});
}

View File

@@ -0,0 +1,44 @@
import type { FastifyInstance } from 'fastify';
import { getPocketBase } from '../lib/pocketbase.js';
import { PublishBodySchema } from '../types.js';
export async function publishRoutes(app: FastifyInstance): Promise<void> {
app.patch<{ Params: { id: string } }>('/micro-learnings/:id', async (request, reply) => {
const parsed = PublishBodySchema.safeParse(request.body);
if (!parsed.success) {
return reply.status(400).send({ error: 'invalid request', details: parsed.error.issues });
}
const { id } = request.params;
const { status: newStatus } = parsed.data;
const pb = await getPocketBase();
let existing: Record<string, unknown>;
try {
existing = await pb.collection('micro_learnings').getOne(id) as Record<string, unknown>;
} catch {
return reply.status(404).send({ error: 'micro learning not found' });
}
if (existing['status'] !== 'generated') {
return reply.status(400).send({
error: 'only generated records can be published or rejected',
currentStatus: existing['status'],
});
}
const updates: Record<string, unknown> = { status: newStatus };
if (newStatus === 'published') {
updates['published_at'] = new Date().toISOString();
}
const updated = await pb.collection('micro_learnings').update(id, updates);
return reply.send({
id: updated.id,
status: updated['status'],
published_at: updated['published_at'] ?? null,
});
});
}

View File

@@ -0,0 +1,201 @@
import { z } from 'zod';
// ---------------------------------------------------------------------------
// Micro learning types
// ---------------------------------------------------------------------------
export const MICRO_LEARNING_TYPES = [
'concept_explainer',
'scenario_quiz',
'misconceptions',
'how_to',
'comparison_card',
'reflection_prompt',
'flashcard_set',
'case_study',
'glossary_anchor',
'myth_vs_evidence',
] as const;
export type MicroLearningType = (typeof MICRO_LEARNING_TYPES)[number];
// ---------------------------------------------------------------------------
// Content schemas — validated against AI output before PocketBase write
// ---------------------------------------------------------------------------
export const ConceptExplainerSchema = z.object({
paragraphs: z.array(z.string().min(10)).min(2).max(3),
example: z.string().min(20),
});
export const ScenarioQuizSchema = z.object({
scenario: z.string().min(30),
options: z
.array(
z.object({
label: z.enum(['A', 'B', 'C', 'D']),
text: z.string().min(5),
correct: z.boolean(),
explanation: z.string().min(10),
}),
)
.length(4)
.refine(opts => opts.filter(o => o.correct).length === 1, {
message: 'exactly one correct option required',
}),
});
export const MisconceptionsSchema = z.object({
items: z
.array(
z.object({
misconception: z.string().min(10),
correction: z.string().min(10),
}),
)
.min(3)
.max(5),
});
export const HowToSchema = z.object({
steps: z
.array(
z.object({
number: z.number().int().positive(),
instruction: z.string().min(10),
}),
)
.min(3)
.max(8),
});
export const ComparisonCardSchema = z.object({
subject_a: z.string().min(2),
subject_b: z.string().min(2),
dimensions: z
.array(
z.object({
label: z.string().min(2),
a: z.string().min(5),
b: z.string().min(5),
}),
)
.min(3)
.max(6),
});
export const ReflectionPromptSchema = z.object({
prompt: z.string().min(20),
model_answer: z.string().min(50),
});
export const FlashcardSetSchema = z.object({
cards: z
.array(
z.object({
question: z.string().min(5),
answer: z.string().min(5),
}),
)
.min(5)
.max(10),
});
export const CaseStudySchema = z.object({
scenario: z.string().min(150),
questions: z.array(z.string().min(10)).min(2).max(4),
});
export const GlossaryAnchorSchema = z.object({
term: z.string().min(2),
definition: z.string().min(20),
correct_use: z.string().min(20),
misuse: z.string().min(20),
});
export const MythVsEvidenceSchema = z.object({
myth: z.string().min(20),
evidence: z.string().min(30),
sources: z.array(z.string()),
});
// Map type → schema for lookup
export const CONTENT_SCHEMAS: Record<MicroLearningType, z.ZodTypeAny> = {
concept_explainer: ConceptExplainerSchema,
scenario_quiz: ScenarioQuizSchema,
misconceptions: MisconceptionsSchema,
how_to: HowToSchema,
comparison_card: ComparisonCardSchema,
reflection_prompt: ReflectionPromptSchema,
flashcard_set: FlashcardSetSchema,
case_study: CaseStudySchema,
glossary_anchor: GlossaryAnchorSchema,
myth_vs_evidence: MythVsEvidenceSchema,
};
// Map type → human-readable label for prompts
export const TYPE_LABELS: Record<MicroLearningType, string> = {
concept_explainer: 'Concept Explainer',
scenario_quiz: 'Scenario Quiz',
misconceptions: 'Misconceptions',
how_to: 'How-To Guide',
comparison_card: 'Comparison Card',
reflection_prompt: 'Reflection Prompt',
flashcard_set: 'Flashcard Set',
case_study: 'Case Study',
glossary_anchor: 'Glossary Anchor',
myth_vs_evidence: 'Myth vs Evidence',
};
// ---------------------------------------------------------------------------
// PocketBase: Topic (fetched from PB before generation)
// ---------------------------------------------------------------------------
export interface TopicRecord {
id: string;
title: string;
body: string;
difficulty: 'introductory' | 'intermediate' | 'advanced';
key_terms: string[];
status: string;
}
// ---------------------------------------------------------------------------
// Job system
// ---------------------------------------------------------------------------
export type JobStatus = 'queued' | 'running' | 'done' | 'failed';
export interface JobProgress {
topicsTotal: number;
topicsProcessed: number;
itemsTotal: number;
itemsGenerated: number;
itemsFailed: number;
}
export interface GenerationJob {
id: string;
themeId: string;
status: JobStatus;
progress: JobProgress;
error: string | null;
createdAt: Date;
updatedAt: Date;
}
// ---------------------------------------------------------------------------
// API request schemas (Zod — validates external input)
// ---------------------------------------------------------------------------
export const GenerateBodySchema = z.object({
themeId: z.string().min(1),
});
export type GenerateBody = z.infer<typeof GenerateBodySchema>;
export const PublishBodySchema = z.object({
status: z.enum(['published', 'rejected']),
});
export type PublishBody = z.infer<typeof PublishBodySchema>;

View File

@@ -0,0 +1,16 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"rootDir": "src",
"outDir": "dist",
"strict": true,
"noUncheckedIndexedAccess": true,
"forceConsistentCasingInFileNames": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src/**/*.ts"],
"exclude": ["dist", "node_modules"]
}

1888
app/services/ingestion/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -2,6 +2,7 @@
"name": "ingestion",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"dev": "tsx watch src/index.ts",
"build": "tsc",
@@ -10,21 +11,22 @@
"migrate:qdrant": "tsx src/migrations/002_qdrant_setup.ts"
},
"dependencies": {
"fastify": "^4",
"@anthropic-ai/sdk": "^0.24",
"openai": "^4",
"@qdrant/js-client-rest": "^1.9",
"pocketbase": "^0.21",
"fastify": "^4",
"openai": "^4",
"pdf-parse": "^1.1",
"pocketbase": "^0.21",
"uuid": "^9",
"zod": "^3"
},
"devDependencies": {
"typescript": "^5",
"tsx": "^4",
"dotenv": "^16",
"@types/node": "^20",
"@types/pdf-parse": "^1.1",
"@types/uuid": "^9"
"@types/uuid": "^9",
"dotenv": "^16",
"pdfkit": "^0.18.0",
"tsx": "^4",
"typescript": "^5"
}
}

View File

@@ -0,0 +1,16 @@
C:\Users\RaymondVerhoef\Gitea\learning-platform\app\services\ingestion\node_modules\.bin\tsx:2
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
^^^^^^^
SyntaxError: missing ) after argument list
at wrapSafe (node:internal/modules/cjs/loader:1662:18)
at Module._compile (node:internal/modules/cjs/loader:1704:20)
at Object..js (node:internal/modules/cjs/loader:1895:10)
at Module.load (node:internal/modules/cjs/loader:1465:32)
at Function._load (node:internal/modules/cjs/loader:1282:12)
at TracingChannel.traceSync (node:diagnostics_channel:322:14)
at wrapModuleLoad (node:internal/modules/cjs/loader:235:24)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:171:5)
at node:internal/main/run_main_module:36:49
Node.js v22.16.0

View File

@@ -46,7 +46,7 @@ const field = {
}),
json: (name: string): FieldDef => ({
name, type: 'json', required: false, options: {},
name, type: 'json', required: false, options: { maxSize: 2097152 },
}),
date: (name: string): FieldDef => ({
@@ -146,18 +146,17 @@ async function run(): Promise<void> {
const usersCol = await pb.collections.getFirstListItem<CollectionModel>('name="users"');
ids.set('users', usersCol.id);
const hasRole = usersCol.schema.some(s => s.name === 'role');
if (!hasRole) {
const existingFieldNames = new Set(usersCol.schema.map((s: { name: string }) => s.name));
const fieldsToAdd: FieldDef[] = [];
if (!existingFieldNames.has('role')) fieldsToAdd.push(field.select('role', ['admin', 'employee'], true));
if (!existingFieldNames.has('display_name')) fieldsToAdd.push(field.text('display_name'));
if (fieldsToAdd.length > 0) {
const updateBody: Record<string, unknown> = {
schema: [
...usersCol.schema,
field.select('role', ['admin', 'employee'], true),
field.text('display_name'),
field.file('avatar', ['image/jpeg', 'image/png', 'image/webp']),
],
schema: [...usersCol.schema, ...fieldsToAdd],
};
await pb.collections.update(usersCol.id, updateBody);
console.log(' extended with role, display_name, avatar');
console.log(` extended with: ${fieldsToAdd.map((f: FieldDef) => f.name).join(', ')}`);
} else {
console.log(' skip users (already extended)');
}

View File

@@ -26,11 +26,16 @@ export async function embedAndStore(
writtenTopics: WrittenTopic[],
onProgress: (embedded: number) => void,
): Promise<void> {
// Build chunk → topic mapping
// Build chunk → topic mapping.
// The AI labels chunks as [CHUNK-<uuid>] so sourceChunkIds may carry that prefix;
// strip it so lookups match the bare UUID used as the Qdrant point ID.
const normalise = (id: string): string => id.replace(/^CHUNK-/i, '');
const chunkTopicMap = new Map<string, string>();
const chunkThemeMap = new Map<string, string>();
for (const topic of writtenTopics) {
for (const chunkId of topic.sourceChunkIds) {
for (const rawId of topic.sourceChunkIds) {
const chunkId = normalise(rawId);
chunkTopicMap.set(chunkId, topic.id);
chunkThemeMap.set(chunkId, topic.themeId);
}
@@ -94,7 +99,9 @@ export async function embedAndStore(
// Update topics.qdrant_chunk_ids in PocketBase
// -------------------------------------------------------------------------
for (const topic of writtenTopics) {
const qdrantIds = topic.sourceChunkIds.filter(id => chunkTopicMap.get(id) === topic.id);
const qdrantIds = topic.sourceChunkIds
.map(id => normalise(id))
.filter(id => chunkTopicMap.get(id) === topic.id);
if (qdrantIds.length > 0) {
await updateTopicQdrantIds(topic.id, qdrantIds);
}

View File

@@ -61,7 +61,7 @@ function buildUserPrompt(chunks: Chunk[], filename: string, format: DocumentForm
async function callClaude(chunks: Chunk[], filename: string, format: DocumentFormat, strict: boolean): Promise<DraftKB> {
const response = await anthropic.messages.create({
model: MODELS.SONNET,
max_tokens: 8000,
max_tokens: 16000,
temperature: 0,
system: SYSTEM_PROMPT,
messages: [{ role: 'user', content: buildUserPrompt(chunks, filename, format, strict) }],
@@ -76,12 +76,14 @@ async function callClaude(chunks: Chunk[], filename: string, format: DocumentFor
try {
parsed = JSON.parse(textBlock.text);
} catch {
console.error(`[structure] JSON parse failed (strict=${strict}), response length=${textBlock.text.length}, stop_reason=${response.stop_reason}`);
if (strict) throw new Error('structure_extraction_failed');
return callClaude(chunks, filename, format, true);
}
const result = DraftKBSchema.safeParse(parsed);
if (!result.success) {
console.error(`[structure] Zod validation failed (strict=${strict}):`, JSON.stringify(result.error.issues.slice(0,3)));
if (strict) throw new Error('structure_extraction_failed');
return callClaude(chunks, filename, format, true);
}

View File

@@ -82,6 +82,7 @@ export interface WrittenTopic {
// ---------------------------------------------------------------------------
export interface SourceChunkPayload {
[key: string]: unknown;
source_document_id: string;
chunk_index: number;
text: string;
@@ -91,6 +92,7 @@ export interface SourceChunkPayload {
}
export interface TopicSummaryPayload {
[key: string]: unknown;
topic_id: string;
theme_id: string;
title: string;

View File

@@ -0,0 +1,605 @@
# Comprehensive Guide to Enterprise Software Architecture
## Introduction to Software Architecture
Software architecture defines the high-level structure of a software system. It encompasses the decisions made about the organization of a system, the selection of structural elements and their interfaces, and the composition of these elements.
### Microservices Architecture Part 1
Microservices is an architectural style that structures applications as a collection of small, independently deployable services. Each service runs in its own process and communicates through lightweight APIs.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
## Event-Driven Architecture Part 1
Event-driven architecture is a software design pattern where components communicate through events. Producers publish events to an event broker, and consumers subscribe to receive relevant events asynchronously.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
## Domain-Driven Design Part 1
Domain-driven design is an approach to software development that centers the development on programming a domain model that has a rich understanding of the processes and rules of a domain.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
### CQRS Pattern Part 1
Command Query Responsibility Segregation separates read and write operations for a data store. The read and write sides can be scaled independently and use different models optimized for their specific purpose.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
## Event Sourcing Part 1
Event sourcing stores the state of a business entity as a sequence of state-changing events. Instead of storing just the current state, the complete history of all events is stored.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
## Saga Pattern Part 1
The Saga pattern manages distributed transactions in microservices. It breaks a long transaction into a sequence of smaller local transactions, each with compensating actions for rollback.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
### API Gateway Pattern Part 1
An API gateway is a single entry point for client requests in a microservices architecture. It handles cross-cutting concerns like authentication, rate limiting, and request routing.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
## Service Mesh Part 1
A service mesh is a dedicated infrastructure layer for handling service-to-service communication. It provides features like load balancing, service discovery, health checking, and observability.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
## Hexagonal Architecture Part 1
Hexagonal architecture, also known as Ports and Adapters, separates the application core from external concerns. The domain logic sits in the center, surrounded by ports and adapters.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
### Clean Architecture Part 1
Clean architecture organizes code into concentric layers with dependencies pointing inward. The innermost layer contains enterprise business rules, followed by application rules, interface adapters, and frameworks.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
## Repository Pattern Part 1
The repository pattern provides an abstraction layer between the data access layer and the business logic. It centralizes data access logic and provides a consistent interface for querying data.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
## Unit of Work Pattern Part 1
The Unit of Work pattern maintains a list of objects affected by a business transaction. It coordinates the writing of changes and resolves concurrency problems.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
### Factory Pattern Part 1
The factory pattern provides an interface for creating objects in a superclass while allowing subclasses to alter the type of objects created. It promotes loose coupling between object creation and usage.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
## Observer Pattern Part 1
The observer pattern defines a one-to-many dependency between objects. When one object changes state, all its dependents are notified and updated automatically.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
## Strategy Pattern Part 1
The strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. It lets the algorithm vary independently from clients that use it.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
### Decorator Pattern Part 1
The decorator pattern attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
## Circuit Breaker Pattern Part 1
The circuit breaker pattern prevents cascading failures in distributed systems. It wraps calls to external services and monitors for failures, opening the circuit when a threshold is exceeded.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
## Bulkhead Pattern Part 1
The bulkhead pattern isolates elements of an application into pools so that if one fails, the others will continue to function. It limits the impact of a failure by containing it within a section.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
### Retry Pattern Part 1
The retry pattern enables an application to retry a failed operation. It handles transient failures that might occur when connecting to a service or network resource.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
## Throttling Pattern Part 1
The throttling pattern controls the consumption of resources used by an instance of an application. It allows resources to be fairly distributed and prevents any single instance from consuming too many resources.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
## Cache-Aside Pattern Part 1
The cache-aside pattern loads data into the cache on demand from the data store. It improves performance by keeping frequently accessed data in cache while allowing the data store to be the source of truth.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
### Sharding Pattern Part 1
The sharding pattern divides a data store into horizontal partitions called shards. Each shard can be maintained on a separate server, allowing the load to be distributed across multiple machines.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
## Data Lake Architecture Part 1
A data lake is a centralized repository that stores all structured and unstructured data at any scale. It enables you to store data as-is without first having to structure it.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
## Lambda Architecture Part 1
Lambda architecture is a data-processing design that handles massive quantities of data. It uses both batch and real-time processing methods, providing a balance between latency and throughput.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
### Kappa Architecture Part 1
Kappa architecture is a simplification of the lambda architecture. It removes the batch layer and processes everything as a stream, using a replayable log as the source of truth.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
## Microservices Architecture Part 2
Microservices is an architectural style that structures applications as a collection of small, independently deployable services. Each service runs in its own process and communicates through lightweight APIs.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
## Event-Driven Architecture Part 2
Event-driven architecture is a software design pattern where components communicate through events. Producers publish events to an event broker, and consumers subscribe to receive relevant events asynchronously.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
### Domain-Driven Design Part 2
Domain-driven design is an approach to software development that centers the development on programming a domain model that has a rich understanding of the processes and rules of a domain.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
## CQRS Pattern Part 2
Command Query Responsibility Segregation separates read and write operations for a data store. The read and write sides can be scaled independently and use different models optimized for their specific purpose.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
## Event Sourcing Part 2
Event sourcing stores the state of a business entity as a sequence of state-changing events. Instead of storing just the current state, the complete history of all events is stored.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
### Saga Pattern Part 2
The Saga pattern manages distributed transactions in microservices. It breaks a long transaction into a sequence of smaller local transactions, each with compensating actions for rollback.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
## API Gateway Pattern Part 2
An API gateway is a single entry point for client requests in a microservices architecture. It handles cross-cutting concerns like authentication, rate limiting, and request routing.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
## Service Mesh Part 2
A service mesh is a dedicated infrastructure layer for handling service-to-service communication. It provides features like load balancing, service discovery, health checking, and observability.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
### Hexagonal Architecture Part 2
Hexagonal architecture, also known as Ports and Adapters, separates the application core from external concerns. The domain logic sits in the center, surrounded by ports and adapters.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
## Clean Architecture Part 2
Clean architecture organizes code into concentric layers with dependencies pointing inward. The innermost layer contains enterprise business rules, followed by application rules, interface adapters, and frameworks.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
## Repository Pattern Part 2
The repository pattern provides an abstraction layer between the data access layer and the business logic. It centralizes data access logic and provides a consistent interface for querying data.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
### Unit of Work Pattern Part 2
The Unit of Work pattern maintains a list of objects affected by a business transaction. It coordinates the writing of changes and resolves concurrency problems.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
## Factory Pattern Part 2
The factory pattern provides an interface for creating objects in a superclass while allowing subclasses to alter the type of objects created. It promotes loose coupling between object creation and usage.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
## Observer Pattern Part 2
The observer pattern defines a one-to-many dependency between objects. When one object changes state, all its dependents are notified and updated automatically.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
### Strategy Pattern Part 2
The strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. It lets the algorithm vary independently from clients that use it.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
## Decorator Pattern Part 2
The decorator pattern attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
## Circuit Breaker Pattern Part 2
The circuit breaker pattern prevents cascading failures in distributed systems. It wraps calls to external services and monitors for failures, opening the circuit when a threshold is exceeded.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
### Bulkhead Pattern Part 2
The bulkhead pattern isolates elements of an application into pools so that if one fails, the others will continue to function. It limits the impact of a failure by containing it within a section.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
## Retry Pattern Part 2
The retry pattern enables an application to retry a failed operation. It handles transient failures that might occur when connecting to a service or network resource.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
## Throttling Pattern Part 2
The throttling pattern controls the consumption of resources used by an instance of an application. It allows resources to be fairly distributed and prevents any single instance from consuming too many resources.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
### Cache-Aside Pattern Part 2
The cache-aside pattern loads data into the cache on demand from the data store. It improves performance by keeping frequently accessed data in cache while allowing the data store to be the source of truth.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
## Sharding Pattern Part 2
The sharding pattern divides a data store into horizontal partitions called shards. Each shard can be maintained on a separate server, allowing the load to be distributed across multiple machines.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
## Data Lake Architecture Part 2
A data lake is a centralized repository that stores all structured and unstructured data at any scale. It enables you to store data as-is without first having to structure it.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
### Lambda Architecture Part 2
Lambda architecture is a data-processing design that handles massive quantities of data. It uses both batch and real-time processing methods, providing a balance between latency and throughput.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
## Kappa Architecture Part 2
Kappa architecture is a simplification of the lambda architecture. It removes the batch layer and processes everything as a stream, using a replayable log as the source of truth.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
## Microservices Architecture Part 3
Microservices is an architectural style that structures applications as a collection of small, independently deployable services. Each service runs in its own process and communicates through lightweight APIs.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
### Event-Driven Architecture Part 3
Event-driven architecture is a software design pattern where components communicate through events. Producers publish events to an event broker, and consumers subscribe to receive relevant events asynchronously.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
## Domain-Driven Design Part 3
Domain-driven design is an approach to software development that centers the development on programming a domain model that has a rich understanding of the processes and rules of a domain.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
## CQRS Pattern Part 3
Command Query Responsibility Segregation separates read and write operations for a data store. The read and write sides can be scaled independently and use different models optimized for their specific purpose.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
### Event Sourcing Part 3
Event sourcing stores the state of a business entity as a sequence of state-changing events. Instead of storing just the current state, the complete history of all events is stored.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
## Saga Pattern Part 3
The Saga pattern manages distributed transactions in microservices. It breaks a long transaction into a sequence of smaller local transactions, each with compensating actions for rollback.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
## API Gateway Pattern Part 3
An API gateway is a single entry point for client requests in a microservices architecture. It handles cross-cutting concerns like authentication, rate limiting, and request routing.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
### Service Mesh Part 3
A service mesh is a dedicated infrastructure layer for handling service-to-service communication. It provides features like load balancing, service discovery, health checking, and observability.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
## Hexagonal Architecture Part 3
Hexagonal architecture, also known as Ports and Adapters, separates the application core from external concerns. The domain logic sits in the center, surrounded by ports and adapters.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
## Clean Architecture Part 3
Clean architecture organizes code into concentric layers with dependencies pointing inward. The innermost layer contains enterprise business rules, followed by application rules, interface adapters, and frameworks.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
### Repository Pattern Part 3
The repository pattern provides an abstraction layer between the data access layer and the business logic. It centralizes data access logic and provides a consistent interface for querying data.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
## Unit of Work Pattern Part 3
The Unit of Work pattern maintains a list of objects affected by a business transaction. It coordinates the writing of changes and resolves concurrency problems.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
## Factory Pattern Part 3
The factory pattern provides an interface for creating objects in a superclass while allowing subclasses to alter the type of objects created. It promotes loose coupling between object creation and usage.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
### Observer Pattern Part 3
The observer pattern defines a one-to-many dependency between objects. When one object changes state, all its dependents are notified and updated automatically.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
## Strategy Pattern Part 3
The strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. It lets the algorithm vary independently from clients that use it.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
## Decorator Pattern Part 3
The decorator pattern attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
### Circuit Breaker Pattern Part 3
The circuit breaker pattern prevents cascading failures in distributed systems. It wraps calls to external services and monitors for failures, opening the circuit when a threshold is exceeded.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
## Bulkhead Pattern Part 3
The bulkhead pattern isolates elements of an application into pools so that if one fails, the others will continue to function. It limits the impact of a failure by containing it within a section.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
## Retry Pattern Part 3
The retry pattern enables an application to retry a failed operation. It handles transient failures that might occur when connecting to a service or network resource.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
### Throttling Pattern Part 3
The throttling pattern controls the consumption of resources used by an instance of an application. It allows resources to be fairly distributed and prevents any single instance from consuming too many resources.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
## Cache-Aside Pattern Part 3
The cache-aside pattern loads data into the cache on demand from the data store. It improves performance by keeping frequently accessed data in cache while allowing the data store to be the source of truth.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
## Sharding Pattern Part 3
The sharding pattern divides a data store into horizontal partitions called shards. Each shard can be maintained on a separate server, allowing the load to be distributed across multiple machines.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
### Data Lake Architecture Part 3
A data lake is a centralized repository that stores all structured and unstructured data at any scale. It enables you to store data as-is without first having to structure it.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
## Lambda Architecture Part 3
Lambda architecture is a data-processing design that handles massive quantities of data. It uses both batch and real-time processing methods, providing a balance between latency and throughput.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.
## Kappa Architecture Part 3
Kappa architecture is a simplification of the lambda architecture. It removes the batch layer and processes everything as a stream, using a replayable log as the source of truth.
This concept is fundamental to building scalable distributed systems. Organizations that adopt this pattern report significant improvements in system reliability and maintainability. The implementation requires careful consideration of the trade-offs involved.
When implementing this pattern, teams must consider the operational overhead, the learning curve, and the impact on the existing codebase. Success depends on having the right tooling, monitoring, and team expertise in place.

View File

@@ -0,0 +1,2 @@
%PDF-1.4
This is not a valid PDF file. It contains garbage content that pdf-parse cannot handle.

View File

@@ -0,0 +1,6 @@
{
"documentId": "i9ue488qb30owl4",
"filename": "sample.md",
"format": "md",
"filePath": "C:\\Users\\RaymondVerhoef\\Gitea\\learning-platform\\app\\services\\ingestion\\test-files\\sample.md"
}

View File

@@ -0,0 +1,72 @@
# Introduction to TypeScript
TypeScript is a strongly typed programming language that builds on JavaScript, giving you better tooling at any scale.
## Type System
TypeScript adds optional static typing and class-based object-oriented programming to the language. Types provide a way to describe the shape of an object, providing better documentation, and allowing TypeScript to validate that your code is working correctly.
### Basic Types
TypeScript supports several basic types including `string`, `number`, `boolean`, `array`, and `tuple`. These types allow you to add type annotations to your variables and function parameters.
## Interfaces
Interfaces define the shape of an object in TypeScript. They are a powerful way to define contracts within your code as well as contracts with code outside of your project.
```typescript
interface User {
name: string;
age: number;
email?: string;
}
```
An interface can define optional properties using the `?` operator. This is useful when some properties may or may not be present.
## Classes
TypeScript supports full class-based object-oriented programming with inheritance, interfaces, and access modifiers. Classes provide a clean and reusable way to create objects.
```typescript
class Animal {
private name: string;
constructor(name: string) {
this.name = name;
}
public move(distance: number = 0): void {
console.log(`${this.name} moved ${distance}m.`);
}
}
```
Access modifiers (`public`, `private`, `protected`) control visibility of class members.
## Generics
Generics provide a way to make components work with any data type and not restrict to one data type. They allow users to consume these components and use their own types.
```typescript
function identity<T>(arg: T): T {
return arg;
}
```
Generics are particularly useful for building reusable data structures and functions that work across multiple types.
## Enums
Enumerations allow you to define a set of named constants. TypeScript provides both numeric and string-based enums.
```typescript
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
}
```
String enums are more readable than numeric enums because they provide meaningful string values at runtime.

View File

@@ -0,0 +1,55 @@
%PDF-1.4
1 0 obj
<</Type /Catalog /Pages 2 0 R>>
endobj
2 0 obj
<</Type /Pages /Kids [3 0 R] /Count 1>>
endobj
3 0 obj
<</Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Resources <</Font <</F1 4 0 R>>>> /Contents 5 0 R>>
endobj
4 0 obj
<</Type /Font /Subtype /Type1 /BaseFont /Helvetica>>
endobj
5 0 obj
<</Length 582>>
stream
BT
/F1 14 Tf
50 750 Td
(Introduction to Docker Containers) Tj
0 -25 Td
/F1 12 Tf
(Docker is a platform for developing shipping and running applications.) Tj
0 -18 Td
(Containers are lightweight portable units that include all dependencies.) Tj
0 -25 Td
/F1 14 Tf
(Container Images) Tj
0 -25 Td
/F1 12 Tf
(A Docker image is a read-only template used to create containers.) Tj
0 -18 Td
(Images are built from a Dockerfile with build instructions.) Tj
0 -25 Td
/F1 14 Tf
(Docker Compose) Tj
0 -25 Td
/F1 12 Tf
(Docker Compose defines multi-container applications in compose.yml.) Tj
ET
endstream
endobj
xref
0 6
0000000000 65535 f
0000000009 00000 n
0000000056 00000 n
0000000111 00000 n
0000000231 00000 n
0000000299 00000 n
trailer
<</Size 6 /Root 1 0 R>>
startxref
930
%%EOF

View File

@@ -0,0 +1,31 @@
Introduction to Software Architecture Patterns
Software architecture patterns are reusable solutions to commonly occurring problems in software architecture within a given context. They are similar to design patterns but with a broader scope.
The most common architecture patterns include layered architecture, event-driven architecture, microservices, and service-oriented architecture. Each pattern has its own strengths and weaknesses that make it more or less appropriate for different use cases.
Layered Architecture
The layered architecture pattern organizes code into layers of functionality. The most common is the n-tier architecture with presentation, business logic, and data access layers. Each layer has a specific role and responsibility.
The presentation layer handles all user interface and browser communication logic. The business logic layer executes specific business rules associated with the request. The data access layer handles all aspects of the database interaction.
Benefits of the layered approach include separation of concerns, ease of testing, and good maintainability. Each layer can be developed and maintained independently, reducing the complexity of the system.
Event-Driven Architecture
Event-driven architecture is a software design pattern in which decoupled applications can asynchronously publish and subscribe to events via an event broker. This pattern promotes loose coupling and scalability.
Events represent things that have happened in the system. An event producer publishes an event to an event broker. Event consumers subscribe to event types and receive notifications when events are published.
Message queues like RabbitMQ and Apache Kafka are commonly used as event brokers. They provide durable, reliable messaging between producers and consumers, even when consumers are temporarily offline.
Microservices Architecture
Microservices is an architectural style that structures an application as a collection of small, independently deployable services. Each service is focused on a specific business capability.
Services communicate over well-defined APIs, usually HTTP REST or message queues. Each service can be deployed, scaled, and updated independently. This allows teams to work on different services without affecting the others.
Container orchestration platforms like Kubernetes are commonly used to manage microservices deployments. They handle service discovery, load balancing, and automatic scaling.
The challenges of microservices include distributed system complexity, data consistency, and operational overhead. Teams need to invest in DevOps tooling and practices to manage microservices effectively.

Binary file not shown.

View File

@@ -0,0 +1,6 @@
{
"documentId": "gjntkc43rs6s3e3",
"filename": "sample.md",
"format": "md",
"filePath": "C:\\Users\\RaymondVerhoef\\Gitea\\learning-platform\\app\\services\\ingestion\\test-files\\sample.md"
}