bat-6 commited on
Commit
e2b6cfb
·
1 Parent(s): d1e4586
Files changed (1) hide show
  1. src/similarity_model/preprocessing.py +58 -10
src/similarity_model/preprocessing.py CHANGED
@@ -4,14 +4,12 @@
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
11
  from sklearn.metrics.pairwise import cosine_similarity
12
- from .llm_feature_extractor import (
13
- extract_features_llm
14
- )
15
 
16
 
17
 
@@ -120,18 +118,68 @@ def semantic_deduplicate(
120
 
121
 
122
  # =====================================================
123
- # Main Feature Extraction
124
  # =====================================================
125
- def extract_features(text):
 
126
 
127
- logger.info("Using Gemini feature extractor")
128
 
129
- features = extract_features_llm(
130
- text
131
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
 
133
  return semantic_deduplicate(
134
- features,
135
  _get_embed_model(),
136
  threshold=0.85
137
  )
 
4
 
5
  import re
6
  import logging
7
+ import numpy as np
8
  from functools import lru_cache
9
  from pathlib import Path
10
  import pandas as pd
11
  from sentence_transformers import SentenceTransformer
12
  from sklearn.metrics.pairwise import cosine_similarity
 
 
 
13
 
14
 
15
 
 
118
 
119
 
120
  # =====================================================
121
+ # Local Feature Dictionary (cached)
122
  # =====================================================
123
+ _PROJECT_ROOT = Path(__file__).resolve().parents[2]
124
+ _METADATA_PATH = _PROJECT_ROOT / "models" / "metadata.parquet"
125
 
 
126
 
127
+ @lru_cache(maxsize=1)
128
+ def _load_known_features():
129
+ """
130
+ Load all known feature strings from the training metadata.
131
+ Cached — reads the parquet file only once per process.
132
+ Sorted longest-first so multi-word features (e.g. 'deep learning')
133
+ are matched before their sub-words (e.g. 'learning').
134
+ """
135
+ if not _METADATA_PATH.exists():
136
+ logger.warning("metadata.parquet not found; feature extraction will return []")
137
+ return []
138
+
139
+ df = pd.read_parquet(str(_METADATA_PATH))
140
+
141
+ features_set = set()
142
+ for f_list in df.get("features", pd.Series(dtype=object)):
143
+ if isinstance(f_list, (list, np.ndarray, tuple, set)):
144
+ for f in f_list:
145
+ val = str(f).strip().lower()
146
+ if val and val != "nan" and len(val) >= 3:
147
+ features_set.add(val)
148
+
149
+ logger.info(f"Loaded {len(features_set)} known features from metadata")
150
+
151
+ # longest first → greedy multi-word match wins
152
+ return sorted(features_set, key=len, reverse=True)
153
+
154
+
155
+ # =====================================================
156
+ # Main Feature Extraction (fully local, no API)
157
+ # =====================================================
158
+ def extract_features(text: str) -> list:
159
+ """
160
+ Match technical features from text against the known feature
161
+ dictionary built from training data.
162
+ No external API required.
163
+ """
164
+ known_features = _load_known_features()
165
+
166
+ if not known_features:
167
+ return []
168
+
169
+ text_norm = normalize_text(text)
170
+ matched = []
171
+
172
+ for feat in known_features:
173
+ pattern = r'\b' + re.escape(feat) + r'\b'
174
+ if re.search(pattern, text_norm):
175
+ matched.append(feat)
176
+ if len(matched) >= 15: # collect up to 15, dedup will trim
177
+ break
178
+
179
+ logger.info(f"Local feature extraction matched {len(matched)} features")
180
 
181
  return semantic_deduplicate(
182
+ matched,
183
  _get_embed_model(),
184
  threshold=0.85
185
  )