grantforge-api / backend /tests /test_hgis.py
GrantForge Bot
Deploy sha-fa63b2c4b490e66ba3ddbbe9af20584a8573fddd — source build (no GHCR)
6b9ceb7
Raw
History Blame Contribute Delete
7.13 kB
"""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"
@pytest.mark.skipif(
os.environ.get("ENABLE_GRANT_RESEARCH_AGENT", "true").lower() == "false",
reason="agent disabled",
)
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
@pytest.mark.skipif(
os.environ.get("PYTEST_USE_VERIFY_DB", "0").lower() not in ("1", "true", "yes"),
reason="requires shared verify_hgis.db",
)
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()