bat-6 commited on
Commit
41bd215
·
1 Parent(s): 3d4e2d3
api/main.py CHANGED
@@ -1,6 +1,7 @@
1
  # api/main.py
2
 
3
- from fastapi import FastAPI
 
4
  from fastapi.middleware.cors import CORSMiddleware
5
 
6
  from api.schemas import AnalyzeRequest, ChatRequest, ChatResponse
@@ -46,16 +47,22 @@ def health():
46
 
47
  @app.post("/analyze")
48
  def analyze(data: AnalyzeRequest):
49
-
50
- result = analyze_project(
51
- title=data.title,
52
- description=data.description,
53
- abstract=data.abstract,
54
- features=data.features,
55
- top_k=data.top_k
56
- )
57
-
58
- return result
 
 
 
 
 
 
59
 
60
 
61
  @app.post("/chat", response_model=ChatResponse)
 
1
  # api/main.py
2
 
3
+ from fastapi import FastAPI, HTTPException
4
+
5
  from fastapi.middleware.cors import CORSMiddleware
6
 
7
  from api.schemas import AnalyzeRequest, ChatRequest, ChatResponse
 
47
 
48
  @app.post("/analyze")
49
  def analyze(data: AnalyzeRequest):
50
+ try:
51
+ result = analyze_project(
52
+ title=data.title,
53
+ description=data.description,
54
+ abstract=data.abstract,
55
+ features=data.features,
56
+ top_k=data.top_k
57
+ )
58
+ return result
59
+ except HTTPException:
60
+ raise
61
+ except Exception as exc:
62
+ raise HTTPException(
63
+ status_code=500,
64
+ detail=f"Analysis failed: {exc}"
65
+ )
66
 
67
 
68
  @app.post("/chat", response_model=ChatResponse)
api/services.py CHANGED
@@ -199,7 +199,8 @@ def analyze_project(
199
  if not isinstance(results, pd.DataFrame) or len(results) == 0:
200
  return {
201
  "message": "No similar projects found",
202
- "extracted_features": merged
 
203
  }
204
 
205
  # -----------------------------------
@@ -216,12 +217,17 @@ def analyze_project(
216
  "final_originality_score": round(float(row.get("originality_score", 0)), 4)
217
  })
218
 
 
 
 
219
  return {
220
  "extracted_features": merged,
 
221
  "top_similar_projects": top_projects
222
  }
223
 
224
 
 
225
  def chat_with_llm(user_id: str, message: str):
226
  try:
227
  from src.recommendation_engine.chatbot_engine import chatbot
 
199
  if not isinstance(results, pd.DataFrame) or len(results) == 0:
200
  return {
201
  "message": "No similar projects found",
202
+ "extracted_features": merged,
203
+ "overall_originality_score": 100.0
204
  }
205
 
206
  # -----------------------------------
 
217
  "final_originality_score": round(float(row.get("originality_score", 0)), 4)
218
  })
219
 
220
+ # Overall = worst-case originality (against the most similar project)
221
+ overall_originality_score = top_projects[0]["final_originality_score"]
222
+
223
  return {
224
  "extracted_features": merged,
225
+ "overall_originality_score": overall_originality_score,
226
  "top_similar_projects": top_projects
227
  }
228
 
229
 
230
+
231
  def chat_with_llm(user_id: str, message: str):
232
  try:
233
  from src.recommendation_engine.chatbot_engine import chatbot
src/similarity_model/hybrid_ranker.py CHANGED
@@ -53,24 +53,20 @@ def get_dynamic_weights(
53
  coverage: float
54
  ):
55
  """
56
- Adaptive weights depending on
57
- feature richness.
58
  """
59
 
60
- semantic_w = DEFAULT_SEMANTIC_WEIGHT
61
- feature_w = DEFAULT_FEATURE_WEIGHT
62
-
63
- # many strong features
64
  if feature_count >= 5 and coverage >= 0.60:
65
- semantic_w = 0.30
66
- feature_w = HIGH_FEATURE_WEIGHT
67
 
68
- # weak features -> trust semantic more
69
- elif feature_count <= 2:
70
- semantic_w = 0.80
71
- feature_w = LOW_FEATURE_WEIGHT
72
 
73
- return semantic_w, feature_w
 
74
 
75
 
76
  # =====================================================
@@ -85,10 +81,19 @@ def compute_hybrid_score(
85
  ) -> float:
86
 
87
  semantic_score = clamp(semantic_score)
88
- feature_score = clamp(feature_score)
89
- coverage = clamp(coverage)
 
 
 
 
 
 
 
 
 
90
  # ==========================================
91
- # Strong feature overlap case
92
  # ==========================================
93
  if coverage >= 0.90 and feature_score >= 0.65:
94
  return round(
@@ -101,31 +106,23 @@ def compute_hybrid_score(
101
  )
102
 
103
  # ==========================================
104
- # Normal scoring
105
  # ==========================================
106
- shared_ratio = (
107
- (feature_count - unique_query_count)
108
- / max(feature_count, 1)
109
- )
110
-
111
- score = (
112
- 0.90 * (shared_ratio ** 2.0)
113
- + 0.07 * feature_score
114
- + 0.03 * semantic_score
115
- )
116
-
117
- # No feature overlap
118
- # No feature overlap
119
  if feature_score == 0 or coverage == 0:
120
- return 0.03
121
 
122
- shared_count = (
123
- feature_count - unique_query_count
 
 
 
124
  )
125
 
126
- # Near duplicate
127
- if shared_count >= 6 and unique_query_count <= 1:
128
- return 0.95
 
 
129
 
130
  return round(clamp(score), 4)
131
 
 
53
  coverage: float
54
  ):
55
  """
56
+ Adaptive weights depending on feature richness.
57
+ Returns (semantic_w, feature_w, coverage_w) — always sum to 1.0
58
  """
59
 
60
+ # Rich features → trust feature matching more
 
 
 
61
  if feature_count >= 5 and coverage >= 0.60:
62
+ return 0.40, 0.45, 0.15
 
63
 
64
+ # Sparse features trust semantic more
65
+ if feature_count <= 2:
66
+ return 0.70, 0.20, 0.10
 
67
 
68
+ # Default balance
69
+ return 0.55, 0.35, 0.10
70
 
71
 
72
  # =====================================================
 
81
  ) -> float:
82
 
83
  semantic_score = clamp(semantic_score)
84
+ feature_score = clamp(feature_score)
85
+ coverage = clamp(coverage)
86
+
87
+ shared_count = feature_count - unique_query_count
88
+
89
+ # ==========================================
90
+ # Near-duplicate fast path
91
+ # ==========================================
92
+ if shared_count >= 6 and unique_query_count <= 1:
93
+ return 0.95
94
+
95
  # ==========================================
96
+ # Strong feature overlap fast path
97
  # ==========================================
98
  if coverage >= 0.90 and feature_score >= 0.65:
99
  return round(
 
106
  )
107
 
108
  # ==========================================
109
+ # No feature overlap → rely on semantics only
110
  # ==========================================
 
 
 
 
 
 
 
 
 
 
 
 
 
111
  if feature_score == 0 or coverage == 0:
112
+ return round(clamp(0.20 * semantic_score), 4)
113
 
114
+ # ==========================================
115
+ # Normal scoring with dynamic weights
116
+ # ==========================================
117
+ semantic_w, feature_w, coverage_w = get_dynamic_weights(
118
+ feature_count, coverage
119
  )
120
 
121
+ score = (
122
+ semantic_w * semantic_score +
123
+ feature_w * feature_score +
124
+ coverage_w * coverage
125
+ )
126
 
127
  return round(clamp(score), 4)
128
 
src/similarity_model/preprocessing.py CHANGED
@@ -4,6 +4,7 @@
4
 
5
  import re
6
  import logging
 
7
  from pathlib import Path
8
  import pandas as pd
9
  from sentence_transformers import SentenceTransformer
@@ -13,6 +14,7 @@ from .llm_feature_extractor import (
13
  )
14
 
15
 
 
16
  # =====================================================
17
  # Logging
18
  # =====================================================
@@ -27,9 +29,12 @@ logger = logging.getLogger(__name__)
27
  # =====================================================
28
  MODEL_NAME = "all-MiniLM-L6-v2"
29
 
30
- embed_model = SentenceTransformer(
31
- MODEL_NAME
32
- )
 
 
 
33
 
34
  # =====================================================
35
  # Config
@@ -119,7 +124,7 @@ def semantic_deduplicate(
119
  # =====================================================
120
  def extract_features(text):
121
 
122
- print("USING GEMINI FEATURE EXTRACTOR")
123
 
124
  features = extract_features_llm(
125
  text
@@ -127,11 +132,12 @@ def extract_features(text):
127
 
128
  return semantic_deduplicate(
129
  features,
130
- embed_model,
131
  threshold=0.85
132
  )
133
 
134
 
 
135
  # =====================================================
136
  # Main Pipeline
137
  # =====================================================
 
4
 
5
  import re
6
  import logging
7
+ from functools import lru_cache
8
  from pathlib import Path
9
  import pandas as pd
10
  from sentence_transformers import SentenceTransformer
 
14
  )
15
 
16
 
17
+
18
  # =====================================================
19
  # Logging
20
  # =====================================================
 
29
  # =====================================================
30
  MODEL_NAME = "all-MiniLM-L6-v2"
31
 
32
+ @lru_cache(maxsize=1)
33
+ def _get_embed_model():
34
+ """Lazy-load the embedding model once on first use."""
35
+ logger.info(f"Loading embed model: {MODEL_NAME}")
36
+ return SentenceTransformer(MODEL_NAME)
37
+
38
 
39
  # =====================================================
40
  # Config
 
124
  # =====================================================
125
  def extract_features(text):
126
 
127
+ logger.info("Using Gemini feature extractor")
128
 
129
  features = extract_features_llm(
130
  text
 
132
 
133
  return semantic_deduplicate(
134
  features,
135
+ _get_embed_model(),
136
  threshold=0.85
137
  )
138
 
139
 
140
+
141
  # =====================================================
142
  # Main Pipeline
143
  # =====================================================
src/similarity_model/similarity_engine.py CHANGED
@@ -39,10 +39,11 @@ TITLE_COL = "project_title"
39
  FEATURE_COL = "features"
40
 
41
  DEFAULT_TOP_K = 5
42
- DEFAULT_SEARCH_POOL = 20
43
  DEFAULT_MIN_SEMANTIC_SCORE = 0.30
44
  MAX_QUERY_FEATURES = 12
45
 
 
46
  # =====================================================
47
  # Query Builders
48
  # =====================================================
@@ -186,11 +187,13 @@ def compare_candidate(
186
 
187
  hybrid_score = base_similarity
188
 
189
- originality_score = round(
190
- (1.0 - hybrid_score) * 100,
191
- 2
 
192
  )
193
 
 
194
  confidence_score = compute_confidence(
195
  semantic_score=semantic_score,
196
  feature_score=feature_score,
 
39
  FEATURE_COL = "features"
40
 
41
  DEFAULT_TOP_K = 5
42
+ DEFAULT_SEARCH_POOL = 50
43
  DEFAULT_MIN_SEMANTIC_SCORE = 0.30
44
  MAX_QUERY_FEATURES = 12
45
 
46
+
47
  # =====================================================
48
  # Query Builders
49
  # =====================================================
 
187
 
188
  hybrid_score = base_similarity
189
 
190
+ originality_score = compute_originality(
191
+ hybrid_score=hybrid_score,
192
+ unique_query_features=unique_query_count,
193
+ total_query_features=query_feature_count
194
  )
195
 
196
+
197
  confidence_score = compute_confidence(
198
  semantic_score=semantic_score,
199
  feature_score=feature_score,