File size: 4,968 Bytes
3b7f713
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
from pydantic import BaseModel, Field
from typing import Literal
from core.llm_router import get_llm
from langchain_core.prompts import PromptTemplate
from rag_pipeline import get_hybrid_retriever, rerank_documents
import logging
from tenacity import retry, stop_after_attempt, wait_exponential

logger = logging.getLogger(__name__)


class ExpenseEvaluationResponse(BaseModel):
    czy_wydatek_kwalifikowalny: bool = Field(
        description="Zwr贸膰 True je艣li wydatek jest w 100% zgodny z regulaminem i wytycznymi programu (kwalifikowalny)."
    )
    uzasadnienie_prawne: str = Field(
        description="Cytat lub konkretne odwo艂anie do regulaminu uzasadniaj膮ce kwalifikowalno艣膰 lub jej brak."
    )
    kategoria_badan: Literal[
        "badania przemys艂owe",
        "prace rozwojowe",
        "prace przedwdro偶eniowe",
        "brak/nie dotyczy",
    ] = Field(
        description="Wybierz do jakiej kategorii zgodnie z polskim/unijnym prawem nale偶y ten wydatek. Wybierz 'brak/nie dotyczy' tylko je艣li wydatek jest ca艂kowicie poza B+R."
    )
    intensywnosc_pomocy: float = Field(
        description="Zwr贸膰 w formie warto艣ci zmiennoprzecinkowej np. 0.50 (co oznacza 50%), 0.80 (co oznacza 80%) bazuj膮c na wielko艣ci firmy i rodzaju bada艅. 0.0 oznacza wydatek niekwalifikowalny."
    )


def evaluate_project_expense(
    expense_description: str,
    expense_amount: float,
    project_title: str,
    program_name: str,
    company_size: str,
    tenant_id: str = None,
) -> ExpenseEvaluationResponse:
    """
    Agent ds. Oceny Kwalifikowalno艣ci (FAZA 4).
    Wymusza twarde, ustrukturyzowane ramy JSON za pomoc膮 Pydantic.
    Opiera si臋 na wiedzy RAG dotycz膮cej wybranego programu.
    """

    # Pr贸ba za艂adowania kontekstu z RAG - Hard Filtering na aktualn膮 perspektyw臋
    # Domy艣lnie wyszukujemy tylko w najnowszej perspektywie (FAZA 3, zapobieganie aplikacji starych przepis贸w)
    hard_filter = {"rok_perspektywy": {"$eq": "2021-2027"}}
    if program_name:
        # Operator $and dla Pinecone Vector Store
        hard_filter = {
            "$and": [
                {"program_name": {"$eq": program_name}},
                {"rok_perspektywy": {"$eq": "2021-2027"}},
            ]
        }

    context_text = "Brak specyficznego regulaminu programu w bazie."
    try:
        retriever = get_hybrid_retriever(
            k=10, metadata_filter=hard_filter, namespace=tenant_id
        )
        if retriever:
            query_for_rag = f"kwalifikowalno艣膰 wydatku badania kategoria intensywno艣膰 dotacji pomoc publiczna: {expense_description}"
            docs = retriever.invoke(query_for_rag)
            reranked_docs = rerank_documents(query_for_rag, docs, top_n=4)
            context_text = "\n\n".join(
                [
                    f"[殴R脫D艁O: {d.metadata.get('source', 'Brak')}]: {d.page_content}"
                    for d in reranked_docs
                ]
            )
    except Exception as e:
        logger.error(f"[ExpenseEvaluator] Error fetching RAG context: {str(e)}")

    template = """
    Jeste艣 G艂贸wnym Prawnikiem i Audytorem Dotacyjnym oceniaj膮cym kwalifikowalno艣膰 wydatk贸w.
    Oceniasz pojedynczy wydatek dla projektu w ramach programu: {program_name}.
    Wielko艣膰 przedsi臋biorstwa wnioskodawcy: {company_size}.
    
    Opis wydatku do weryfikacji:
    "{expense_description}" (Kwota: {expense_amount} PLN)
    
    Kontekst z regulamin贸w z bazy wiedzy:
    --------------------------------------------------
    {context}
    --------------------------------------------------
    
    Zasady:
    1. Przeanalizuj czy podany wydatek kwalifikuje si臋 do obj臋cia wsparciem zgodnie z baz膮 wiedzy.
    2. Okre艣l kategori臋 bada艅 dla wydatku, zgodnie z definicjami (badania przemys艂owe, prace rozwojowe, przedwdro偶eniowe).
    3. Je艣li wydatek jest kwalifikowalny, przypisz prawid艂ow膮 intensywno艣膰 pomocy (zazwyczaj mniejszy procent dla prac rozwojowych/du偶ych firm, wi臋kszy dla bada艅 przemys艂owych/M艢P).
    4. Podaj bardzo precyzyjne uzasadnienie prawne odnosz膮ce si臋 do regulaminu.
    """

    prompt = PromptTemplate.from_template(template)

    # LLM z typowaniem - GPT-4o jest du偶o lepszy do takich zada艅 analitycznych
    structured_llm = get_llm(
        task_type="legal_audit", structured_output_schema=ExpenseEvaluationResponse
    )

    chain = prompt | structured_llm

    @retry(
        stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=2, max=10)
    )
    def _invoke_chain():
        return chain.invoke(
            {
                "program_name": program_name or "Og贸lne zasady dotacji B+R",
                "company_size": company_size or "M艢P (nieokre艣lona wielko艣膰)",
                "expense_description": expense_description,
                "expense_amount": expense_amount,
                "context": context_text,
            }
        )

    result = _invoke_chain()

    return result