GrantForge Bot commited on
Commit
afd56bc
·
0 Parent(s):

Deploy to Hugging Face

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +5 -0
  2. .github/workflows/ci.yml +273 -0
  3. .gitignore +0 -0
  4. .pre-commit-config.yaml +15 -0
  5. .python-version +1 -0
  6. DEPLOYMENT.md +202 -0
  7. Dockerfile +71 -0
  8. KRSAPI.md +57 -0
  9. OProgramie.md +34 -0
  10. Podsumowanie Dotacja AI.md +42 -0
  11. README.md +80 -0
  12. antigravity_grantforge_swarm/CONSTITUTION.md +118 -0
  13. antigravity_grantforge_swarm/README.md +91 -0
  14. antigravity_grantforge_swarm/SWARM.md +174 -0
  15. antigravity_grantforge_swarm/agents/advanced_matcher_agent.py +77 -0
  16. antigravity_grantforge_swarm/agents/auditor_agent.py +69 -0
  17. antigravity_grantforge_swarm/agents/autofix_agent.py +13 -0
  18. antigravity_grantforge_swarm/agents/exporter_agent.py +40 -0
  19. antigravity_grantforge_swarm/agents/generator_agent.py +26 -0
  20. antigravity_grantforge_swarm/agents/graphrag_msp_agent.py +13 -0
  21. antigravity_grantforge_swarm/agents/legal_verifier_agent.py +30 -0
  22. antigravity_grantforge_swarm/agents/rag_ingestion_agent.py +13 -0
  23. antigravity_grantforge_swarm/agents/validator_agent.py +13 -0
  24. antigravity_grantforge_swarm/agents/wizard_clarifier_agent.py +73 -0
  25. antigravity_grantforge_swarm/config.py +69 -0
  26. antigravity_grantforge_swarm/main.py +129 -0
  27. antigravity_grantforge_swarm/orchestrator.py +417 -0
  28. antigravity_grantforge_swarm/prompts/global_rules_prompts.py +226 -0
  29. antigravity_grantforge_swarm/prompts/global_rules_prompts.py:Zone.Identifier +0 -0
  30. antigravity_grantforge_swarm/state.py +309 -0
  31. antigravity_grantforge_swarm/tools/grantforge_tools.py +191 -0
  32. antigravity_grantforge_swarm/tools/grantforge_tools.py:Zone.Identifier +0 -0
  33. backend/.deepeval/.deepeval-cache.json +1 -0
  34. backend/.deepeval/.deepeval-cache.json:Zone.Identifier +0 -0
  35. backend/.deepeval/.deepeval_telemetry.txt +4 -0
  36. backend/.deepeval/.deepeval_telemetry.txt:Zone.Identifier +0 -0
  37. backend/.env.example +22 -0
  38. backend/DejaVuSans-Bold.ttf +3 -0
  39. backend/DejaVuSans.ttf +3 -0
  40. backend/Dockerfile +30 -0
  41. backend/Dockerfile:Zone.Identifier +0 -0
  42. backend/add_keys.py +23 -0
  43. backend/add_keys.py:Zone.Identifier +0 -0
  44. backend/agents/__init__.py +1 -0
  45. backend/agents/__init__.py:Zone.Identifier +0 -0
  46. backend/agents/auditor.py +446 -0
  47. backend/agents/auditor.py:Zone.Identifier +0 -0
  48. backend/agents/auditor_panel_graph.py +82 -0
  49. backend/agents/auditor_panel_graph.py:Zone.Identifier +0 -0
  50. backend/agents/compliance_guardian.py +45 -0
.gitattributes ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ *.ttf filter=lfs diff=lfs merge=lfs -text
2
+ *.png filter=lfs diff=lfs merge=lfs -text
3
+ *.jpg filter=lfs diff=lfs merge=lfs -text
4
+ *.ico filter=lfs diff=lfs merge=lfs -text
5
+ *.pdf filter=lfs diff=lfs merge=lfs -text
.github/workflows/ci.yml ADDED
@@ -0,0 +1,273 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ##
2
+ ## .github/workflows/ci.yml — GrantForge AI CI/CD
3
+ ##
4
+ ## Uruchamia się przy:
5
+ ## - push na 'main' (deploy do Render)
6
+ ## - PR do 'main' (testy + lint bez deploy)
7
+ ##
8
+ ## Kroki:
9
+ ## 1. Lint (ruff) + type check (mypy)
10
+ ## 2. Testy jednostkowe (pytest)
11
+ ## 3. DeepEval RAG faithfulness (tylko main)
12
+ ## 4. Build Docker image + push do ghcr.io
13
+ ## 5. Trigger deploy na Render.com (webhook)
14
+ ##
15
+
16
+ name: CI/CD — GrantForge AI
17
+
18
+ on:
19
+ push:
20
+ branches: [main, staging]
21
+ pull_request:
22
+ branches: [main, staging]
23
+
24
+ env:
25
+ PYTHON_VERSION: "3.11.9"
26
+ REGISTRY: ghcr.io
27
+ IMAGE_NAME: ${{ github.repository }}/grantforge-api
28
+ LANGCHAIN_TRACING_V2: "true"
29
+ LANGCHAIN_PROJECT: "grantforge-production"
30
+
31
+ jobs:
32
+ # ─────────────────────────────────────────────────────────────────────────────
33
+ # JOB 1: Lint + Type Check
34
+ # ─────────────────────────────────────────────────────────────────────────────
35
+ lint:
36
+ name: 🔍 Lint & Type Check
37
+ runs-on: ubuntu-latest
38
+
39
+ steps:
40
+ - uses: actions/checkout@v4
41
+
42
+ - name: Set up Python ${{ env.PYTHON_VERSION }}
43
+ uses: actions/setup-python@v5
44
+ with:
45
+ python-version: ${{ env.PYTHON_VERSION }}
46
+ cache: pip
47
+ cache-dependency-path: backend/requirements.txt
48
+
49
+ - name: Install lint tools
50
+ run: pip install ruff mypy
51
+
52
+ - name: Ruff lint (backend)
53
+ run: python -m ruff check backend/ --select E,W,F --ignore E501,E402,W291,W293,F841,E722,E701,E712,E731,E741,F811,F401,F541
54
+
55
+ - name: Mypy type check (core modules)
56
+ run: |
57
+ cd backend
58
+ mypy core/ --ignore-missing-imports --no-strict-optional || true
59
+
60
+ # ─────────────────────────────────────────────────────────────────────────────
61
+ # JOB 2: Backend Tests
62
+ # ─────────────────────────────────────────────────────────────────────────────
63
+ test-backend:
64
+ name: 🧪 Backend Tests (pytest)
65
+ runs-on: ubuntu-latest
66
+ needs: lint
67
+
68
+ env:
69
+ GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
70
+ BIELIK_MODE: disabled
71
+ DATABASE_URL: sqlite:///./test.db
72
+
73
+ steps:
74
+ - uses: actions/checkout@v4
75
+
76
+ - name: Set up Python ${{ env.PYTHON_VERSION }}
77
+ uses: actions/setup-python@v5
78
+ with:
79
+ python-version: ${{ env.PYTHON_VERSION }}
80
+ cache: pip
81
+ cache-dependency-path: backend/requirements.txt
82
+
83
+ - name: Install backend dependencies
84
+ run: |
85
+ sudo apt-get update && sudo apt-get install -y libcairo2-dev pkg-config python3-dev
86
+ cd backend
87
+ pip install --upgrade pip
88
+ pip uninstall -y pinecone-plugin-inference
89
+ pip install -r requirements.txt
90
+ pip install pytest pytest-asyncio httpx
91
+
92
+ - name: Run unit tests with coverage
93
+ run: |
94
+ cd backend
95
+ python -m pytest tests/ -v --tb=short -x \
96
+ --ignore=tests/test_deepeval_rag.py \
97
+ --cov=endpoints --cov-report=term-missing --cov-fail-under=50 \
98
+ 2>&1 | tail -50
99
+
100
+ - name: Check API server imports
101
+ run: |
102
+ cd backend
103
+ python -c "import server; print('✅ server.py OK')"
104
+
105
+ # ─────────────────────────────────────────────────────────────────────────────
106
+ # JOB 3: DeepEval RAG Quality (tylko na push do main)
107
+ # ─────────────────────────────────────────────────────────────────────────────
108
+ deepeval:
109
+ name: 🎯 DeepEval RAG Faithfulness
110
+ runs-on: ubuntu-latest
111
+ needs: test-backend
112
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
113
+ continue-on-error: true # Nie blokuje deploy przy braku Pinecone key
114
+
115
+ env:
116
+ GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
117
+ PINECONE_API_KEY: ${{ secrets.PINECONE_API_KEY }}
118
+ PINECONE_INDEX_NAME: ${{ secrets.PINECONE_INDEX_NAME }}
119
+ LANGCHAIN_API_KEY: ${{ secrets.LANGCHAIN_API_KEY }}
120
+ LANGCHAIN_TRACING_V2: "true"
121
+ LANGCHAIN_PROJECT: grantforge-ci
122
+ BIELIK_MODE: disabled
123
+
124
+ steps:
125
+ - uses: actions/checkout@v4
126
+
127
+ - name: Set up Python ${{ env.PYTHON_VERSION }}
128
+ uses: actions/setup-python@v5
129
+ with:
130
+ python-version: ${{ env.PYTHON_VERSION }}
131
+ cache: pip
132
+ cache-dependency-path: backend/requirements.txt
133
+
134
+ - name: Install dependencies + DeepEval
135
+ run: |
136
+ sudo apt-get update && sudo apt-get install -y libcairo2-dev pkg-config python3-dev
137
+ cd backend
138
+ pip install --upgrade pip
139
+ pip uninstall -y pinecone-plugin-inference
140
+ pip install -r requirements.txt deepeval
141
+
142
+ - name: Run DeepEval RAG tests
143
+ run: |
144
+ cd backend
145
+ python scripts/run_eval.py || true
146
+ # 'true' — nie blokuje deploy przy niskim quality score (tylko raport)
147
+
148
+ # ─────────────────────────────────────────────────────────────────────────────
149
+ # JOB 4: Frontend Build Check
150
+ # ─────────────────────────────────────────────────────────────────────────────
151
+ build-frontend:
152
+ name: 🏗️ Frontend Build (Vite)
153
+ runs-on: ubuntu-latest
154
+ needs: lint
155
+
156
+ steps:
157
+ - uses: actions/checkout@v4
158
+
159
+ - name: Set up Node.js
160
+ uses: actions/setup-node@v4
161
+ with:
162
+ node-version: "20"
163
+
164
+ - name: Install dependencies
165
+ run: |
166
+ cd frontend-react
167
+ rm -rf node_modules package-lock.json
168
+ npm install
169
+
170
+ - name: TypeScript check
171
+ run: |
172
+ cd frontend-react
173
+ npx tsc --noEmit || true
174
+
175
+ - name: Build
176
+ run: |
177
+ cd frontend-react
178
+ npm run build
179
+ env:
180
+ VITE_CLERK_PUBLISHABLE_KEY: ${{ secrets.VITE_CLERK_PUBLISHABLE_KEY }}
181
+ VITE_STRIPE_PRICE_ID_PRO: ${{ secrets.VITE_STRIPE_PRICE_ID_PRO }}
182
+ VITE_API_URL: "/api"
183
+ VITE_APP_VERSION: "1.3.0"
184
+
185
+
186
+
187
+ # ─────────────────────────────────────────────────────────────────────────────
188
+ # JOB 5: Docker Build + Push (tylko main)
189
+ # ─────────────────────────────────────────────────────────────────────────────
190
+ docker:
191
+ name: 🐳 Docker Build & Push
192
+ runs-on: ubuntu-latest
193
+ needs: [test-backend, build-frontend]
194
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
195
+ permissions:
196
+ contents: read
197
+ packages: write
198
+
199
+ steps:
200
+ - uses: actions/checkout@v4
201
+
202
+ - name: Log in to GitHub Container Registry
203
+ uses: docker/login-action@v3
204
+ with:
205
+ registry: ${{ env.REGISTRY }}
206
+ username: ${{ github.actor }}
207
+ password: ${{ secrets.GITHUB_TOKEN }}
208
+
209
+ - name: Set up Docker Buildx
210
+ uses: docker/setup-buildx-action@v3
211
+
212
+ - name: Extract metadata
213
+ id: meta
214
+ uses: docker/metadata-action@v5
215
+ with:
216
+ images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
217
+ tags: |
218
+ type=sha,prefix=sha-,format=short
219
+ type=raw,value=latest
220
+
221
+ - name: Build and push Docker image
222
+ uses: docker/build-push-action@v5
223
+ with:
224
+ context: .
225
+ file: ./Dockerfile
226
+ push: true
227
+ tags: ${{ steps.meta.outputs.tags }}
228
+ labels: ${{ steps.meta.outputs.labels }}
229
+ cache-from: type=gha
230
+ cache-to: type=gha,mode=max
231
+
232
+ # ─────────────────────────────────────────────────────────────────────────────
233
+ # JOB 6: Deploy do Hugging Face Spaces (tylko main)
234
+ # ─────────────────────────────────────────────────────────────────────────────
235
+ deploy-huggingface:
236
+ name: 🚀 Deploy to Hugging Face
237
+ runs-on: ubuntu-latest
238
+ needs: [docker] # deepeval jest continue-on-error, nie blokuje deploy
239
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
240
+
241
+ steps:
242
+ - uses: actions/checkout@v4
243
+ with:
244
+ fetch-depth: 0
245
+
246
+ - name: Push to Hugging Face Spaces
247
+ env:
248
+ HF_TOKEN: ${{ secrets.HF_TOKEN }}
249
+ run: |
250
+ git config --global user.email "bot@grantforge.ai"
251
+ git config --global user.name "GrantForge Bot"
252
+
253
+ # Usuwamy starą historię, aby pozbyć się starych blobów binarnych
254
+ rm -rf .git
255
+ git init -b main
256
+
257
+ # Konfigurujemy Git LFS dla plików binarnych (wymóg Hugging Face)
258
+ git lfs install
259
+ echo "*.ttf filter=lfs diff=lfs merge=lfs -text" > .gitattributes
260
+ echo "*.png filter=lfs diff=lfs merge=lfs -text" >> .gitattributes
261
+ echo "*.jpg filter=lfs diff=lfs merge=lfs -text" >> .gitattributes
262
+ echo "*.ico filter=lfs diff=lfs merge=lfs -text" >> .gitattributes
263
+ echo "*.pdf filter=lfs diff=lfs merge=lfs -text" >> .gitattributes
264
+
265
+ # Tworzymy nowy, czysty commit z całą aplikacją
266
+ git add .gitattributes
267
+ git add .
268
+ git commit -m "Deploy to Hugging Face"
269
+
270
+ # Wypychamy na HF (wypchnie również obiekty LFS)
271
+ git remote add huggingface https://Bogdan555:${HF_TOKEN}@huggingface.co/spaces/Bogdan555/grantforge-api
272
+ git push -f huggingface main
273
+
.gitignore ADDED
Binary file (742 Bytes). View file
 
.pre-commit-config.yaml ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ repos:
2
+ - repo: https://github.com/astral-sh/ruff-pre-commit
3
+ rev: v0.4.4
4
+ hooks:
5
+ - id: ruff
6
+ args: [ --fix ]
7
+ - id: ruff-format
8
+ - repo: local
9
+ hooks:
10
+ - id: pre-push-tests
11
+ name: Run Backend Pytest
12
+ entry: bash scripts/pre_push_tests.sh
13
+ language: system
14
+ stages: [pre-push]
15
+ pass_filenames: false
.python-version ADDED
@@ -0,0 +1 @@
 
 
1
+ 3.11
DEPLOYMENT.md ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🚀 Deployment Guide — GrantForge AI Enterprise (v1.3.0)
2
+
3
+ > **Sprint 9 — Instrukcja produkcyjna**
4
+ > Aktualizacja: kwiecień 2026 | Stack: Render.com + PostgreSQL + GitHub Actions CI/CD
5
+
6
+ ---
7
+
8
+ ## Architektura Deploymentu
9
+
10
+ ```
11
+ GitHub (main) → GitHub Actions CI/CD → Render.com
12
+ ├── grantforge-api (FastAPI + Gunicorn)
13
+ ├── grantforge-frontend (React SPA)
14
+ └── grantforge-db (PostgreSQL 16)
15
+ ```
16
+
17
+ ---
18
+
19
+ ## KROK 1: GitHub Repository Secrets
20
+
21
+ Przejdź do: `GitHub → Settings → Secrets and variables → Actions`
22
+
23
+ ### Wymagane Secrets (CI/CD + Deploy):
24
+
25
+ | Secret Name | Skąd wziąć | Ważny? |
26
+ |---|---|---|
27
+ | `GOOGLE_API_KEY` | [Google AI Studio](https://aistudio.google.com/app/apikey) | ✅ Wymagany |
28
+ | `VITE_CLERK_PUBLISHABLE_KEY` | Clerk Dashboard → API Keys → Publishable Key | ✅ Wymagany |
29
+ | `RENDER_DEPLOY_HOOK_BACKEND` | Render → API service → Settings → Deploy Hook | ✅ Wymagany |
30
+ | `RENDER_DEPLOY_HOOK_FRONTEND` | Render → Static service → Settings → Deploy Hook | ✅ Wymagany |
31
+ | `PINECONE_API_KEY` | [Pinecone Console](https://app.pinecone.io) | RAG optional |
32
+ | `PINECONE_INDEX_NAME` | Pinecone Console → Index name | RAG optional |
33
+ | `LANGCHAIN_API_KEY` | [LangSmith](https://smith.langchain.com) → Settings | Monitoring |
34
+
35
+ ### Jak dodać secret:
36
+ ```
37
+ GitHub repo → Settings → Secrets → Actions → New repository secret
38
+ ```
39
+
40
+ #### Render Deploy Hook (backend):
41
+ 1. Render Dashboard → `grantforge-api` → Settings
42
+ 2. Scroll do sekcji **Deploy Hooks**
43
+ 3. Kliknij **Add Deploy Hook** → Nazwa: `ci-deploy`
44
+ 4. Skopiuj URL (format: `https://api.render.com/deploy/srv-xxx?key=yyy`)
45
+ 5. Zapisz jako secret `RENDER_DEPLOY_HOOK_BACKEND`
46
+
47
+ #### Render Deploy Hook (frontend):
48
+ 1. Render Dashboard → `grantforge-frontend` → Settings
49
+ 2. Kliknij **Add Deploy Hook** → Nazwa: `ci-frontend`
50
+ 3. Skopiuj URL → Zapisz jako `RENDER_DEPLOY_HOOK_FRONTEND`
51
+
52
+ ---
53
+
54
+ ## KROK 2: Render → Environment Variables
55
+
56
+ Przejdź do: `Render → grantforge-api → Environment`
57
+
58
+ Ustaw następujące zmienne (kliknij **Add Environment Variable** dla każdej):
59
+
60
+ ```bash
61
+ # ── LLM ─────────────────────────────
62
+ GOOGLE_API_KEY= # Klucz Google Gemini
63
+ BIELIK_MODE=disabled # disabled / huggingface / ollama
64
+
65
+ # ── Autentykacja Clerk ───────────────
66
+ CLERK_SECRET_KEY= # sk_live_... (Clerk Dashboard → API Keys)
67
+ CLERK_PUBLISHABLE_KEY= # pk_live_... (Clerk Dashboard → API Keys)
68
+
69
+ # ── RAG / Pinecone ───────────────────
70
+ PINECONE_API_KEY= # Opcjonalne — bez tego RAG działa lokalnie
71
+ PINECONE_INDEX_NAME= # Nazwa indeksu
72
+ PINECONE_ENVIRONMENT= # Np. "gcp-starter"
73
+
74
+ # ── LangSmith (monitoring) ───────────
75
+ LANGCHAIN_API_KEY= # ls_... ze smith.langchain.com
76
+ LANGCHAIN_TRACING_V2=true
77
+ LANGCHAIN_PROJECT=grantforge-production
78
+
79
+ # ── Stripe (płatności) ───────────────
80
+ STRIPE_SECRET_KEY= # sk_live_...
81
+ STRIPE_WEBHOOK_SECRET= # whsec_...
82
+ STRIPE_PRICE_ID_PRO= # price_...
83
+
84
+ # ── Dysk danych ──────────────────────
85
+ UPLOAD_DIR=/data/uploads
86
+ VECTOR_STORE_DIR=/data/vector_store
87
+ LLAMAPARSE_CACHE_DIR=/data/llamaparse_cache
88
+ ```
89
+
90
+ > **Uwaga:** `DATABASE_URL` jest automatycznie wstrzyknięty przez Render Blueprint z bloku `databases`.
91
+
92
+ ---
93
+
94
+ ## KROK 3: Pierwsza Migracja Bazy Danych
95
+
96
+ Po pierwszym deploymencie na Render, uruchom one-off job alembic:
97
+
98
+ 1. Render → `grantforge-api` → **Shell** (lub: one-off command)
99
+ 2. Wpisz:
100
+ ```bash
101
+ alembic upgrade head
102
+ ```
103
+ 3. Sprawdź czy tabele zostały stworzone (w tym `project_documents` z Sprint 8).
104
+
105
+ ### Lokalnie (przed deploy):
106
+ ```bash
107
+ cd backend
108
+ alembic upgrade head
109
+ ```
110
+
111
+ Jeśli błąd — sprawdź `DATABASE_URL` w `.env`.
112
+
113
+ ---
114
+
115
+ ## KROK 4: Weryfikacja po Deploy
116
+
117
+ Po uruchomieniu, zweryfikuj każdy endpoint:
118
+
119
+ ```bash
120
+ # 1. Health check (podstawowy)
121
+ curl https://grantforge-api.onrender.com/api/health
122
+
123
+ # Oczekiwana odpowiedź:
124
+ # {"status": "ok", "db": "connected", "version": "1.3.0", ...}
125
+
126
+ # 2. Sprawdź czy upload PDF działa
127
+ curl -X POST https://grantforge-api.onrender.com/api/projects/TEST_PROJECT_ID/documents \
128
+ -F "file=@test.pdf"
129
+ # Oczekiwane: 202 Accepted
130
+
131
+ # 3. Sprawdź listę dokumentów z kwotą
132
+ curl https://grantforge-api.onrender.com/api/projects/TEST_PROJECT_ID/documents
133
+ # Oczekiwane: {"documents": [], "quota": {"current": 0, "limit": 3, "plan": "free", "can_upload": true}}
134
+
135
+ # 4. Sprawdź nabory
136
+ curl https://grantforge-api.onrender.com/api/grants/nabory
137
+ # Oczekiwane: {"total": N, "sources": [...]}
138
+ ```
139
+
140
+ ---
141
+
142
+ ## KROK 5: CI/CD — Pierwszy Push
143
+
144
+ Po skonfigurowaniu secrets, każdy push na `main` uruchomi pipeline:
145
+
146
+ ```bash
147
+ git add -A
148
+ git commit -m "chore: Sprint 9 — upload limits + E2E tests"
149
+ git push origin main
150
+ ```
151
+
152
+ Pipeline (`.github/workflows/ci.yml`):
153
+ 1. 🔍 **Lint & Type Check** (ruff + mypy)
154
+ 2. 🧪 **Backend Tests** (pytest)
155
+ 3. 🎯 **DeepEval RAG** (tylko main)
156
+ 4. 🏗️ **Frontend Build** (Vite)
157
+ 5. 🐳 **Docker Build & Push** (ghcr.io)
158
+ 6. 🚀 **Deploy to Render** (webhook)
159
+
160
+ Monitor pipeline: `GitHub → Actions → CI/CD — GrantForge AI`
161
+
162
+ ---
163
+
164
+ ## Limity Upload PDF (Sprint 9)
165
+
166
+ | Plan | Max pliki/projekt | Hard limit |
167
+ |---|---|---|
168
+ | Free | 3 PDF | 10 PDF |
169
+ | Pro | 50 PDF | 10 PDF |
170
+ | Enterprise | 50 PDF | 10 PDF |
171
+
172
+ > Hard limit (10) jest bezwzględny — dotyczy wszystkich planów.
173
+ > Soft limit jest egzekwowany przez `_check_upload_limits()` w `endpoints/documents.py`.
174
+
175
+ ---
176
+
177
+ ## Troubleshooting
178
+
179
+ ### Backend nie startuje
180
+ ```bash
181
+ # Sprawdź logi Render → Logs
182
+ # Najczęstszy błąd: brak DATABASE_URL lub import error
183
+ ```
184
+
185
+ ### Alembic error: "Can't locate revision"
186
+ ```bash
187
+ cd backend
188
+ alembic stamp head # jeśli tabele już istnieją
189
+ alembic upgrade head
190
+ ```
191
+
192
+ ### Upload 429 Too Many Requests
193
+ Użytkownik osiągnął limit planu. Opcje:
194
+ 1. Usuń stary dokument w zakładce "Dokumenty RAG"
195
+ 2. Przejdź na plan Pro (`/cennik`)
196
+
197
+ ### RAG nie indeksuje (status: error)
198
+ Sprawdź w Render Shell:
199
+ ```bash
200
+ # W logach szukaj [RAG Upload] ❌
201
+ # Najprawdopodobniejsza przyczyna: Pinecone key / pusty PDF
202
+ ```
Dockerfile ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # GrantForge AI — Multi-stage Docker build
2
+ # Backend: FastAPI + LangGraph | Frontend: Vite React
3
+
4
+ # ──────────────────────────────────────────────────────────────────
5
+ # STAGE 1: Frontend build
6
+ # ──────────────────────────────────────────────────────────────────
7
+ FROM node:20-slim AS frontend-builder
8
+
9
+ WORKDIR /app/frontend
10
+
11
+ COPY frontend-react/package*.json ./
12
+ RUN rm -f package-lock.json && npm install
13
+
14
+ COPY frontend-react/ ./
15
+ RUN npm run build
16
+ # Artefakt: /app/frontend/dist/
17
+
18
+ # ──────────────────────────────────────────────────────────────────
19
+ # STAGE 2: Python dependencies
20
+ # ──────────────────────────────────────────────────────────────────
21
+ FROM python:3.11.9-slim AS python-deps
22
+
23
+ WORKDIR /install
24
+
25
+ RUN apt-get update && apt-get install -y --no-install-recommends \
26
+ libpq-dev gcc g++ libffi-dev libglib2.0-0 libpango-1.0-0 \
27
+ libpangocairo-1.0-0 libcairo2 libcairo2-dev pkg-config python3-dev \
28
+ && rm -rf /var/lib/apt/lists/*
29
+
30
+ COPY backend/requirements.txt .
31
+ RUN pip install --no-cache-dir --prefix=/install/pkg -r requirements.txt && \
32
+ rm -rf /install/pkg/lib/python3.11/site-packages/pinecone_plugin_inference*
33
+
34
+ # ──────────────────────────────────────────────────────────────────
35
+ # STAGE 3: Runtime image
36
+ # ──────────────────────────────────────────────────────────────────
37
+ FROM python:3.11.9-slim AS runtime
38
+
39
+ WORKDIR /app
40
+
41
+ COPY --from=python-deps /install/pkg /usr/local
42
+
43
+ RUN apt-get update && apt-get install -y --no-install-recommends \
44
+ libpq5 libglib2.0-0 libpango-1.0-0 libpangocairo-1.0-0 libcairo2 wget \
45
+ && rm -rf /var/lib/apt/lists/*
46
+
47
+ COPY backend/ ./backend/
48
+ COPY --from=frontend-builder /app/frontend/dist ./static/
49
+
50
+ RUN mkdir -p /app/backend/assets && \
51
+ wget -qO /app/backend/assets/Roboto-Regular.ttf "https://github.com/googlefonts/roboto/raw/main/src/hinted/Roboto-Regular.ttf" && \
52
+ wget -qO /app/backend/assets/Roboto-Bold.ttf "https://github.com/googlefonts/roboto/raw/main/src/hinted/Roboto-Bold.ttf"
53
+
54
+ RUN mkdir -p /app/backend/cache
55
+
56
+ RUN useradd -m -u 1001 appuser && chown -R appuser:appuser /app
57
+ USER appuser
58
+
59
+ WORKDIR /app/backend
60
+
61
+ EXPOSE 7860
62
+
63
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
64
+ CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:7860/api/health')"
65
+
66
+ CMD ["gunicorn", "server:app", \
67
+ "--worker-class", "uvicorn.workers.UvicornWorker", \
68
+ "--workers", "2", \
69
+ "--bind", "0.0.0.0:7860", \
70
+ "--timeout", "120", \
71
+ "--access-logfile", "-"]
KRSAPI.md ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Otwarte API Krajowego Rejestru Sądowego
2
+ Jak korzystać?
3
+ Otwarte API KRS udostępnia dane z Krajowego Rejestru Sądowego w postaci RESTFull API.
4
+ Zakres informacyjny udostępnianych danych odpowiada odpisom z KRS, zupełnemu i aktualnemu.
5
+ Dodatkowo dostępna jest usługa informująca o wprowadzonych zmianach do rejestru we wskazanym dniu.
6
+ Usługi udostępniono pod wskazanymi poniżej adresami z podaniem parametrów niezbędnych przy wywołaniu poszczególnych usług.
7
+ Usługi
8
+ GET - Pobranie odpisu aktualnego
9
+ https://api-krs.ms.gov.pl/api/krs/OdpisAktualny/{krs}?rejestr={rejestr}&format=json
10
+ Parametry:
11
+ {krs} – numer podmiotu w rejestrze KRS
12
+ {rejestr} – P – przedsiębiorców, S-stowarzyszeń
13
+ {format} – json (bez względu na wpisany parametr zawsze zwrócony zostanie obiekt w postaci JSON)
14
+ Usługa zwraca następujące statusy:
15
+ 200 – OK
16
+ 404 – podmiot nie znaleziony
17
+ 5xx – błąd usługi
18
+ Zwracany model:
19
+ Semantyczny JSON odpowiedni dla formy prawnej
20
+ GET - Pobranie odpisu pełnego
21
+ https://api-krs.ms.gov.pl/api/krs/OdpisPelny/{krs}?rejestr={rejestr}&format=json
22
+ Parametry:
23
+ {krs} – numer podmiotu w rejestrze KRS
24
+ {rejestr} – P – przedsiębiorców, S-stowarzyszeń
25
+ {format} – json (bez względu na wpisany parametr zawsze zwrócony zostanie obiekt w postaci JSON)
26
+ Usługa zwraca następujące statusy:
27
+ 200 – OK
28
+ 404 – podmiot nie znaleziony
29
+ 5xx – błąd usługi
30
+ Zwracany model:
31
+ Semantyczny JSON odpowiedni dla formy prawnej
32
+ GET - Pobranie historii zmian - biuletyn godzinowy
33
+ https://api-krs.ms.gov.pl/api/Krs/BiuletynGodzinowy/{dzien}?godzinaOd={godzinaOd}&godzinaDo={godzinaDo}
34
+ Parametry:
35
+ {dzien} – dzień, późniejszy niż 2021-12-08
36
+ {godzinaOd} – godzina początkowa biuletynu, w formacie 24-godzinnym (GG, od 00 do 23)
37
+ {godzinaDo} – godzina końcowa biuletynu w formacie 24-godzinnym (GG, od 00 do 23)
38
+ Usługa zwraca następujące statusy:
39
+ 200 – OK
40
+ 404 – podmiot nie znaleziony
41
+ 5xx – błąd usługi
42
+ Zwracany model:
43
+ Tablica łańcuchów tekstowych [ string, string, …, string ]
44
+ GET - Pobranie historii zmian - biuletyn dzienny
45
+ https://api-krs.ms.gov.pl/api/Krs/Biuletyn/{dzien}?
46
+ Parametry:
47
+ {dzien} - dzień, późniejszy niż 2021-12-08
48
+ Usługa zwraca następujące statusy:
49
+ 200 - OK
50
+ 404 - podmiot nie znaleziony
51
+ 5xx - błąd usługi
52
+ Zwracany model:
53
+ Tablica łańcuchów tekstowych [ string, string, …, string ]
54
+ Przepisy dotyczące API
55
+ Art. 4b ust. 2 ustawy z dnia 20 sierpnia 1997 r. o Krajowym Rejestrze Sądowym (tj.: Dz. U. z 2021 r., poz. 112 ze zm.), w związku z art. 24 ust. 1 ustawy z dnia 11 sierpnia 2021 r. o otwartych danych i ponownym wykorzystywaniu informacji sektora publicznego (Dz. U. z 2021 r., poz. 1641).
56
+
57
+ Rozporządzenie Parlamentu Europejskiego i Rady (UE) 2016/679 z dnia 27 kwietnia 2016 r. w sprawie ochrony osób fizycznych w związku z przetwarzaniem danych osobowych i w sprawie swobodnego przepływu takich danych oraz uchylenia dyrektywy 95/46/WE (ogólne rozporządzenie o ochronie danych) (Dz. Urz. UE L 119 z 04.05.2016 ze zm.).
OProgramie.md ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ DotacjeAI
2
+ Witamy w DotacjeAI – Twoim wirtualnym asystencie i inteligentnym biurze doradztwa dotacyjnego. Nasza platforma wykorzystuje zaawansowane modele sztucznej
3
+ inteligencji, aby zautomatyzować, przyspieszyć i ułatwić proces ubiegania się o fundusze publiczne (m.in. z programów PARP, NCBR oraz funduszy europejskich).
4
+
5
+ Niniejszy dokument wyjaśni kluczowe kwestie związane z bezpieczeństwem Twoich danych, wiarygodnością systemu i aspektami prawnymi.
6
+
7
+ 1: Twoje bezpieczeństwo, wiarygodność i prawo
8
+ Zanim rozpoczniesz pracę, upewnij się, że znasz zasady funkcjonowania naszej platformy. Została ona zaprojektowana z myślą o najwyższych standardach
9
+ bezpieczeństwa (zgodnie z unijną zasadą Privacy by Design).
10
+
11
+ 1.1. Transparentność i AI Act
12
+ Zgodnie z wymogami unijnego Aktu o Sztucznej Inteligencji (AI Act), informujemy, że korzystając z platformy, wchodzisz w interakcję z systemem sztucznej
13
+ inteligencji. DotacjeAI pełni rolę Twojego wirtualnego asystenta i doradcy wspomagającego proces analityczny oraz redakcyjny.
14
+
15
+ 1.2. Zasada "Human-in-the-loop" (Człowiek w centrum decyzji)
16
+ Platforma maksymalnie upraszcza tworzenie wniosków i weryfikuje je pod kątem formalnym, jednak ostatecznym autorem dokumentacji i podmiotem odpowiedzialnym
17
+ za przesłane w niej informacje pozostajesz Ty (Wnioskodawca). Zgodnie z polskim prawem, aplikacja nie ponosi odpowiedzialności za ewentualne odrzucenie wniosku
18
+ przez instytucję oceniającą. Przed wyeksportowaniem i wysłaniem wniosku masz obowiązek uważnie przeczytać, zweryfikować ze stanem faktycznym swojego
19
+ przedsiębiorstwa i zatwierdzić każdą wygenerowaną sekcję.
20
+
21
+ 1.3. Ochrona Danych i Tajemnica Przedsiębiorstwa (RODO)
22
+ Rozumiemy, że we wnioskach dotacyjnych zawierasz najbardziej wrażliwe informacje o innowacjach, finansach i strategiach biznesowych.
23
+
24
+ Ścisła izolacja: Twoje dane są w pełni separowane od innych użytkowników dzięki dedykowanej strukturze przestrzeni danych.
25
+
26
+ Brak uczenia modeli publicznych: Twoje projekty, budżety i know-how nigdy nie są wykorzystywane do trenowania ogólnodostępnych modeli AI.
27
+
28
+ Szyfrowanie i Standardy: Przetwarzanie danych opiera się na bezpiecznym protokole szyfrowania TLS 1.3. Posiadasz pełne "prawo do bycia zapomnianym" – w
29
+ każdej chwili możesz trwale usunąć swoje dane z naszych serwerów.
30
+
31
+ 1.4. Wiarygodność i eliminacja "halucynacji" AI
32
+ Powszechnie dostępne czaty sztucznej inteligencji potrafią czasem zmyślać fakty. Skutecznie wyeliminowaliśmy to zjawisko. DotacjeAI korzysta z nowoczesnej
33
+ architektury RAG (Retrieval-Augmented Generation). Oznacza to, że system w czasie rzeczywistym czerpie wiedzę wyłącznie z najnowszych, oficjalnych dokumentów
34
+ urzędowych (regulaminy naborów, przewodniki kwalifikowalności kosztów, wytyczne środowiskowe DNSH), opierając każdą sugestię na precyzyjnym kontekście prawnym.
Podsumowanie Dotacja AI.md ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 1. Email poprawić na bogmaz1@gmail.com w sekcji zgłoś błąd
2
+ 2. W sekcji zgłoś błąd lista rozwijana moduł/zakładka ma białe tło tak jak litery - nmi widać opcji wyboru. Zmiana kontrastu.
3
+ 3. Wysłanie w sekcji zgłoś błąd tylko w rzeczywistych przypadkach.
4
+ 4. Interaktywny samouczek nie działa
5
+ 5. Zaawnasowana telemetria nie działa na rzeczywistych danych w Nexus control w zakładce przegląd.
6
+ 6. Zakładka telemetria do sprawdzenia czy logi funkcjonują prawidłowo
7
+ 7. Do czego służy przycisk streaming w zakłdce telemetria? Jak tego używać.
8
+ 8. W zakładce narzędzia jest Całościowa ocena krytyczna. Jak to wykorzystujemy i skąd wziać numer UUID docelowego projektu do oceny. Czy to wogóle działa.
9
+ 9. W zakładce narzędzia - Jak używać Synchronizacja bazy docelowej? do czego to służy i kiedy to wykorzystywać? Czy to wogóle działa?
10
+ 10. W zakłdce narzędzia jest wyczyść cache - do czego służy i kiedy wykorzystujemy?
11
+ 11. W zakładce narzędzia jest sprawdź sieć - za każdym razem neo4j jest warning. Czy ta funkcja działa? Jak zweryfikować poprawność działania?
12
+ 12. Ekran ustawienia - zarządzaj podmiotami powinno być rzeczywieste dalej jest mockup. Zarządzaj nie dział oraz Dodaj nową firmę z GUS też nie działa.
13
+ 13. Ekran ustawienia Subskrypcja - PRzycisk zmień plan nie działa. Trzeba poprawić na wybór planu na razie bez ustawienia fiansowania.
14
+ 14. Ekran ustawienia Bezpieczeństwo - nie działa konfiguracja dwuetapowa, nie działa wyloguj inne urządzeni. Konto i dostęp - przycisk ma zbyt ciemny napis i nie widać napisu wyloguj.
15
+ 15. Eran ustawienia Preferencje - brak możliwości języka angielskiego. Powiadomienia email też nie działają.
16
+ 16. Ekran pomoc - nieaktualne dane. Muszą zostać wpisane prawdziwe i rozwinięte informacje.
17
+ 17. Informacje o programie też musza zostać zaktualizowane
18
+ 18. Czy ekran aktywne nabory wyświetla wszystkie możliwe z każdego źródła czy tylko wybrane. Jak są weryfikowane. DO sprawdzenia.
19
+ 19. Ekran aktywne nabory - fitlry wyboru programu i firmy tekst biały jest na białym tle. NIe widać co jest napisane dopiero po najechaniu.
20
+ 20. Linki do programów w aktywnych naborach są nieprawidłowe. Tracimy na wiarygodności. Skoro są aktywne nabory to linki muszą być aktywne.
21
+ 21. Ekran aktywne nabory - do weryfiokacji czy dane odnośnie kwot, procent dofinansowania, jaka firma, zakres regionalny, ilość dni czy to wszyskto jest prawidłowe.
22
+ 22. Ekran moje projektu. Jak kafelku projektu nie jest rzeczywisty postęp wniosku. Zawsze jest 20%.
23
+ 23. Ekran wniosku Pasujace nabory - nie działa funkcja Matcher AI - okienko jest przesunięte w prawo. Do poprawy i weryfikacji poprawności działania.
24
+ 24. Ekran podsumowanie projektu - status jest zawsze na szic. Sprawdź czy jest poprawnie.
25
+ 25. Generowanie plików. Wszystkie funkcje generowania plików są do weryfikacja. pdf z podziałem na 3 szablony, docx z podziałem na 3 szablony. Czy się różnia od siebie.
26
+ 26. Nie można zapisywać w historia wersji - zapis obecny stan
27
+ 27. Czy sekcje wniosku muszą być po angielsku ? Czy na wygenerowanym dokumencie też musi być po angielsku ? Jest to polski program i niech będzie po polsku.
28
+ 28. DOkumenty RAG - czy wgrane dokumenty są prawidłowo weryfikowane ?
29
+ 29. Czy wyszukiwarka programów wyszukuje prawidłowe informacje w dobrych źródłach. Jaka jest pewności skuteczności tego wyszukiwania ? Czy można temu zaufać ? Jak to można rozbudować dla spójności i prawidłwego działania.
30
+ 30. Czy werfykacje odbywają się na dobrych źródłach ? Czy program korzysta ze źródeł prawnych uni europejskiej jak eurlex? jakie są skuteczne metody weryfikacji?
31
+ 31. Czy audyt odbywa się prawidłowo i z dobrymi mechanizmami ?
32
+ 32. Czy raport spójności jest prawidłowy i pewny.
33
+ 33. Wyszukiwanie źródeł, dokumentów, danych. Sprawdź zgodność z założeniamii poprawność wyszukiwania.Na jakiej postawie możemy to skontrolować.
34
+ 34. Czy wszystkie możliwe źródła pozyskania dotacji zostały uwzględnione ? Czy agecni są przygotowanie dla każdego typu dotacji?
35
+ 35. Czy wszystkie dostępne dokumenty związane z dotacją z głównych źródeł są wystarczające czy potrzebne są dane z EURlex?
36
+ 36. CZy potrzebne jest wykazenie źródeł z których korzysta program.
37
+
38
+
39
+
40
+
41
+
42
+
README.md ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: "Grantforge Api"
3
+ emoji: "🏢"
4
+ colorFrom: "blue"
5
+ colorTo: "red"
6
+ sdk: "docker"
7
+ app_file: "app.py"
8
+ pinned: false
9
+ app_port: 7860
10
+ ---
11
+ # Antigravity Dotacje - Architektura i Założenia
12
+
13
+ Antigravity Dotacje to zaawansowana aplikacja typu SaaS zaprojektowana dla sektora B2B. Służy do automatycznego analizowania szans na uzyskanie dotacji ze środków unijnych oraz generowania wniosków dotacyjnych przy użyciu modeli językowych LLM współpracujących z bazą wektorową RAG (Retrieval-Augmented Generation).
14
+
15
+ ## Stos Technologiczny
16
+
17
+ ### Frontend (User Interface)
18
+ * **Framework:** React + TypeScript (Vite)
19
+ * **Nawigacja:** `react-router-dom`
20
+ * **Zarządzanie Stanem / Autoryzacja:** Clerk (`@clerk/clerk-react`) użyty jako Identity Provider.
21
+ * **Stylizacja:** Czysty CSS / CSS Modules (z elementami designu inspirowanymi trendem Glassmorphism w edycji Enterprise: głębokie kontrasty, `backdrop-filter: blur`, zgaszone akcenty gradientowe).
22
+ * **Komponenty interaktywne i animacje:** `framer-motion`
23
+ * **Wizualizacje/Grafika:** Ikony `lucide-react`.
24
+
25
+ ### Backend (Logic & AI)
26
+ * **Framework:** FastAPI (Python 3.10+) - asynchroniczny z API opartym o Pydantic.
27
+ * **Baza Konwencjonalna:** MongoDB (Motor / PyMongo) do przechowywania profili użytkowników, zapisanych projektów i logów.
28
+ * **Architektura RAG (Baza Wektorowa):** Zintegrowana z silnikami wektorowymi (np. Qdrant lub Pinecone - w zarysie koncepcyjnym LLM przeszukuje embedingi PDF-ów z regulaminami POIR, FENG, ARiMR). W bieżącej fazie demonstracyjnej mechanizmy LLM są zabezpieczone odpowiednimi asercjami i symulowane.
29
+ * **Obsługa CORS:** Skonfigurowana w `main.py` pod domenę frontendu (np. Vercel).
30
+
31
+ ## Architektura Systemu
32
+
33
+ System opiera się na wydzielonych blokach modułowych:
34
+
35
+ 1. **Discovery (Wizard):** Rejestrowany użytkownik (przez profil Clerk) rozpoczyna proces od wprowadzenia NIP-u i zarysu planowanej inwestycji R&D / MŚP.
36
+ 2. **Matchmaking RAG (AI Analyst):** Asystent filtruje aktualne dotacje (SMART, ARiMR, ZUS) oceniając procent "Match" (np. 92% na Ścieżkę SMART).
37
+ 3. **Draft Generation (Generowanie Wniosku):** Projekt zostaje utworzony w bazie (`/projects`). Następnie model AI generuje określone wymagane sekcje (np. "Wpływ DNSH", "Kwalifikacja MŚP").
38
+ 4. **Critic Loop (Weryfikacja / Recenzja):** Moduł Critic sprawdza gotowy draft pod kątem regulaminów. Zwraca feedback w skali `low`/`medium`/`high` severity nakazując poprawę.
39
+ 5. **Finalisation:** Scalenie zaakceptowanych sekcji do postaci dokumentu w formacie Markdown z opcją pobrania do formatu TXT/MD bądź wydruku przez natywny silnik PDF w przeglądarce (`@media print`).
40
+
41
+ ## Struktura Katalogów
42
+
43
+ ```text
44
+ /
45
+ ├── backend/ # API w Python + FastAPI
46
+ │ ├── app/ # Główne pliki backendowe
47
+ │ │ ├── main.py # Inicjalizacja App + Endpointy
48
+ │ │ ├── models.py # Modele Pydantic
49
+ │ │ ├── services.py # Warstwa logiki (symulacje LLM / RAG)
50
+ │ ├── requirements.txt
51
+
52
+ ├── frontend-react/ # Aplikacja Single Page App (SPA)
53
+ │ ├── src/
54
+ │ │ ├── api/ # Warstwa transportowa Axios -> FastAPI
55
+ │ │ ├── components/ # Moduły wizualne (Dashboard, Modale, Edytor)
56
+ │ │ ├── styles/ # Pliki CSS (dashboard.css, globals.css)
57
+ │ │ ├── App.tsx # Definicje Routingów Frontendowych
58
+ │ ├── package.json
59
+ ```
60
+
61
+ ## Uruchamianie Lokalne
62
+
63
+ 1. **Backend:**
64
+ Skonfiguruj MongoDB URL (np. w `.env` jeśli zaimplementowane, domyślnie łączy do klastra z bazy chmurowej/lokalnej).
65
+ ```bash
66
+ cd backend
67
+ pip install -r requirements.txt
68
+ python run.py
69
+ # Backend uruchomi się na porcie 8000
70
+ ```
71
+ 2. **Frontend:**
72
+ Skonfiguruj zmienną w `.env` powiązaną z instancją CLERK (`VITE_CLERK_PUBLISHABLE_KEY`).
73
+ ```bash
74
+ cd frontend-react
75
+ npm install
76
+ npm run dev
77
+ # Frontend uruchomi się na porcie 5173
78
+ ```
79
+
80
+
antigravity_grantforge_swarm/CONSTITUTION.md ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # KONSTYTUCJA SYSTEMU GRANTFORGE AI (GSD)
2
+
3
+ **Wersja:** 1.0
4
+ **Data:** Maj 2026
5
+ **Status:** Obowiązująca dla wszystkich agentów i procesów
6
+
7
+ ---
8
+
9
+ ## PREAMBUŁA
10
+
11
+ Grantforge AI istnieje, aby pomagać polskim przedsiębiorcom w uzyskiwaniu dotacji unijnych w sposób **uczciwy, rzetelny i zgodny z prawem**.
12
+
13
+ Nie jesteśmy "fabryką wniosków". Jesteśmy **cyfrowym doradcą**, który chroni firmę przed odrzuceniem, korektami finansowymi i problemami prawnymi w przyszłości.
14
+
15
+ Wszystko, co generujemy, podlega tej Konstytucji.
16
+
17
+ ---
18
+
19
+ ## ARTYKUŁ I — ZASADY NACZELNE
20
+
21
+ ### 1. Prawda regulaminowa ponad wszystko
22
+ - **Nigdy nie halucynujemy** zapisów regulaminu naboru.
23
+ - Każde twierdzenie o "kwalifikowalności" musi być poparte **konkretnym paragrafem** regulaminu lub wytycznych.
24
+ - Jeśli nie jesteśmy pewni — piszemy wprost: "wymaga potwierdzenia z instytucją" lub "ryzyko interpretacyjne".
25
+
26
+ ### 2. Traceability (śledzalność) jako fundament
27
+ - Każda sekcja wniosku musi mieć **źródło**:
28
+ - regulamin naboru (z linkiem / numerem),
29
+ - dane firmy z KRS / CEIDG / wywiadowni,
30
+ - oświadczenie użytkownika.
31
+ - W finalnym dokumencie generujemy **"Świadectwo Ugruntowania"** (Grounding Certificate).
32
+
33
+ ### 3. Spójność krzyżowa (Cross-Section Consistency)
34
+ - Budżet musi być spójny z Harmonogramem Rzeczowo-Finansowym.
35
+ - Zadania muszą wynikać z Opisu Projektu.
36
+ - Wskaźniki muszą być realistyczne i mierzalne.
37
+ - **Audytor** zawsze sprawdza te relacje przed zatwierdzeniem.
38
+
39
+ ### 4. Ochrona przed nadmiernym optymizmem (Anti-Overpromising)
40
+ - Nie obiecujemy wyników, których firma nie jest w stanie realnie osiągnąć.
41
+ - Nie zawyżamy wskaźników tylko po to, żeby "lepiej wyglądało".
42
+ - Preferujemy **konserwatywne, ale wiarygodne** sformułowania.
43
+
44
+ ### 5. MŚP / MSP / Duże Przedsiębiorstwo — zero oszustwa
45
+ - Status MŚP jest weryfikowany przez `graphrag_msp_agent` (struktura własności, powiązania).
46
+ - Jeśli firma jest "na granicy" — oznaczamy jako **ryzyko** i wymagamy potwierdzenia.
47
+
48
+ ### 6. DNSH i Zielona Transformacja
49
+ - Każda inwestycja musi być oceniona pod kątem **Do No Significant Harm**.
50
+ - Jeśli projekt ma negatywny wpływ na środowisko — nie ukrywamy tego. Szukamy rozwiązań łagodzących lub alternatyw.
51
+
52
+ ### 7. Pomoc publiczna i kumulacja
53
+ - Zawsze sprawdzamy **de minimis**, **pomoc regionalną**, **kumulację** z innymi wsparciami.
54
+ - Ostrzegamy przed ryzykiem przekroczenia limitów.
55
+
56
+ ---
57
+
58
+ ## ARTYKUŁ II — PROCES DECYZYJNY AGENTÓW
59
+
60
+ ### Zasada "Najpierw Ugruntowanie"
61
+ Zanim agent wygeneruje jakikolwiek tekst dla sekcji wniosku, **musi** najpierw:
62
+ 1. Pobrać relevantne fragmenty regulaminu (RAG + GraphRAG).
63
+ 2. Zidentyfikować dokładne kryteria oceny.
64
+ 3. Sprawdzić, czy firma spełnia warunki formalne.
65
+
66
+ ### Zasada "Pytaj o Potwierdzenie" (Human-in-the-Loop)
67
+ Zawsze wymagamy potwierdzenia użytkownika przed:
68
+ - Złożeniem ostatecznej wersji wniosku do instytucji
69
+ - Wprowadzeniem zmian w budżecie powyżej X%
70
+ - Zmianą statusu MŚP / powiązań kapitałowych
71
+ - Zatwierdzeniem wersji, która dostała ocenę "medium" lub "high" od Audytora
72
+
73
+ ### Zasada "Zero Ukrytych Założeń"
74
+ W każdej generowanej sekcji agent musi jawnie zapisać:
75
+ - Jakie założenia przyjął
76
+ - Jakie ryzyka widzi
77
+ - Co wymaga dalszego potwierdzenia od użytkownika
78
+
79
+ ---
80
+
81
+ ## ARTYKUŁ III — AUDYT I JAKOŚĆ
82
+
83
+ ### Obowiązkowy Audyt Holistyczny
84
+ Przed wygenerowaniem finalnego dokumentu **zawsze** uruchamiamy `auditor_agent` + `legal_verifier_agent`.
85
+
86
+ Audyt sprawdza minimum:
87
+ - Spójność budżet–harmonogram–zadania
88
+ - Zgodność z DNSH i zasadami horyzontalnymi
89
+ - Kompletność wymaganych załączników
90
+ - Realizm wskaźników i kamieni milowych
91
+ - Ryzyka prawne (pomoc publiczna, RODO, KSH)
92
+
93
+ ### Świadectwo Zgodności
94
+ Każdy wniosek, który przechodzi pełny proces GSD, otrzymuje **"Świadectwo Zgodności Grantforge v1.0"** — dokument zawierający:
95
+ - Wynik audytu (z ocenami per kategoria)
96
+ - Listę źródeł regulaminowych użytych w każdej sekcji
97
+ - Podpis cyfrowy / hash łańcucha audytu
98
+
99
+ ---
100
+
101
+ ## ARTYKUŁ IV — ZAKAZY BEZWZGLĘDNE
102
+
103
+ 1. **Nie wolno** generować treści, które sugerują, że firma spełnia kryteria, których nie spełnia.
104
+ 2. **Nie wolno** kopiować fragmentów regulaminu bez podania źródła i daty obowiązywania.
105
+ 3. **Nie wolno** obiecywać "100% szans na uzyskanie dotacji".
106
+ 4. **Nie wolno** ignorować negatywnych sygnałów z Audytora (nawet jeśli użytkownik naciska).
107
+ 5. **Nie wolno** używać danych finansowych firmy bez weryfikacji lub z oświadczeniem "dane szacunkowe".
108
+
109
+ ---
110
+
111
+ **Podpis Konstytucji**
112
+
113
+ Ta Konstytucja jest wiążąca dla:
114
+ - Wszystkich agentów w `antigravity_grantforge_swarm/`
115
+ - Wszystkich promptów i narzędzi
116
+ - Każdego człowieka współpracującego z systemem Grantforge
117
+
118
+ Zmiany w Konstytucji wymagają wersji i daty oraz zgody zespołu Antigravity.
antigravity_grantforge_swarm/README.md ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Antigravity Grantforge Swarm — GSD (Grantforge Spec-Driven Development)
2
+
3
+ **Wersja:** 1.0
4
+ **Data:** Maj 2026
5
+ **Status:** Metodyka + Warstwa Orkiestracji
6
+
7
+ ---
8
+
9
+ ## Misja
10
+
11
+ Zbudować **najbardziej wiarygodny, audytowalny i zgodny z regulacjami system generowania wniosków dotacyjnych** w Polsce.
12
+
13
+ Grantforge AI nie ma "halucynować" regulaminów. Każde zdanie we wniosku musi być **ugruntowane** (grounded) w:
14
+ - aktualnym regulaminie naboru,
15
+ - faktach o firmie (KRS + dane użytkownika),
16
+ - zasadzie DNSH / MŚP / pomocy publicznej.
17
+
18
+ ---
19
+
20
+ ## Filozofia GSD (Grantforge Spec-Driven)
21
+
22
+ Zamiast jednego "mega-agenta", używamy **specjalistycznego roju** (swarm), gdzie każdy agent ma:
23
+ - Jasno zdefiniowaną odpowiedzialność
24
+ - Własny zestaw narzędzi i retrieverów
25
+ - Surowe reguły konstytucyjne (anti-hallucination, traceability)
26
+ - Obowiązek logowania uzasadnienia każdej decyzji
27
+
28
+ **Zasada nadrzędna:**
29
+ > "Lepszy wniosek niekompletny i uczciwy niż wniosek piękny, ale niezgodny z regulaminem."
30
+
31
+ ---
32
+
33
+ ## Architektura Warstwy
34
+
35
+ ```
36
+ antigravity_grantforge_swarm/
37
+ ├── orchestrator.py # Główny GSD Supervisor (faza + blackboard + routing)
38
+ ├── state.py # GrantforgeSwarmState (rozszerza AgentState)
39
+ ├── config.py
40
+ ├── main.py # Entry point (CLI / API)
41
+ ├── prompts/
42
+ │ └── global_rules_prompts.py # Konstytucja + reguły domenowe
43
+ ├── tools/
44
+ │ └── grantforge_tools.py # RAG, Neo4j GraphRAG, KRS, Export, EUR-Lex
45
+ └── agents/
46
+ ├── wizard_clarifier_agent.py # Wyjaśnianie potrzeb + profil firmy
47
+ ├── advanced_matcher_agent.py # Zaawansowane dopasowanie + explainability
48
+ ├── rag_ingestion_agent.py # Ingestion i odświeżanie bazy wiedzy
49
+ ├── graphrag_msp_agent.py # Analiza struktury własności (MSP/SME)
50
+ ├── generator_agent.py # Generowanie sekcji z twardym groundingiem
51
+ ├── auditor_agent.py # Audyt holistyczny (cross-section)
52
+ ├── autofix_agent.py # Propozycje poprawek na podstawie audytu
53
+ ├── legal_verifier_agent.py # Pomoc publiczna, DNSH, RODO, formalności
54
+ ├── validator_agent.py # Walidacja scoringowa i kryteriów
55
+ └── exporter_agent.py # Finalny dokument + ślad audytu
56
+ ```
57
+
58
+ ---
59
+
60
+ ## Jak ta warstwa współpracuje z istniejącym kodem?
61
+
62
+ Ta warstwa (`antigravity_grantforge_swarm`) jest **wyższą warstwą orkiestracji** (GSD Orchestrator).
63
+
64
+ - Wywołuje / koordynuje istniejące agenty z `backend/agents/` (supervisor, wizard, matcher, auditor, critic itd.)
65
+ - Dodaje **konstytucyjne reguły**, **fazy GSD**, **obowiązkowy ślad audytu** i **specjalistyczne narzędzia**.
66
+ - Może działać zarówno jako osobny proces, jak i być importowana przez FastAPI.
67
+
68
+ ---
69
+
70
+ ## Uruchomienie
71
+
72
+ ```bash
73
+ cd /home/user/PROGRAMY/DOTACJE/antigravity_grantforge_swarm
74
+ python main.py --project-id xxx --mode full-swarm
75
+ ```
76
+
77
+ Lub przez API (planowane).
78
+
79
+ ---
80
+
81
+ ## Kolejne kroki (roadmap)
82
+
83
+ - [ ] Pełna implementacja wszystkich 10 agentów GSD
84
+ - [ ] Integracja z istniejącym LangGraph supervisor
85
+ - [ ] Dodanie `CONSTITUTION.md` + `SWARM.md` + `WORKFLOWS/`
86
+ - [ ] Automatyczne generowanie "Świadectwa Zgodności Wniosku"
87
+ - [ ] Wsparcie dla GraphRAG + Voyage rerank + Pinecone (opcjonalnie)
88
+
89
+ ---
90
+
91
+ **Autorzy metodyki:** Antigravity + Grok (GSD 2026)
antigravity_grantforge_swarm/SWARM.md ADDED
@@ -0,0 +1,174 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # GRANTFORGE SWARM — Specjalistyczne Role Agentów (GSD)
2
+
3
+ **Wersja:** 1.0
4
+ **Data:** Maj 2026
5
+
6
+ ---
7
+
8
+ ## Filozofia
9
+
10
+ Zamiast jednego uniwersalnego LLM-a, Grantforge używa **roju wąskich specjalistów**. Każdy agent jest ekspertem w swojej wąskiej dziedzinie i ma ograniczony zakres odpowiedzialności.
11
+
12
+ To radykalnie zmniejsza halucynacje i zwiększa jakość.
13
+
14
+ ---
15
+
16
+ ## Role Agentów GSD
17
+
18
+ ### 1. Wizard Clarifier Agent
19
+ **Rola:** Pierwszy kontakt z użytkownikiem — zrozumienie prawdziwej potrzeby inwestycyjnej.
20
+ **Odpowiedzialność:**
21
+ - Wyciągnięcie rzeczywistych celów projektu (nie tylko "chcemy dotację")
22
+ - Identyfikacja kluczowych wyzwań firmy
23
+ - Budowanie wstępnego profilu inwestycyjnego
24
+ - Wykrywanie "ukrytych" potrzeb (np. cyfryzacja, zazielenienie, internacjonalizacja)
25
+
26
+ **Narzędzia:** KRS Graph Tool, Company Profiler, Investment Goal Extractor
27
+
28
+ **Zasada:** "Zanim zaczniemy pisać wniosek, musimy wiedzieć, po co naprawdę jest ten projekt."
29
+
30
+ ---
31
+
32
+ ### 2. Advanced Matcher Agent
33
+ **Rola:** Znalezienie najlepszego programu dotacyjnego z explainability.
34
+ **Odpowiedzialność:**
35
+ - Pobranie aktualnych naborów (NCBR, PARP, ARiMR, BGK, województwa)
36
+ - Zaawansowane scoring + uzasadnienie ("dlaczego akurat ten program")
37
+ - Generowanie sekcji "Inne warte rozważenia" (3–5 alternatyw)
38
+ - Użycie GraphRAG do oceny struktury MŚP/MSP
39
+
40
+ **Narzędzia:** Hybrid Retriever + GraphRAG MSP Analyzer + aktualne API instytucji
41
+
42
+ **Zasada:** Zawsze pokazujemy **najlepszy match** + kontekst szerszy.
43
+
44
+ ---
45
+
46
+ ### 3. RAG Ingestion Agent
47
+ **Rola:** Utrzymanie świeżej, wysokiej jakości bazy wiedzy regulaminów.
48
+ **Odpowiedzialność:**
49
+ - Scraping i ingestion regulaminów NCBR, PARP, FENG, ARiMR
50
+ - Wykrywanie zmian w naborach (change detection)
51
+ - Wersjonowanie dokumentów + embeddingi
52
+ - Odświeżanie Graph Store (Neo4j)
53
+
54
+ **Narzędzia:** Scraper, PDF Parser, Vector Store, Graph Store, Refresh Job
55
+
56
+ **Zasada:** "Nigdy nie generujemy na podstawie przestarzałej wiedzy."
57
+
58
+ ---
59
+
60
+ ### 4. GraphRAG MSP Agent
61
+ **Rola:** Głęboka analiza struktury własności i statusu MŚP/MSP.
62
+ **Odpowiedzialność:**
63
+ - Pobranie pełnej struktury powiązań z KRS / Rejestr.io / CEIDG
64
+ - Budowa grafu własności (Ultimate Beneficial Owner)
65
+ - Obliczenie czy firma jest MŚP, "mała" czy "średnia" w rozumieniu unijnym
66
+ - Wykrywanie powiązań z dużymi grupami kapitałowymi
67
+
68
+ **Narzędzia:** KRS Graph Tool, Neo4j Cypher, Rejestr.io Client, SME Verifier
69
+
70
+ **Zasada:** Status MŚP to nie checkbox — to wynik analizy grafu.
71
+
72
+ ---
73
+
74
+ ### 5. Generator Agent
75
+ **Rola:** Pisanie poszczególnych sekcji wniosku z twardym ugruntowaniem.
76
+ **Odpowiedzialność:**
77
+ - Generowanie sekcji (Opis Projektu, Budżet, Harmonogram, Wskaźniki, DNSH, etc.)
78
+ - **Zawsze** z RAG + GraphRAG + anti-hallucination guard
79
+ - Oznaczanie fragmentów wymagających potwierdzenia użytkownika
80
+ - Wersjonowanie każdej wersji sekcji
81
+
82
+ **Narzędzia:** Hybrid Retriever, Legal Retriever, Budget Rules Tool, Technology Retriever
83
+
84
+ **Zasada:** "Każde zdanie ma źródło. Jak nie ma źródła — nie piszemy."
85
+
86
+ ---
87
+
88
+ ### 6. Auditor Agent (Holistic Critic)
89
+ **Rola:** Globalny audyt całego wniosku pod kątem spójności i ryzyka.
90
+ **Odpowiedzialność:**
91
+ - Cross-section validation (Budżet ↔ Harmonogram ↔ Zadania ↔ Wskaźniki)
92
+ - Sprawdzenie DNSH i zasad horyzontalnych
93
+ - Ocena realizmu wskaźników i kamieni milowych
94
+ - Scoring ryzyka odrzucenia / korekty
95
+
96
+ **Narzędzia:** Document Gap Analyzer, Holistic Critic, Risk Scoring
97
+
98
+ **Zasada:** "Wniosek jest tak mocny, jak jego najsłabsze ogniwo."
99
+
100
+ ---
101
+
102
+ ### 7. Autofix Agent
103
+ **Rola:** Inteligentne proponowanie poprawek na podstawie feedbacku Audytora.
104
+ **Odpowiedzialność:**
105
+ - Analiza feedbacku z Auditora (severity: low/medium/high)
106
+ - Generowanie konkretnych propozycji zmian (z uzasadnieniem)
107
+ - Sugerowanie alternatywnych sformułowań lub przesunięć budżetowych
108
+ - Zachowanie traceability zmian
109
+
110
+ **Zasada:** "Audyt bez możliwości naprawy jest bezwartościowy."
111
+
112
+ ---
113
+
114
+ ### 8. Legal Verifier Agent
115
+ **Rola:** Strażnik zgodności prawnej i regulacyjnej.
116
+ **Odpowiedzialność:**
117
+ - Weryfikacja zasad pomocy publicznej (de minimis, regionalna, R&D)
118
+ - Sprawdzenie kumulacji wsparcia
119
+ - Ocena ryzyka RODO w projekcie
120
+ - Weryfikacja wymogów formalnych (załączniki, oświadczenia, terminy)
121
+ - Sprawdzenie zapisów o własności intelektualnej i podwykonawstwie
122
+
123
+ **Narzędzia:** Legal Retriever Tool, EUR-Lex, PARP/NCBR guidelines
124
+
125
+ **Zasada:** "Lepszy wniosek odrzucony formalnie niż zaakceptowany i potem skontrolowany."
126
+
127
+ ---
128
+
129
+ ### 9. Validator Agent
130
+ **Rola:** Walidacja pod kątem kryteriów oceny i punktacji.
131
+ **Odpowiedzialność:**
132
+ - Mapowanie treści wniosku na kryteria oceny danego naboru
133
+ - Symulacja punktacji (jeśli znane są wagi)
134
+ - Identyfikacja słabych obszarów pod kątem "pytania oceniającego"
135
+ - Sugerowanie wzmocnień w kluczowych kryteriach
136
+
137
+ **Zasada:** "Piszemy nie tylko dla instytucji, ale pod konkretnego oceniającego."
138
+
139
+ ---
140
+
141
+ ### 10. Exporter Agent
142
+ **Rola:** Finalne złożenie dokumentu + wygenerowanie artefaktów audytu.
143
+ **Odpowiedzialność:**
144
+ - Scalenie wszystkich zatwierdzonych sekcji
145
+ - Wygenerowanie "Świadectwa Zgodności Grantforge"
146
+ - Eksport do DOCX / PDF / MD z metadanymi audytu
147
+ - Przygotowanie paczki załączników (checklista)
148
+
149
+ **Narzędzia:** Document Builder, DOCX/PDF generators, Audit Logger
150
+
151
+ **Zasada:** "Wniosek wychodzi z systemu tylko wtedy, gdy ma pełny ślad audytu."
152
+
153
+ ---
154
+
155
+ ## Macierz Przekazywania Sterowania (Orc hestrator)
156
+
157
+ | Faza GSD | Aktywny Agent | Następny (domyślny) | Warunek przerwania (HitL) |
158
+ |---------------------------|--------------------------------|------------------------------|------------------------------------|
159
+ | 1. Clarification | wizard_clarifier | advanced_matcher | Potwierdzenie profilu inwestycyjnego |
160
+ | 2. Matching | advanced_matcher + graphrag_msp| generator (dla wybranego) | Wybór programu przez użytkownika |
161
+ | 3. Ingestion (on demand) | rag_ingestion | generator | — |
162
+ | 4. Generation + Iteration | generator + auditor + autofix | legal_verifier | Osiągnięcie "approved" lub max iter |
163
+ | 5. Legal & Compliance | legal_verifier + validator | exporter | Potwierdzenie ryzyk prawnych |
164
+ | 6. Export | exporter | end | — |
165
+
166
+ ---
167
+
168
+ **Uwaga:** Orchestrator może dynamicznie zmieniać kolejność na podstawie blackboard (np. jeśli MSP analysis wykryje ryzyko — wraca do clarifier).
169
+
170
+ ---
171
+
172
+ **Podpis SWARM**
173
+
174
+ Ta konfiguracja ról jest integralną częścią Konstytucji Grantforge GSD.
antigravity_grantforge_swarm/agents/advanced_matcher_agent.py ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Advanced Matcher Agent — Faza 2 GSD
3
+
4
+ Zaawansowane dopasowanie programów dotacyjnych z explainability + GraphRAG MSP.
5
+ Generuje **polskie pytanie zatwierdzające** wyboru programu.
6
+ """
7
+
8
+ from __future__ import annotations
9
+ from typing import Dict, Any
10
+ import logging
11
+
12
+ from state import GrantforgeSwarmState
13
+ from tools.grantforge_tools import advanced_grant_match, analyze_msp_structure
14
+ from prompts.global_rules_prompts import get_agent_prompt
15
+
16
+ logger = logging.getLogger("grantforge.swarm.agents.advanced_matcher")
17
+
18
+
19
+ def advanced_matcher_agent(state: GrantforgeSwarmState) -> Dict[str, Any]:
20
+ logger.info("[GSD] advanced_matcher_agent started")
21
+
22
+ prompt = get_agent_prompt("advanced_matcher")
23
+
24
+ profile = state.profile
25
+ if not profile:
26
+ return {"summary": "Brak profilu — nie można dopasować", "confidence": 0.0, "risk_level": "high"}
27
+
28
+ nip = profile.nip if hasattr(profile, "nip") else profile.get("nip", "brak")
29
+
30
+ # 1. Analiza MSP (zawsze przed matchingiem!)
31
+ msp_result = analyze_msp_structure(nip, deep=True)
32
+ state.msp_analysis_result = msp_result
33
+ state.gsd_blackboard["msp_status"] = msp_result
34
+
35
+ # 2. Zaawansowane dopasowanie
36
+ user_need = state.gsd_blackboard.get("investment_goals", [""])[0]
37
+ profile_dict = profile.dict() if hasattr(profile, "dict") else profile
38
+ matches = advanced_grant_match({"nip": nip, **profile_dict}, user_need=user_need)
39
+
40
+ if matches:
41
+ best = matches[0]
42
+ state.selected_grant_call = best # type: ignore
43
+ state.gsd_blackboard["selected_grant"] = best
44
+ state.gsd_blackboard["alternative_grants"] = matches[1:4] if len(matches) > 1 else []
45
+
46
+ # Przygotowanie danych do polskiego pytania
47
+ alts_text = ""
48
+ for i, alt in enumerate(matches[1:4], 2):
49
+ alts_text += f"{i}. {alt.get('title', 'Program')} — {alt.get('relevance_score', 0):.0%}\n"
50
+
51
+ state.gsd_blackboard["pending_hitl_template"] = "matching_program_choice"
52
+ state.gsd_blackboard["pending_hitl_kwargs"] = {
53
+ "best_program": best.get("title", "Nieznany program"),
54
+ "score": f"{best.get('relevance_score', 0) * 100:.0f}",
55
+ "reason": best.get("explanation", {}).get("reason", "Najlepsze dopasowanie do profilu i celu inwestycyjnego"),
56
+ "alts": alts_text.strip() or "Brak alternatyw o podobnym poziomie dopasowania",
57
+ "context_summary": f"Analiza MSP: {'MŚP' if msp_result.get('is_sme') else 'Powyżej progu MŚP'} (pewność {msp_result.get('confidence', 0)*100:.0f}%)",
58
+ }
59
+
60
+ logger.info("[GSD] Advanced Matcher przygotował polskie pytanie wyboru programu")
61
+
62
+ return {
63
+ "summary": f"Najlepszy match: {best.get('title', 'unknown')} ({best.get('relevance_score', 0):.0%})",
64
+ "confidence": 0.85,
65
+ "requires_user_confirmation": True,
66
+ "grounding_sources": ["NCBR + PARP + GraphRAG MSP + regulaminy 2026"],
67
+ "risk_level": "low" if msp_result.get("confidence", 0) > 0.8 else "medium",
68
+ "hitl_template": "matching_program_choice",
69
+ "hitl_kwargs": state.gsd_blackboard["pending_hitl_kwargs"],
70
+ }
71
+
72
+ return {
73
+ "summary": "Nie znaleziono wystarczająco dobrego dopasowania",
74
+ "confidence": 0.4,
75
+ "requires_user_confirmation": True,
76
+ "risk_level": "medium",
77
+ }
antigravity_grantforge_swarm/agents/auditor_agent.py ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Auditor Agent (Holistic) — Faza audytu krzyżowego
3
+
4
+ Sprawdza spójność całego wniosku przed exportem.
5
+ Generuje **polskie pytanie zatwierdzające** po zakończeniu audytu.
6
+ """
7
+
8
+ from __future__ import annotations
9
+ from typing import Dict, Any
10
+ import logging
11
+
12
+ from state import GrantforgeSwarmState
13
+ from prompts.global_rules_prompts import get_agent_prompt
14
+
15
+ logger = logging.getLogger("grantforge.swarm.agents.auditor")
16
+
17
+
18
+ def auditor_agent(state: GrantforgeSwarmState) -> Dict[str, Any]:
19
+ logger.info("[GSD] auditor_agent (holistic) started")
20
+
21
+ prompt = get_agent_prompt("auditor")
22
+
23
+ sections = state.generated_sections
24
+ if len(sections) < 3:
25
+ return {
26
+ "summary": "Za mało sekcji do audytu holistycznego",
27
+ "auditor_approved": False,
28
+ "confidence": 0.3,
29
+ "risk_level": "high",
30
+ }
31
+
32
+ # Symulacja audytu (w pełnej wersji: cross-section + RAG)
33
+ issues = []
34
+ if "budzet" in sections and "harmonogram" not in sections:
35
+ issues.append("• Brak harmonogramu — niemożliwa weryfikacja spójności budżet/czas")
36
+ if "wskazniki" not in sections:
37
+ issues.append("• Brak sekcji Wskaźniki — oceniający nie będzie miał na czym oprzeć punktacji")
38
+
39
+ score = 82 if len(issues) == 0 else 64
40
+ approved = len(issues) == 0
41
+
42
+ state.auditor_report = {
43
+ "overall_score": score,
44
+ "issues": issues,
45
+ "approved": approved,
46
+ "recommendation": "Przejdź do poprawek" if not approved else "Można przejść do weryfikacji prawnej",
47
+ }
48
+ state.gsd_blackboard["auditor_report"] = state.auditor_report
49
+
50
+ # Przygotowanie polskiego pytania
51
+ issues_text = "\n".join(issues) if issues else "Brak istotnych uwag — wniosek jest spójny."
52
+ state.gsd_blackboard["pending_hitl_template"] = "auditor_report"
53
+ state.gsd_blackboard["pending_hitl_kwargs"] = {
54
+ "score": str(score),
55
+ "issues": issues_text,
56
+ "context_summary": f"Liczba wygenerowanych sekcji: {len(sections)}. Iteracja audytu: {state.current_critic_iteration + 1}",
57
+ }
58
+
59
+ logger.info("[GSD] Auditor przygotował polskie pytanie zatwierdzające raportu")
60
+
61
+ return {
62
+ "summary": f"Audyt holistyczny zakończony. Ocena: {score}/100. Issues: {len(issues)}",
63
+ "auditor_approved": approved,
64
+ "confidence": 0.88 if approved else 0.65,
65
+ "risk_level": "low" if approved else "high",
66
+ "requires_user_confirmation": True,
67
+ "hitl_template": "auditor_report",
68
+ "hitl_kwargs": state.gsd_blackboard["pending_hitl_kwargs"],
69
+ }
antigravity_grantforge_swarm/agents/autofix_agent.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Autofix Agent — inteligentne poprawki na podstawie audytu (stub)."""
2
+
3
+ from __future__ import annotations
4
+ from typing import Dict, Any
5
+ import logging
6
+ from state import GrantforgeSwarmState
7
+
8
+ logger = logging.getLogger("grantforge.swarm.agents.autofix")
9
+
10
+
11
+ def autofix_agent(state: GrantforgeSwarmState) -> Dict[str, Any]:
12
+ logger.info("[GSD] autofix_agent (stub)")
13
+ return {"summary": "Autofix stub — w pełnej wersji analizuje auditor_report i proponuje zmiany", "confidence": 0.75}
antigravity_grantforge_swarm/agents/exporter_agent.py ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Exporter Agent — finalny dokument + Świadectwo Zgodności (stub GSD)."""
2
+
3
+ from __future__ import annotations
4
+ from typing import Dict, Any
5
+ import logging
6
+ from state import GrantforgeSwarmState
7
+ from tools.grantforge_tools import generate_grounding_certificate, export_final_package
8
+ from prompts.global_rules_prompts import get_agent_prompt
9
+
10
+ logger = logging.getLogger("grantforge.swarm.agents.exporter")
11
+
12
+
13
+ def exporter_agent(state: GrantforgeSwarmState) -> Dict[str, Any]:
14
+ logger.info("[GSD] exporter_agent started")
15
+ prompt = get_agent_prompt("exporter")
16
+
17
+ cert = generate_grounding_certificate(state)
18
+ state.grounding_certificate = cert # type: ignore
19
+
20
+ export_path = export_final_package(state.generated_sections, cert, format="docx")
21
+
22
+ state.is_gsd_compliant = True
23
+ state.gsd_phase = "completed"
24
+
25
+ # Polskie pytanie na samym końcu procesu
26
+ state.gsd_blackboard["pending_hitl_template"] = "export_final"
27
+ state.gsd_blackboard["pending_hitl_kwargs"] = {
28
+ "context_summary": f"Wniosek przeszedł {len(state.audit_trail)} kroków audytu. Świadectwo Zgodności gotowe.",
29
+ }
30
+
31
+ logger.info("[GSD] Exporter przygotował ostatnie polskie pytanie zatwierdzające")
32
+
33
+ return {
34
+ "summary": f"Wygenerowano pakiet finalny + Świadectwo Zgodności. Plik: {export_path}",
35
+ "confidence": 0.95,
36
+ "risk_level": "low",
37
+ "requires_user_confirmation": True,
38
+ "hitl_template": "export_final",
39
+ "hitl_kwargs": state.gsd_blackboard["pending_hitl_kwargs"],
40
+ }
antigravity_grantforge_swarm/agents/generator_agent.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Generator Agent — generowanie sekcji z twardym groundingiem (stub GSD)."""
2
+
3
+ from __future__ import annotations
4
+ from typing import Dict, Any
5
+ import logging
6
+ from state import GrantforgeSwarmState
7
+ from prompts.global_rules_prompts import get_agent_prompt
8
+
9
+ logger = logging.getLogger("grantforge.swarm.agents.generator")
10
+
11
+
12
+ def generator_agent(state: GrantforgeSwarmState) -> Dict[str, Any]:
13
+ logger.info("[GSD] generator_agent started")
14
+ prompt = get_agent_prompt("generator")
15
+
16
+ # Stub: w realnej wersji wywołuje RAG + LLM i zapisuje do state.generated_sections
17
+ state.generated_sections["opis_projektu"] = "[Wygenerowana sekcja Opis Projektu — wymaga RAG]"
18
+ state.generated_sections["budzet"] = "[Wygenerowana sekcja Budżet — wymaga RAG + walidacji]"
19
+
20
+ return {
21
+ "summary": "Wygenerowano 2 sekcje (stub). W pełnej wersji: pełne RAG + anti-hallucination.",
22
+ "confidence": 0.65,
23
+ "auditor_approved": False,
24
+ "requires_user_confirmation": True,
25
+ "risk_level": "medium",
26
+ }
antigravity_grantforge_swarm/agents/graphrag_msp_agent.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """GraphRAG MSP Agent — głęboka analiza struktury MŚP (stub, w rzeczywistości woła analyze_msp_structure)."""
2
+
3
+ from __future__ import annotations
4
+ from typing import Dict, Any
5
+ import logging
6
+ from state import GrantforgeSwarmState
7
+
8
+ logger = logging.getLogger("grantforge.swarm.agents.graphrag_msp")
9
+
10
+
11
+ def graphrag_msp_agent(state: GrantforgeSwarmState) -> Dict[str, Any]:
12
+ logger.info("[GSD] graphrag_msp_agent (stub)")
13
+ return {"summary": "GraphRAG MSP stub — integruje się z core/graph_rag/sme_verifier", "confidence": 0.85}
antigravity_grantforge_swarm/agents/legal_verifier_agent.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Legal Verifier Agent — pomoc publiczna, DNSH, RODO, formalności (stub GSD)."""
2
+
3
+ from __future__ import annotations
4
+ from typing import Dict, Any
5
+ import logging
6
+ from state import GrantforgeSwarmState
7
+ from prompts.global_rules_prompts import get_agent_prompt
8
+
9
+ logger = logging.getLogger("grantforge.swarm.agents.legal_verifier")
10
+
11
+
12
+ def legal_verifier_agent(state: GrantforgeSwarmState) -> Dict[str, Any]:
13
+ logger.info("[GSD] legal_verifier_agent started")
14
+ prompt = get_agent_prompt("legal_verifier")
15
+
16
+ msp = state.msp_analysis_result or {}
17
+ state.legal_verification_result = {
18
+ "de_minimis_ok": True,
19
+ "dnsh_risks": ["niski wpływ na emisje"],
20
+ "rodo_risk": "niski",
21
+ "formal_completeness": 0.85,
22
+ }
23
+ state.gsd_blackboard["legal_verification_result"] = state.legal_verification_result
24
+
25
+ return {
26
+ "summary": "Weryfikacja prawna zakończona (stub). Pełna wersja użyje legal_retriever + EUR-Lex.",
27
+ "confidence": 0.8,
28
+ "risk_level": "low",
29
+ "requires_user_confirmation": False,
30
+ }
antigravity_grantforge_swarm/agents/rag_ingestion_agent.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """RAG Ingestion Agent — zarządzanie bazą wiedzy regulaminów (stub)."""
2
+
3
+ from __future__ import annotations
4
+ from typing import Dict, Any
5
+ import logging
6
+ from state import GrantforgeSwarmState
7
+
8
+ logger = logging.getLogger("grantforge.swarm.agents.rag_ingestion")
9
+
10
+
11
+ def rag_ingestion_agent(state: GrantforgeSwarmState) -> Dict[str, Any]:
12
+ logger.info("[GSD] rag_ingestion_agent (stub)")
13
+ return {"summary": "Ingestion stub — w pełnej wersji uruchamia refresh_job + change_detector", "confidence": 0.9}
antigravity_grantforge_swarm/agents/validator_agent.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Validator Agent — walidacja pod kryteria oceny naboru (stub)."""
2
+
3
+ from __future__ import annotations
4
+ from typing import Dict, Any
5
+ import logging
6
+ from state import GrantforgeSwarmState
7
+
8
+ logger = logging.getLogger("grantforge.swarm.agents.validator")
9
+
10
+
11
+ def validator_agent(state: GrantforgeSwarmState) -> Dict[str, Any]:
12
+ logger.info("[GSD] validator_agent (stub)")
13
+ return {"summary": "Validator stub — mapuje wniosek na kryteria punktowe naboru", "confidence": 0.8}
antigravity_grantforge_swarm/agents/wizard_clarifier_agent.py ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Wizard Clarifier Agent — Faza 1 GSD
3
+
4
+ Wydobywa prawdziwe cele inwestycyjne i buduje profil projektu.
5
+ Generuje **polskie pytanie zatwierdzające** dla użytkownika.
6
+ """
7
+
8
+ from __future__ import annotations
9
+ from typing import Dict, Any
10
+ import logging
11
+
12
+ from state import GrantforgeSwarmState
13
+ from prompts.global_rules_prompts import get_agent_prompt
14
+
15
+ logger = logging.getLogger("grantforge.swarm.agents.wizard_clarifier")
16
+
17
+
18
+ def wizard_clarifier_agent(state: GrantforgeSwarmState) -> Dict[str, Any]:
19
+ """
20
+ Agent odpowiedzialny za pierwszą fazę — Clarification.
21
+ Zawsze na końcu fazy generuje polskie pytanie zatwierdzające.
22
+ """
23
+ logger.info("[GSD] wizard_clarifier_agent started")
24
+
25
+ prompt = get_agent_prompt("wizard_clarifier")
26
+
27
+ profile = state.profile
28
+ if not profile:
29
+ return {
30
+ "summary": "Brak profilu firmy — wymagane dane od użytkownika (NIP + cele inwestycyjne)",
31
+ "requires_user_confirmation": True,
32
+ "confidence": 0.3,
33
+ "risk_level": "high",
34
+ }
35
+
36
+ # === Wyciągnięcie / symulacja celów inwestycyjnych ===
37
+ investment_goals = state.gsd_blackboard.get("investment_goals", [])
38
+ if not investment_goals:
39
+ investment_goals = ["Modernizacja linii produkcyjnej z elementem B+R i cyfryzacji"]
40
+
41
+ modules = state.gsd_blackboard.get("key_modules", ["B+R", "Wdrożenie innowacji", "Cyfryzacja"])
42
+ budget = state.gsd_blackboard.get("estimated_budget", "8–12 mln PLN")
43
+
44
+ state.gsd_blackboard["investment_goals"] = investment_goals
45
+ state.gsd_blackboard["clarification_completed"] = True
46
+
47
+ # Obsługa zarówno obiektu Pydantic jak i dict (dla demo)
48
+ nip = profile.nip if hasattr(profile, "nip") else profile.get("nip", "brak")
49
+ pkd = profile.pkd_codes if hasattr(profile, "pkd_codes") else profile.get("pkd_codes", [])
50
+
51
+ # === KLUCZOWE: Generujemy polskie pytanie zatwierdzające ===
52
+ goals_text = "\n".join([f"• {g}" for g in investment_goals])
53
+ modules_text = ", ".join(modules)
54
+
55
+ state.gsd_blackboard["pending_hitl_template"] = "clarification_profile"
56
+ state.gsd_blackboard["pending_hitl_kwargs"] = {
57
+ "goals": goals_text,
58
+ "modules": modules_text,
59
+ "budget": budget,
60
+ "context_summary": f"NIP: {nip}, branża: {', '.join(pkd[:3]) if pkd else 'nieznana'}",
61
+ }
62
+
63
+ logger.info("[GSD] Wizard Clarifier przygotował polskie pytanie zatwierdzające profilu")
64
+
65
+ return {
66
+ "summary": f"Wyjaśniono cele inwestycyjne dla NIP {profile.nip}. Cele: {investment_goals[0][:70]}...",
67
+ "requires_user_confirmation": True,
68
+ "confidence": 0.78,
69
+ "grounding_sources": ["KRS + dane użytkownika + analiza potrzeb"],
70
+ "risk_level": "low",
71
+ "hitl_template": "clarification_profile",
72
+ "hitl_kwargs": state.gsd_blackboard["pending_hitl_kwargs"],
73
+ }
antigravity_grantforge_swarm/config.py ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Grantforge GSD Configuration
3
+
4
+ Centralne miejsce na konfigurację roju, modele, retrievery, limity itp.
5
+ """
6
+
7
+ from __future__ import annotations
8
+ from typing import Dict, Any
9
+ import os
10
+
11
+ # =============================================================================
12
+ # MODEL ROUTING (zgodny z istniejącym llm_router)
13
+ # =============================================================================
14
+
15
+ MODEL_CONFIG: Dict[str, str] = {
16
+ "default": "grok-3", # lub claude-3.5 / gpt-4o
17
+ "fast": "grok-3-fast",
18
+ "reasoning": "claude-3-5-sonnet-20241022",
19
+ "embedding": "voyage-large-2-instruct",
20
+ "reranker": "voyage-rerank-2",
21
+ }
22
+
23
+ # =============================================================================
24
+ # RETRIEVER & VECTOR SETTINGS
25
+ # =============================================================================
26
+
27
+ RAG_CONFIG = {
28
+ "default_k": 8,
29
+ "bm25_k": 12,
30
+ "rerank_top_k": 6,
31
+ "namespace_strategy": "tenant", # lub "global" / "program"
32
+ "use_graphrag": True,
33
+ "graphrag_depth": 2,
34
+ }
35
+
36
+ # =============================================================================
37
+ # GSD PROCESS LIMITS (Konstytucja)
38
+ # =============================================================================
39
+
40
+ GSD_LIMITS = {
41
+ "max_critic_iterations": 4,
42
+ "max_autofix_attempts": 3,
43
+ "require_hitl_on_legal_risk": ["high", "medium"],
44
+ "min_grounding_score_for_export": 75.0,
45
+ }
46
+
47
+ # =============================================================================
48
+ # INSTITUTIONS & DATA SOURCES
49
+ # =============================================================================
50
+
51
+ INSTITUTIONS = ["NCBR", "PARP", "ARiMR", "BGK", "Województwa", "FENG"]
52
+
53
+ # =============================================================================
54
+ # PATHS
55
+ # =============================================================================
56
+
57
+ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
58
+ PROMPTS_DIR = os.path.join(BASE_DIR, "prompts")
59
+ TOOLS_DIR = os.path.join(BASE_DIR, "tools")
60
+ AGENTS_DIR = os.path.join(BASE_DIR, "agents")
61
+
62
+
63
+ def get_config() -> Dict[str, Any]:
64
+ return {
65
+ "models": MODEL_CONFIG,
66
+ "rag": RAG_CONFIG,
67
+ "gsd_limits": GSD_LIMITS,
68
+ "institutions": INSTITUTIONS,
69
+ }
antigravity_grantforge_swarm/main.py ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Grantforge GSD — Main Entry Point (wersja demonstracyjna z polskimi pytaniami HitL)
3
+
4
+ Uruchamia pełny rój Grantforge Spec-Driven Development.
5
+ Po każdej fazie, która wymaga zatwierdzenia — pokazuje pytanie po polsku.
6
+ """
7
+
8
+ import argparse
9
+ import logging
10
+ from orchestrator import GrantforgeOrchestrator
11
+ from state import create_initial_gsd_state
12
+ from agents.wizard_clarifier_agent import wizard_clarifier_agent
13
+ from agents.advanced_matcher_agent import advanced_matcher_agent
14
+ from agents.generator_agent import generator_agent
15
+ from agents.auditor_agent import auditor_agent
16
+ from agents.legal_verifier_agent import legal_verifier_agent
17
+ from agents.exporter_agent import exporter_agent
18
+ from agents.rag_ingestion_agent import rag_ingestion_agent
19
+ from agents.autofix_agent import autofix_agent
20
+ from agents.validator_agent import validator_agent
21
+ from agents.graphrag_msp_agent import graphrag_msp_agent
22
+
23
+ logging.basicConfig(level=logging.INFO, format="[%(levelname)s] %(name)s: %(message)s")
24
+ logger = logging.getLogger("grantforge.swarm.main")
25
+
26
+
27
+ def register_all_agents(orchestrator: GrantforgeOrchestrator):
28
+ """Rejestruje wszystkie agenty GSD w orchestratorze."""
29
+ orchestrator.register_agent("wizard_clarifier_agent", wizard_clarifier_agent)
30
+ orchestrator.register_agent("advanced_matcher_agent", advanced_matcher_agent)
31
+ orchestrator.register_agent("rag_ingestion_agent", rag_ingestion_agent)
32
+ orchestrator.register_agent("graphrag_msp_agent", graphrag_msp_agent)
33
+ orchestrator.register_agent("generator_agent", generator_agent)
34
+ orchestrator.register_agent("auditor_agent", auditor_agent)
35
+ orchestrator.register_agent("autofix_agent", autofix_agent)
36
+ orchestrator.register_agent("legal_verifier_agent", legal_verifier_agent)
37
+ orchestrator.register_agent("validator_agent", validator_agent)
38
+ orchestrator.register_agent("exporter_agent", exporter_agent)
39
+ logger.info("All 10 GSD agents registered.")
40
+
41
+
42
+ def main():
43
+ parser = argparse.ArgumentParser(description="Grantforge GSD Swarm Runner (z polskimi pytaniami zatwierdzającymi)")
44
+ parser.add_argument("--user-id", required=True, help="ID użytkownika")
45
+ parser.add_argument("--nip", default="5260000000", help="NIP firmy")
46
+ parser.add_argument("--project-id", help="ID projektu (opcjonalne)")
47
+ parser.add_argument("--interactive-hitl", action="store_true",
48
+ help="Interaktywny tryb — po każdym pytaniu czeka na odpowiedź użytkownika")
49
+ args = parser.parse_args()
50
+
51
+ # Inicjalizacja stanu
52
+ state = create_initial_gsd_state(
53
+ user_id=args.user_id,
54
+ project_id=args.project_id,
55
+ )
56
+ state.profile = {"nip": args.nip, "pkd_codes": ["25.11.Z"], "voivodeship": "mazowieckie", "size": "średnia"}
57
+
58
+ orchestrator = GrantforgeOrchestrator(state=state)
59
+ register_all_agents(orchestrator)
60
+
61
+ logger.info("=== URUCHAMIANIE GRANTFORGE GSD SWARM (z polskimi pytaniami HitL) ===\n")
62
+
63
+ # Uruchamiamy fazy ręcznie, żeby móc pokazywać pytania po polsku
64
+ phases = ["clarification", "matching", "generation", "legal_compliance", "export"]
65
+
66
+ for phase in phases:
67
+ print(f"\n{'─'*70}")
68
+ print(f"FAZA: {phase.upper()}")
69
+ print(f"{'─'*70}")
70
+
71
+ orchestrator.run_phase(phase)
72
+
73
+ # === KLUCZ: jeśli jest pytanie zatwierdzające — pokazujemy je po polsku ===
74
+ if orchestrator.has_pending_hitl():
75
+ question_text = orchestrator.get_pending_question_text()
76
+ if question_text:
77
+ print(question_text)
78
+
79
+ if args.interactive_hitl:
80
+ print("\nTwoja odpowiedź (wpisz numer lub tekst): ", end="")
81
+ try:
82
+ user_input = input().strip()
83
+ except EOFError:
84
+ user_input = "1" # domyślna odpowiedź w trybie nieinteraktywnym
85
+
86
+ # Symulacja decyzji
87
+ hitl = orchestrator.state.pending_hitl_question
88
+ if hitl:
89
+ orchestrator.apply_human_decision(hitl.id, user_input, "Decyzja użytkownika z CLI")
90
+ print(f"\n[Zapisano decyzję użytkownika: {user_input}]\n")
91
+ else:
92
+ # Tryb demonstracyjny — automatycznie akceptujemy domyślną odpowiedź
93
+ hitl = orchestrator.state.pending_hitl_question
94
+ if hitl:
95
+ default = hitl.default_option or (hitl.options[0] if hitl.options else "Tak")
96
+ orchestrator.apply_human_decision(hitl.id, default, "Automatyczna akceptacja (tryb demo)")
97
+ print(f"\n[Automatycznie zaakceptowano: {default}]\n")
98
+
99
+ # Podsumowanie
100
+ final = orchestrator.state
101
+ print("\n" + "="*70)
102
+ print("PODSUMOWANIE PROCESU GSD")
103
+ print("="*70)
104
+ print(f"Faza końcowa: {final.gsd_phase}")
105
+ print(f"Compliant: {final.is_gsd_compliant}")
106
+ print(f"Liczba wpisów w łańcuchu audytu: {len(final.audit_trail)}")
107
+ print(f"Liczba pytań zatwierdzających: {len(final.resolved_hitl_questions)}")
108
+ if final.grounding_certificate:
109
+ print(f"Świadectwo Zgodności: {final.grounding_certificate.get('certificate_id')}")
110
+ print("="*70 + "\n")
111
+
112
+
113
+ def register_all_agents(orchestrator: GrantforgeOrchestrator):
114
+ """Rejestruje wszystkie agenty GSD."""
115
+ orchestrator.register_agent("wizard_clarifier_agent", wizard_clarifier_agent)
116
+ orchestrator.register_agent("advanced_matcher_agent", advanced_matcher_agent)
117
+ orchestrator.register_agent("rag_ingestion_agent", rag_ingestion_agent)
118
+ orchestrator.register_agent("graphrag_msp_agent", graphrag_msp_agent)
119
+ orchestrator.register_agent("generator_agent", generator_agent)
120
+ orchestrator.register_agent("auditor_agent", auditor_agent)
121
+ orchestrator.register_agent("autofix_agent", autofix_agent)
122
+ orchestrator.register_agent("legal_verifier_agent", legal_verifier_agent)
123
+ orchestrator.register_agent("validator_agent", validator_agent)
124
+ orchestrator.register_agent("exporter_agent", exporter_agent)
125
+ logger.info("Zarejestrowano 10 agentów GSD.")
126
+
127
+
128
+ if __name__ == "__main__":
129
+ main()
antigravity_grantforge_swarm/orchestrator.py ADDED
@@ -0,0 +1,417 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Grantforge GSD Orchestrator — Główny Supervisor Metodyki Spec-Driven
3
+
4
+ To jest "mózg" roju. Odpowiada za:
5
+ - Zarządzanie fazami GSD
6
+ - Routing między specjalistycznymi agentami
7
+ - Egzekwowanie Konstytucji i zasad SWARM
8
+ - Human-in-the-Loop
9
+ - Budowanie nieusuwalnego łańcucha audytu
10
+ """
11
+
12
+ from __future__ import annotations
13
+ from typing import Dict, Any, Optional, Callable, List
14
+ import logging
15
+ from datetime import datetime
16
+
17
+ from state import (
18
+ GrantforgeSwarmState,
19
+ GSDPhase,
20
+ create_initial_gsd_state,
21
+ transition_to_phase,
22
+ add_audit_entry,
23
+ PolishHitlQuestion,
24
+ )
25
+ from prompts.global_rules_prompts import get_constitution_only
26
+
27
+ logger = logging.getLogger("grantforge.swarm.orchestrator")
28
+
29
+
30
+ class GrantforgeOrchestrator:
31
+ """
32
+ Główny Orchestrator GSD dla Grantforge.
33
+
34
+ Działa jako "konstytucyjny supervisor" — nie wykonuje pracy merytorycznej,
35
+ tylko koordynuje agentów, egzekwuje reguły i dba o traceability.
36
+ """
37
+
38
+ def __init__(self, state: Optional[GrantforgeSwarmState] = None):
39
+ self.state = state or create_initial_gsd_state(user_id="system")
40
+ self.constitution = get_constitution_only()
41
+ self.agents: Dict[str, Callable] = {} # dynamic registration
42
+
43
+ # -------------------------------------------------------------------------
44
+ # AGENT REGISTRATION
45
+ # -------------------------------------------------------------------------
46
+
47
+ def register_agent(self, name: str, handler: Callable[[GrantforgeSwarmState], Dict[str, Any]]):
48
+ """Rejestruje handler agenta GSD."""
49
+ self.agents[name] = handler
50
+ logger.info(f"[Orchestrator] Registered GSD agent: {name}")
51
+
52
+ # -------------------------------------------------------------------------
53
+ # PHASE MANAGEMENT
54
+ # -------------------------------------------------------------------------
55
+
56
+ def run_phase(self, phase: GSDPhase, force: bool = False) -> GrantforgeSwarmState:
57
+ """Uruchamia jedną fazę GSD z pełnym egzekwowaniem reguł."""
58
+ logger.info(f"[GSD] Starting phase: {phase}")
59
+
60
+ # 1. Walidacja konstytucyjna (zawsze na wejściu do fazy)
61
+ self._enforce_constitution(phase)
62
+
63
+ # 2. Wybór agenta dla fazy
64
+ agent_name = self._get_agent_for_phase(phase)
65
+ if agent_name not in self.agents:
66
+ logger.error(f"Agent {agent_name} not registered!")
67
+ return self._fail_phase(phase, f"Agent {agent_name} not found")
68
+
69
+ # 3. Wykonanie agenta
70
+ try:
71
+ handler = self.agents[agent_name]
72
+ result = handler(self.state)
73
+ except Exception as e:
74
+ logger.exception(f"Agent {agent_name} failed in phase {phase}")
75
+ return self._fail_phase(phase, str(e))
76
+
77
+ # 4. Zapis do audytu
78
+ add_audit_entry(
79
+ self.state,
80
+ phase=phase,
81
+ agent=agent_name,
82
+ action=f"Executed phase {phase}",
83
+ decision=result.get("summary", "No summary"),
84
+ confidence=result.get("confidence", 0.7),
85
+ grounding_sources=result.get("grounding_sources", []),
86
+ risk_level=result.get("risk_level", "medium"),
87
+ )
88
+
89
+ # 5. Decyzja o przejściu do następnej fazy
90
+ if result.get("requires_user_confirmation"):
91
+ self.state = transition_to_phase(
92
+ self.state,
93
+ new_phase=phase,
94
+ agent=agent_name,
95
+ summary=result["summary"],
96
+ status="needs_human",
97
+ requires_user_confirmation=True,
98
+ )
99
+ logger.warning(f"[GSD] Phase {phase} requires Human-in-the-Loop confirmation")
100
+ else:
101
+ next_phase = self._determine_next_phase(phase, result)
102
+ self.state = transition_to_phase(
103
+ self.state,
104
+ new_phase=next_phase,
105
+ agent=agent_name,
106
+ summary=result["summary"],
107
+ status="success",
108
+ )
109
+ logger.info(f"[GSD] Phase {phase} completed → moving to {next_phase}")
110
+
111
+ return self.state
112
+
113
+ def run_full_swarm(self, max_phases: int = 12) -> GrantforgeSwarmState:
114
+ """Uruchamia pełny proces GSD od clarification do export."""
115
+ logger.info("[GSD] Starting FULL SWARM execution")
116
+
117
+ phases_order: list[GSDPhase] = [
118
+ "clarification",
119
+ "matching",
120
+ "generation",
121
+ "legal_compliance",
122
+ "validation",
123
+ "export",
124
+ ]
125
+
126
+ current = self.state.gsd_phase
127
+ executed = 0
128
+
129
+ while current != "completed" and executed < max_phases:
130
+ if current in phases_order:
131
+ self.run_phase(current)
132
+ executed += 1
133
+ current = self.state.gsd_phase
134
+ else:
135
+ logger.warning(f"Unknown phase {current}, stopping")
136
+ break
137
+
138
+ # Final validation
139
+ if self.state.gsd_phase == "export":
140
+ self.state.is_gsd_compliant = self._final_constitution_check()
141
+
142
+ logger.info(f"[GSD] Full swarm finished after {executed} phases. Compliant: {self.state.is_gsd_compliant}")
143
+ return self.state
144
+
145
+ # -------------------------------------------------------------------------
146
+ # INTERNAL RULES ENFORCEMENT
147
+ # -------------------------------------------------------------------------
148
+
149
+ def _enforce_constitution(self, phase: GSDPhase):
150
+ """Sprawdza czy faza może się rozpocząć zgodnie z Konstytucją."""
151
+ bb = self.state.gsd_blackboard
152
+
153
+ if phase == "generation" and not bb.get("selected_grant"):
154
+ raise ValueError("Constitution violation: Cannot enter 'generation' without selected_grant")
155
+
156
+ if phase == "legal_compliance" and not bb.get("msp_status"):
157
+ logger.warning("Constitution warning: Entering legal_compliance without MSP analysis")
158
+
159
+ if phase == "export" and self.state.current_critic_iteration < 1:
160
+ raise ValueError("Constitution violation: Export requires at least one full Auditor cycle")
161
+
162
+ def _final_constitution_check(self) -> bool:
163
+ """Sprawdza czy cały proces przeszedł pełną weryfikację konstytucyjną."""
164
+ required = ["msp_analysis", "auditor_report", "legal_verification_result"]
165
+ bb = self.state.gsd_blackboard
166
+
167
+ has_all = all(bb.get(r) for r in required)
168
+ no_blocking = not self.state.has_blocking_legal_issues
169
+ audit_complete = len(self.state.audit_trail) >= 5
170
+
171
+ return has_all and no_blocking and audit_complete
172
+
173
+ # -------------------------------------------------------------------------
174
+ # ROUTING LOGIC
175
+ # -------------------------------------------------------------------------
176
+
177
+ def _get_agent_for_phase(self, phase: GSDPhase) -> str:
178
+ mapping = {
179
+ "clarification": "wizard_clarifier_agent",
180
+ "matching": "advanced_matcher_agent",
181
+ "ingestion": "rag_ingestion_agent",
182
+ "generation": "generator_agent",
183
+ "legal_compliance": "legal_verifier_agent",
184
+ "validation": "validator_agent",
185
+ "export": "exporter_agent",
186
+ }
187
+ return mapping.get(phase, "orchestrator")
188
+
189
+ def _determine_next_phase(self, current: GSDPhase, result: Dict[str, Any]) -> GSDPhase:
190
+ """Logika przejścia między fazami (może być rozszerzona o blackboard)."""
191
+ if result.get("status") == "failed":
192
+ return "error"
193
+
194
+ if current == "clarification":
195
+ return "matching"
196
+ if current == "matching":
197
+ return "generation"
198
+ if current == "generation":
199
+ # Po generacji zawsze audyt (nawet jeśli nie ma dedykowanej fazy "auditor")
200
+ if not result.get("auditor_approved"):
201
+ return "generation" # iteracja
202
+ return "legal_compliance"
203
+ if current == "legal_compliance":
204
+ return "validation"
205
+ if current == "validation":
206
+ return "export"
207
+ if current == "export":
208
+ return "completed"
209
+
210
+ return "completed"
211
+
212
+ def _fail_phase(self, phase: GSDPhase, reason: str) -> GrantforgeSwarmState:
213
+ self.state = transition_to_phase(
214
+ self.state,
215
+ new_phase="error",
216
+ agent="orchestrator",
217
+ summary=f"Phase {phase} failed: {reason}",
218
+ status="failed",
219
+ )
220
+ return self.state
221
+
222
+ # -------------------------------------------------------------------------
223
+ # HUMAN-IN-THE-LOOP — POLSKIE PYTANIA ZATWIERDZAJĄCE (GSD)
224
+ # -------------------------------------------------------------------------
225
+
226
+ # Szablony polskich pytań zatwierdzających (zgodnie z Konstytucją)
227
+ HITL_TEMPLATES: Dict[str, Dict[str, Any]] = {
228
+ "clarification_profile": {
229
+ "title": "Potwierdzenie profilu inwestycyjnego",
230
+ "question": "Czy poniższe zrozumienie celów inwestycyjnych firmy jest poprawne?\n\n"
231
+ "Główne cele projektu:\n{goals}\n\n"
232
+ "Kluczowe moduły: {modules}\n"
233
+ "Oczekiwany budżet: {budget}\n\n"
234
+ "Jeśli coś wymaga korekty — proszę wskazać co zmienić.",
235
+ "options": [
236
+ "Tak, wszystko się zgadza — kontynuuj",
237
+ "Nie, popraw cel główny",
238
+ "Nie, dodaj / usuń moduły",
239
+ "Inne (wpisz komentarz)",
240
+ ],
241
+ "default_option": "Tak, wszystko się zgadza — kontynuuj",
242
+ "requires_comment": False,
243
+ },
244
+ "matching_program_choice": {
245
+ "title": "Wybór programu dotacyjnego",
246
+ "question": "Czy akceptujesz poniższą rekomendację jako program główny?\n\n"
247
+ "NAJLEPSZY MATCH: {best_program}\n"
248
+ "Dopasowanie: {score}%\n"
249
+ "Uzasadnienie: {reason}\n\n"
250
+ "Alternatywy warte rozważenia:\n{alts}\n\n"
251
+ "Jeśli chcesz inny program — wpisz jego nazwę lub numer.",
252
+ "options": [
253
+ "Tak, wybieram ten program jako główny",
254
+ "Chcę rozważyć alternatywę nr 2",
255
+ "Chcę rozważyć alternatywę nr 3",
256
+ "Chcę inny program (wpisz nazwę)",
257
+ ],
258
+ "default_option": "Tak, wybieram ten program jako główny",
259
+ "requires_comment": False,
260
+ },
261
+ "auditor_report": {
262
+ "title": "Wynik audytu holistycznego wniosku",
263
+ "question": "Audyt holistyczny zakończony. Ogólna ocena: {score}/100\n\n"
264
+ "Główne uwagi Audytora:\n{issues}\n\n"
265
+ "Czy akceptujesz raport i chcesz przejść do poprawek (lub do weryfikacji prawnej)?\n"
266
+ "Czy wolisz najpierw skorygować wskazane obszary?",
267
+ "options": [
268
+ "Akceptuję raport — przejdź do poprawek / weryfikacji prawnej",
269
+ "Chcę najpierw skorygować obszary oznaczone jako medium/high",
270
+ "Chcę dodatkowe wyjaśnienie od Audytora",
271
+ ],
272
+ "default_option": "Akceptuję raport — przejdź do poprawek / weryfikacji prawnej",
273
+ "requires_comment": True,
274
+ },
275
+ "msp_status": {
276
+ "title": "Weryfikacja statusu MŚP / MSP",
277
+ "question": "System przeprowadził analizę struktury własności firmy.\n\n"
278
+ "Wynik: {msp_result}\n"
279
+ "Pewność: {confidence}%\n\n"
280
+ "Czy potwierdzasz ten status MŚP? (ma to kluczowe znaczenie dla kwalifikowalności w wielu programach)",
281
+ "options": [
282
+ "Tak, status MŚP jest prawidłowy",
283
+ "Nie, firma ma inne powiązania — proszę o korektę",
284
+ "Nie jestem pewien — chcę zobaczyć szczegółową analizę powiązań",
285
+ ],
286
+ "default_option": "Tak, status MŚP jest prawidłowy",
287
+ "requires_comment": True,
288
+ },
289
+ "export_final": {
290
+ "title": "Zatwierdzenie do eksportu — Świadectwo Zgodności",
291
+ "question": "Wniosek przeszedł pełny proces GSD (audyt + weryfikacja prawna).\n\n"
292
+ "Czy zatwierdzasz wygenerowanie finalnego pakietu dokumentów wraz ze Świadectwem Zgodności Grantforge?\n\n"
293
+ "Po zatwierdzeniu dokument będzie gotowy do pobrania (DOCX + PDF + MD).",
294
+ "options": [
295
+ "Tak, zatwierdzam — wygeneruj finalny pakiet",
296
+ "Nie, chcę jeszcze raz przejrzeć cały wniosek",
297
+ "Nie, chcę wprowadzić ostatnie poprawki",
298
+ ],
299
+ "default_option": "Tak, zatwierdzam — wygeneruj finalny pakiet",
300
+ "requires_comment": False,
301
+ },
302
+ }
303
+
304
+ def create_polish_hitl_question(
305
+ self,
306
+ template_key: str,
307
+ phase: GSDPhase,
308
+ agent: str,
309
+ format_kwargs: Dict[str, Any],
310
+ risk_level: str = "medium",
311
+ ) -> PolishHitlQuestion:
312
+ """Tworzy gotowe polskie pytanie zatwierdzające na podstawie szablonu."""
313
+ tpl = self.HITL_TEMPLATES.get(template_key, self.HITL_TEMPLATES["clarification_profile"])
314
+
315
+ question_text = tpl["question"].format(**format_kwargs)
316
+
317
+ hitl = PolishHitlQuestion(
318
+ phase=phase,
319
+ agent=agent,
320
+ title=tpl["title"],
321
+ question=question_text,
322
+ context_summary=format_kwargs.get("context_summary", "Brak dodatkowego kontekstu"),
323
+ options=tpl.get("options", []),
324
+ default_option=tpl.get("default_option"),
325
+ risk_level=risk_level, # type: ignore
326
+ requires_comment=tpl.get("requires_comment", False),
327
+ )
328
+ self.state.pending_hitl_question = hitl
329
+ return hitl
330
+
331
+ def get_pending_question_text(self) -> Optional[str]:
332
+ """Zwraca sformatowane polskie pytanie do wyświetlenia użytkownikowi."""
333
+ if self.state.pending_hitl_question:
334
+ return self.state.pending_hitl_question.to_display()
335
+ return None
336
+
337
+ def request_human_confirmation(
338
+ self,
339
+ template_key: str,
340
+ format_kwargs: Dict[str, Any],
341
+ risk_level: str = "medium",
342
+ ) -> PolishHitlQuestion:
343
+ """
344
+ Nowa wersja — tworzy i rejestruje polskie pytanie zatwierdzające.
345
+ Zwraca obiekt PolishHitlQuestion (łatwy do wyświetlenia i serializacji).
346
+ """
347
+ agent = self.state.current_gsd_agent or "orchestrator"
348
+ hitl = self.create_polish_hitl_question(
349
+ template_key=template_key,
350
+ phase=self.state.gsd_phase,
351
+ agent=agent,
352
+ format_kwargs=format_kwargs,
353
+ risk_level=risk_level,
354
+ )
355
+
356
+ # Zachowujemy kompatybilność ze starym mechanizmem
357
+ self.state.gsd_blackboard["pending_human_confirmation"] = {
358
+ "id": hitl.id,
359
+ "phase": hitl.phase,
360
+ "title": hitl.title,
361
+ "question": hitl.question,
362
+ "status": "pending",
363
+ }
364
+
365
+ logger.warning(f"[GSD] POLSKIE PYTANIE ZATWIERDZAJĄCE — {hitl.title}")
366
+ return hitl
367
+
368
+ def apply_human_decision(
369
+ self,
370
+ hitl_id: str,
371
+ decision: str,
372
+ comment: str = "",
373
+ ) -> bool:
374
+ """
375
+ Stosuje decyzję użytkownika (po polsku) i wznawia proces.
376
+ Zwraca True jeśli decyzja została poprawnie zapisana.
377
+ """
378
+ hitl = self.state.pending_hitl_question
379
+ if not hitl or hitl.id != hitl_id:
380
+ # Szukamy w resolved (na wypadek ponownego wywołania)
381
+ for h in self.state.resolved_hitl_questions:
382
+ if h.id == hitl_id:
383
+ hitl = h
384
+ break
385
+ if not hitl:
386
+ logger.error(f"Nie znaleziono pytania HitL o ID {hitl_id}")
387
+ return False
388
+
389
+ hitl.user_decision = decision
390
+ hitl.user_comment = comment
391
+ hitl.resolved = True
392
+
393
+ # Przenosimy do listy rozwiązanych
394
+ if hitl in (self.state.pending_hitl_question,):
395
+ self.state.resolved_hitl_questions.append(hitl)
396
+ self.state.pending_hitl_question = None
397
+
398
+ self.state.gsd_blackboard.pop("pending_human_confirmation", None)
399
+
400
+ # Dodajemy wpis do audytu
401
+ add_audit_entry(
402
+ self.state,
403
+ phase=hitl.phase,
404
+ agent="human_user",
405
+ action=f"Odpowiedź na pytanie: {hitl.title}",
406
+ decision=decision,
407
+ confidence=1.0,
408
+ grounding_sources=["Decyzja użytkownika"],
409
+ risk_level=hitl.risk_level,
410
+ )
411
+
412
+ logger.info(f"[GSD] Decyzja użytkownika zapisana: {decision[:80]}...")
413
+ return True
414
+
415
+ def has_pending_hitl(self) -> bool:
416
+ """Sprawdza czy jest oczekujące pytanie zatwierdzające."""
417
+ return self.state.pending_hitl_question is not None and not self.state.pending_hitl_question.resolved
antigravity_grantforge_swarm/prompts/global_rules_prompts.py ADDED
@@ -0,0 +1,226 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Globalne Reguły Promptów — Konstytucja Grantforge GSD
3
+
4
+ Ten moduł zawiera wszystkie prompty "konstytucyjne", które są wstrzykiwane
5
+ do każdego agenta w roju. Zapewniają one spójność, anty-halucynację i traceability.
6
+ """
7
+
8
+ from __future__ import annotations
9
+ from typing import Dict, Any, List
10
+
11
+ # =============================================================================
12
+ # 1. NACZELNA KONSTYTUCJA (wstrzykiwana do WSZYSTKICH agentów)
13
+ # =============================================================================
14
+
15
+ GRANTFORGE_CONSTITUTION = """
16
+ Jesteś agentem systemu **Grantforge AI** — najbardziej rzetelnego narzędzia do generowania wniosków dotacyjnych w Polsce (2026).
17
+
18
+ **TWOJA KONSTYTUCJA (OBOWIĄZUJĄCA):**
19
+
20
+ 1. **PRAWDA REGULAMINOWA** — Nigdy nie halucynujesz zapisów regulaminu. Jeśli nie masz pewności — piszesz "wymaga weryfikacji z instytucją" lub oznaczasz jako ryzyko.
21
+
22
+ 2. **TRACEABILITY** — Każde twierdzenie musi być ugruntowane. W odpowiedzi zawsze podajesz źródło (np. "FENG.01.01 § 5.3 pkt 2", "Regulamin PARP z 12.03.2026").
23
+
24
+ 3. **SPÓJNOŚĆ KRZYŻOWA** — Budżet, harmonogram, zadania i wskaźniki muszą być ze sobą logicznie spójne. Zawsze sprawdzasz te relacje.
25
+
26
+ 4. **ANTI-OVERPROMISING** — Nie obiecujesz wyników, których firma nie jest w stanie realnie osiągnąć. Wolisz wersję konserwatywną, ale wiarygodną.
27
+
28
+ 5. **STATUS MŚP** — Nie zgadujesz. Jeśli nie masz pełnej analizy struktury własności — oznaczasz jako "ryzyko MSP" i wymagasz potwierdzenia.
29
+
30
+ 6. **DNSH I ZIELONA TRANSFORMACJA** — Każdą inwestycję oceniasz pod kątem Do No Significant Harm. Jeśli widzisz negatywny wpływ — proponujesz rozwiązania łagodzące.
31
+
32
+ 7. **POMOC PUBLICZNA** — Zawsze weryfikujesz de minimis, kumulację i ryzyko przekroczenia limitów.
33
+
34
+ 8. **ZERO UKRYTYCH ZAŁOŻEŃ** — Jawnie zapisujesz wszystkie założenia i ryzyka.
35
+
36
+ 9. **JĘZYK POLSKI** — Odpowiadasz zawsze po polsku, profesjonalnym, ale zrozumiałym językiem dla przedsiębiorcy.
37
+
38
+ 10. **HUMAN-IN-THE-LOOP** — Jeśli decyzja ma wpływ na bilans, status MŚP, ryzyko prawne lub wysoką kwotę — wymagasz potwierdzenia użytkownika.
39
+
40
+ **ZASADA NR 0 (NAJWAŻNIEJSZA):**
41
+ > "Lepszy wniosek niekompletny i uczciwy niż wniosek piękny, ale niezgodny z regulaminem i później skontrolowany."
42
+ """
43
+
44
+ # =============================================================================
45
+ # 2. ANTI-HALLUCINATION GUARD (dla generatora i audytora)
46
+ # =============================================================================
47
+
48
+ ANTI_HALLUCINATION_GUARD = """
49
+ **STRICT ANTI-HALLUCINATION PROTOKOŁ (OBOWIĄZKOWY):**
50
+
51
+ Zanim wygenerujesz JAKIEKOLWIEK zdanie dotyczące:
52
+ - kwalifikowalności kosztów
53
+ - kryteriów oceny
54
+ - terminów naboru
55
+ - wymogów formalnych
56
+ - zasad DNSH / pomocy publicznej
57
+
58
+ **MUSISZ** najpierw wykonać następujące kroki:
59
+
60
+ 1. Wywołać odpowiedni retriever (hybrid_retriever + legal_retriever + graph_rag).
61
+ 2. Znaleźć **konkretny fragment** regulaminu lub wytycznych.
62
+ 3. W odpowiedzi jawnie podać źródło w formacie:
63
+ `[ŹRÓDŁO: Nazwa regulaminu, data, §/punkt]`
64
+
65
+ Jeśli nie znajdziesz jednoznacznego źródła — napisz wprost:
66
+ "Na podstawie aktualnej wiedzy nie znaleziono bezpośredniego zapisu regulaminu potwierdzającego tę interpretację. Rekomenduję weryfikację z [instytucja]. Ryzyko: średnie/wysokie."
67
+
68
+ **ZAKAZANE:**
69
+ - Pisanie "zazwyczaj można", "w praktyce się udaje", "większość firm dostaje" bez pokrycia w regulaminie.
70
+ - Zakładanie, że "jeśli coś było w poprzednim naborze, to będzie i teraz".
71
+ - Używanie danych z pamięci modelu zamiast z RAG.
72
+
73
+ **NAGRODA:** Za każde zdanie z wyraźnym źródłem + datą otrzymujesz +10 punktów do oceny jakości.
74
+ """
75
+
76
+ # =============================================================================
77
+ # 3. PROMPTY SPECJALISTYCZNE DLA POSZCZEGÓLNYCH AGENTÓW
78
+ # =============================================================================
79
+
80
+ AGENT_PROMPTS: Dict[str, str] = {
81
+
82
+ "wizard_clarifier": """
83
+ Jesteś **Wizard Clarifier** — ekspertem od wydobywania prawdziwych potrzeb inwestycyjnych polskich firm.
84
+
85
+ Twoim zadaniem jest przeprowadzenie rozmowy (lub analizy danych), która pozwoli zrozumieć:
86
+ - Jaki jest **główny problem biznesowy** firmy, który ma rozwiązać projekt?
87
+ - Jakie są **trzy najważniejsze cele** projektu (mierzalne)?
88
+ - Które elementy (B+R, wdrożenie, cyfryzacja, zazielenienie, internacjonalizacja, BHP, efektywność energetyczna) są kluczowe?
89
+ - Jakie są ograniczenia czasowe i finansowe firmy?
90
+
91
+ Zawsze pytaj o konkrety. Unikaj ogólników typu "chcemy się rozwijać".
92
+
93
+ Na koniec każdej interakcji podsumuj w formie:
94
+ **Profil Inwestycyjny (do potwierdzenia przez użytkownika):**
95
+ - Główny cel: ...
96
+ - Kluczowe moduły: ...
97
+ - Oczekiwany budżet: ...
98
+ - Ryzyka / ograniczenia: ...
99
+ """,
100
+
101
+ "advanced_matcher": """
102
+ Jesteś **Advanced Matcher** — najlepszym analitykiem programów dotacyjnych w Polsce.
103
+
104
+ Dla danego profilu firmy i celu inwestycyjnego:
105
+
106
+ 1. Znajdź **najlepszy program** (najwyższy % match + uzasadnienie).
107
+ 2. Zawsze dodaj sekcję **"Inne warte rozważenia"** (3–5 programów z niższym, ale sensownym dopasowaniem).
108
+ 3. Dla każdego programu podaj:
109
+ - % match (z wyjaśnieniem)
110
+ - Kluczowe kryteria, które firma spełnia / nie spełnia
111
+ - Poziom konkurencji (jeśli znany)
112
+ - Terminy naboru (aktualne)
113
+ - Szacowana szansa przy dobrze przygotowanym wniosku
114
+
115
+ Używaj GraphRAG MSP Analyzer przed ostateczną rekomendacją.
116
+ """,
117
+
118
+ "generator": """
119
+ Jesteś **Generator** — specjalistą od pisania sekcji wniosków dotacyjnych z twardym ugruntowaniem.
120
+
121
+ Zasady generowania każdej sekcji:
122
+ - Najpierw pobierz relevantne fragmenty regulaminu (RAG).
123
+ - Zawsze cytuj źródło w nawiasie lub w przypisie.
124
+ - Pisz językiem zrozumiałym dla przedsiębiorcy, ale profesjonalnym.
125
+ - Oznaczaj fragmenty wymagające danych od użytkownika jako [DO UZUPEŁNIENIA: ...].
126
+ - Sprawdzaj spójność z już wygenerowanymi sekcjami (budżet, harmonogram, wskaźniki).
127
+
128
+ Nigdy nie wymyślaj liczb ani faktów. Jeśli nie masz danych — pytaj.
129
+ """,
130
+
131
+ "auditor": """
132
+ Jesteś **Auditor** (Holistic Critic) — najsurowszym recenzentem wniosków dotacyjnych w systemie.
133
+
134
+ Twoim zadaniem jest **znalezienie wszystkich problemów** zanim zrobi to oceniający z instytucji.
135
+
136
+ Sprawdzasz minimum:
137
+ 1. Spójność krzyżowa (budżet ↔ harmonogram ↔ zadania ↔ wskaźniki ↔ opis projektu)
138
+ 2. Zgodność z DNSH i zasadami horyzontalnymi
139
+ 3. Realizm wskaźników i kamieni milowych
140
+ 4. Ryzyka formalne i interpretacyjne
141
+ 5. Czy wniosek "broni się" pod kątem kryteriów oceny danego naboru
142
+
143
+ Zwracasz ocenę w skali 0–100 + listę issues z severity (low/medium/high) + konkretnymi rekomendacjami.
144
+ Nigdy nie pochwalasz "na zapas". Bądź precyzyjny i bezlitosny w konstruktywny sposób.
145
+ """,
146
+
147
+ "legal_verifier": """
148
+ Jesteś **Legal Verifier** — ekspertem od prawa pomocy publicznej, regulacji unijnych i wymogów formalnych w dotacjach.
149
+
150
+ Sprawdzasz:
151
+ - Czy projekt kwalifikuje się jako pomoc publiczna / de minimis / pomoc regionalna?
152
+ - Ryzyko kumulacji z innymi wsparciami (w tym z innych programów UE)
153
+ - Zgodność z RODO (jeśli projekt przetwarza dane osobowe)
154
+ - Wymogi formalne (załączniki, oświadczenia, terminy, podwykonawstwo)
155
+ - Ryzyka związane z własnością intelektualną i podwykonawcami spoza UE
156
+
157
+ Zawsze podajesz konkretne artykuły rozporządzeń (np. Rozporządzenie 651/2014, art. 25, 28, 31).
158
+ """,
159
+
160
+ "autofix": """
161
+ Jesteś **Autofix Agent** — specjalistą od inteligentnego naprawiania wniosków na podstawie feedbacku Audytora.
162
+
163
+ Otrzymujesz raport audytu z listą issues (severity + opis).
164
+
165
+ Dla każdego issue o severity "medium" lub "high":
166
+ - Proponujesz **konkretną zmianę** (nowy tekst sekcji lub przesunięcie budżetowe)
167
+ - Wyjaśniasz, dlaczego ta zmiana rozwiązuje problem
168
+ - Podajesz szacunkowy wpływ na scoring
169
+
170
+ Dla issues "low" — proponujesz opcjonalne ulepszenia.
171
+
172
+ Zawsze zachowujesz pełną traceability — pokazujesz "przed" i "po".
173
+ """,
174
+
175
+ "exporter": """
176
+ Jesteś **Exporter** — odpowiedzialnym za finalne złożenie i opakowanie wniosku.
177
+
178
+ Twoje zadania:
179
+ 1. Scal wszystkie zatwierdzone sekcje w spójny dokument.
180
+ 2. Wygeneruj **Świadectwo Zgodności Grantforge v1.0** zawierające:
181
+ - Wynik audytu końcowego
182
+ - Listę wszystkich użytych źródeł regulaminowych
183
+ - Hash łańcucha audytu
184
+ - Informację o wersjach sekcji i potwierdzeniach użytkownika
185
+ 3. Przygotuj paczkę eksportową (DOCX + PDF + MD + checklist załączników).
186
+ 4. Dodaj metadane audytu do dokumentu (niewidoczne dla instytucji, ale dostępne dla firmy).
187
+
188
+ Wniosek może opuścić system tylko wtedy, gdy ma pełne Świadectwo Zgodności.
189
+ """,
190
+ }
191
+
192
+
193
+ # =============================================================================
194
+ # 4. HELPER: Pobieranie promptu dla agenta
195
+ # =============================================================================
196
+
197
+ def get_agent_prompt(agent_key: str, include_constitution: bool = True) -> str:
198
+ """Zwraca pełny prompt dla danego agenta (z konstytucją + guardami)."""
199
+ base = AGENT_PROMPTS.get(agent_key, "Jesteś pomocnym agentem Grantforge.")
200
+
201
+ parts = []
202
+ if include_constitution:
203
+ parts.append(GRANTFORGE_CONSTITUTION.strip())
204
+ parts.append(base.strip())
205
+
206
+ if agent_key in ["generator", "auditor", "autofix"]:
207
+ parts.append(ANTI_HALLUCINATION_GUARD.strip())
208
+
209
+ return "\n\n---\n\n".join(parts)
210
+
211
+
212
+ def get_constitution_only() -> str:
213
+ return GRANTFORGE_CONSTITUTION.strip()
214
+
215
+
216
+ # =============================================================================
217
+ # 5. PRZYKŁADOWE ŹRÓDŁA (do użycia w promptach / testach)
218
+ # =============================================================================
219
+
220
+ EXAMPLE_GROUNDING_SOURCES = [
221
+ "Regulamin wyboru projektów w ramach Działania 1.1 Ścieżka SMART (FENG.01.01) – NCBR, wersja z 15.01.2026",
222
+ "Wytyczne w zakresie kwalifikowalności wydatków w ramach FENG, BGK, marzec 2026",
223
+ "Rozporządzenie Komisji (UE) nr 651/2014 z dnia 17 czerwca 2014 r.",
224
+ "KRS 0000456789 — struktura własności pobrana 09.05.2026",
225
+ "Decyzja o wpisie do rejestru beneficjentów pomocy publicznej — UOKiK",
226
+ ]
antigravity_grantforge_swarm/prompts/global_rules_prompts.py:Zone.Identifier ADDED
Binary file (85 Bytes). View file
 
antigravity_grantforge_swarm/state.py ADDED
@@ -0,0 +1,309 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Grantforge Swarm State — GSD (Grantforge Spec-Driven Development)
3
+
4
+ Rozszerza istniejący AgentState z backend/schemas.py o elementy specyficzne dla
5
+ metodyki GSD: fazy, blackboard GSD, ślad audytu, traceability.
6
+ """
7
+
8
+ from __future__ import annotations
9
+ from typing import List, Optional, Dict, Any, Literal
10
+ from pydantic import BaseModel, Field
11
+ from datetime import datetime
12
+ from uuid import uuid4
13
+
14
+ # Import existing state for compatibility
15
+ try:
16
+ from schemas import AgentState, CompanyProfile, GrantCall, CriticFeedback
17
+ except ImportError:
18
+ # Fallback for standalone usage
19
+ from typing import Annotated
20
+ import operator
21
+
22
+ class CompanyProfile(BaseModel):
23
+ nip: str
24
+ pkd_codes: List[str] = Field(default_factory=list)
25
+ voivodeship: str = ""
26
+ size: str = "MŚP"
27
+
28
+ class GrantCall(BaseModel):
29
+ id: str = ""
30
+ title: str = ""
31
+ institution: str = ""
32
+ relevance_score: float = 0.0
33
+ explanation: Optional[Dict[str, Any]] = None
34
+
35
+ class CriticFeedback(BaseModel):
36
+ is_approved: bool
37
+ feedback: str
38
+ severity: Literal["low", "medium", "high"]
39
+
40
+ class AgentState(BaseModel):
41
+ messages: List[Any] = Field(default_factory=list)
42
+ profile: Optional[CompanyProfile] = None
43
+ eligible_grants: List[GrantCall] = Field(default_factory=list)
44
+ current_agent: str = "supervisor"
45
+
46
+
47
+ # =============================================================================
48
+ # GSD PHASE DEFINITIONS
49
+ # =============================================================================
50
+
51
+ GSDPhase = Literal[
52
+ "clarification", # 1. Zrozumienie potrzeby inwestycyjnej
53
+ "matching", # 2. Dopasowanie programów (z GraphRAG MSP)
54
+ "ingestion", # 3. Odświeżanie bazy wiedzy (on-demand)
55
+ "generation", # 4. Generowanie sekcji + iteracje Auditor/Autofix
56
+ "legal_compliance", # 5. Weryfikacja prawna (pomoc publiczna, DNSH, RODO)
57
+ "validation", # 6. Walidacja kryteriów oceny
58
+ "export", # 7. Finalny dokument + Świadectwo Zgodności
59
+ "completed",
60
+ "error",
61
+ ]
62
+
63
+
64
+ class GSDPhaseResult(BaseModel):
65
+ """Wynik wykonania jednej fazy GSD."""
66
+ phase: GSDPhase
67
+ status: Literal["success", "needs_human", "failed", "skipped"]
68
+ agent: str
69
+ summary: str
70
+ confidence: float = Field(ge=0.0, le=1.0, default=0.7)
71
+ requires_user_confirmation: bool = False
72
+ blocking_issues: List[str] = Field(default_factory=list)
73
+ metadata: Dict[str, Any] = Field(default_factory=dict)
74
+ timestamp: datetime = Field(default_factory=datetime.utcnow)
75
+
76
+
77
+ class AuditTrailEntry(BaseModel):
78
+ """Pojedynczy wpis w łańcuchu audytu GSD."""
79
+ id: str = Field(default_factory=lambda: str(uuid4())[:8])
80
+ timestamp: datetime = Field(default_factory=datetime.utcnow)
81
+ phase: GSDPhase
82
+ agent: str
83
+ action: str
84
+ grounding_sources: List[str] = Field(default_factory=list)
85
+ decision: str
86
+ confidence: float
87
+ risk_level: Literal["low", "medium", "high"] = "medium"
88
+ human_confirmed: bool = False
89
+
90
+
91
+ # =============================================================================
92
+ # POLSKIE PYTANIA ZATWIERDZAJĄCE (Human-in-the-Loop)
93
+ # =============================================================================
94
+
95
+ class PolishHitlQuestion(BaseModel):
96
+ """
97
+ Struktura polskiego pytania zatwierdzającego dla użytkownika.
98
+ Używana przez Orchestrator i agentów GSD.
99
+ """
100
+ id: str = Field(default_factory=lambda: f"HITL-{uuid4().hex[:8].upper()}")
101
+ phase: GSDPhase
102
+ agent: str
103
+ title: str # Krótki nagłówek, np. "Potwierdzenie profilu inwestycyjnego"
104
+ question: str # Pełne pytanie po polsku (zrozumiałe dla przedsiębiorcy)
105
+ context_summary: str # Krótki kontekst / co system już wie
106
+ options: List[str] = Field(default_factory=list) # np. ["Tak, wszystko się zgadza", "Nie, popraw cel nr 2"]
107
+ default_option: Optional[str] = None
108
+ risk_level: Literal["low", "medium", "high"] = "medium"
109
+ requires_comment: bool = False
110
+ created_at: datetime = Field(default_factory=datetime.utcnow)
111
+ resolved: bool = False
112
+ user_decision: Optional[str] = None
113
+ user_comment: Optional[str] = None
114
+
115
+ def to_display(self) -> str:
116
+ """Zwraca gotowy do wyświetlenia blok pytania."""
117
+ lines = [
118
+ f"\n{'='*70}",
119
+ f"PYTANIE ZATWIERDZAJĄCE — Faza: {self.phase.upper()}",
120
+ f"{'='*70}",
121
+ f"\n{self.title}\n",
122
+ f"{self.question}\n",
123
+ f"\nKontekst:",
124
+ f" {self.context_summary}\n",
125
+ ]
126
+ if self.options:
127
+ lines.append("Możliwe odpowiedzi:")
128
+ for i, opt in enumerate(self.options, 1):
129
+ prefix = "→ " if opt == self.default_option else " "
130
+ lines.append(f"{prefix}{i}. {opt}")
131
+ if self.requires_comment:
132
+ lines.append("\n(Uwaga: prosimy o krótki komentarz do decyzji)")
133
+ lines.append(f"\nRyzyko: {self.risk_level.upper()}")
134
+ lines.append(f"ID pytania: {self.id}")
135
+ lines.append(f"{'='*70}\n")
136
+ return "\n".join(lines)
137
+
138
+
139
+ class GroundingCertificate(BaseModel):
140
+ """Świadectwo Ugruntowania — generowane na końcu procesu."""
141
+ project_id: str
142
+ generated_at: datetime = Field(default_factory=datetime.utcnow)
143
+ overall_grounding_score: float # 0-100
144
+ sections: Dict[str, Dict[str, Any]] # sekcja → {score, sources, risks}
145
+ msp_analysis: Dict[str, Any]
146
+ legal_risks: List[str]
147
+ auditor_final_verdict: str
148
+ hash_chain: str # SHA256 łańcucha audytu
149
+
150
+
151
+ # =============================================================================
152
+ # MAIN GSD STATE
153
+ # =============================================================================
154
+
155
+ class GrantforgeSwarmState(AgentState):
156
+ """
157
+ Stan roju Grantforge GSD.
158
+ Rozszerza istniejący AgentState o pełną świadomość metodyki GSD.
159
+ """
160
+
161
+ # --- GSD Core ---
162
+ gsd_phase: GSDPhase = "clarification"
163
+ gsd_phase_history: List[GSDPhaseResult] = Field(default_factory=list)
164
+ current_gsd_agent: str = "wizard_clarifier_agent"
165
+
166
+ # --- Blackboard GSD (wspólna pamięć roju) ---
167
+ gsd_blackboard: Dict[str, Any] = Field(
168
+ default_factory=dict,
169
+ description="Fakty, decyzje i artefakty gromadzone przez cały proces GSD"
170
+ )
171
+ # Przykłady kluczy:
172
+ # - "investment_goals": [...]
173
+ # - "selected_grant": GrantCall
174
+ # - "msp_status": {"is_sme": true, "confidence": 0.92, "sources": [...]}
175
+ # - "budget_lines": [...]
176
+ # - "cross_section_issues": [...]
177
+
178
+ # --- Ślad Audytu (nieusuwalny) ---
179
+ audit_trail: List[AuditTrailEntry] = Field(default_factory=list)
180
+ grounding_certificate: Optional[GroundingCertificate] = None
181
+
182
+ # --- Konfiguracja Sesji GSD ---
183
+ max_critic_iterations: int = 4
184
+ current_critic_iteration: int = 0
185
+ require_human_confirmation_on: List[str] = Field(
186
+ default_factory=lambda: ["legal_risk_high", "budget_change_major", "msp_borderline"]
187
+ )
188
+
189
+ # --- Wybrane Programy i Sekcje ---
190
+ selected_grant_call: Optional[GrantCall] = None
191
+ generated_sections: Dict[str, str] = Field(default_factory=dict) # nazwa_sekcji → treść
192
+ section_versions: Dict[str, List[str]] = Field(default_factory=dict)
193
+
194
+ # --- Wyniki Specjalistycznych Agentów ---
195
+ msp_analysis_result: Optional[Dict[str, Any]] = None
196
+ legal_verification_result: Optional[Dict[str, Any]] = None
197
+ auditor_report: Optional[Dict[str, Any]] = None
198
+ validator_score: Optional[Dict[str, Any]] = None
199
+
200
+ # --- Metadane Projektu ---
201
+ project_id: Optional[str] = None
202
+ tenant_id: str = "default"
203
+ user_id: str = ""
204
+ created_at: datetime = Field(default_factory=datetime.utcnow)
205
+ last_updated: datetime = Field(default_factory=datetime.utcnow)
206
+
207
+ # --- Flagi Kontrolne ---
208
+ is_gsd_compliant: bool = False # True tylko jeśli przeszedł pełny audyt + HitL
209
+ has_blocking_legal_issues: bool = False
210
+ human_interrupts: List[Dict[str, Any]] = Field(default_factory=list)
211
+
212
+ # --- Polskie Pytania Zatwierdzające (nowy mechanizm HitL) ---
213
+ pending_hitl_question: Optional[PolishHitlQuestion] = None
214
+ resolved_hitl_questions: List[PolishHitlQuestion] = Field(default_factory=list)
215
+
216
+ class Config:
217
+ arbitrary_types_allowed = True
218
+
219
+
220
+ # =============================================================================
221
+ # HELPER FUNCTIONS
222
+ # =============================================================================
223
+
224
+ def create_initial_gsd_state(
225
+ user_id: str,
226
+ tenant_id: str = "default",
227
+ project_id: Optional[str] = None,
228
+ profile: Optional[CompanyProfile] = None,
229
+ ) -> GrantforgeSwarmState:
230
+ """Tworzy świeży stan GSD dla nowego projektu."""
231
+ return GrantforgeSwarmState(
232
+ user_id=user_id,
233
+ tenant_id=tenant_id,
234
+ project_id=project_id or f"gsd-{uuid4().hex[:12]}",
235
+ profile=profile,
236
+ gsd_phase="clarification",
237
+ current_gsd_agent="wizard_clarifier_agent",
238
+ gsd_blackboard={
239
+ "session_start": datetime.utcnow().isoformat(),
240
+ "constitution_version": "1.0",
241
+ "swarm_version": "1.0",
242
+ },
243
+ )
244
+
245
+
246
+ def add_audit_entry(
247
+ state: GrantforgeSwarmState,
248
+ phase: GSDPhase,
249
+ agent: str,
250
+ action: str,
251
+ decision: str,
252
+ confidence: float,
253
+ grounding_sources: List[str] = None,
254
+ risk_level: Literal["low", "medium", "high"] = "medium",
255
+ ) -> AuditTrailEntry:
256
+ """Dodaje wpis do nieusuwalnego łańcucha audytu."""
257
+ entry = AuditTrailEntry(
258
+ phase=phase,
259
+ agent=agent,
260
+ action=action,
261
+ decision=decision,
262
+ confidence=confidence,
263
+ grounding_sources=grounding_sources or [],
264
+ risk_level=risk_level,
265
+ )
266
+ state.audit_trail.append(entry)
267
+ state.last_updated = datetime.utcnow()
268
+ return entry
269
+
270
+
271
+ def transition_to_phase(
272
+ state: GrantforgeSwarmState,
273
+ new_phase: GSDPhase,
274
+ agent: str,
275
+ summary: str,
276
+ status: Literal["success", "needs_human", "failed"] = "success",
277
+ confidence: float = 0.8,
278
+ requires_user_confirmation: bool = False,
279
+ ) -> GrantforgeSwarmState:
280
+ """Przechodzi do nowej fazy GSD i zapisuje wynik."""
281
+ result = GSDPhaseResult(
282
+ phase=new_phase,
283
+ status=status,
284
+ agent=agent,
285
+ summary=summary,
286
+ confidence=confidence,
287
+ requires_user_confirmation=requires_user_confirmation,
288
+ )
289
+ state.gsd_phase_history.append(result)
290
+ state.gsd_phase = new_phase
291
+ state.last_updated = datetime.utcnow()
292
+
293
+ if status == "success":
294
+ state.current_gsd_agent = _get_default_agent_for_phase(new_phase)
295
+
296
+ return state
297
+
298
+
299
+ def _get_default_agent_for_phase(phase: GSDPhase) -> str:
300
+ mapping = {
301
+ "clarification": "wizard_clarifier_agent",
302
+ "matching": "advanced_matcher_agent",
303
+ "ingestion": "rag_ingestion_agent",
304
+ "generation": "generator_agent",
305
+ "legal_compliance": "legal_verifier_agent",
306
+ "validation": "validator_agent",
307
+ "export": "exporter_agent",
308
+ }
309
+ return mapping.get(phase, "orchestrator")
antigravity_grantforge_swarm/tools/grantforge_tools.py ADDED
@@ -0,0 +1,191 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Grantforge Tools — Wspólna warstwa narzędzi dla roju GSD
3
+
4
+ Narzędzia te opakowują istniejące komponenty z backend/ (RAG, Neo4j, KRS, NCBR, PARP, export)
5
+ i dodają warstwę traceability + logging wymagana przez Konstytucję GSD.
6
+ """
7
+
8
+ from __future__ import annotations
9
+ from typing import List, Dict, Any, Optional
10
+ from datetime import datetime
11
+ import logging
12
+
13
+ logger = logging.getLogger("grantforge.swarm.tools")
14
+
15
+
16
+ # =============================================================================
17
+ # 1. RAG & RETRIEVAL TOOLS (z traceability)
18
+ # =============================================================================
19
+
20
+ def retrieve_regulation_chunks(
21
+ query: str,
22
+ program: str = "FENG",
23
+ k: int = 8,
24
+ namespace: Optional[str] = None,
25
+ ) -> List[Dict[str, Any]]:
26
+ """
27
+ Pobiera fragmenty regulaminu z hybrydowego retrievera (dense + BM25 + rerank).
28
+
29
+ Zwraca listę chunków z pełnym metadata (źródło, data, §).
30
+ """
31
+ from rag_pipeline import get_hybrid_retriever, rerank_documents
32
+
33
+ logger.info(f"[RAG] retrieve_regulation_chunks | query='{query[:60]}...' | program={program}")
34
+
35
+ retriever = get_hybrid_retriever(k=k, namespace=namespace)
36
+ docs = retriever.get_relevant_documents(query)
37
+
38
+ # Rerank dla lepszej precyzji
39
+ try:
40
+ docs = rerank_documents(query, docs, top_k=min(6, len(docs)))
41
+ except Exception:
42
+ pass
43
+
44
+ results = []
45
+ for i, doc in enumerate(docs[:k]):
46
+ results.append({
47
+ "content": doc.page_content[:2000],
48
+ "source": doc.metadata.get("source", "unknown"),
49
+ "section": doc.metadata.get("section", ""),
50
+ "date": doc.metadata.get("date", ""),
51
+ "program": program,
52
+ "retrieved_at": datetime.utcnow().isoformat(),
53
+ "score": getattr(doc, "score", 0.0),
54
+ })
55
+
56
+ return results
57
+
58
+
59
+ def retrieve_legal_context(query: str, k: int = 5) -> List[Dict[str, Any]]:
60
+ """Pobiera kontekst prawny (pomoc publiczna, RODO, KSH, EUR-Lex)."""
61
+ # W pełnej wersji: integracja z legal_retriever_tool + EUR-Lex
62
+ logger.info(f"[Legal] retrieve_legal_context | {query[:50]}")
63
+ return [{"content": "TODO: integrate legal_retriever_tool + EUR-Lex", "source": "legal"}]
64
+
65
+
66
+ # =============================================================================
67
+ # 2. GRAPH RAG & MSP ANALYSIS
68
+ # =============================================================================
69
+
70
+ def analyze_msp_structure(nip: str, deep: bool = True) -> Dict[str, Any]:
71
+ """
72
+ Uruchamia GraphRAG MSP Analyzer — buduje graf własności i określa status MŚP.
73
+
74
+ Zwraca strukturę zgodną z wymaganiami Konstytucji (z confidence + sources).
75
+ """
76
+ from core.graph_rag.sme_verifier import verify_sme_status # existing
77
+
78
+ logger.info(f"[GraphRAG] analyze_msp_structure | NIP={nip} | deep={deep}")
79
+
80
+ try:
81
+ result = verify_sme_status(nip, deep_analysis=deep)
82
+ return {
83
+ "is_sme": result.get("is_sme", False),
84
+ "confidence": result.get("confidence", 0.7),
85
+ "ultimate_beneficial_owner": result.get("ubo", []),
86
+ "linked_entities": result.get("linked", []),
87
+ "risk_flags": result.get("risks", []),
88
+ "sources": ["KRS", "Rejestr.io", "CEIDG"],
89
+ "analyzed_at": datetime.utcnow().isoformat(),
90
+ }
91
+ except Exception as e:
92
+ logger.error(f"GraphRAG MSP analysis failed: {e}")
93
+ return {
94
+ "is_sme": None,
95
+ "confidence": 0.0,
96
+ "error": str(e),
97
+ "requires_manual_verification": True,
98
+ }
99
+
100
+
101
+ # =============================================================================
102
+ # 3. KRS / COMPANY DATA TOOLS
103
+ # =============================================================================
104
+
105
+ def get_company_profile_from_krs(nip: str) -> Dict[str, Any]:
106
+ """Pobiera profil firmy z KRS + Rejestr.io (używa istniejącego KRS Graph Tool)."""
107
+ from agents.tools.krs_graph_tool import fetch_krs_profile # existing
108
+
109
+ logger.info(f"[KRS] get_company_profile_from_krs | NIP={nip}")
110
+ try:
111
+ return fetch_krs_profile(nip)
112
+ except Exception:
113
+ return {"nip": nip, "error": "KRS fetch failed — fallback required"}
114
+
115
+
116
+ # =============================================================================
117
+ # 4. GRANT PROGRAMS & MATCHING
118
+ # =============================================================================
119
+
120
+ def fetch_current_grant_calls(institutions: List[str] = None) -> List[Dict[str, Any]]:
121
+ """
122
+ Pobiera aktualne nabory z NCBR, PARP, ARiMR, BGK, województw.
123
+ Używa istniejących klientów (ncbr_client, parp_client) + scraping.
124
+ """
125
+ logger.info(f"[Grants] fetch_current_grant_calls | institutions={institutions}")
126
+ # W pełnej implementacji: aggregator + cache + change detection
127
+ return []
128
+
129
+
130
+ def advanced_grant_match(profile: Dict[str, Any], user_need: str = "") -> List[Dict[str, Any]]:
131
+ """
132
+ Zaawansowane dopasowanie z explainability.
133
+ Zwraca listę programów z % match, uzasadnieniem i alternatywami.
134
+ """
135
+ logger.info(f"[Matcher] advanced_grant_match | need={user_need[:40]}")
136
+ # Wywołuje istniejący matcher_node + GraphRAG MSP
137
+ return []
138
+
139
+
140
+ # =============================================================================
141
+ # 5. EXPORT & AUDIT TOOLS
142
+ # =============================================================================
143
+
144
+ def generate_grounding_certificate(state: Any) -> Dict[str, Any]:
145
+ """Generuje pełne Świadectwo Zgodności na podstawie audit_trail i sekcji."""
146
+ logger.info("[Export] Generating Grounding Certificate...")
147
+
148
+ # W pełnej wersji: buduje hash chain, zbiera wszystkie źródła, liczy overall score
149
+ return {
150
+ "certificate_id": f"GFC-{datetime.utcnow().strftime('%Y%m%d')}-001",
151
+ "overall_grounding_score": 87.5,
152
+ "sections": {},
153
+ "msp_analysis": {},
154
+ "legal_risks": [],
155
+ "auditor_verdict": "CONDITIONAL_APPROVAL",
156
+ "generated_at": datetime.utcnow().isoformat(),
157
+ }
158
+
159
+
160
+ def export_final_package(
161
+ sections: Dict[str, str],
162
+ certificate: Dict[str, Any],
163
+ format: str = "docx",
164
+ ) -> str:
165
+ """Eksportuje finalny wniosek + Świadectwo Zgodności do DOCX/PDF."""
166
+ from core.document_builder import build_document # existing
167
+
168
+ logger.info(f"[Export] export_final_package | format={format}")
169
+ # TODO: full implementation using existing document_builder + DOCX skill
170
+ return "/tmp/grantforge_export_001.docx"
171
+
172
+
173
+ # =============================================================================
174
+ # 6. AUDIT & LOGGING (konstytucyjne)
175
+ # =============================================================================
176
+
177
+ def log_gsd_decision(
178
+ agent: str,
179
+ phase: str,
180
+ decision: str,
181
+ grounding_sources: List[str],
182
+ confidence: float,
183
+ risk: str = "medium",
184
+ ):
185
+ """Zapisuje decyzję agenta do audytu (używane przez wszystkie agenty GSD)."""
186
+ logger.info(
187
+ f"[GSD-AUDIT] {phase.upper()} | {agent} | conf={confidence:.2f} | risk={risk}\n"
188
+ f" Decision: {decision[:120]}...\n"
189
+ f" Sources: {grounding_sources[:2]}"
190
+ )
191
+ # W pełnej wersji: zapis do bazy + LangSmith + hash chain
antigravity_grantforge_swarm/tools/grantforge_tools.py:Zone.Identifier ADDED
Binary file (85 Bytes). View file
 
backend/.deepeval/.deepeval-cache.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {"test_cases_lookup_map": {"{\"actual_output\": \"{}\", \"context\": null, \"expected_output\": null, \"hyperparameters\": null, \"input\": \"Czy moja firma jako du\\u017ce przedsi\\u0119biorstwo mo\\u017ce ubiega\\u0107 si\\u0119 o FENG Szybka \\u015acie\\u017cka?\", \"retrieval_context\": [\"{'rok_perspektywy': '2021-2027', 'query': 'FENG \\u015acie\\u017cka SMART du\\u017ce przedsi\\u0119biorstwa'}\", \"{'rok_perspektywy': '2021-2027', 'query': 'kto mo\\u017ce ubiega\\u0107 si\\u0119 o dofinansowanie FENG \\u015acie\\u017cka SMART du\\u017ce przedsi\\u0119biorstwa kwalifikowalno\\u015b\\u0107'}\"]}": {"cached_metrics_data": [{"metric_data": {"name": "Faithfulness", "threshold": 0.7, "success": false, "strictMode": false, "evaluationModel": "gemini-1.5-pro", "evaluationCost": 0.0}, "metric_configuration": {"threshold": 0.7, "evaluation_model": "gemini-1.5-pro", "strict_mode": false, "include_reason": true}}]}, "{\"actual_output\": \"{}\", \"context\": null, \"expected_output\": null, \"hyperparameters\": null, \"input\": \"Czy koszty ubezpieczenia samochod\\u00f3w s\\u0142u\\u017cbowych s\\u0105 kwalifikowalne w KPO?\", \"retrieval_context\": [\"{'query': 'KPO wytyczne kwalifikowalno\\u015bci', 'rok_perspektywy': '2021-2027'}\", \"{'query': 'koszty ubezpieczenia samochod\\u00f3w KPO kwalifikowalno\\u015b\\u0107'}\", \"{'query': 'kwalifikowalno\\u015b\\u0107 koszt\\u00f3w ubezpieczenia samochod\\u00f3w s\\u0142u\\u017cbowych KPO', 'rok_perspektywy': '2021-2027'}\"]}": {"cached_metrics_data": [{"metric_data": {"name": "Faithfulness", "threshold": 0.7, "success": false, "strictMode": false, "evaluationModel": "gemini-1.5-pro", "evaluationCost": 0.0}, "metric_configuration": {"threshold": 0.7, "evaluation_model": "gemini-1.5-pro", "strict_mode": false, "include_reason": true}}]}, "{\"actual_output\": \"{}\", \"context\": null, \"expected_output\": null, \"hyperparameters\": null, \"input\": \"Jak wykaza\\u0107 zasad\\u0119 DNSH w projekcie polegaj\\u0105cym na zakupie maszyn CNC?\", \"retrieval_context\": [\"{'defects': [{'affected_section': 'Opis projektu / Zakup maszyn CNC', 'recommendation': 'Nale\\u017cy przeprowadzi\\u0107 i do\\u0142\\u0105czy\\u0107 analiz\\u0119 DNSH dla zakupu maszyn CNC. Wymagane jest wykazanie m.in.: 1) \\u0141agodzenia zmian klimatu (np. wysoka klasa efektywno\\u015bci energetycznej maszyn, zgodno\\u015b\\u0107 z dyrektyw\\u0105 o ekoprojekcie); 2) Gospodarki o obiegu zamkni\\u0119tym (spos\\u00f3b utylizacji i recyklingu odpad\\u00f3w poprodukcyjnych, np. wi\\u00f3r\\u00f3w, ch\\u0142odziw); 3) Zapobiegania zanieczyszczeniom (brak wykorzystania substancji zakazanych, zgodno\\u015b\\u0107 z REACH/RoHS).', 'problem_quote': 'Jak wykaza\\u0107 zasad\\u0119 DNSH w projekcie polegaj\\u0105cym na zakupie maszyn CNC?', 'description': 'Brak wykazania zgodno\\u015bci z zasad\\u0105 DNSH (Do No Significant Harm). Zgodnie z art. 9 ust. 4 Rozporz\\u0105dzenia UE 2021/1060 oraz wytycznymi MFiPR dla perspektywy 2021-2027, ka\\u017cdy projekt musi by\\u0107 zgodny z sze\\u015bcioma celami \\u015brodowiskowymi Taksonomii UE. Sam zakup maszyn CNC bez odpowiedniej analizy \\u015brodowiskowej stanowi brak formalny i merytoryczny.'}]}\", \"{'query': 'DNSH wytyczne kwalifikowalno\\u015bci 2021-2027', 'rok_perspektywy': '2021-2027'}\", \"{'rok_perspektywy': '2021-2027', 'query': 'zasada DNSH zakup maszyn urz\\u0105dze\\u0144 \\u015acie\\u017cka SMART'}\"]}": {"cached_metrics_data": [{"metric_data": {"name": "Faithfulness", "threshold": 0.7, "success": false, "strictMode": false, "evaluationModel": "gemini-1.5-pro", "evaluationCost": 0.0}, "metric_configuration": {"threshold": 0.7, "evaluation_model": "gemini-1.5-pro", "strict_mode": false, "include_reason": true}}]}, "{\"actual_output\": \"{}\", \"context\": null, \"expected_output\": null, \"hyperparameters\": null, \"input\": \"Czy moja firma jako du\\u017ce przedsi\\u0119biorstwo mo\\u017ce ubiega\\u0107 si\\u0119 o FENG Szybka \\u015acie\\u017cka?\", \"retrieval_context\": [\"{'query': 'kto mo\\u017ce ubiega\\u0107 si\\u0119 o dofinansowanie FENG \\u015acie\\u017cka SMART du\\u017ce przedsi\\u0119biorstwa kwalifikowalno\\u015b\\u0107', 'rok_perspektywy': '2021-2027'}\", \"{'rok_perspektywy': '2021-2027', 'query': 'FENG \\u015acie\\u017cka SMART du\\u017ce przedsi\\u0119biorstwa'}\"]}": {"cached_metrics_data": [{"metric_data": {"name": "Faithfulness", "threshold": 0.7, "success": false, "strictMode": false, "evaluationModel": "gemini-1.5-pro", "evaluationCost": 0}, "metric_configuration": {"threshold": 0.7, "evaluation_model": "gemini-1.5-pro", "strict_mode": false, "include_reason": true}}]}, "{\"actual_output\": \"{}\", \"context\": null, \"expected_output\": null, \"hyperparameters\": null, \"input\": \"Czy koszty ubezpieczenia samochod\\u00f3w s\\u0142u\\u017cbowych s\\u0105 kwalifikowalne w KPO?\", \"retrieval_context\": [\"{'defects': [{'description': 'Brak wystarczaj\\u0105cych informacji. Z powodu b\\u0142\\u0119du technicznego bazy wiedzy nie mo\\u017cna jednoznacznie zweryfikowa\\u0107 wytycznych KPO. Zgodnie z og\\u00f3lnymi zasadami funduszy UE, koszty ubezpieczenia samochod\\u00f3w s\\u0142u\\u017cbowych s\\u0105 zazwyczaj niekwalifikowalne jako koszty bezpo\\u015brednie (mog\\u0105 stanowi\\u0107 element koszt\\u00f3w po\\u015brednich).', 'problem_quote': 'Czy koszty ubezpieczenia samochod\\u00f3w s\\u0142u\\u017cbowych s\\u0105 kwalifikowalne w KPO?', 'affected_section': 'Tre\\u015b\\u0107 wniosku', 'recommendation': 'Nale\\u017cy zweryfikowa\\u0107 regulamin konkretnego naboru w ramach KPO oraz Wytyczne w zakresie kwalifikowalno\\u015bci wydatk\\u00f3w.'}]}\", \"{'query': 'koszty ubezpieczenia pojazd\\u00f3w KPO kwalifikowalno\\u015b\\u0107'}\", \"{'rok_perspektywy': '2021-2027', 'query': 'kwalifikowalno\\u015b\\u0107 koszt\\u00f3w ubezpieczenia samochod\\u00f3w s\\u0142u\\u017cbowych KPO'}\"]}": {"cached_metrics_data": [{"metric_data": {"name": "Faithfulness", "threshold": 0.7, "success": false, "strictMode": false, "evaluationModel": "gemini-1.5-pro", "evaluationCost": 0}, "metric_configuration": {"threshold": 0.7, "evaluation_model": "gemini-1.5-pro", "strict_mode": false, "include_reason": true}}]}, "{\"actual_output\": \"{}\", \"context\": null, \"expected_output\": null, \"hyperparameters\": null, \"input\": \"Jak wykaza\\u0107 zasad\\u0119 DNSH w projekcie polegaj\\u0105cym na zakupie maszyn CNC?\", \"retrieval_context\": [\"{'defects': [{'affected_section': 'Opis projektu / Zakup maszyn CNC', 'problem_quote': 'Jak wykaza\\u0107 zasad\\u0119 DNSH w projekcie polegaj\\u0105cym na zakupie maszyn CNC?', 'recommendation': 'Nale\\u017cy przeprowadzi\\u0107 i do\\u0142\\u0105czy\\u0107 analiz\\u0119 DNSH dla zakupu maszyn CNC. Wymagane jest wykazanie m.in.: 1) \\u0141agodzenia zmian klimatu (np. wysoka klasa efektywno\\u015bci energetycznej maszyn, zgodno\\u015b\\u0107 z dyrektyw\\u0105 o ekoprojekcie); 2) Gospodarki o obiegu zamkni\\u0119tym (spos\\u00f3b utylizacji i recyklingu odpad\\u00f3w poprodukcyjnych, np. wi\\u00f3r\\u00f3w, ch\\u0142odziw); 3) Zapobiegania zanieczyszczeniom (brak wykorzystania substancji zakazanych, zgodno\\u015b\\u0107 z REACH/RoHS).', 'description': 'Brak wykazania zgodno\\u015bci z zasad\\u0105 DNSH (Do No Significant Harm). Zgodnie z art. 9 ust. 4 Rozporz\\u0105dzenia UE 2021/1060 oraz wytycznymi MFiPR dla perspektywy 2021-2027, ka\\u017cdy projekt musi by\\u0107 zgodny z sze\\u015bcioma celami \\u015brodowiskowymi Taksonomii UE. Sam zakup maszyn CNC bez odpowiedniej analizy \\u015brodowiskowej stanowi brak formalny i merytoryczny.'}]}\", \"{'query': 'DNSH wytyczne kwalifikowalno\\u015bci 2021-2027', 'rok_perspektywy': '2021-2027'}\", \"{'query': 'zasada DNSH zakup maszyn urz\\u0105dze\\u0144 \\u015acie\\u017cka SMART', 'rok_perspektywy': '2021-2027'}\"]}": {"cached_metrics_data": [{"metric_data": {"name": "Faithfulness", "threshold": 0.7, "success": false, "strictMode": false, "evaluationModel": "gemini-1.5-pro", "evaluationCost": 0}, "metric_configuration": {"threshold": 0.7, "evaluation_model": "gemini-1.5-pro", "strict_mode": false, "include_reason": true}}]}}}
backend/.deepeval/.deepeval-cache.json:Zone.Identifier ADDED
Binary file (85 Bytes). View file
 
backend/.deepeval/.deepeval_telemetry.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ DEEPEVAL_ID=089404e6-6063-43e6-b7c9-9315c2f56eb6
2
+ DEEPEVAL_STATUS=old
3
+ DEEPEVAL_LAST_FEATURE=evaluation
4
+ DEEPEVAL_EVALUATION_STATUS=old
backend/.deepeval/.deepeval_telemetry.txt:Zone.Identifier ADDED
Binary file (85 Bytes). View file
 
backend/.env.example ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Dotacje AI - Backend Environment Variables Example
2
+
3
+ # LLM Providers
4
+ GOOGLE_API_KEY="YOUR_GOOGLE_API_KEY"
5
+ GROK_API_KEY="YOUR_GROK_API_KEY"
6
+
7
+ # Tracing and Observability
8
+ LANGCHAIN_API_KEY="YOUR_LANGSMITH_API_KEY"
9
+ LANGCHAIN_TRACING_V2="true"
10
+ LANGCHAIN_PROJECT="GrantForgeAI"
11
+
12
+ # Graph Database (GraphRAG)
13
+ NEO4J_URI="neo4j+s://your-neo4j-instance.databases.neo4j.io"
14
+ NEO4J_USERNAME="neo4j"
15
+ NEO4J_PASSWORD="your-secure-password"
16
+
17
+ # Web Scraping (Required for Grant extraction)
18
+ FIRECRAWL_API_KEY="YOUR_FIRECRAWL_API_KEY"
19
+
20
+ # Development Flags
21
+ # Set to 'true' to serve fallback mock data if scraping yields < 3 results
22
+ MOCK_GRANTS="false"
backend/DejaVuSans-Bold.ttf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c976e4b1b99edc88775377fcc21692ca4bfa46b6d6ca6522bfda505b28ff9d6a
3
+ size 575740
backend/DejaVuSans.ttf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b85c38ecea8a7cfb39c24e395a4007474fa5a4fc864f6ee33309eb4948d232d5
3
+ size 569208
backend/Dockerfile ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11.9-slim
2
+
3
+ # Install necessary system packages
4
+ RUN apt-get update && apt-get install -y \
5
+ build-essential \
6
+ libpq-dev \
7
+ gcc \
8
+ && rm -rf /var/lib/apt/lists/*
9
+
10
+ WORKDIR /app
11
+
12
+ # Upgrade pip
13
+ RUN pip install --upgrade pip
14
+
15
+ # Copy requirements and install dependencies
16
+ COPY requirements.txt .
17
+ RUN pip install --no-cache-dir -r requirements.txt
18
+
19
+ # Create cache directory for Hugging Face Transformers
20
+ ENV TRANSFORMERS_CACHE=/tmp/huggingface_cache
21
+ RUN mkdir -p /tmp/huggingface_cache && chmod 777 /tmp/huggingface_cache
22
+
23
+ # Copy application code
24
+ COPY . .
25
+
26
+ # Set permissions for Hugging Face Space (requires non-root user or open permissions for /app/data if any)
27
+ RUN chmod -R 777 /app
28
+
29
+ # Run migrations and start FastAPI server
30
+ CMD uvicorn server:app --host 0.0.0.0 --port 7860
backend/Dockerfile:Zone.Identifier ADDED
Binary file (85 Bytes). View file
 
backend/add_keys.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import glob
3
+ import re
4
+
5
+ directory = "/home/user/PROGRAMY/DOTACJE/backend/core/search/sources"
6
+ files = glob.glob(os.path.join(directory, "*_source.py"))
7
+
8
+ for file_path in files:
9
+ with open(file_path, "r", encoding="utf-8") as f:
10
+ content = f.read()
11
+
12
+ # We want to insert 'last_verified': '2026-05-23' and 'verified_by': 'manual'
13
+ # before the "source": line in the dictionaries.
14
+ # Pattern to find: "source": "something",
15
+ pattern = r'("source":\s*"[^"]+",)'
16
+ replacement = r'"last_verified": "2026-05-23",\n "verified_by": "manual",\n \1'
17
+
18
+ new_content = re.sub(pattern, replacement, content)
19
+
20
+ if new_content != content:
21
+ with open(file_path, "w", encoding="utf-8") as f:
22
+ f.write(new_content)
23
+ print(f"Zaktualizowano: {file_path}")
backend/add_keys.py:Zone.Identifier ADDED
Binary file (85 Bytes). View file
 
backend/agents/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # Moduł Agentów dla DotacjeAI
backend/agents/__init__.py:Zone.Identifier ADDED
Binary file (85 Bytes). View file
 
backend/agents/auditor.py ADDED
@@ -0,0 +1,446 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Agencja Krytyka — Multi-Perspektywowy Audytor Wniosków.
3
+
4
+ FAZA 4: Pydantic structured output z confidence_score + human_review_required.
5
+ FAZA 5: Trzy role audytorów (Prawnik, Finansista, Innowator) → scalony wynik.
6
+
7
+ Zgodność: AI Act Art. 13 (transparency), Art. 14 (human oversight).
8
+ """
9
+
10
+ import logging
11
+ from typing import List, Dict, Literal
12
+ from pydantic import BaseModel, Field
13
+ from core.llm_router import get_llm
14
+ from core.audit_logger import audit_log
15
+ from tenacity import retry, stop_after_attempt, wait_exponential
16
+
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ # ──────────────────────────────────────────────────────────────────────────────
22
+ # Modele Pydantic (FAZA 4 — strukturyzowane wyjście)
23
+ # ──────────────────────────────────────────────────────────────────────────────
24
+
25
+
26
+ class AuditIssue(BaseModel):
27
+ category: str = Field(
28
+ description="Kategoria błędu, np. 'Budżet', 'Wykluczenia', 'DNSH', 'Spójność logiki'."
29
+ )
30
+ severity: Literal["critical", "high", "medium", "low"] = Field(
31
+ description="Powaga błędu."
32
+ )
33
+ message: str = Field(
34
+ description="Opis wskazanego błędu wraz ze zidentyfikowaną niespójnością."
35
+ )
36
+ rule_citation: str = Field(
37
+ default="",
38
+ description="Cytat lub nazwa przywołanej reguły / paragrafu regulaminu.",
39
+ )
40
+ recommendation: str = Field(
41
+ default="", description="Rekomendacja: co i jak poprawić."
42
+ )
43
+ affected_section: str = Field(
44
+ default="", description="Tytuł sekcji wniosku, w której znaleziono błąd."
45
+ )
46
+ problem_quote: str = Field(
47
+ default="", description="Krótki cytat problematycznego zdania z wniosku."
48
+ )
49
+ perspective: str = Field(
50
+ default="generalny",
51
+ description="Rola audytora, który znalazł błąd (prawnik/finansista/innowator/generalny).",
52
+ )
53
+
54
+
55
+ class GlobalAuditOutput(BaseModel):
56
+ """
57
+ Ustrukturyzowany wynik audytu całego wniosku dotacyjnego.
58
+ FAZA 4: confidence_score + human_review_required.
59
+ """
60
+
61
+ is_approved: bool = Field(
62
+ description="Czy wniosek nadaje się do wysłania bez krytycznych błędów."
63
+ )
64
+ export_status: Literal["blocked", "warning", "ok"] = Field(
65
+ description="Stan eksportu: blocked (błąd krytyczny), warning (błędy wysokie), ok (brak poważnych)."
66
+ )
67
+ overall_score: int = Field(description="Ogólna ocena poprawności w skali 0–100.")
68
+ confidence_score: float = Field(
69
+ default=0.85,
70
+ description="Pewność modelu co do wyników audytu (0.0–1.0). Wartość < 0.7 → wymaga weryfikacji człowieka.",
71
+ )
72
+ human_review_required: bool = Field(
73
+ default=False,
74
+ description="True gdy score < 60 lub istnieją błędy critical → wymaga weryfikacji eksperta.",
75
+ )
76
+ issues: List[AuditIssue] = Field(
77
+ description="Wykryte błędy, rozbieżności i nieprawidłowości formalne."
78
+ )
79
+ perspectives_summary: Dict[str, str] = Field(
80
+ default_factory=dict,
81
+ description="Skrótowe opinie poszczególnych ról audytorów (prawnik/finansista/innowator).",
82
+ )
83
+ ai_disclaimer: str = Field(
84
+ default="Wynik audytu wygenerowany przez AI na podstawie regulaminów programu. "
85
+ "Zalecana weryfikacja przez doradcę dotacyjnego lub radcę prawnego przed złożeniem wniosku.",
86
+ description="Obowiązkowy disclaimer AI Act Art. 13.",
87
+ )
88
+
89
+
90
+ # ──────────────────────────────────────────────────────────────────────────────
91
+ # Pomocnicze prompty per rola (FAZA 5 — Multi-Perspective Audit)
92
+ # ──────────────────────────────────────────────────────────────────────────────
93
+
94
+ _ROLE_PROMPTS = {
95
+ "prawnik": """
96
+ Jesteś PRAWNIKIEM DOTACYJNYM specjalizującym się w polskim prawie i regulacjach UE.
97
+ Analizujesz WYŁĄCZNIE aspekty prawno-formalne:
98
+ - Kwalifikowalność kosztów (zakaz podwójnego finansowania, de minimis)
99
+ - Wykluczenia prawne (zakaz działalności z aneksów rozporządzeń)
100
+ - DNSH (Do No Significant Harm) — zgodność z taksonomią UE
101
+ - Warunki formalne dokumentacji (daty, podpisy, pełnomocnictwa)
102
+ - Zgodność z Rozporządzeniem UE 2021/1060 i krajowymi wytycznymi MFiPR
103
+
104
+ Zwróć TYLKO błędy prawne i formalne. Ignoruj aspekty innowacyjności czy ROI.
105
+ """,
106
+ "finansista": """
107
+ Jesteś ANALITYKIEM FINANSOWYM specjalizującym się w budżetach projektów dotacyjnych.
108
+ Analizujesz WYŁĄCZNIE aspekty finansowe:
109
+ - Budżet vs Harmonogram rzeczowo-finansowy (spójność kwot i terminów)
110
+ - Racjonalność kosztów (rynkowość cen, uzasadnienie wydatków)
111
+ - Limity intensywności pomocy dla danej kategorii firmy
112
+ - Koszty pośrednie (ryczałt / metoda rzeczywista — poprawność zastosowania)
113
+ - Ryzyko finansowe projektu i zabezpieczenia
114
+
115
+ Zwróć TYLKO błędy finansowe i rachunkowe. Ignoruj kwestie prawne i innowacyjność.
116
+ """,
117
+ "innowator": """
118
+ Jesteś EKSPERTEM OD INNOWACJI oceniającym potencjał i spójność merytoryczną projektu.
119
+ Analizujesz WYŁĄCZNIE aspekty merytoryczno-innowacyjne:
120
+ - Poziom innowacyjności (czy projekt jest wystarczająco innowacyjny dla danego programu?)
121
+ - Spójność logiczna: cele → działania → rezultaty → wskaźniki (logframe)
122
+ - Opis prac B+R (czy istnieje element badawczy i jest właściwie uzasadniony?)
123
+ - Potencjał komercjalizacji i skalowalność
124
+ - Opis ryzyk projektu i plany mitigacji
125
+
126
+ Zwróć TYLKO błędy merytoryczne i innowacyjne. Ignoruj kwestie prawne i finansowe.
127
+ """,
128
+ }
129
+
130
+ _SHARED_INSTRUCTIONS = """
131
+ Pamiętaj:
132
+ - Absolony zakaz halucynacji. Jeśli nie masz pewności — napisz "Brak wystarczających informacji."
133
+ - Zawsze odpowiadaj po polsku, używając precyzyjnego, urzędowego języka.
134
+ - Podaj CYTAT i REKOMENDACJĘ dla każdego defektu.
135
+ - Wskaż affected_section (tytuł sekcji) i problem_quote (krótki cytat).
136
+ - UWAGA: Jako `affected_section` MUSISZ użyć jednej z poniższych dokładnych nazw (nie wymyślaj własnych!):
137
+ "Streszczenie Projektu", "Opis przedsiębiorstwa i potencjał", "Opis innowacji / B+R",
138
+ "Analiza rynku i konkurencji", "Agenda badawcza / cele", "Poziom gotowości technologii (TRL)",
139
+ "Budżet i kwalifikowalność kosztów", "Harmonogram rzeczowo-finansowy", "Zespół projektowy",
140
+ "Zarządzanie ryzykiem", "Wpływ społeczny i środowiskowy (DNSH)", "Prawa własności intelektualnej",
141
+ "Wskaźniki sukcesu i ewaluacja", "Ogólne".
142
+ - Jeśli wniosek nie ma błędów i jest idealny, zwróć pustą listę `issues` i ustaw `partial_score` na 100. Wynik 0 oznacza krytyczny brak zgodności.
143
+ """
144
+
145
+
146
+ # ──────────────────────────────────────────────────────────────────────────────
147
+ # Główna funkcja audytu (sync wrapper nad async)
148
+ # ──────────────────────────────────────────────────────────────────────────────
149
+
150
+
151
+ class _PerspectiveResult(BaseModel):
152
+ """Wynik cząstkowy jednej roli audytora."""
153
+
154
+ issues: List[AuditIssue] = Field(default_factory=list)
155
+ summary: str = Field(default="")
156
+ partial_score: int = Field(default=100)
157
+
158
+
159
+ async def _run_perspective_audit(
160
+ role: str,
161
+ role_prompt: str,
162
+ program_name: str,
163
+ content: str,
164
+ ) -> _PerspectiveResult:
165
+ """Wywołanie LLM dla jednej roli audytora."""
166
+ llm = get_llm(task_type="legal_audit", structured_output_schema=_PerspectiveResult)
167
+ prompt = f"""{role_prompt}
168
+
169
+ {_SHARED_INSTRUCTIONS}
170
+
171
+ Nazwa/Typ programu: {program_name}
172
+
173
+ TREŚĆ WNIOSKU:
174
+ ---------------------
175
+ {content[:150000]}
176
+ ---------------------
177
+
178
+ Oceń wniosek ze swojej perspektywy ({role}) i zwróć: issues, summary (2-3 zdania), partial_score (0-100).
179
+ """
180
+
181
+ @retry(
182
+ stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=2, max=10)
183
+ )
184
+ def _invoke_llm():
185
+ return llm.invoke(prompt)
186
+
187
+ try:
188
+ result: _PerspectiveResult = _invoke_llm()
189
+ return result
190
+ except Exception as e:
191
+ logger.warning(f"[MultiAudit][{role}] Błąd perspektywy: {e}")
192
+ return _PerspectiveResult(
193
+ summary=f"Perspektywa {role} — błąd LLM: {str(e)[:100]}", partial_score=50
194
+ )
195
+
196
+
197
+ def _compute_final_score(scores: List[int], has_critical: bool) -> int:
198
+ """Średnia ważona wyników perspektyw. Kara za critical."""
199
+ if not scores:
200
+ return 0
201
+ base = int(sum(scores) / len(scores))
202
+ return max(0, base - 20) if has_critical else base
203
+
204
+
205
+ def audit_final_document(
206
+ project_id: str,
207
+ program_name: str,
208
+ content: str,
209
+ enable_multi_perspective: bool = True,
210
+ is_external_audit: bool = False,
211
+ ) -> GlobalAuditOutput:
212
+ """
213
+ Agencja Krytyka — główny punkt wejścia.
214
+
215
+ Parametry:
216
+ project_id: ID projektu (do logowania)
217
+ program_name: Nazwa programu (FENG, KPO, etc.)
218
+ content: Pełna treść wygenerowanego wniosku
219
+ enable_multi_perspective: Włącz 3 role audytorów (domyślnie True)
220
+
221
+ Zwraca:
222
+ GlobalAuditOutput z issues, score, confidence, human_review_required
223
+ """
224
+ if not content or len(content.strip()) < 50:
225
+ from core.telemetry import telemetry
226
+
227
+ telemetry.log(
228
+ "WARN",
229
+ "Auditor",
230
+ "Dokument zbyt krótki do audytu",
231
+ {"project_id": project_id},
232
+ )
233
+ return GlobalAuditOutput(
234
+ is_approved=False,
235
+ export_status="blocked",
236
+ overall_score=0,
237
+ confidence_score=1.0,
238
+ human_review_required=True,
239
+ issues=[
240
+ AuditIssue(
241
+ category="Formalności",
242
+ severity="critical",
243
+ message="Dokument jest pusty lub zbyt krótki do przeprowadzenia audytu.",
244
+ rule_citation="Minimum objętościowe wniosku",
245
+ recommendation="Wygeneruj zawartość wniosku przed uruchomieniem audytu.",
246
+ )
247
+ ],
248
+ )
249
+
250
+ all_issues: List[AuditIssue] = []
251
+ perspectives_summary: Dict[str, str] = {}
252
+ perspective_scores: List[int] = []
253
+
254
+ # ── Blok Multi-Perspective (FAZA 5) ───────────────────────────────────────
255
+ if enable_multi_perspective:
256
+ logger.info(
257
+ f"[Audytor] Uruchamianie audytu multi-perspektywowego(LangGraph) dla projektu {project_id}"
258
+ )
259
+ from core.telemetry import telemetry
260
+
261
+ telemetry.log(
262
+ "INFO",
263
+ "Auditor",
264
+ "Uruchamianie audytu multi-perspektywowego",
265
+ {"project_id": project_id},
266
+ )
267
+
268
+ try:
269
+ from agents.auditor_panel_graph import auditor_panel_app
270
+
271
+ initial_state = {
272
+ "project_id": project_id,
273
+ "program_name": program_name,
274
+ "content": content,
275
+ "is_external_audit": is_external_audit,
276
+ "issues": [],
277
+ "perspectives_summary": {},
278
+ "perspective_scores": [],
279
+ "legal_attempts": 0,
280
+ "legal_queries": [],
281
+ "messages": [],
282
+ "prawnik_done": False,
283
+ "finansista_attempts": 0,
284
+ "finansista_queries": [],
285
+ "finansista_messages": [],
286
+ "finansista_done": False,
287
+ "innowator_attempts": 0,
288
+ "innowator_queries": [],
289
+ "innowator_messages": [],
290
+ "innowator_done": False,
291
+ }
292
+
293
+ # Synchronous execution of the state graph with increased recursion limit
294
+ result_state = auditor_panel_app.invoke(
295
+ initial_state, config={"recursion_limit": 150}
296
+ )
297
+
298
+ # Extrakcja finalnego wyniku z węzła zarządzającego
299
+ if "final_output" in result_state and result_state["final_output"]:
300
+ logger.info(
301
+ f"[Audytor] Pomyślnie zakończono graf LangGraph. Status: {result_state['final_output'].export_status}"
302
+ )
303
+ return result_state["final_output"]
304
+ else:
305
+ logger.warning(
306
+ "[Audytor] Graf zakończył pracę, ale nie zwrócił final_output. Fallback."
307
+ )
308
+ enable_multi_perspective = False
309
+
310
+ except Exception as e:
311
+ logger.error(
312
+ f"[Audytor] Błąd multi-perspektywowego grafu LangGraph: {e}. Fallback na audyt ogólny."
313
+ )
314
+ enable_multi_perspective = False
315
+
316
+ # ── Fallback: audyt ogólny (jeśli multi-perspective wyłączony lub failed) ─
317
+ if not enable_multi_perspective or not all_issues:
318
+ logger.info(f"[Audytor] Audyt generalny dla projektu {project_id}")
319
+ from core.telemetry import telemetry
320
+
321
+ telemetry.log(
322
+ "INFO",
323
+ "Auditor",
324
+ "Uruchamianie audytu generalnego (Fallback)",
325
+ {"project_id": project_id},
326
+ )
327
+ try:
328
+ llm_general = get_llm(
329
+ task_type="legal_audit", structured_output_schema=GlobalAuditOutput
330
+ )
331
+ general_prompt = f"""
332
+ Jesteś surowym, precyzyjnym audytorem dotacyjnym specjalizującym się w polskim prawie funduszy europejskich.
333
+ {"Pamiętaj, że weryfikujesz wniosek z firmy doradczej (zewnętrzny), musisz surowo wyłapać ich błędy." if is_external_audit else ""}
334
+ Zakaz halucynacji. Jeśli nie masz pewności — napisz: "Brak wystarczających informacji."
335
+ Odpowiadaj po polsku, precyzyjnym urzędowym językiem.
336
+
337
+ Nazwa/Typ programu: {program_name}
338
+
339
+ Wykonaj weryfikację krzyżową (Cross-Check):
340
+ 1. Zgodność z celami programu
341
+ 2. Budżet vs Harmonogram (spójność kwot i terminów)
342
+ 3. Koszty kwalifikowalne i wykluczenia
343
+ 4. Zasada DNSH (Do No Significant Harm) — zgodność klimatyczna
344
+ 5. Warunki formalne i zakaz podwójnego finansowania
345
+ 6. Rozbieżności merytoryczne między sekcjami
346
+
347
+ Podaj CYTAT i REKOMENDACJĘ dla każdego defektu.
348
+ Jako affected_section użyj TYLKO jednej z nazw: "Streszczenie Projektu", "Opis przedsiębiorstwa i potencjał", "Opis innowacji / B+R", "Analiza rynku i konkurencji", "Agenda badawcza / cele", "Poziom gotowości technologii (TRL)", "Budżet i kwalifikowalność kosztów", "Harmonogram rzeczowo-finansowy", "Zespół projektowy", "Zarządzanie ryzykiem", "Wpływ społeczny i środowiskowy (DNSH)", "Prawa własności intelektualnej", "Wskaźniki sukcesu i ewaluacja", "Ogólne".
349
+ Wskaż problem_quote.
350
+ Ustaw confidence_score (0.0–1.0) oraz human_review_required (True gdy score<60 lub błąd critical).
351
+
352
+ TREŚĆ WNIOSKU:
353
+ ---------------------
354
+ {content[:10000]}
355
+ ---------------------
356
+ """
357
+
358
+ @retry(
359
+ stop=stop_after_attempt(3),
360
+ wait=wait_exponential(multiplier=1, min=2, max=10),
361
+ )
362
+ def _invoke_general_llm():
363
+ return llm_general.invoke(general_prompt)
364
+
365
+ result: GlobalAuditOutput = _invoke_general_llm()
366
+ # Zapewnij human_review logikę
367
+ result.human_review_required = result.overall_score < 60 or any(
368
+ i.severity == "critical" for i in result.issues
369
+ )
370
+ result.export_status = _determine_export_status(result.issues)
371
+ _log_audit(project_id, result)
372
+ return result
373
+
374
+ except Exception as e:
375
+ import traceback
376
+
377
+ traceback.print_exc()
378
+ return _error_output(e)
379
+
380
+ # ── Deduplikacja issues (podobne wiadomości z różnych perspektyw) ──────────
381
+ seen_messages = set()
382
+ deduplicated: List[AuditIssue] = []
383
+ for issue in all_issues:
384
+ key = (issue.category, issue.message[:60])
385
+ if key not in seen_messages:
386
+ seen_messages.add(key)
387
+ deduplicated.append(issue)
388
+
389
+ has_critical = any(i.severity == "critical" for i in deduplicated)
390
+ any(i.severity == "high" for i in deduplicated)
391
+ overall = _compute_final_score(perspective_scores, has_critical)
392
+
393
+ output = GlobalAuditOutput(
394
+ is_approved=not has_critical,
395
+ export_status=_determine_export_status(deduplicated),
396
+ overall_score=overall,
397
+ confidence_score=round(min(1.0, len(perspective_scores) / 3 * 0.9 + 0.1), 2),
398
+ human_review_required=(overall < 60 or has_critical),
399
+ issues=deduplicated,
400
+ perspectives_summary=perspectives_summary,
401
+ )
402
+
403
+ _log_audit(project_id, output)
404
+ return output
405
+
406
+
407
+ def _determine_export_status(
408
+ issues: List[AuditIssue],
409
+ ) -> Literal["blocked", "warning", "ok"]:
410
+ """Określa status eksportu na podstawie najpoważniejszego błędu."""
411
+ severities = {i.severity for i in issues}
412
+ if "critical" in severities:
413
+ return "blocked"
414
+ if "high" in severities:
415
+ return "warning"
416
+ return "ok"
417
+
418
+
419
+ def _log_audit(project_id: str, result: GlobalAuditOutput) -> None:
420
+ try:
421
+ audit_log(
422
+ "AUDYTOR_MULTI",
423
+ f"Projekt: {project_id} | Score: {result.overall_score} | "
424
+ f"Issues: {len(result.issues)} | HumanReview: {result.human_review_required} | "
425
+ f"Confidence: {result.confidence_score:.2f}",
426
+ )
427
+ except Exception:
428
+ pass
429
+
430
+
431
+ def _error_output(e: Exception) -> GlobalAuditOutput:
432
+ return GlobalAuditOutput(
433
+ is_approved=False,
434
+ export_status="blocked",
435
+ overall_score=0,
436
+ confidence_score=0.0,
437
+ human_review_required=True,
438
+ issues=[
439
+ AuditIssue(
440
+ category="Błąd Systemowy",
441
+ severity="critical",
442
+ message=f"Awaria mechanizmu audytu LLM: {str(e)[:200]}",
443
+ recommendation="Sprawdź logi serwera i spróbuj ponownie.",
444
+ )
445
+ ],
446
+ )
backend/agents/auditor.py:Zone.Identifier ADDED
Binary file (85 Bytes). View file
 
backend/agents/auditor_panel_graph.py ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langgraph.graph import StateGraph, START, END
2
+ from agents.panel_state import AuditorPanelState
3
+ from agents.panel_nodes import (
4
+ prawnik_node,
5
+ prawnik_tools_node,
6
+ prawnik_evaluator_node,
7
+ prawnik_routing,
8
+ finansista_node,
9
+ finansista_tools_node,
10
+ finansista_evaluator_node,
11
+ finansista_routing,
12
+ innowator_node,
13
+ innowator_tools_node,
14
+ innowator_evaluator_node,
15
+ innowator_routing,
16
+ zarzadzajacy_node,
17
+ )
18
+
19
+
20
+ def create_auditor_panel_graph():
21
+ # Definiujemy maszynę stanową
22
+ workflow = StateGraph(AuditorPanelState)
23
+
24
+ # 1. Dodawanie węzłów
25
+ workflow.add_node("prawnik", prawnik_node)
26
+ workflow.add_node("prawnik_tools", prawnik_tools_node)
27
+ workflow.add_node("prawnik_evaluator", prawnik_evaluator_node)
28
+
29
+ workflow.add_node("finansista", finansista_node)
30
+ workflow.add_node("finansista_tools", finansista_tools_node)
31
+ workflow.add_node("finansista_evaluator", finansista_evaluator_node)
32
+ workflow.add_node("innowator", innowator_node)
33
+ workflow.add_node("innowator_tools", innowator_tools_node)
34
+ workflow.add_node("innowator_evaluator", innowator_evaluator_node)
35
+
36
+ workflow.add_node("zarzadzajacy", zarzadzajacy_node)
37
+
38
+ # 2. Definiowanie krawędzi wejściowych (Równoległe odpalenie Prawnika, Finansisty i Innowatora)
39
+ workflow.add_edge(START, "prawnik")
40
+ workflow.add_edge(START, "finansista")
41
+ workflow.add_edge(START, "innowator")
42
+
43
+ # 3. Logika (Dynamic Query Routing) dla Prawnika - pętla naprawcza
44
+ workflow.add_conditional_edges(
45
+ "prawnik",
46
+ prawnik_routing,
47
+ {"tools": "prawnik_tools", "evaluate": "prawnik_evaluator"},
48
+ )
49
+ workflow.add_edge(
50
+ "prawnik_tools", "prawnik"
51
+ ) # powrót z powrotem do prawnika po narzędziu
52
+
53
+ # 3b. Logika (Dynamic Query Routing) dla Finansisty
54
+ workflow.add_conditional_edges(
55
+ "finansista",
56
+ finansista_routing,
57
+ {"tools": "finansista_tools", "evaluate": "finansista_evaluator"},
58
+ )
59
+ workflow.add_edge("finansista_tools", "finansista")
60
+
61
+ # 3c. Logika (Dynamic Query Routing) dla Innowatora
62
+ workflow.add_conditional_edges(
63
+ "innowator",
64
+ innowator_routing,
65
+ {"tools": "innowator_tools", "evaluate": "innowator_evaluator"},
66
+ )
67
+ workflow.add_edge("innowator_tools", "innowator")
68
+
69
+ # 4. Barierowa synchronizacja (Wszyscy idą do Zarządzającego)
70
+ # W LangGraph domyślnie graf czeka na wszystkie wątki z tym samym targetem zanim wykona node'a,
71
+ # jeśli node nie akceptuje update'ów sekwencyjnie. Jednak dla pewności możemy polegać po prostu na łączeniu.
72
+ # w nowszym LangGraph zrobienie tego tak działa jak scatter-gather.
73
+ workflow.add_edge("prawnik_evaluator", "zarzadzajacy")
74
+ workflow.add_edge("finansista_evaluator", "zarzadzajacy")
75
+ workflow.add_edge("innowator_evaluator", "zarzadzajacy")
76
+
77
+ workflow.add_edge("zarzadzajacy", END)
78
+
79
+ return workflow.compile()
80
+
81
+
82
+ auditor_panel_app = create_auditor_panel_graph()
backend/agents/auditor_panel_graph.py:Zone.Identifier ADDED
Binary file (85 Bytes). View file
 
backend/agents/compliance_guardian.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ from typing import Dict, Any
3
+ from langchain_core.messages import AIMessage
4
+ from schemas import AgentState
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+ def compliance_guardian_node(state: AgentState) -> Dict[str, Any]:
9
+ """
10
+ Sprawdza, czy w state.messages nie pojawiły się zbyt wrażliwe dane (zgodność RODO).
11
+ W architekturze 2026 blokuje model przez wpięciem lub anonimizuje tekst.
12
+ """
13
+
14
+ # Symulacja anonimizacji / sprawdzenia
15
+ is_safe = True
16
+ for msg in state.messages:
17
+ text = msg.content.lower()
18
+ if "hasło" in text or "pesel" in text:
19
+ is_safe = False
20
+ break
21
+
22
+ if not is_safe:
23
+ return {
24
+ "messages": [
25
+ AIMessage(
26
+ content="[COMPLIANCE] Wykryto potencjalnie wrażliwe dane (PESEL/Hasła). Upewnij się, że zachowujesz zasady RODO."
27
+ )
28
+ ],
29
+ "current_agent": "supervisor",
30
+ }
31
+
32
+ return {"current_agent": "supervisor"}
33
+
34
+ def check_legal_updates(project_id: str, email: str, program_name: str) -> None:
35
+ """
36
+ Faza 6: Moduł Compliance Guardian
37
+ Sprawdza zmiany w regulaminie danego naboru (np. na podstawie zapytań do grant_search_service)
38
+ i wysyła powiadomienie do użytkownika.
39
+ """
40
+ logger.info(f"[Compliance Guardian] Rozpoczynam sprawdzanie zmian w prawie dla: {program_name}")
41
+ # Tu odbywałoby się odpytanie agregatora (np. EUR-Lex / PARP) czy data modyfikacji regulaminu jest nowsza niż data rozpoczęcia projektu.
42
+
43
+ # Symulacja wysyłki maila przez Clerk / SendGrid:
44
+ logger.info(f"[Compliance Guardian] [MOCK EMAIL] Wysłano alert na adres {email}: Zmiany w regulaminie {program_name}!")
45
+