"""Deterministic question parser for `/ask`: countries, industries, intents → event types, windows, answer style; never invents results.""" from __future__ import annotations from companyatlas.services.llm.ask import Interpretation, build_answer, parse_question, route_question INDUSTRIES = {"fintech": "Fintech", "artificial-intelligence": "Artificial intelligence", "retail": "Retail", "software": "Software"} COUNTRIES = {"CA": "Canada", "US": "United States", "JP": "Japan", "DE": "Germany"} def test_hiring_ai_in_canada_last_month(): it = parse_question("Which companies are hiring AI engineers in Canada in the last month?", industries=INDUSTRIES, countries=COUNTRIES) assert it.countries == ["CA"] assert it.intent == "ai" and "hiring" in it.intents assert "HIRING" in it.event_types and "AI_HIRING" in it.event_subtypes and "ai" in it.tags assert it.window_days == 30 assert "engineers" in it.keywords and "canada" not in it.keywords assert it.answer_style == "list" and 0.5 <= it.confidence <= 0.9 assert it.filters["countries"] == ["CA"] def test_pricing_increase_fintech_this_week(): it = parse_question("fintech companies that raised prices this week", industries=INDUSTRIES) assert it.industries == ["fintech"] and it.intent == "pricing" assert it.event_types == ["PRICING"] and "PRICE_INCREASE" in it.event_subtypes and "PRICE_DECREASE" not in it.event_subtypes assert it.window_days == 7 def test_launch_expansion_leadership_and_count(): launch = parse_question("How many Japanese companies launched new products since 2026-01-01?", countries=COUNTRIES) assert launch.countries == ["JP"] and launch.intent == "launch" and launch.event_types == ["PRODUCT"] assert launch.answer_style == "count" and launch.window_days is not None and launch.window_days > 200 exp = parse_question("companies expanding into new countries", countries=COUNTRIES) assert exp.intent == "expansion" and "COUNTRY_EXPANSION" in exp.event_subtypes lead = parse_question("new CEO appointed at German companies", countries=COUNTRIES) assert lead.intent == "leadership" and lead.countries == ["DE"] def test_compare_timeline_trend_and_quotes(): cmp = parse_question('compare "Stripe" vs "Adyen" hiring over the past 90 days') assert cmp.answer_style == "compare" and cmp.companies == ["Stripe", "Adyen"] and cmp.window_days == 90 and "HIRING" in cmp.event_types tl = parse_question("when did Acme change its terms of service?") assert tl.answer_style == "timeline" and tl.intent == "legal" and tl.event_types == ["LEGAL"] tr = parse_question("which industries are trending in developer docs") assert tr.answer_style == "trend" and "DEVELOPER" in tr.event_types def test_plain_search_has_no_filters_and_low_confidence(): it = parse_question("acme corporation") assert it.intent == "search" and it.filters == {"keywords": ["acme", "corporation"]} assert it.confidence < 0.5 empty = parse_question("") assert empty.filters == {} and empty.to_dict()["parser_version"].startswith("ask-parser") def test_important_events_and_regions(): it = parse_question("major pricing changes at European software companies", industries=INDUSTRIES) assert it.min_importance == 0.6 and it.regions == ["europe"] and it.countries == [] and it.industries == ["software"] async def test_route_question_without_llm_is_deterministic(monkeypatch): from companyatlas.config import settings monkeypatch.setattr(settings, "llm_base_url", "") it = await route_question("companies hiring in Canada", countries=COUNTRIES) assert it.source == "deterministic" and it.countries == ["CA"] def test_build_answer_only_phrases_measured_counts(): it = Interpretation(question="q", intent="hiring", countries=["CA"], window_days=30) assert build_answer(it, companies=0, events=0).startswith("No monitored evidence matches this question yet in CA over the last 30 days") text = build_answer(it, companies=12, events=48) assert text.startswith("48 hiring-related events in CA over the last 30 days across 12 monitored companies.") assert "confidence" in text