| """ |
| Parameter Extractor |
| Extracts parameters (city, location, etc.) from natural language queries |
| """ |
|
|
| import re |
| import spacy |
| from typing import Optional, Dict, List |
|
|
| |
| try: |
| nlp = spacy.load("en_core_web_sm") |
| except OSError: |
| print("⚠️ spaCy model not found. Run: python -m spacy download en_core_web_sm") |
| nlp = None |
|
|
|
|
| def extract_city_from_query(query: str) -> Optional[str]: |
| """ |
| Extract city name from a natural language query |
| |
| Args: |
| query: User's natural language question |
| |
| Returns: |
| City name if found, None otherwise |
| |
| Examples: |
| "What's the weather in London?" -> "London" |
| "Show me weather for New York" -> "New York" |
| "Temperature in Tokyo" -> "Tokyo" |
| """ |
| if nlp is None: |
| |
| return _extract_city_regex(query) |
| |
| |
| doc = nlp(query) |
| for ent in doc.ents: |
| if ent.label_ in ["GPE", "LOC"]: |
| return ent.text |
| |
| |
| city = _extract_city_regex(query) |
| if city: |
| return city |
| |
| return None |
|
|
|
|
| def _extract_city_regex(query: str) -> Optional[str]: |
| """ |
| Extract city using regex patterns |
| |
| Args: |
| query: User query |
| |
| Returns: |
| City name if found |
| """ |
| |
| patterns = [ |
| r"(?:weather|temperature|forecast|climate)\s+(?:in|for|at|near)\s+([A-Z][a-zA-Z\s]+?)(?:\?|$|,)", |
| r"(?:in|for|at|near)\s+([A-Z][a-zA-Z\s]+?)(?:\?|$|,)", |
| r"([A-Z][a-zA-Z\s]+?)\s+(?:weather|temperature|forecast)", |
| ] |
| |
| for pattern in patterns: |
| match = re.search(pattern, query) |
| if match: |
| city = match.group(1).strip() |
| |
| exclude_words = {"weather", "temperature", "the", "show", "me", "get"} |
| if city.lower() not in exclude_words: |
| return city |
| |
| return None |
|
|
|
|
| def extract_location_from_soil_query(query: str, conversation_context: list = None) -> Optional[Dict]: |
| """ |
| Extract location from soil-related queries, using conversation context for follow-ups |
| |
| Args: |
| query: User's natural language question |
| conversation_context: Optional list of previous messages for context |
| Format: [{"role": "user/assistant", "content": "..."}, ...] |
| |
| Returns: |
| Dict with location info, or None |
| {"location": "California"} or {"lat": 40.7, "lon": -74.0} |
| |
| Examples: |
| "soil data for Iowa" -> {"location": "Iowa"} |
| "Show me soil in California" -> {"location": "California"} |
| Previous: "soil data for ames" (failed), Current: "Ames iowa" -> {"location": "Ames, Iowa"} |
| """ |
| |
| location_info = None |
| |
| if nlp is None: |
| location_info = _extract_location_regex(query) |
| else: |
| |
| doc = nlp(query) |
| for ent in doc.ents: |
| if ent.label_ in ["GPE", "LOC"]: |
| location_info = {"location": ent.text} |
| break |
| |
| |
| if not location_info: |
| location_info = _extract_location_regex(query) |
| |
| |
| if location_info: |
| return location_info |
| |
| |
| if conversation_context: |
| |
| |
| for msg in reversed(conversation_context): |
| content = msg.get("content", "") |
| if not content: |
| continue |
| |
| |
| content_lower = content.lower() |
| is_soil_related = any(kw in content_lower for kw in ["soil", "dirt", "ground", "earth"]) |
| |
| |
| if nlp is None: |
| prev_location = _extract_location_regex(content) |
| else: |
| doc = nlp(content) |
| prev_location = None |
| for ent in doc.ents: |
| if ent.label_ in ["GPE", "LOC"]: |
| prev_location = {"location": ent.text} |
| break |
| if not prev_location: |
| prev_location = _extract_location_regex(content) |
| |
| |
| if prev_location and is_soil_related: |
| |
| |
| current_location = _extract_location_regex(query) |
| if current_location: |
| |
| prev_loc = prev_location.get("location", "") |
| curr_loc = current_location.get("location", "") |
| if prev_loc and curr_loc and prev_loc.lower() != curr_loc.lower(): |
| combined = f"{prev_loc}, {curr_loc}" |
| return {"location": combined} |
| elif curr_loc: |
| return current_location |
| |
| return prev_location |
| |
| |
| |
| if prev_location and len(query.split()) <= 3: |
| |
| |
| current_location = _extract_location_regex(query) |
| if current_location: |
| |
| prev_loc = prev_location.get("location", "") |
| curr_loc = current_location.get("location", "") |
| if prev_loc and curr_loc: |
| |
| if prev_loc.lower() not in curr_loc.lower() and curr_loc.lower() not in prev_loc.lower(): |
| combined = f"{prev_loc}, {curr_loc}" |
| return {"location": combined} |
| else: |
| |
| return current_location if len(curr_loc) > len(prev_loc) else prev_location |
| return current_location |
| else: |
| |
| |
| words = query.split() |
| if len(words) <= 3 and all(w[0].isupper() if w else False for w in words): |
| return {"location": query} |
| |
| return None |
|
|
|
|
| def _extract_location_regex(query: str) -> Optional[Dict]: |
| """Extract location using regex patterns""" |
| patterns = [ |
| r"soil\s+(?:in|for|at|near)\s+([A-Z][a-zA-Z\s]+?)(?:\?|$|,)", |
| r"(?:in|for|at|near)\s+([A-Z][a-zA-Z\s]+?)(?:\?|$|,)", |
| r"([A-Z][a-zA-Z\s]+?)\s+soil", |
| ] |
| |
| for pattern in patterns: |
| match = re.search(pattern, query) |
| if match: |
| location = match.group(1).strip() |
| return {"location": location} |
| |
| |
| |
| words = query.strip().split() |
| if len(words) >= 1 and len(words) <= 3: |
| |
| if all(word and word[0].isupper() for word in words): |
| |
| exclude_words = {"The", "A", "An", "And", "Or", "But", "For", "With"} |
| location_words = [w for w in words if w not in exclude_words] |
| if location_words: |
| return {"location": " ".join(location_words)} |
| |
| return None |
|
|
|
|
| def detect_temperature_unit(query: str) -> str: |
| """ |
| Detect if user wants Celsius or Fahrenheit |
| |
| Args: |
| query: User query |
| |
| Returns: |
| "metric" for Celsius, "imperial" for Fahrenheit |
| """ |
| query_lower = query.lower() |
| |
| |
| if any(word in query_lower for word in ["fahrenheit", "°f", "f"]): |
| return "imperial" |
| |
| |
| return "metric" |
|
|
|
|
| def extract_keywords_from_query(query: str) -> List[str]: |
| """ |
| Extract important keywords from query |
| |
| Args: |
| query: User query |
| |
| Returns: |
| List of keywords |
| """ |
| if nlp is None: |
| |
| words = re.findall(r'\b\w+\b', query.lower()) |
| |
| stop_words = {'the', 'a', 'an', 'is', 'are', 'was', 'were', 'in', 'on', 'at', 'to', 'for', 'of', 'and', 'or'} |
| return [w for w in words if w not in stop_words] |
| |
| doc = nlp(query) |
| |
| |
| keywords = [] |
| for token in doc: |
| if token.pos_ in ["NOUN", "VERB", "ADJ", "PROPN"]: |
| if not token.is_stop and len(token.text) > 2: |
| keywords.append(token.lemma_.lower()) |
| |
| return keywords |
|
|
|
|
| |
| if __name__ == "__main__": |
| print("Testing Parameter Extractor...") |
| print("-" * 50) |
| |
| test_queries = [ |
| "What's the weather in London?", |
| "Show me temperature in New York", |
| "Is it raining in Tokyo?", |
| "Weather in Paris in Fahrenheit", |
| "How's the weather in San Francisco?", |
| "soil data for California", |
| "Show me soil in Iowa" |
| ] |
| |
| for query in test_queries: |
| print(f"\nQuery: {query}") |
| |
| |
| city = extract_city_from_query(query) |
| print(f" City: {city}") |
| |
| |
| keywords = extract_keywords_from_query(query) |
| print(f" Keywords: {keywords}") |
| |
| |
| units = detect_temperature_unit(query) |
| print(f" Units: {units}") |
|
|