Spaces:
Sleeping
Sleeping
Download apps/api/dist/network/graph.service.js from Benaah/MicProtocol: direct link, hf CLI and curl.
- Browser
- Download file 15.1 kB
-
https://huggingface.co/spaces/Benaah/MicProtocol/resolve/main/apps/api/dist/network/graph.service.js
- Command line
-
hf download hf://spaces/Benaah/MicProtocol/apps/api/dist/network/graph.service.js
-
curl -L -o graph.service.js https://huggingface.co/spaces/Benaah/MicProtocol/resolve/main/apps/api/dist/network/graph.service.js
15.1 kB
| ; | |
| var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { | |
| var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; | |
| if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); | |
| else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; | |
| return c > 3 && r && Object.defineProperty(target, key, r), r; | |
| }; | |
| var __metadata = (this && this.__metadata) || function (k, v) { | |
| if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); | |
| }; | |
| var GraphService_1; | |
| Object.defineProperty(exports, "__esModule", { value: true }); | |
| exports.GraphService = void 0; | |
| const common_1 = require("@nestjs/common"); | |
| const prisma_service_1 = require("../prisma/prisma.service"); | |
| let GraphService = GraphService_1 = class GraphService { | |
| prisma; | |
| logger = new common_1.Logger(GraphService_1.name); | |
| adjacencyList = new Map(); | |
| constructor(prisma) { | |
| this.prisma = prisma; | |
| } | |
| async onModuleInit() { | |
| await this.loadEdgesFromDatabase(); | |
| } | |
| async loadEdgesFromDatabase() { | |
| try { | |
| const edges = await this.prisma.graphEdge.findMany(); | |
| for (const edge of edges) { | |
| const sourceId = edge.source; | |
| const targetId = edge.target; | |
| const storedEdge = { | |
| source: edge.source, | |
| target: edge.target, | |
| type: edge.type, | |
| weight: edge.weight, | |
| metadata: edge.metadata, | |
| }; | |
| if (!this.adjacencyList.has(sourceId)) { | |
| this.adjacencyList.set(sourceId, new Map()); | |
| } | |
| this.adjacencyList.get(sourceId).set(targetId, storedEdge); | |
| if (edge.type === "knows" || edge.type === "similar_to" || edge.type === "appeared_on") { | |
| const reverseEdge = { ...storedEdge, source: edge.target, target: edge.source }; | |
| if (!this.adjacencyList.has(edge.target)) { | |
| this.adjacencyList.set(edge.target, new Map()); | |
| } | |
| this.adjacencyList.get(edge.target).set(edge.source, reverseEdge); | |
| } | |
| if (edge.source.startsWith("guest:") || edge.source.startsWith("podcast:")) { | |
| const edges = this.adjacencyList.get(edge.source); | |
| if (edge.metadata && typeof edge.metadata === "object") { | |
| const meta = edge.metadata; | |
| if (meta.nodeData) { | |
| edges.nodeData = meta.nodeData; | |
| } | |
| } | |
| } | |
| } | |
| this.logger.log(`Loaded ${edges.length} graph edges from database`); | |
| } | |
| catch (error) { | |
| this.logger.warn("Failed to load graph edges from database, starting with empty graph", error); | |
| } | |
| } | |
| async ingestProfile(profileId) { | |
| this.logger.log(`Ingesting profile ${profileId} into graph`); | |
| const profile = await this.prisma.guestPublicProfile.findUnique({ | |
| where: { id: profileId }, | |
| include: { | |
| expertiseTags: true, | |
| bookingHistory: true, | |
| }, | |
| }); | |
| if (!profile) { | |
| this.logger.warn(`Profile ${profileId} not found`); | |
| return; | |
| } | |
| this.addNode({ | |
| type: "guest", | |
| id: `guest:${profileId}`, | |
| data: { | |
| name: profile.headline, | |
| country: profile.country, | |
| expertise: profile.expertiseTags.map((t) => t.name), | |
| tier: profile.tier, | |
| }, | |
| }); | |
| for (const booking of profile.bookingHistory) { | |
| const podcastNodeId = `podcast:${booking.podcastName}`; | |
| this.addNode({ | |
| type: "podcast", | |
| id: podcastNodeId, | |
| data: { name: booking.podcastName }, | |
| }); | |
| this.addEdge({ | |
| source: `guest:${profileId}`, | |
| target: podcastNodeId, | |
| type: "appeared_on", | |
| weight: 1, | |
| metadata: { | |
| date: booking.appearedAt, | |
| topics: booking.topicsDiscussed, | |
| }, | |
| }); | |
| } | |
| await this.calculateSimilarityEdges(profileId, profile.expertiseTags); | |
| this.logger.log(`Profile ${profileId} ingested successfully`); | |
| } | |
| addNode(node) { | |
| if (!this.adjacencyList.has(node.id)) { | |
| this.adjacencyList.set(node.id, new Map()); | |
| } | |
| const edges = this.adjacencyList.get(node.id); | |
| edges.nodeData = node.data; | |
| } | |
| addEdge(edge) { | |
| if (!this.adjacencyList.has(edge.source)) { | |
| this.adjacencyList.set(edge.source, new Map()); | |
| } | |
| this.adjacencyList.get(edge.source).set(edge.target, edge); | |
| if (edge.type === "knows" || edge.type === "similar_to" || edge.type === "appeared_on") { | |
| const reverseEdge = { ...edge, source: edge.target, target: edge.source }; | |
| if (!this.adjacencyList.has(edge.target)) { | |
| this.adjacencyList.set(edge.target, new Map()); | |
| } | |
| this.adjacencyList.get(edge.target).set(edge.source, reverseEdge); | |
| } | |
| this.persistEdge(edge).catch(err => this.logger.error(`Failed to persist graph edge`, err)); | |
| } | |
| async persistEdge(edge) { | |
| try { | |
| await this.prisma.graphEdge.upsert({ | |
| where: { | |
| source_target_type: { | |
| source: edge.source, | |
| target: edge.target, | |
| type: edge.type, | |
| }, | |
| }, | |
| create: { | |
| source: edge.source, | |
| target: edge.target, | |
| type: edge.type, | |
| weight: edge.weight, | |
| metadata: (edge.metadata || {}), | |
| }, | |
| update: { | |
| weight: edge.weight, | |
| metadata: (edge.metadata || {}), | |
| }, | |
| }); | |
| } | |
| catch (error) { | |
| this.logger.error(`Failed to persist edge ${edge.source} -> ${edge.target} (${edge.type})`, error); | |
| } | |
| } | |
| async calculateSimilarityEdges(profileId, expertiseTags) { | |
| const expertiseNames = expertiseTags.map((t) => t.name); | |
| if (expertiseNames.length === 0) | |
| return; | |
| const similarGuests = await this.prisma.guestPublicProfile.findMany({ | |
| where: { | |
| id: { not: profileId }, | |
| isPublic: true, | |
| expertiseTags: { | |
| some: { | |
| name: { in: expertiseNames }, | |
| }, | |
| }, | |
| }, | |
| include: { | |
| expertiseTags: true, | |
| }, | |
| take: 20, | |
| }); | |
| for (const other of similarGuests) { | |
| const otherExpertise = other.expertiseTags.map((t) => t.name); | |
| const sharedExpertise = expertiseNames.filter((e) => otherExpertise.includes(e)); | |
| if (sharedExpertise.length > 0) { | |
| const similarityScore = sharedExpertise.length / | |
| Math.max(expertiseNames.length, otherExpertise.length); | |
| this.addEdge({ | |
| source: `guest:${profileId}`, | |
| target: `guest:${other.id}`, | |
| type: "similar_to", | |
| weight: similarityScore, | |
| metadata: { | |
| sharedExpertise, | |
| score: similarityScore, | |
| }, | |
| }); | |
| } | |
| } | |
| } | |
| async getRecommendations(profileId, limit = 5) { | |
| const nodeId = `guest:${profileId}`; | |
| const scores = new Map(); | |
| const reasons = new Map(); | |
| const appearedOn = this.getNeighbors(nodeId, "appeared_on"); | |
| for (const podcastId of appearedOn) { | |
| const coGuests = this.getNeighbors(podcastId, "appeared_on") | |
| .filter((g) => g !== nodeId); | |
| for (const coGuest of coGuests) { | |
| const guestId = coGuest.replace("guest:", ""); | |
| const currentScore = scores.get(guestId) || 0; | |
| scores.set(guestId, currentScore + 1); | |
| if (!reasons.has(guestId)) { | |
| reasons.set(guestId, []); | |
| } | |
| const podcastName = this.getNodeData(podcastId)?.name || "a podcast"; | |
| reasons.get(guestId).push(`Appeared on ${podcastName}`); | |
| } | |
| } | |
| const similar = this.getNeighbors(nodeId, "similar_to"); | |
| for (const simGuest of similar) { | |
| const guestId = simGuest.replace("guest:", ""); | |
| const edge = this.getEdge(nodeId, simGuest); | |
| const currentScore = scores.get(guestId) || 0; | |
| const weight = edge?.weight || 0.5; | |
| scores.set(guestId, currentScore + weight); | |
| if (!reasons.has(guestId)) { | |
| reasons.set(guestId, []); | |
| } | |
| const shared = edge?.metadata?.sharedExpertise || ["similar expertise"]; | |
| reasons.get(guestId).push(`Shares expertise in ${shared.slice(0, 2).join(", ")}`); | |
| } | |
| const sorted = Array.from(scores.entries()) | |
| .sort((a, b) => b[1] - a[1]) | |
| .slice(0, limit); | |
| return sorted.map(([guestId, score]) => ({ | |
| guestId, | |
| reason: [...new Set(reasons.get(guestId) || [])].join("; "), | |
| score, | |
| })); | |
| } | |
| async findTrustPath(fromProfileId, toProfileId) { | |
| const start = `guest:${fromProfileId}`; | |
| const end = `guest:${toProfileId}`; | |
| if (!this.adjacencyList.has(start) || !this.adjacencyList.has(end)) { | |
| return null; | |
| } | |
| const queue = [{ node: start, path: [start] }]; | |
| const visited = new Set(); | |
| while (queue.length > 0) { | |
| const { node, path } = queue.shift(); | |
| if (node === end) { | |
| return path.map((n) => n.replace("guest:", "").replace("podcast:", "")); | |
| } | |
| if (visited.has(node)) | |
| continue; | |
| visited.add(node); | |
| const neighbors = this.adjacencyList.get(node); | |
| if (neighbors) { | |
| for (const [neighbor] of Array.from(neighbors.entries())) { | |
| if (!visited.has(neighbor)) { | |
| queue.push({ node: neighbor, path: [...path, neighbor] }); | |
| } | |
| } | |
| } | |
| } | |
| return null; | |
| } | |
| async getNetworkStats(profileId) { | |
| const nodeId = `guest:${profileId}`; | |
| if (!this.adjacencyList.has(nodeId)) { | |
| return { connections: 0, reach: 0, centrality: 0, communities: [] }; | |
| } | |
| const directConnections = this.adjacencyList.get(nodeId).size; | |
| const visited = new Set(); | |
| visited.add(nodeId); | |
| const neighbors = this.getAllNeighbors(nodeId); | |
| for (const n of Array.from(neighbors)) { | |
| visited.add(n); | |
| } | |
| for (const n of Array.from(neighbors)) { | |
| const secondHop = this.getAllNeighbors(n); | |
| for (const sh of Array.from(secondHop)) { | |
| visited.add(sh); | |
| } | |
| } | |
| const communities = this.identifyCommunities(nodeId); | |
| return { | |
| connections: directConnections, | |
| reach: visited.size - 1, | |
| centrality: this.calculateCentrality(nodeId), | |
| communities, | |
| }; | |
| } | |
| async getCommunityMembers(communityType, value, limit = 20) { | |
| const members = []; | |
| for (const [nodeId, edges] of Array.from(this.adjacencyList.entries())) { | |
| if (!nodeId.startsWith("guest:")) | |
| continue; | |
| const data = edges.nodeData; | |
| if (!data) | |
| continue; | |
| if (communityType === "country" && data.country === value) { | |
| members.push(nodeId.replace("guest:", "")); | |
| } | |
| else if (communityType === "expertise" && data.expertise?.includes(value)) { | |
| members.push(nodeId.replace("guest:", "")); | |
| } | |
| } | |
| return members.slice(0, limit); | |
| } | |
| getNeighbors(nodeId, edgeType) { | |
| const edges = this.adjacencyList.get(nodeId); | |
| if (!edges) | |
| return []; | |
| return Array.from(edges.entries()) | |
| .filter(([, edge]) => edge.type === edgeType) | |
| .map(([target]) => target); | |
| } | |
| getAllNeighbors(nodeId) { | |
| const edges = this.adjacencyList.get(nodeId); | |
| if (!edges) | |
| return []; | |
| return Array.from(edges.keys()); | |
| } | |
| getEdge(source, target) { | |
| return this.adjacencyList.get(source)?.get(target); | |
| } | |
| getNodeData(nodeId) { | |
| const edges = this.adjacencyList.get(nodeId); | |
| if (!edges) | |
| return null; | |
| return edges.nodeData; | |
| } | |
| identifyCommunities(nodeId) { | |
| const data = this.getNodeData(nodeId); | |
| if (!data) | |
| return []; | |
| const communities = []; | |
| if (data.country) { | |
| communities.push(`${data.country} Community`); | |
| } | |
| if (data.expertise) { | |
| communities.push(...data.expertise.slice(0, 2).map((e) => `${e} Network`)); | |
| } | |
| return communities; | |
| } | |
| calculateCentrality(nodeId) { | |
| const direct = this.adjacencyList.get(nodeId)?.size || 0; | |
| const twoHop = new Set(); | |
| const neighbors = this.getAllNeighbors(nodeId); | |
| for (const n of neighbors) { | |
| const secondHop = this.getAllNeighbors(n); | |
| for (const sh of secondHop) { | |
| if (sh !== nodeId) { | |
| twoHop.add(sh); | |
| } | |
| } | |
| } | |
| return direct + twoHop.size * 0.5; | |
| } | |
| getStats() { | |
| let edges = 0; | |
| let guests = 0; | |
| let podcasts = 0; | |
| for (const [nodeId] of Array.from(this.adjacencyList.entries())) { | |
| if (nodeId.startsWith("guest:")) | |
| guests++; | |
| if (nodeId.startsWith("podcast:")) | |
| podcasts++; | |
| edges += this.adjacencyList.get(nodeId).size; | |
| } | |
| return { | |
| nodes: this.adjacencyList.size, | |
| edges: edges / 2, | |
| guests, | |
| podcasts, | |
| }; | |
| } | |
| }; | |
| exports.GraphService = GraphService; | |
| exports.GraphService = GraphService = GraphService_1 = __decorate([ | |
| (0, common_1.Injectable)(), | |
| __metadata("design:paramtypes", [prisma_service_1.PrismaService]) | |
| ], GraphService); | |
| //# sourceMappingURL=graph.service.js.map |