Spaces:
Running
Running
Download tests/test_subject_nearest.py from nikeshn/researchbee: direct link, hf CLI and curl.
- Browser
- Download file 7.13 kB
-
https://huggingface.co/spaces/nikeshn/researchbee/resolve/main/tests/test_subject_nearest.py
- Command line
-
hf download hf://spaces/nikeshn/researchbee/tests/test_subject_nearest.py
-
curl -L -o test_subject_nearest.py https://huggingface.co/spaces/nikeshn/researchbee/resolve/main/tests/test_subject_nearest.py
7.13 kB
| """Nearest-subject matching for journal browse (v1.8.3). | |
| A non-exact but meaningful subject such as "physics" must return journals for | |
| the nearest verified SCImago term and say so, whether or not the LLM | |
| classifier is available. Meaningless input must still return no journals. | |
| """ | |
| from __future__ import annotations | |
| import pytest | |
| from fastapi.testclient import TestClient | |
| import app | |
| from scimago_index import ( | |
| _SUBJECT_ALIASES, nearest_subject_term, subject_vocabulary, | |
| ) | |
| def test_every_alias_target_and_alternative_is_real(): | |
| vocab = subject_vocabulary() | |
| for key, alias in _SUBJECT_ALIASES.items(): | |
| pool = vocab["areas"] if alias["kind"] == "area" else vocab["categories"] | |
| assert alias["name"] in pool, (key, alias["name"]) | |
| for alt in alias["alternatives"]: | |
| assert alt in vocab["categories"], (key, alt) | |
| def test_nearest_subject_term(text, kind, name): | |
| match = nearest_subject_term(text) | |
| assert match is not None | |
| assert (match["kind"], match["name"]) == (kind, name) | |
| vocab = subject_vocabulary()["categories"] | |
| assert all(alt in vocab for alt in match["alternatives"]) | |
| def test_generic_or_meaningless_input_is_not_forced(text): | |
| assert nearest_subject_term(text) is None | |
| def test_partial_match_only_when_requested(): | |
| assert nearest_subject_term("quantum physics") is not None # alias | |
| assert nearest_subject_term("geology rocks") is None | |
| partial = nearest_subject_term("geology rocks", allow_partial=True) | |
| assert partial["name"] == "geology" and partial["method"] == "partial_token_match" | |
| # ββ Endpoint βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def _client(monkeypatch, llm): | |
| calls = [] | |
| async def fake_openai(_client, system, user, max_tokens=0, model=None): | |
| calls.append(user) | |
| if llm == "down": | |
| raise app.HTTPException(503, "AI service is temporarily unavailable.") | |
| if llm == "not_found": | |
| return {"confidence": "not_found", "normalised": "", "scimago_category": "", | |
| "scimago_area": "", "nearest_categories": [], "mapping_type": "none", | |
| "use_nearest_category": False, "user_message": "Could not classify."} | |
| if llm == "maps": | |
| return {"confidence": "near", "normalised": "Robotics-like", | |
| "scimago_category": "Control and Systems Engineering", "scimago_area": "", | |
| "nearest_categories": ["Control and Systems Engineering"], | |
| "mapping_type": "nearest_category", "use_nearest_category": True, | |
| "user_message": ""} | |
| return {"selected": [], "global_notes": ""} | |
| async def no_evidence(*_a, **_k): | |
| return None | |
| monkeypatch.setattr(app, "call_openai", fake_openai) | |
| monkeypatch.setattr(app, "get_client", lambda: None) | |
| monkeypatch.setattr(app, "check_rate_limit", lambda *a: True) | |
| monkeypatch.setattr(app, "_attach_article_evidence", no_evidence) | |
| return TestClient(app.app), calls | |
| def _browse(client, subject): | |
| response = client.post("/api/browse-subject", json={"subject": subject}) | |
| assert response.status_code == 200 | |
| return response.json()["result"] | |
| def test_physics_returns_nearest_area_with_disclosure(monkeypatch, llm): | |
| client, calls = _client(monkeypatch, llm) | |
| result = _browse(client, "physics") | |
| assert calls == [] # resolved deterministically; no model cost | |
| assert result["total"] > 0 and result["journals"] | |
| assert result["confidence"] == "near" | |
| mapping = result["subject_mapping"] | |
| assert mapping["selected_area"] == "physics and astronomy" | |
| assert mapping["used_nearest_category"] is True | |
| assert 'no exact subject category for "physics"' in result["user_message"] | |
| assert "nearest verified subject area" in result["user_message"] | |
| assert mapping["alternatives"] | |
| def test_synonym_is_disclosed_as_interpretation(monkeypatch): | |
| client, _ = _client(monkeypatch, "down") | |
| result = _browse(client, "maths") | |
| assert result["total"] > 0 | |
| assert result["subject_mapping"]["match_type"] == "synonym" | |
| assert result["user_message"].startswith('Interpreted "maths"') | |
| def test_llm_mapping_still_used_for_unknown_terms(monkeypatch): | |
| client, calls = _client(monkeypatch, "maps") | |
| result = _browse(client, "swarm robotics for warehouses") | |
| assert len(calls) == 1 | |
| assert result["subject_mapping"]["selected_category"].lower() == "control and systems engineering" | |
| assert result["total"] > 0 | |
| def test_partial_fallback_after_llm_failure(monkeypatch): | |
| client, calls = _client(monkeypatch, "down") | |
| result = _browse(client, "geology rocks") | |
| assert len(calls) == 1 | |
| assert result["total"] > 0 | |
| assert result["subject_mapping"]["selected_category"] == "geology" | |
| assert "nearest verified category" in result["user_message"] | |
| assert "classifier was unavailable" not in result["user_message"] | |
| def test_meaningless_input_still_returns_no_journals(monkeypatch, llm): | |
| client, _ = _client(monkeypatch, llm) | |
| result = _browse(client, "xyzzy") | |
| assert result["confidence"] == "not_found" | |
| assert result["journals"] == [] and result["total"] == 0 | |
| def test_exact_and_spelling_paths_unchanged(monkeypatch): | |
| client, calls = _client(monkeypatch, "down") | |
| exact = _browse(client, "Condensed Matter Physics") | |
| assert exact["confidence"] == "exact" and exact["total"] > 0 | |
| spelled = _browse(client, "compter scince") | |
| assert spelled["subject_mapping"]["spelling_corrected"] is True | |
| # The correction notice appears once, not twice. | |
| assert spelled["user_message"].count("Spelling corrected") == 1 | |
| assert calls == [] | |