MicProtocol / apps /api /dist /network /graph.service.js
Benaah's picture
Upload folder using huggingface_hub (part 2)
9f29339 verified
Raw History Blame Contribute Delete
15.1 kB
"use strict";
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