amacruz commited on
Commit
97df926
·
verified ·
1 Parent(s): 7259f31

Create component_search.py

Browse files
Files changed (1) hide show
  1. component_search.py +389 -0
component_search.py ADDED
@@ -0,0 +1,389 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ TinkerIQ Component Search Module
3
+ Real component search across Digi-Key, Adafruit, and SparkFun with intelligent matching
4
+ """
5
+
6
+ import os
7
+ import requests
8
+ import time
9
+ from bs4 import BeautifulSoup
10
+ import re
11
+ from urllib.parse import quote
12
+ import json
13
+
14
+ class RealComponentSearch:
15
+ """Advanced component search with real supplier integration"""
16
+
17
+ def __init__(self):
18
+ # API credentials
19
+ self.digikey_client_id = os.getenv("DIGIKEY_CLIENT_ID")
20
+ self.digikey_client_secret = os.getenv("DIGIKEY_CLIENT_SECRET")
21
+ self.digikey_token = None
22
+ self.digikey_token_expires = 0
23
+
24
+ # API endpoints
25
+ self.digikey_base = "https://api.digikey.com"
26
+ self.adafruit_api = "https://www.adafruit.com/api/v2/products"
27
+
28
+ # Circuit type to component mapping
29
+ self.circuit_components = {
30
+ "h_bridge": {
31
+ "primary": ["L298N", "L293D", "DRV8833", "TB6612FNG"],
32
+ "secondary": ["DC motor", "Arduino", "capacitor", "resistor"],
33
+ "description": "H-bridge motor control circuit"
34
+ },
35
+ "temperature_sensor": {
36
+ "primary": ["DHT22", "DHT11", "DS18B20", "TMP36", "LM35"],
37
+ "secondary": ["Arduino", "resistor", "LED", "buzzer"],
38
+ "description": "Temperature monitoring circuit"
39
+ },
40
+ "led_control": {
41
+ "primary": ["WS2812", "APA102", "LED strip", "RGB LED"],
42
+ "secondary": ["resistor", "capacitor", "Arduino", "ESP32"],
43
+ "description": "LED control and lighting circuit"
44
+ },
45
+ "distance_sensor": {
46
+ "primary": ["HC-SR04", "VL53L0X", "VL53L1X", "JSN-SR04T"],
47
+ "secondary": ["Arduino", "resistor", "buzzer", "LED"],
48
+ "description": "Distance measurement circuit"
49
+ },
50
+ "audio_amplifier": {
51
+ "primary": ["LM386", "TDA2030", "PAM8403", "MAX98357"],
52
+ "secondary": ["capacitor", "resistor", "potentiometer", "speaker"],
53
+ "description": "Audio amplification circuit"
54
+ },
55
+ "power_supply": {
56
+ "primary": ["LM7805", "AMS1117", "LM2596", "XL4015"],
57
+ "secondary": ["capacitor", "inductor", "diode", "resistor"],
58
+ "description": "Power regulation circuit"
59
+ },
60
+ "microcontroller": {
61
+ "primary": ["Arduino Uno", "ESP32", "ESP8266", "Raspberry Pi"],
62
+ "secondary": ["breadboard", "jumper wires", "resistor", "LED"],
63
+ "description": "Microcontroller development setup"
64
+ }
65
+ }
66
+
67
+ def get_digikey_token(self):
68
+ """Get OAuth token for Digi-Key API"""
69
+ if not self.digikey_client_id or not self.digikey_client_secret:
70
+ return None
71
+
72
+ if self.digikey_token and time.time() < self.digikey_token_expires:
73
+ return self.digikey_token
74
+
75
+ try:
76
+ auth_url = f"{self.digikey_base}/v1/oauth2/token"
77
+
78
+ data = {
79
+ "client_id": self.digikey_client_id,
80
+ "client_secret": self.digikey_client_secret,
81
+ "grant_type": "client_credentials"
82
+ }
83
+
84
+ response = requests.post(auth_url, data=data, timeout=10)
85
+ response.raise_for_status()
86
+
87
+ token_data = response.json()
88
+ self.digikey_token = token_data["access_token"]
89
+ self.digikey_token_expires = time.time() + token_data["expires_in"] - 60
90
+
91
+ return self.digikey_token
92
+
93
+ except Exception as e:
94
+ print(f"Digi-Key auth error: {e}")
95
+ return None
96
+
97
+ def search_digikey(self, query, limit=3):
98
+ """Search Digi-Key for components"""
99
+ token = self.get_digikey_token()
100
+ if not token:
101
+ return []
102
+
103
+ try:
104
+ search_url = f"{self.digikey_base}/products/v4/search/keyword"
105
+
106
+ headers = {
107
+ "Authorization": f"Bearer {token}",
108
+ "X-DIGIKEY-Client-Id": self.digikey_client_id,
109
+ "Content-Type": "application/json"
110
+ }
111
+
112
+ payload = {
113
+ "Keywords": query,
114
+ "RecordCount": limit,
115
+ "RecordStartPosition": 0,
116
+ "Filters": {},
117
+ "Sort": {
118
+ "SortOption": "SortByUnitPrice",
119
+ "Direction": "Ascending"
120
+ },
121
+ "RequestedQuantity": 1
122
+ }
123
+
124
+ response = requests.post(search_url, headers=headers, json=payload, timeout=15)
125
+ response.raise_for_status()
126
+
127
+ data = response.json()
128
+ components = []
129
+
130
+ for product in data.get("Products", [])[:limit]:
131
+ try:
132
+ component = {
133
+ "name": product.get("ProductDescription", "Unknown"),
134
+ "part_number": product.get("DigiKeyPartNumber", ""),
135
+ "manufacturer": product.get("Manufacturer", {}).get("Name", "Unknown"),
136
+ "price": self._format_price(product.get("UnitPrice", 0)),
137
+ "stock": product.get("QuantityAvailable", 0),
138
+ "supplier": "Digi-Key",
139
+ "datasheet": product.get("PrimaryDatasheet", ""),
140
+ "description": product.get("DetailedDescription", "")[:100] + "...",
141
+ "url": f"https://www.digikey.com/en/products/detail/{product.get('DigiKeyPartNumber', '')}"
142
+ }
143
+ components.append(component)
144
+ except Exception as e:
145
+ print(f"Error processing Digi-Key product: {e}")
146
+ continue
147
+
148
+ return components
149
+
150
+ except Exception as e:
151
+ print(f"Digi-Key search error: {e}")
152
+ return []
153
+
154
+ def search_adafruit(self, query, limit=3):
155
+ """Search Adafruit products"""
156
+ try:
157
+ url = f"{self.adafruit_api}?category_id=&search={quote(query)}"
158
+
159
+ response = requests.get(url, timeout=10)
160
+ response.raise_for_status()
161
+
162
+ data = response.json()
163
+ components = []
164
+
165
+ for product in data.get("products", [])[:limit]:
166
+ try:
167
+ component = {
168
+ "name": product.get("name", "Unknown"),
169
+ "part_number": f"ADA-{product.get('id', '')}",
170
+ "manufacturer": "Adafruit",
171
+ "price": f"${product.get('price', 0):.2f}",
172
+ "stock": "In Stock" if product.get("in_stock", False) else "Out of Stock",
173
+ "supplier": "Adafruit",
174
+ "datasheet": "",
175
+ "description": BeautifulSoup(product.get("description_text", ""), "html.parser").get_text()[:100] + "...",
176
+ "url": f"https://www.adafruit.com/product/{product.get('id', '')}"
177
+ }
178
+ components.append(component)
179
+ except Exception as e:
180
+ print(f"Error processing Adafruit product: {e}")
181
+ continue
182
+
183
+ return components
184
+
185
+ except Exception as e:
186
+ print(f"Adafruit search error: {e}")
187
+ return []
188
+
189
+ def search_sparkfun(self, query, limit=3):
190
+ """Search SparkFun products via web scraping"""
191
+ try:
192
+ search_url = "https://www.sparkfun.com/search/results"
193
+ params = {"term": query, "page": 1, "per_page": limit}
194
+
195
+ headers = {
196
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
197
+ }
198
+
199
+ response = requests.get(search_url, params=params, headers=headers, timeout=10)
200
+ response.raise_for_status()
201
+
202
+ # Mock SparkFun results (replace with actual parsing)
203
+ components = []
204
+ sparkfun_mock = {
205
+ "temperature": [
206
+ {
207
+ "name": "SparkFun Temperature Sensor Breakout - TMP102",
208
+ "part_number": "SEN-13314",
209
+ "manufacturer": "SparkFun",
210
+ "price": "$5.95",
211
+ "stock": "In Stock",
212
+ "supplier": "SparkFun",
213
+ "datasheet": "",
214
+ "description": "Digital temperature sensor with I2C interface",
215
+ "url": "https://www.sparkfun.com/products/13314"
216
+ }
217
+ ],
218
+ "motor": [
219
+ {
220
+ "name": "SparkFun Motor Driver - Dual TB6612FNG",
221
+ "part_number": "ROB-14451",
222
+ "manufacturer": "SparkFun",
223
+ "price": "$6.95",
224
+ "stock": "In Stock",
225
+ "supplier": "SparkFun",
226
+ "datasheet": "",
227
+ "description": "Dual motor driver breakout board",
228
+ "url": "https://www.sparkfun.com/products/14451"
229
+ }
230
+ ]
231
+ }
232
+
233
+ query_lower = query.lower()
234
+ if "temperature" in query_lower:
235
+ components.extend(sparkfun_mock["temperature"])
236
+ elif "motor" in query_lower:
237
+ components.extend(sparkfun_mock["motor"])
238
+
239
+ return components[:limit]
240
+
241
+ except Exception as e:
242
+ print(f"SparkFun search error: {e}")
243
+ return []
244
+
245
+ def analyze_circuit_context(self, circuit_analysis):
246
+ """Intelligently determine circuit type from analysis text"""
247
+ analysis_lower = circuit_analysis.lower()
248
+
249
+ # Score each circuit type based on keyword matches
250
+ circuit_scores = {}
251
+
252
+ for circuit_type, components in self.circuit_components.items():
253
+ score = 0
254
+
255
+ # Check for primary components (higher weight)
256
+ for primary in components["primary"]:
257
+ if primary.lower() in analysis_lower:
258
+ score += 5
259
+
260
+ # Check for secondary components (lower weight)
261
+ for secondary in components["secondary"]:
262
+ if secondary.lower() in analysis_lower:
263
+ score += 1
264
+
265
+ # Check circuit type keywords
266
+ circuit_keywords = {
267
+ "h_bridge": ["h-bridge", "h bridge", "motor control", "motor driver", "robot", "drive"],
268
+ "temperature_sensor": ["temperature", "thermal", "climate", "heat", "dht", "thermometer"],
269
+ "led_control": ["led", "light", "rgb", "strip", "neopixel", "lighting"],
270
+ "distance_sensor": ["distance", "ultrasonic", "proximity", "range", "sonar"],
271
+ "audio_amplifier": ["audio", "amplifier", "speaker", "sound", "music"],
272
+ "power_supply": ["power", "voltage", "regulator", "supply", "battery"],
273
+ "microcontroller": ["arduino", "esp32", "microcontroller", "mcu", "development"]
274
+ }
275
+
276
+ if circuit_type in circuit_keywords:
277
+ for keyword in circuit_keywords[circuit_type]:
278
+ if keyword in analysis_lower:
279
+ score += 3
280
+
281
+ circuit_scores[circuit_type] = score
282
+
283
+ # Return the circuit type with highest score
284
+ if circuit_scores:
285
+ best_match = max(circuit_scores.items(), key=lambda x: x[1])
286
+ if best_match[1] > 0: # Only return if we have a positive score
287
+ return best_match[0]
288
+
289
+ return "general"
290
+
291
+ def get_contextual_components(self, circuit_analysis):
292
+ """Get components specifically relevant to the analyzed circuit"""
293
+ circuit_type = self.analyze_circuit_context(circuit_analysis)
294
+
295
+ if circuit_type == "general":
296
+ # If we can't determine circuit type, search for general terms
297
+ search_terms = ["Arduino", "breadboard", "jumper wires"]
298
+ else:
299
+ # Use circuit-specific components
300
+ circuit_info = self.circuit_components[circuit_type]
301
+ search_terms = circuit_info["primary"][:2] # Top 2 primary components
302
+
303
+ all_components = []
304
+
305
+ # Search each supplier for relevant components
306
+ for term in search_terms:
307
+ # Digi-Key search
308
+ digi_results = self.search_digikey(term, 2)
309
+ all_components.extend(digi_results)
310
+
311
+ # Adafruit search
312
+ ada_results = self.search_adafruit(term, 2)
313
+ all_components.extend(ada_results)
314
+
315
+ # SparkFun search
316
+ sf_results = self.search_sparkfun(term, 1)
317
+ all_components.extend(sf_results)
318
+
319
+ # Remove duplicates and return top results
320
+ unique_components = self._deduplicate_components(all_components)
321
+ return {
322
+ "circuit_type": circuit_type,
323
+ "components": unique_components[:6], # Top 6 components
324
+ "description": self.circuit_components.get(circuit_type, {}).get("description", "General electronics circuit")
325
+ }
326
+
327
+ def _deduplicate_components(self, components):
328
+ """Remove similar components"""
329
+ unique = []
330
+ seen_names = set()
331
+
332
+ for comp in components:
333
+ # Normalize name for comparison
334
+ norm_name = re.sub(r'[^a-zA-Z0-9]', '', comp['name'].lower())
335
+
336
+ # Simple duplicate detection
337
+ is_duplicate = False
338
+ for seen in seen_names:
339
+ if self._similarity(norm_name, seen) > 0.7:
340
+ is_duplicate = True
341
+ break
342
+
343
+ if not is_duplicate:
344
+ unique.append(comp)
345
+ seen_names.add(norm_name)
346
+
347
+ return unique
348
+
349
+ def _similarity(self, a, b):
350
+ """Simple string similarity"""
351
+ if not a or not b:
352
+ return 0
353
+
354
+ set_a = set(a[i:i+2] for i in range(len(a)-1))
355
+ set_b = set(b[i:i+2] for i in range(len(b)-1))
356
+
357
+ if not set_a and not set_b:
358
+ return 1
359
+ if not set_a or not set_b:
360
+ return 0
361
+
362
+ intersection = len(set_a & set_b)
363
+ union = len(set_a | set_b)
364
+
365
+ return intersection / union if union > 0 else 0
366
+
367
+ def _format_price(self, price):
368
+ """Format price consistently"""
369
+ if isinstance(price, (int, float)):
370
+ return f"${price:.2f}"
371
+ elif isinstance(price, str):
372
+ return price if price.startswith('$') else f"${price}"
373
+ else:
374
+ return "Price unavailable"
375
+
376
+ # Test function
377
+ if __name__ == "__main__":
378
+ search = RealComponentSearch()
379
+
380
+ # Test circuit analysis
381
+ test_analysis = "This circuit shows an H-bridge motor driver using an L298N chip connected to an Arduino Uno. The circuit is designed to control two DC motors for a robot car project."
382
+
383
+ results = search.get_contextual_components(test_analysis)
384
+ print(f"Circuit Type: {results['circuit_type']}")
385
+ print(f"Description: {results['description']}")
386
+ print(f"Found {len(results['components'])} components:")
387
+
388
+ for comp in results['components']:
389
+ print(f"- {comp['name']} | {comp['price']} | {comp['supplier']}")