From 3fc032233990bc6952b2c5b74e77239f30f26660 Mon Sep 17 00:00:00 2001 From: Suhani Bhati Date: Wed, 8 Apr 2026 20:15:38 +0530 Subject: [PATCH 1/3] feat: Bug Recommendation system using vector cosine-similarity --- .gitignore | 5 +- ai-service/app.py | 15 ++ ai-service/model.py | 6 + ai-service/requirements.txt | 4 + ai-service/utils.py | 17 +++ core-services/package-lock.json | 135 ++++++++++++++++++ core-services/package.json | 1 + core-services/src/app.js | 5 + .../controllers/recommendation.Controller.js | 45 ++++++ .../src/controllers/vector.Controller.js | 46 ++++++ core-services/src/models/Bug.js | 19 ++- core-services/src/models/Vector.js | 26 ++-- .../src/routes/recommendation.Routes.js | 8 ++ core-services/src/routes/vector.Routes.js | 8 ++ .../src/services/embedding.Service.js | 26 ++++ core-services/src/utils/similarityScore.js | 13 ++ 16 files changed, 355 insertions(+), 24 deletions(-) create mode 100644 ai-service/app.py create mode 100644 ai-service/model.py create mode 100644 ai-service/requirements.txt create mode 100644 ai-service/utils.py create mode 100644 core-services/src/controllers/recommendation.Controller.js create mode 100644 core-services/src/controllers/vector.Controller.js create mode 100644 core-services/src/routes/recommendation.Routes.js create mode 100644 core-services/src/routes/vector.Routes.js create mode 100644 core-services/src/services/embedding.Service.js create mode 100644 core-services/src/utils/similarityScore.js diff --git a/.gitignore b/.gitignore index 3a1c910..893f16e 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,7 @@ dist-ssr/ .DS_Store Thumbs.db -.vscode/ \ No newline at end of file +.vscode/ + +__pycache__/ +*.pyc \ No newline at end of file diff --git a/ai-service/app.py b/ai-service/app.py new file mode 100644 index 0000000..ce75fdc --- /dev/null +++ b/ai-service/app.py @@ -0,0 +1,15 @@ +from flask import Flask, request, jsonify +from model import get_embedding +from utils import build_text + +app = Flask(__name__) + +@app.route("/embed", methods=["POST"]) +def embed(): + data = request.json + text = build_text(data) + embedding = get_embedding(text) + return jsonify({"embedding": embedding}) + +if __name__ == "__main__": + app.run(port=3000, debug=True) \ No newline at end of file diff --git a/ai-service/model.py b/ai-service/model.py new file mode 100644 index 0000000..38a8fd4 --- /dev/null +++ b/ai-service/model.py @@ -0,0 +1,6 @@ +from sentence_transformers import SentenceTransformer + +model = SentenceTransformer('all-MiniLM-L6-v2') + +def get_embedding(text): + return model.encode(text).tolist() \ No newline at end of file diff --git a/ai-service/requirements.txt b/ai-service/requirements.txt new file mode 100644 index 0000000..55b696b --- /dev/null +++ b/ai-service/requirements.txt @@ -0,0 +1,4 @@ +flask +sentence-transformers +numpy +scikit-learn \ No newline at end of file diff --git a/ai-service/utils.py b/ai-service/utils.py new file mode 100644 index 0000000..c6ba873 --- /dev/null +++ b/ai-service/utils.py @@ -0,0 +1,17 @@ +def build_text(data): + # Safe extraction + title = data.get("title", "") + description = data.get("description", "") + tags = data.get("tags", []) + tech_stack = data.get("techStack", []) + difficulty = data.get("difficulty", "") + tags_text = " ".join(tags) + tech_text = " ".join(tech_stack) + text = f""" + BUG TITLE: {title}. {title}. + BUG DESCRIPTION: {description}. + BUG TAGS: {tags_text}. + TECHNOLOGIES: {tech_text}. + DIFFICULTY LEVEL: {difficulty}. + """ + return text.lower().strip() \ No newline at end of file diff --git a/core-services/package-lock.json b/core-services/package-lock.json index 33cb959..b0b1018 100644 --- a/core-services/package-lock.json +++ b/core-services/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "axios": "^1.14.0", "cors": "^2.8.6", "dotenv": "^17.3.1", "express": "^5.2.1", @@ -69,6 +70,23 @@ "node": ">= 8" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.14.0.tgz", + "integrity": "sha512-3Y8yrqLSwjuzpXuZ0oIYZ/XGgLwUIBU3uLvbcpb0pidD9ctpShJd43KSlEEkVQg6DS0G9NKyzOvBfUtDKEyHvQ==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^2.1.0" + } + }, "node_modules/balanced-match": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", @@ -214,6 +232,18 @@ "fsevents": "~2.3.2" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/content-disposition": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", @@ -288,6 +318,15 @@ } } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -368,6 +407,21 @@ "node": ">= 0.4" } }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -460,6 +514,63 @@ "url": "https://opencollective.com/express" } }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/form-data/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -586,6 +697,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -1033,6 +1159,15 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", diff --git a/core-services/package.json b/core-services/package.json index 2ac36f1..f2204ce 100644 --- a/core-services/package.json +++ b/core-services/package.json @@ -13,6 +13,7 @@ "license": "ISC", "type": "commonjs", "dependencies": { + "axios": "^1.14.0", "cors": "^2.8.6", "dotenv": "^17.3.1", "express": "^5.2.1", diff --git a/core-services/src/app.js b/core-services/src/app.js index c2b8a80..1c27bca 100644 --- a/core-services/src/app.js +++ b/core-services/src/app.js @@ -1,5 +1,7 @@ const express = require("express"); const cors = require("cors"); +const vectorRouter = require("./routes/vector.Routes"); +const recommendationRouter = require("./routes/recommendation.Routes"); const app = express(); @@ -10,4 +12,7 @@ app.get("/", (req, res) => { res.send("Backend is running!!"); }); +app.use("/api/vectors", vectorRouter); +app.use("/api/recommendations", recommendationRouter); + module.exports = app; \ No newline at end of file diff --git a/core-services/src/controllers/recommendation.Controller.js b/core-services/src/controllers/recommendation.Controller.js new file mode 100644 index 0000000..9745318 --- /dev/null +++ b/core-services/src/controllers/recommendation.Controller.js @@ -0,0 +1,45 @@ +const Bug = require("../models/Bug"); +const Vector = require("../models/Vector"); +const { cosineSimilarity } = require("../utils/similarityScore"); + +exports.getSimilarBugs = async (req, res) => { + try { + const bugId = req.params.id; + + const currentVectorDoc = await Vector.findOne({ bugId }); + + if (!currentVectorDoc) { + return res.status(404).json({ message: "Vector not found" }); + } + const currentVector = currentVectorDoc.vector; + const allVectors = await Vector.find({ bugId: { $ne: bugId } }); + const similarities = allVectors.map((item) => ({ + bugId: item.bugId, + score: cosineSimilarity(currentVector, item.vector) + })); + const top5 = similarities + .sort((a, b) => b.score - a.score) + .slice(0, 5); + const bugs = await Bug.find({ + _id: { $in: top5.map(i => i.bugId) } + }); + const bugMap = new Map(); + bugs.forEach(b => bugMap.set(b._id.toString(), b)); + + const finalRecommendations = top5.map(item => { + const bug = bugMap.get(item.bugId.toString()); + if (!bug) return null; + return { + ...bug.toObject(), + similarityScore: item.score + }; + }).filter(Boolean); + res.json({ + recommendations: finalRecommendations + }); + + } catch (err) { + console.error(err); + res.status(500).json({ message: "Error fetching recommendations" }); + } +}; \ No newline at end of file diff --git a/core-services/src/controllers/vector.Controller.js b/core-services/src/controllers/vector.Controller.js new file mode 100644 index 0000000..d1121e1 --- /dev/null +++ b/core-services/src/controllers/vector.Controller.js @@ -0,0 +1,46 @@ +const mongoose = require("mongoose"); +const Bug = require("../models/Bug"); +const Vector = require("../models/Vector"); +const { getEmbedding } = require("../services/embedding.Service"); + +exports.createVector = async (req, res) => { + try { + const { bugId } = req.body; + + if (!bugId) { + return res.status(400).json({ message: "bugId is required" }); + } + + if (!mongoose.Types.ObjectId.isValid(bugId)) { + return res.status(400).json({ message: "Invalid bugId format" }); + } + + const bug = await Bug.findById(bugId); + + if (!bug) { + return res.status(404).json({ message: "Bug not found" }); + } + + const embedding = await getEmbedding(bug); + + const vectorDoc = await Vector.findOneAndUpdate( + { bugId: bug._id }, + { + bugId: bug._id, + vector: embedding, + modelVersion: "all-MiniLM-L6-v2", + updatedAt: new Date(), + }, + { upsert: true, returnDocument: "after"} + ); + + return res.status(200).json({ + message: "Vector created successfully", + data: vectorDoc, + }); + + } catch (error) { + console.error("VECTOR ERROR:", error); + return res.status(500).json({ message: "Error creating vector" }); + } +}; \ No newline at end of file diff --git a/core-services/src/models/Bug.js b/core-services/src/models/Bug.js index e517293..3e3ffaf 100644 --- a/core-services/src/models/Bug.js +++ b/core-services/src/models/Bug.js @@ -1,7 +1,7 @@ -import mongoose from "mongoose"; - -const bugSchema = new mongoose.Schema( - { +const mongoose = require("mongoose"); +const { ObjectId } = mongoose.Schema.Types; + +const bugSchema = new mongoose.Schema({ _id: ObjectId, title: { type: String, @@ -22,8 +22,7 @@ const bugSchema = new mongoose.Schema( type: String, lowercase: true, trim: true - } - ], + }], difficulty: { type: String, enum: ["EASY", "MEDIUM", "HARD"], @@ -64,18 +63,16 @@ const bugSchema = new mongoose.Schema( type: Boolean, default: true }, - isAbusive: { type: Boolean, default: false }, - refundProcessed: { type: Boolean, default: false }, createdAt: Date, updatedAt: Date -} -); -export default mongoose.model("Bug", bugSchema); +}); + +module.exports = mongoose.model("Bug", bugSchema); diff --git a/core-services/src/models/Vector.js b/core-services/src/models/Vector.js index c8fcad8..33ce55f 100644 --- a/core-services/src/models/Vector.js +++ b/core-services/src/models/Vector.js @@ -1,25 +1,27 @@ -import mongoose from "mongoose"; +const mongoose = require("mongoose"); +const { ObjectId } = mongoose.Schema.Types; -const vectorSchema = new mongoose.Schema( -{ +const vectorSchema = new mongoose.Schema({ _id: ObjectId, - bugId: { - type: ObjectId, + type: mongoose.Schema.Types.ObjectId, ref: "Bug", unique: true, index: true, required: true }, - vector: { type: [Number], required: true }, + modelVersion: { + type: String, + required: true + }, + updatedAt: { + type: Date, + default: Date.now + } +}); - modelVersion: String, - updatedAt: Date -} -); - -export default mongoose.model("Vector", vectorSchema); \ No newline at end of file +module.exports = mongoose.model("Vector", vectorSchema); \ No newline at end of file diff --git a/core-services/src/routes/recommendation.Routes.js b/core-services/src/routes/recommendation.Routes.js new file mode 100644 index 0000000..4829023 --- /dev/null +++ b/core-services/src/routes/recommendation.Routes.js @@ -0,0 +1,8 @@ +const express = require("express"); +const { getSimilarBugs } = require("../controllers/recommendation.Controller"); + +const router = express.Router(); + +router.get("/:id", getSimilarBugs); + +module.exports = router; \ No newline at end of file diff --git a/core-services/src/routes/vector.Routes.js b/core-services/src/routes/vector.Routes.js new file mode 100644 index 0000000..65a34a4 --- /dev/null +++ b/core-services/src/routes/vector.Routes.js @@ -0,0 +1,8 @@ +const express = require("express"); +const { createVector } = require("../controllers/vector.Controller"); + +const router = express.Router(); + +router.post("/", createVector); + +module.exports = router; \ No newline at end of file diff --git a/core-services/src/services/embedding.Service.js b/core-services/src/services/embedding.Service.js new file mode 100644 index 0000000..d7c8cc3 --- /dev/null +++ b/core-services/src/services/embedding.Service.js @@ -0,0 +1,26 @@ +const axios = require("axios"); + +exports.getEmbedding = async (bug) => { + try { + const res = await axios.post( + `${process.env.AI_SERVICE_URL}/embed`, + { + title: bug.title, + description: bug.description, + tags: Array.isArray(bug.tags) + ? bug.tags.join(", ") + : bug.tags, + techStack: Array.isArray(bug.techStack) + ? bug.techStack.join(", ") + : bug.techStack, + difficulty: bug.difficulty + } + ); + + return res.data.embedding; + + } catch (err) { + console.error("Embedding error:", err.message); + throw err; + } +}; \ No newline at end of file diff --git a/core-services/src/utils/similarityScore.js b/core-services/src/utils/similarityScore.js new file mode 100644 index 0000000..24de783 --- /dev/null +++ b/core-services/src/utils/similarityScore.js @@ -0,0 +1,13 @@ +exports.cosineSimilarity = (A, B) => { + let dot = 0.0; + let normA = 0.0; + let normB = 0.0; + + for (let i = 0; i < A.length; i++) { + dot += A[i] * B[i]; + normA += A[i] * A[i]; + normB += B[i] * B[i]; + } + + return dot / (Math.sqrt(normA) * Math.sqrt(normB)); +}; \ No newline at end of file From 56de14b5366ab96514559c87e188df10fc06a06a Mon Sep 17 00:00:00 2001 From: Suhani Bhati Date: Thu, 9 Apr 2026 00:44:21 +0530 Subject: [PATCH 2/3] fix: add request validation , safe debug mode , handle data type mismatch , improve cosine similarity --- ai-service/app.py | 24 ++++++++++++++----- ai-service/requirements.txt | 3 ++- ai-service/utils.py | 14 +++++++---- .../controllers/recommendation.Controller.js | 6 ++++- .../src/services/embedding.Service.js | 8 ++----- core-services/src/utils/similarityScore.js | 4 +++- 6 files changed, 39 insertions(+), 20 deletions(-) diff --git a/ai-service/app.py b/ai-service/app.py index ce75fdc..057b85a 100644 --- a/ai-service/app.py +++ b/ai-service/app.py @@ -1,15 +1,27 @@ +import os +from dotenv import load_dotenv from flask import Flask, request, jsonify from model import get_embedding from utils import build_text +load_dotenv() app = Flask(__name__) @app.route("/embed", methods=["POST"]) def embed(): - data = request.json - text = build_text(data) - embedding = get_embedding(text) - return jsonify({"embedding": embedding}) - + try: + data = request.json + if not data: + return jsonify({"error": "Invalid input"}), 400 + text = build_text(data) + embedding = get_embedding(text) + text = build_text(data) + return jsonify({"embedding": embedding}), 200 + except Exception as e: + print("Error in /embed:", str(e)) + return jsonify({"error": "Internal server error"}), 500 + if __name__ == "__main__": - app.run(port=3000, debug=True) \ No newline at end of file + PORT = int(os.getenv("PORT", 3000)) + DEBUG = os.getenv("FLASK_DEBUG", "false").lower() == ["true", "1", "yes"] + app.run(port=PORT, debug=DEBUG) \ No newline at end of file diff --git a/ai-service/requirements.txt b/ai-service/requirements.txt index 55b696b..3ef5d10 100644 --- a/ai-service/requirements.txt +++ b/ai-service/requirements.txt @@ -1,4 +1,5 @@ flask sentence-transformers numpy -scikit-learn \ No newline at end of file +scikit-learn +python-dotenv \ No newline at end of file diff --git a/ai-service/utils.py b/ai-service/utils.py index c6ba873..cc963b6 100644 --- a/ai-service/utils.py +++ b/ai-service/utils.py @@ -1,12 +1,16 @@ +def safe_join(value): + if isinstance(value, list): + return " ".join(value) + if isinstance(value, str): + return value + return "" def build_text(data): - # Safe extraction title = data.get("title", "") description = data.get("description", "") - tags = data.get("tags", []) - tech_stack = data.get("techStack", []) + tags_text = safe_join(data.get("tags")) + tech_text = safe_join(data.get("techStack")) difficulty = data.get("difficulty", "") - tags_text = " ".join(tags) - tech_text = " ".join(tech_stack) + text = f""" BUG TITLE: {title}. {title}. BUG DESCRIPTION: {description}. diff --git a/core-services/src/controllers/recommendation.Controller.js b/core-services/src/controllers/recommendation.Controller.js index 9745318..efa7fd3 100644 --- a/core-services/src/controllers/recommendation.Controller.js +++ b/core-services/src/controllers/recommendation.Controller.js @@ -1,11 +1,15 @@ const Bug = require("../models/Bug"); const Vector = require("../models/Vector"); const { cosineSimilarity } = require("../utils/similarityScore"); +const mongoose = require("mongoose"); exports.getSimilarBugs = async (req, res) => { try { const bugId = req.params.id; - + if (!mongoose.Types.ObjectId.isValid(bugId)) { + return res.status(400).json({ message: "Invalid bugId" }); + } + const currentVectorDoc = await Vector.findOne({ bugId }); if (!currentVectorDoc) { diff --git a/core-services/src/services/embedding.Service.js b/core-services/src/services/embedding.Service.js index d7c8cc3..099d431 100644 --- a/core-services/src/services/embedding.Service.js +++ b/core-services/src/services/embedding.Service.js @@ -7,12 +7,8 @@ exports.getEmbedding = async (bug) => { { title: bug.title, description: bug.description, - tags: Array.isArray(bug.tags) - ? bug.tags.join(", ") - : bug.tags, - techStack: Array.isArray(bug.techStack) - ? bug.techStack.join(", ") - : bug.techStack, + tags: bug.tags, + techStack: bug.techStack, difficulty: bug.difficulty } ); diff --git a/core-services/src/utils/similarityScore.js b/core-services/src/utils/similarityScore.js index 24de783..82e3b0f 100644 --- a/core-services/src/utils/similarityScore.js +++ b/core-services/src/utils/similarityScore.js @@ -1,4 +1,5 @@ exports.cosineSimilarity = (A, B) => { + if (!A || !B || A.length !== B.length) return 0; let dot = 0.0; let normA = 0.0; let normB = 0.0; @@ -8,6 +9,7 @@ exports.cosineSimilarity = (A, B) => { normA += A[i] * A[i]; normB += B[i] * B[i]; } - + const denom = Math.sqrt(normA) * Math.sqrt(normB); + if (denom === 0) return 0; return dot / (Math.sqrt(normA) * Math.sqrt(normB)); }; \ No newline at end of file From d76494e44cfd1cb7572558582bc589571854f038 Mon Sep 17 00:00:00 2001 From: Suhani Bhati Date: Thu, 9 Apr 2026 00:52:36 +0530 Subject: [PATCH 3/3] fix: improve app.py --- ai-service/app.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ai-service/app.py b/ai-service/app.py index 057b85a..82ea896 100644 --- a/ai-service/app.py +++ b/ai-service/app.py @@ -15,7 +15,6 @@ def embed(): return jsonify({"error": "Invalid input"}), 400 text = build_text(data) embedding = get_embedding(text) - text = build_text(data) return jsonify({"embedding": embedding}), 200 except Exception as e: print("Error in /embed:", str(e)) @@ -23,5 +22,5 @@ def embed(): if __name__ == "__main__": PORT = int(os.getenv("PORT", 3000)) - DEBUG = os.getenv("FLASK_DEBUG", "false").lower() == ["true", "1", "yes"] + DEBUG = os.getenv("FLASK_DEBUG", "false").lower() in ["true", "1", "yes"] app.run(port=PORT, debug=DEBUG) \ No newline at end of file