Spaces:
Running
Running
| """Testy Hybrid Grant Intelligence System (bez mocków jednostek rdzenia).""" | |
| from __future__ import annotations | |
| import json | |
| import os | |
| import tempfile | |
| from pathlib import Path | |
| import pytest | |
| from core.grants.completeness import compute_completeness | |
| from core.grants.credibility import score_source_credibility | |
| from core.grants.versioning import detect_field_changes | |
| from core.grants.recommendations import score_grant_for_company | |
| from core.grants.catalog_service import _matches_filter, filter_catalog_rows, resolve_catalog_plan | |
| import importlib.util | |
| _cd_path = Path(__file__).resolve().parents[1] / "rag_pipeline" / "change_detector.py" | |
| _spec = importlib.util.spec_from_file_location("change_detector", _cd_path) | |
| _cd_mod = importlib.util.module_from_spec(_spec) | |
| _spec.loader.exec_module(_cd_mod) | |
| ChangeDetector = _cd_mod.ChangeDetector | |
| FIXTURE_PATH = Path(__file__).resolve().parent / "fixtures" / "real_grant_sample.json" | |
| def _load_real_grant() -> dict: | |
| assert FIXTURE_PATH.exists(), f"missing fixture: {FIXTURE_PATH}" | |
| return json.loads(FIXTURE_PATH.read_text(encoding="utf-8")) | |
| REAL_GRANT = _load_real_grant() if FIXTURE_PATH.exists() else None | |
| SAMPLE_GRANT = { | |
| "id": "test-001", | |
| "name": "Fundusze Europejskie dla MŚP 2025", | |
| "description": "Wsparcie inwestycji MŚP w Mazowieckiem.", | |
| "status": "active", | |
| "operator": "PARP", | |
| "program_year": 2025, | |
| "eligible_regions": ["mazowieckie", "cała polska"], | |
| "eligible_company_sizes": ["mikro", "małe"], | |
| "eligible_beneficiaries": ["mśp", "przedsiębiorca"], | |
| "eligible_pkd": ["62.01", "62.02"], | |
| "regulation_url": "https://www.parp.gov.pl/regulamin.pdf", | |
| "official_page_url": "https://www.parp.gov.pl/", | |
| "application_url": "https://www.parp.gov.pl/wniosek", | |
| "deadline": "2026-12-31", | |
| "budget_max_pln": 500000, | |
| "instrument_type": "grant", | |
| "poziom_weryfikacji": "regulamin", | |
| } | |
| def test_real_grant_fixture_loaded(): | |
| assert REAL_GRANT is not None | |
| assert REAL_GRANT.get("name") | |
| assert str(REAL_GRANT.get("regulation_url") or "").startswith("http") | |
| assert REAL_GRANT.get("official_page_url") or REAL_GRANT.get("url") | |
| def test_completeness_high_for_real_grant(): | |
| result = compute_completeness(REAL_GRANT) | |
| assert result["completeness_score"] >= 70 | |
| assert "regulation_url" in result["fields_present"] | |
| def test_credibility_real_parp_source(): | |
| score = score_source_credibility(REAL_GRANT) | |
| assert score >= 0.75 | |
| def test_recommendation_scoring_real_grant(): | |
| company = {"size": "mikro", "region": "mazowieckie", "pkd": ["62.01"], "entity_type": "startup"} | |
| rec = score_grant_for_company(REAL_GRANT, company) | |
| assert rec["relevance_score"] >= 40 | |
| assert len(rec.get("why_it_fits", "")) > 10 | |
| def test_versioning_detects_deadline_change(): | |
| old = {**SAMPLE_GRANT, "deadline": "2025-06-30"} | |
| new = {**SAMPLE_GRANT, "deadline": "2026-12-31"} | |
| changes = detect_field_changes("test-001", old, new) | |
| assert any(c["field_name"] == "deadline" for c in changes) | |
| def test_pkd_filter(): | |
| item = {**SAMPLE_GRANT} | |
| assert _matches_filter(item, {"pkd": "62.01"}) | |
| assert not _matches_filter(item, {"pkd": "01.11"}) | |
| def test_change_detector_incremental(): | |
| with tempfile.TemporaryDirectory() as tmp: | |
| store = Path(tmp) / "hashes.json" | |
| det = ChangeDetector(store_path=str(store)) | |
| assert det.record("url1", "content A")["is_new"] is True | |
| assert det.record("url1", "content A")["changed"] is False | |
| assert det.record("url1", "content B")["changed"] is True | |
| def test_hybrid_search_fts_only_without_pinecone(): | |
| """Bez PINECONE_API_KEY wyszukiwanie działa w trybie fts_only (Pinecone wymaga klucza).""" | |
| from sqlalchemy import create_engine | |
| from sqlalchemy.orm import sessionmaker | |
| from core.subscription.db import Base, init_models | |
| from core.grants.models import Grant | |
| from core.grants.hybrid_search import hybrid_search_catalog | |
| old_key = os.environ.pop("PINECONE_API_KEY", None) | |
| try: | |
| init_models() | |
| engine = create_engine("sqlite:///:memory:", connect_args={"check_same_thread": False}) | |
| Base.metadata.create_all(bind=engine) | |
| Session = sessionmaker(bind=engine) | |
| db = Session() | |
| db.add( | |
| Grant( | |
| source_id="hyb-1", | |
| name="Dotacja MŚP innowacje", | |
| status="active", | |
| source="test", | |
| description="Wsparcie dla mikro i małych firm w Mazowieckiem.", | |
| eligible_company_sizes=["mikro"], | |
| eligible_regions=["mazowieckie"], | |
| ) | |
| ) | |
| db.commit() | |
| results = hybrid_search_catalog(db, query="dotacja MŚP", filters={"status": "active"}, limit=5) | |
| assert results | |
| assert results[0].get("search_mode") in ("fts_only", "fts+pinecone") | |
| db.close() | |
| finally: | |
| if old_key: | |
| os.environ["PINECONE_API_KEY"] = old_key | |
| def test_catalog_hidden_filter(): | |
| items = [ | |
| {**SAMPLE_GRANT, "catalog_hidden": True}, | |
| {**SAMPLE_GRANT, "id": "visible", "catalog_hidden": False}, | |
| ] | |
| plan = resolve_catalog_plan({}) | |
| out = filter_catalog_rows(items, "", plan.post_filters, plan, hide_stale=False) | |
| assert len(out) == 1 | |
| assert out[0]["id"] == "visible" | |
| def test_grant_research_agent_on_real_grant(): | |
| from agents.grant_research_agent import run_grant_research_agent | |
| changes = detect_field_changes( | |
| REAL_GRANT["id"], | |
| {**REAL_GRANT, "deadline": "2025-01-01"}, | |
| REAL_GRANT, | |
| ) | |
| company = {"size": "mikro", "region": "mazowieckie", "pkd": ["62.01"]} | |
| result = run_grant_research_agent(REAL_GRANT, changes=changes, company_profile=company) | |
| assert result.get("enabled") is True | |
| assert result.get("change_summary") | |
| assert result.get("enriched_metadata", {}).get("completeness_score", 0) >= 70 | |
| assert len(result.get("recommendations", [])) >= 1 | |
| def test_verify_db_imported_catalog(): | |
| from sqlalchemy import create_engine | |
| from sqlalchemy.orm import sessionmaker | |
| from core.subscription.db import init_models | |
| from core.grants.models import Grant | |
| from core.grants.regulation_backfill import regulation_completeness_report | |
| db_url = os.environ.get("DATABASE_URL") | |
| assert db_url and "verify_hgis" in db_url | |
| init_models() | |
| engine = create_engine(db_url, connect_args={"check_same_thread": False}) | |
| Session = sessionmaker(bind=engine) | |
| db = Session() | |
| try: | |
| count = db.query(Grant).count() | |
| assert count > 1000 | |
| parp = db.query(Grant).filter(Grant.source.like("%PARP%")).first() | |
| assert parp is not None | |
| report = regulation_completeness_report(db) | |
| assert report["with_regulation_url"] > 200 | |
| finally: | |
| db.close() | |