feat(p4): content analysis service with Neo4j knowledge graph and ClickHouse analytics
P4 阶段交付物: - services/content: 内容资源服务(NestJS) - textbooks: 教材 CRUD + 知识图谱绑定 - config/neo4j.ts: Neo4j driver 单例 - textbooks.service.ts: MySQL CRUD + Neo4j 知识图谱(createKnowledgeGraph/getPrerequisites) - package.json: 补充 @opentelemetry/sdk-node + exporter-trace-otlp-http - services/data-ana: 数据分析服务(Python FastAPI) - main.py: FastAPI + /healthz + class_performance + student_weakness 骨架 - clickhouse_client.py: ClickHouse 客户端封装 - config.py: 环境变量配置 - packages/shared-proto/proto/content.proto: TextbookService + KnowledgeGraphService 契约 - packages/shared-proto/proto/analytics.proto: AnalyticsService 契约(class_performance/student_weakness)
This commit is contained in:
69
services/content/src/textbooks/textbooks.service.ts
Normal file
69
services/content/src/textbooks/textbooks.service.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { getDb } from '../config/database.js';
|
||||
import { neo4jDriver } from '../config/neo4j.js';
|
||||
import { textbooks, chapters, knowledgePoints } from './textbooks.schema.js';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { eq } from 'drizzle-orm';
|
||||
|
||||
@Injectable()
|
||||
export class TextbooksService {
|
||||
async create(data: { title: string; subjectId: string; gradeId: string; version: string }) {
|
||||
const id = uuidv4();
|
||||
const db = getDb();
|
||||
await db.insert(textbooks).values({ id, ...data });
|
||||
return { id, ...data };
|
||||
}
|
||||
|
||||
async list() {
|
||||
const db = getDb();
|
||||
return db.select().from(textbooks);
|
||||
}
|
||||
|
||||
async getById(id: string) {
|
||||
const db = getDb();
|
||||
const [result] = await db.select().from(textbooks).where(eq(textbooks.id, id));
|
||||
return result;
|
||||
}
|
||||
|
||||
// 知识图谱:在 Neo4j 中创建知识点节点和关系
|
||||
async createKnowledgeGraph(knowledgePointId: string, title: string, prerequisiteIds: string[]): Promise<void> {
|
||||
const session = neo4jDriver.session();
|
||||
try {
|
||||
await session.executeWrite((tx) =>
|
||||
tx.run(
|
||||
'MERGE (kp:KnowledgePoint {id: $id, title: $title})',
|
||||
{ id: knowledgePointId, title }
|
||||
)
|
||||
);
|
||||
|
||||
for (const prereqId of prerequisiteIds) {
|
||||
await session.executeWrite((tx) =>
|
||||
tx.run(
|
||||
`MATCH (prereq:KnowledgePoint {id: $prereqId}), (kp:KnowledgePoint {id: $kpId})
|
||||
MERGE (prereq)-[:PREREQUISITE_OF]->(kp)`,
|
||||
{ prereqId, kpId: knowledgePointId }
|
||||
)
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
await session.close();
|
||||
}
|
||||
}
|
||||
|
||||
// 查询知识图谱(前置知识点)
|
||||
async getPrerequisites(knowledgePointId: string): Promise<unknown[]> {
|
||||
const session = neo4jDriver.session();
|
||||
try {
|
||||
const result = await session.executeRead((tx) =>
|
||||
tx.run(
|
||||
`MATCH (kp:KnowledgePoint {id: $id})<-[:PREREQUISITE_OF*1..5]-(prereq)
|
||||
RETURN prereq.id as id, prereq.title as title`,
|
||||
{ id: knowledgePointId }
|
||||
)
|
||||
);
|
||||
return result.records.map((r) => ({ id: r.get('id'), title: r.get('title') }));
|
||||
} finally {
|
||||
await session.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user