import axios from 'axios'; import toast from 'react-hot-toast'; import { safeSignInRedirectPath, shouldHardRedirectOn401 } from '../utils/authGate'; /** Strip trailing /api from env URLs without mangling hosts that contain "api" elsewhere. */ export function resolveApiBaseUrl( apiUrl?: string, langserveUrl?: string, fallback = 'http://localhost:8000', ): string { const raw = apiUrl || langserveUrl || fallback; return raw.replace(/\/api\/?$/, ''); } // Bazowy URL to backend export const apiClient = axios.create({ baseURL: resolveApiBaseUrl( import.meta.env.VITE_API_URL, import.meta.env.VITE_LANGSERVE_URL, ), headers: { 'Content-Type': 'application/json' }, }); // --- Interceptory --- // Request: Clerk token is injected by ApiInterceptor; keep localStorage only as last-resort legacy fallback apiClient.interceptors.request.use((config) => { const token = localStorage.getItem('token'); if (token && !config.headers['Authorization']) { config.headers['Authorization'] = `Bearer ${token}`; } return config; }); // Response: obsługa błędów globalnych apiClient.interceptors.response.use( (response) => response, (error) => { const status = error?.response?.status; if (status === 429) { const retryAfter = error.response?.data?.retry_after; const msg = retryAfter ? `Przekroczono limit zapytań. Spróbuj za ${retryAfter}s.` : 'Przekroczono limit zapytań. Poczekaj chwilę.'; toast.error(`⏳ ${msg}`, { duration: 8000, id: 'rate-limit' }); } // 401: only hard-redirect when Clerk says user is signed OUT. // Signed-in + 401 is token/backend lag — bouncing to /sign-in causes // sign-in → /dashboard → /projects → 401 loop (prod logs). if (status === 401 && typeof window !== 'undefined') { if (shouldHardRedirectOn401()) { const target = safeSignInRedirectPath( window.location.pathname || '', window.location.search || '', ); if (!window.location.pathname.startsWith('/sign-in')) { window.location.assign(target); } } } return Promise.reject(error); } ); export const getSubscriptionStatus = async () => { // /api/me zwraca {tier, wizard_iterations_today, tokens_used_month, limits} const { data } = await apiClient.get('/api/me'); return data; }; export const createCheckoutSession = async (plan: string = 'pro') => { const { data } = await apiClient.post('/api/subscription/checkout', { plan }); return data as { checkout_url?: string; session_id?: string }; }; export const mintGeneratorStreamTicket = async (payload: { project_id: string; resume?: boolean; full_autopilot?: boolean; document_type?: string; }) => { const { data } = await apiClient.post('/api/generator/stream-ticket', payload); return data as { ticket: string; expires_in: number; stream_url: string }; }; export const resumeGeneratorHil = async (projectId: string, userResponse: string) => { const { data } = await apiClient.post('/api/generator/resume', { project_id: projectId, user_response: userResponse, }); return data; }; /** Holistic critic + multi-section auto-fix (keep good sections). */ export const runDocumentQualityCycle = async (projectId: string, maxPasses = 2) => { const { data } = await apiClient.post( `/api/projects/${projectId}/quality-cycle`, null, { params: { max_passes: maxPasses } }, ); return data as { status: string; passes: Array<{ pass: number; overall_score?: number; is_approved?: boolean; fixed_sections?: string[] }>; holistic?: Record; readiness?: { score: number; complete_sections: number; total: number }; message?: string; }; }; export const prepareFullAutopilot = async (projectId: string, resume = false) => { const { data } = await apiClient.post(`/api/generator/projects/${projectId}/generate-full`, { resume, }); return data as { status: string; ticket?: string; stream_url?: string }; }; export const mintAdminDiagnosticsTicket = async () => { const { data } = await apiClient.post('/api/admin/diagnostics/stream-ticket'); return data as { ticket: string; expires_in: number }; }; export const getTrustSummary = async () => { const { data } = await apiClient.get('/api/trust/summary'); return data; }; /** Multi-registry company lookup (GUS/CEIDG/KRS/MF/SUDOP/Bizraport). */ export type LookupCompanyResult = { nip: string; name: string; status: string; sme_status?: string; sme_confidence?: number; sme_related_entities?: number; regon?: string; krs?: string; address?: string; voivodeship?: string; pkd?: string[]; legal_form?: string; sources_used?: string[]; registry_score?: number; gaps?: string[]; risk_hints?: string[]; sudop?: Record; provenance?: Record; revenue?: number; employment?: number; }; export const lookupCompany = async (nip: string) => { const { data } = await apiClient.get('/api/projects/lookup-company', { params: { nip } }); return data as LookupCompanyResult; }; /** Full credibility pipeline: registries → match → regulation scoring. */ export const runCredibilityPipeline = async (payload: { nip: string; description?: string; limit?: number; user_answers?: { question: string; answer: string }[]; }) => { const { data } = await apiClient.post('/api/projects/credibility-pipeline', payload); return data; }; /** Grant Pulse kinds: new | planned | closing | current | all */ export type PulseKind = 'new' | 'planned' | 'closing' | 'current' | 'all'; export type PulseVerification = { deadline_verified?: boolean; status_verified?: boolean; regulation_verified?: boolean; continuous?: boolean; credibility_ok?: boolean; alert_worthy?: boolean; reasons?: string[]; }; export type PulseItem = { id?: string; name?: string; status?: string; deadline?: string; continuous?: boolean; operator?: string; source?: string; url?: string; regulation_url?: string; eurlex_url?: string; celex_id?: string; source_credibility_score?: number; pulse_kinds?: string[]; primary_kind?: string; first_seen_at?: string | null; verification?: PulseVerification; match_score?: number; why_it_fits?: string; alert_worthy?: boolean; generation_blockers?: string[]; }; export type PulseResponse = { status: string; kind: string; count: number; counts?: Record; quality?: Record; items: PulseItem[]; generated_at?: string; }; export const getGrantPulse = async (kind: PulseKind = 'current', limit = 40) => { const { data } = await apiClient.get('/api/grants/pulse', { params: { kind, limit } }); return data as PulseResponse; }; export const getFirmPulse = async (payload: { nip?: string; description?: string; kind?: PulseKind; limit?: number; min_score?: number; require_alert_worthy?: boolean; company?: Record; }) => { const { data } = await apiClient.post('/api/grants/pulse/firm', payload); return data as PulseResponse & { company?: Record; gaps?: string[]; alert_count?: number; enrichment_status?: string; }; }; export const getPulseMetrics = async () => { const { data } = await apiClient.get('/api/grants/pulse/metrics'); return data; }; export const postUserConsent = async (consent: Record) => { const { data } = await apiClient.post('/api/user/consent', { consent }); return data; }; export const getMe = getSubscriptionStatus; export const deleteUserAccount = async () => { const { data } = await apiClient.delete('/api/account'); return data; }; export const getAccountExport = async () => { const { data } = await apiClient.get('/api/account/export'); return data; }; export const updateAccountSettings = async (settings: { gdpr_consent_accepted?: boolean; ai_disclaimer_enabled?: boolean }) => { const { data } = await apiClient.post('/api/account/settings', settings); return data; }; export const submitFeedback = async (text: string, type: string = "feedback") => { const { data } = await apiClient.post('/api/feedback', { text, type }); return data; }; export const getAdminStats = async () => { const { data } = await apiClient.get('/api/admin/stats'); return data; }; export const getCurrentSession = async () => { const { data } = await apiClient.get('/api/session/current'); return data; }; // Projects API export const getProjects = async () => { const { data } = await apiClient.get('/api/projects'); return data; }; export const getProject = async (projectId: string) => { const { data } = await apiClient.get(`/api/projects/${projectId}`); return data; }; export const updateProject = async (projectId: string, updateData: any) => { const { data } = await apiClient.put(`/api/projects/${projectId}`, updateData); return data; }; export const deleteProject = async (projectId: string) => { await apiClient.delete(`/api/projects/${projectId}`); }; export const createProject = async (projectData: { title: string; program_type: string; description?: string; program_name?: string; estimated_value?: number; external_context?: Record }) => { const { data } = await apiClient.post('/api/projects', projectData); return data; }; export const resolveProjectRegulation = async (projectId: string, regulationUrl?: string) => { const { data } = await apiClient.post(`/api/projects/${projectId}/regulation/resolve`, { regulation_url: regulationUrl, }); return data; }; export const attachRegulationUrl = async (projectId: string, regulationUrl: string) => { const { data } = await apiClient.post(`/api/projects/${projectId}/regulation/attach-url`, { regulation_url: regulationUrl, }); return data; }; export type MatchProgramOptions = { /** Canonical cascade path id (e.g. national_feng) — soft boost on match */ strategy_path?: string | null; /** Optional pre-built eligibility report from spine / strategy recommend */ eligibility?: EligibilityReport | Record | null; }; export const matchProgram = async ( description: string, nip?: string, userAnswers?: { question: string; answer: string }[], options?: MatchProgramOptions, ) => { const { data } = await apiClient.post('/api/projects/match-program', { description: description, nip: nip, user_answers: userAnswers?.length ? userAnswers : undefined, ...(options?.strategy_path ? { strategy_path: options.strategy_path } : {}), ...(options?.eligibility ? { eligibility: options.eligibility } : {}), }); return data; }; export const createWelcomeSeed = async () => { const { data } = await apiClient.post('/api/projects/welcome-seed'); return data; }; // --- SECTION ENDPOINTS --- export const getProjectSections = async (projectId: string) => { const { data } = await apiClient.get(`/api/projects/${projectId}/sections`); return data; }; export const generateProjectSection = async (projectId: string, sectionType: string, promptContext?: string) => { const { data } = await apiClient.post(`/api/projects/${projectId}/generate-section`, { section_type: sectionType, prompt_context: promptContext }); return data; }; // --- Q&A / VERIFIER ENDPOINTS --- export const getProjectQuestionsHistory = async (projectId: string) => { const { data } = await apiClient.get(`/api/projects/${projectId}/ask/history`); return data; }; export const askProjectQuestion = async (projectId: string, question: string) => { const { data } = await apiClient.post(`/api/projects/${projectId}/ask`, { question }); return data; }; export const clearProjectQuestionsHistory = async (projectId: string) => { const { data } = await apiClient.delete(`/api/projects/${projectId}/ask/history`); return data; }; export const updateProjectSection = async (projectId: string, sectionId: string, content: string) => { const { data } = await apiClient.put(`/api/projects/${projectId}/sections/${sectionId}`, { content: content }); return data; }; export const reviewProjectSection = async (projectId: string, sectionType: string, content: string) => { const { data } = await apiClient.post(`/api/projects/${projectId}/review-section`, { section_type: sectionType, content: content }); return data; }; export interface SectionVersion { id: string; old_content: string; author: string; summary: string | null; timestamp: string; } export const getSectionVersions = async (projectId: string, sectionId: string): Promise => { const { data } = await apiClient.get(`/api/projects/${projectId}/sections/${sectionId}/versions`); return data; }; export const restoreSectionVersion = async (projectId: string, sectionId: string, versionId: string) => { const { data } = await apiClient.post(`/api/projects/${projectId}/sections/${sectionId}/versions/${versionId}/restore`); return data; }; export const previewProject = async (projectId: string) => { const { data } = await apiClient.get(`/api/projects/${projectId}/preview`); return data; }; export const compileFinalDocument = async (projectId: string, approvedOnly: boolean = false) => { const { data } = await apiClient.post(`/api/projects/${projectId}/compile-final`, { approved_only: approvedOnly }); return data; }; export const auditFinalDocument = async (projectId: string) => { const { data } = await apiClient.post(`/api/projects/${projectId}/global-audit`); return data; }; export const clearGlobalAudit = async (projectId: string) => { try { const { data } = await apiClient.delete(`/api/projects/${projectId}/global-audit`); return data; } catch { return { status: 'cleared' }; } }; export const autofixProjectSection = async (projectId: string, sectionId: string) => { const { data } = await apiClient.post(`/api/projects/${projectId}/sections/${sectionId}/autofix`); return data; }; export const syncRAGKnowledge = async () => { const { data } = await apiClient.post('/api/rag/sync', { category: "ALL" }); return data; }; // --- CHATBOT PROJECT ENDPOINTS --- export const getProjectChatHistory = async (projectId: string) => { const { data } = await apiClient.get(`/api/projects/${projectId}/chat`); return data; }; export const clearProjectChatHistory = async (projectId: string) => { const { data } = await apiClient.delete(`/api/projects/${projectId}/chat`); return data; }; export const sendProjectChatMessage = async (projectId: string, content: string, activeSectionId?: string, activeSectionTitle?: string) => { const payload: any = { content }; if (activeSectionId) { payload.active_section = activeSectionId; if (activeSectionTitle) payload.active_section_title = activeSectionTitle; } const { data } = await apiClient.post(`/api/projects/${projectId}/chat`, payload); return data; }; // --- EXPORT ENDPOINT --- function parseFilenameFromDisposition(header?: string, fallback = 'Wniosek'): string { if (!header) return fallback; const match = header.match(/filename\*?=(?:UTF-8''|"?)([^";]+)"?/i); return match?.[1] ? decodeURIComponent(match[1]) : fallback; } function triggerBlobDownload(blob: Blob, filename: string) { const url = window.URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.setAttribute('download', filename); document.body.appendChild(link); link.click(); link.remove(); window.URL.revokeObjectURL(url); } export const exportProjectDocument = async ( projectId: string, format: string, template: string, versionId?: string, ) => { const payload: Record = { format, template }; if (versionId) payload.version_id = versionId; const response = await apiClient.post( `/api/projects/${projectId}/export`, payload, { responseType: 'blob' }, ); const contentType = (response.headers['content-type'] as string) || ''; if (contentType.includes('application/json')) { const text = await (response.data as Blob).text(); try { const parsed = JSON.parse(text); const detail = parsed?.detail; throw new Error( typeof detail === 'string' ? detail : detail?.message || text || 'Błąd eksportu', ); } catch (e) { if (e instanceof Error && e.message !== 'Błąd eksportu') throw e; throw new Error(text || 'Błąd eksportu'); } } const resolvedType = contentType || (format === 'pdf' ? 'application/pdf' : 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'); const blob = response.data instanceof Blob ? response.data : new Blob([response.data], { type: resolvedType }); if (blob.size < 128) { throw new Error('Wygenerowany plik jest pusty — uzupełnij sekcje wniosku i spróbuj ponownie.'); } return { data: blob, headers: { 'content-type': resolvedType, 'content-disposition': response.headers['content-disposition'] as string, }, }; }; export const getProjectVersions = async (projectId: string) => { const { data } = await apiClient.get(`/api/projects/${projectId}/versions`); return data; }; export const createProjectVersion = async (projectId: string, title?: string) => { const { data } = await apiClient.post(`/api/projects/${projectId}/versions`, { title }); return data; }; export const exportProjectPDF = async (projectId: string, versionId?: string) => { const response = await exportProjectDocument(projectId, 'pdf', 'standard', versionId); const fileName = parseFilenameFromDisposition( response.headers['content-disposition'], versionId ? `dotacja_v${versionId.substring(0, 6)}.pdf` : `dotacja_${projectId}.pdf`, ); triggerBlobDownload(response.data, fileName); }; export const exportProjectDOCX = async (projectId: string, approvedOnly: boolean = false, versionId?: string) => { void approvedOnly; const response = await exportProjectDocument(projectId, 'docx', 'standard', versionId); const fileName = parseFilenameFromDisposition( response.headers['content-disposition'], versionId ? `dotacja_v${versionId.substring(0, 6)}.docx` : `dotacja_${projectId}.docx`, ); triggerBlobDownload(response.data, fileName); }; // --- EXTERNAL KNOWLEDGE / DOCUMENTS ENDPOINTS --- export const uploadProjectDocument = async (projectId: string, file: File) => { const formData = new FormData(); formData.append('file', file); const { data } = await apiClient.post(`/api/projects/${projectId}/documents`, formData, { headers: { 'Content-Type': 'multipart/form-data' } }); return data; }; export const getExportGateStatus = async (projectId: string) => { const { data } = await apiClient.get(`/api/projects/${projectId}/export-gate`); return data; }; export const getGsdStatus = async (projectId: string) => { const { data } = await apiClient.get(`/api/projects/${projectId}/gsd/status`); return data; }; export const resolveGsdHitl = async (projectId: string, hitlId: string, decision: string, comment = '') => { const { data } = await apiClient.post(`/api/projects/${projectId}/gsd/resolve-hitl`, { hitl_id: hitlId, decision, comment, }); return data; }; export const getProjectDocuments = async (projectId: string) => { const { data } = await apiClient.get(`/api/projects/${projectId}/documents`); if (Array.isArray(data)) return { documents: data, quota: null }; return data; }; export const deleteProjectDocument = async (projectId: string, filename: string) => { const { data } = await apiClient.delete(`/api/projects/${projectId}/documents/${filename}`); return data; }; // --- AUDIT ENDPOINTS --- export type AuditMode = 'pro' | 'llm'; export interface GapReportResult { program: string; overall_score: number; has_critical_gaps: boolean; gaps: Array<{ category: string; severity: string; code?: string; description: string; recommendation?: string; regulation_reference?: string; }>; summary?: string; snapshot_backed?: boolean; } function mapGapReportToAuditUi(report: GapReportResult) { return { is_approved: !report.has_critical_gaps && report.overall_score >= 75, overall_score: report.overall_score, human_review_required: report.has_critical_gaps || report.overall_score < 70, mode: 'pro' as const, summary: report.summary, issues: (report.gaps || []).map((g) => ({ category: g.category, severity: g.severity, perspective: 'deterministic', message: g.description, rule_citation: g.regulation_reference, recommendation: g.recommendation, code: g.code, })), }; } export const requestGapReport = async ( programName: string, documentText: string, companyProfile?: Record, projectId?: string, ): Promise => { const { data } = await apiClient.post('/api/audit/gap-report', { program_name: programName, document_text: documentText, company_profile: companyProfile, project_id: projectId, export_format: 'json', }); return data; }; export const uploadGapReportDocument = async ( file: File, programName: string, ): Promise> => { const formData = new FormData(); formData.append('file', file); formData.append('program_name', programName); formData.append('export_format', 'json'); const { data } = await apiClient.post('/api/audit/gap-report/upload', formData, { headers: { 'Content-Type': 'multipart/form-data' }, }); return mapGapReportToAuditUi(data); }; export const exportGapReport = async ( programName: string, documentText: string, format: 'pdf' | 'docx', projectId?: string, ) => { const response = await apiClient.post( '/api/audit/gap-report', { program_name: programName, document_text: documentText, project_id: projectId, export_format: format, }, { responseType: 'blob' }, ); triggerBlobDownload( response.data instanceof Blob ? response.data : new Blob([response.data]), `gap_report_${programName.replace(/\s+/g, '_')}.${format}`, ); }; export const exportGapReportFromFile = async ( file: File, programName: string, format: 'pdf' | 'docx', ) => { const formData = new FormData(); formData.append('file', file); formData.append('program_name', programName); formData.append('export_format', format); const response = await apiClient.post('/api/audit/gap-report/upload', formData, { headers: { 'Content-Type': 'multipart/form-data' }, responseType: 'blob', }); triggerBlobDownload( response.data instanceof Blob ? response.data : new Blob([response.data]), `gap_report_${programName.replace(/\s+/g, '_')}.${format}`, ); }; export const uploadReverseAuditDocument = async (file: File, programName?: string) => { const formData = new FormData(); formData.append('file', file); if (programName) formData.append('program_name', programName); const { data } = await apiClient.post(`/api/audit/reverse-audit`, formData, { headers: { 'Content-Type': 'multipart/form-data' } }); return data; }; export const getProjectAudit = async (projectId: string) => { const { data } = await apiClient.get(`/api/projects/${projectId}`); return data.final_document_audit_result; }; export const runProjectAudit = async (projectId: string) => { const { data } = await apiClient.post(`/api/projects/${projectId}/global-audit`); return data; }; export const getHolisticReview = async (projectId: string) => { const { data } = await apiClient.get(`/api/projects/${projectId}/holistic-review`); return data; }; export const runHolisticReview = async (projectId: string) => { const { data } = await apiClient.post(`/api/projects/${projectId}/holistic-review`); return data; }; // --- GRANTS / NABORY ENDPOINTS --- export interface NaboryFilters { q?: string; status?: string; source?: string; operator?: string; verification_level?: string; region?: string; company_size?: string; pkd?: string; beneficiary?: string; trusted_only?: boolean; /** Default true on API — only current grants with verified links/sources */ require_certainty?: boolean; hybrid?: boolean; limit?: number; } export const getGrantNabory = async (forceRefresh = false, filters: NaboryFilters = {}) => { const { require_certainty, ...rest } = filters; const { data } = await apiClient.get('/api/grants/nabory', { params: { force_refresh: forceRefresh, require_certainty: require_certainty ?? true, ...rest, }, }); return data as { status: string; count: number; nabory: any[]; catalog_stats?: { total: number; active: number; planned: number; unique_sources: number; with_regulation_url?: number; top_sources?: { source: string; count: number }[]; }; search_meta?: { require_certainty?: boolean; quality?: { returned?: number; with_regulation_url?: number; avg_certainty?: number; min_certainty?: number; }; }; }; }; export const getGrantNaborySources = async () => { const { data } = await apiClient.get('/api/grants/nabory/sources'); return data as { status: string; sources: { source: string; source_key?: string; count: number }[]; sources_by_key?: { source_key: string; source: string; count: number; labels?: string[] }[]; }; }; /** Komplet dokumentów + wymagania programu (dossier doradcy). */ export const getGrantDossier = async (grantId: string, refresh = false) => { const { data } = await apiClient.get(`/api/grants/nabory/${encodeURIComponent(grantId)}/dossier`, { params: { refresh }, }); return data as { ok?: boolean; name?: string; official_page_url?: string; precise_regulation_url?: string; regulation_documents?: Array<{ url: string; title?: string; role?: string }>; document_count?: number; readiness?: { level?: 'blind' | 'partial' | 'ready'; message?: string; can_advise?: boolean; is_blind?: boolean; }; advisor_brief?: { key_rules?: string[]; required_sections?: string[]; required_attachments?: string[]; attention_points?: string[]; }; firm_data_needed?: string[]; from_cache?: boolean; }; }; export const ensureGrantDossier = async (grantId: string) => { const { data } = await apiClient.post(`/api/grants/nabory/${encodeURIComponent(grantId)}/dossier/ensure`); return data; }; /** PLAN_ECOSYSTEM §10/§13 reportable metrics (instrument-stats.plan_12m). */ export interface Plan12mMetricsSnapshot { version?: number; plan_ref?: string; targets?: Record; measured?: { gold_family_count?: number; gold_meets_dod_min_12?: boolean; pct_projects_with_instrument_schema?: number | null; pct_ready_to_generate?: number | null; pct_structure_only?: number | null; pct_instrument_mismatch?: number | null; instrument_mismatch_count?: number | null; pct_firms_with_eligibility_snapshot?: number | null; pct_firms_with_de_minimis_source?: number | null; pct_firms_msp_confidence_ge_0_7?: number | null; pct_active_catalog_not_blind?: number | null; meets_dod_70_not_blind?: boolean | null; dossier_readiness?: Record | null; }; d90_target_hits?: Record; d365_target_hits?: Record; section_13_status?: Record; dod_all_pass?: boolean; dod_product_pass?: boolean; residual?: string[]; cascade?: Record; /** Always false until multi-month production SLAs are proven. */ sla_claim_allowed?: boolean; note?: string; } /** Ops: instrument-first metrics (families, readiness, gold, plan_12m). */ export const getInstrumentStats = async (limit = 2000) => { const { data } = await apiClient.get('/api/grants/nabory/instrument-stats', { params: { limit }, }); return data as { status?: string; gold?: { families?: string[]; count?: number; meets_dod_min_12?: boolean }; projects?: Record; plan_12m?: Plan12mMetricsSnapshot; }; }; /** F2: catalog dossier ready/partial/blind distribution. */ export const getDossierStats = async () => { const { data } = await apiClient.get('/api/grants/nabory/dossier-stats'); return data as { status?: string; sample_size?: number; pct_not_blind?: number | null; meets_dod_70_not_blind?: boolean; ready?: number; partial?: number; blind?: number; levels?: Record; }; }; /** Instrument-first: pre-gen readiness + missing fields for HITL. */ export const getGenerationReadiness = async (projectId: string) => { const { data } = await apiClient.get(`/api/projects/${projectId}/generation-readiness`); return data as { readiness?: { status?: string; ready_to_generate?: boolean; full_autopilot_allowed?: boolean; missing_fields?: Array<{ field: string; label: string; category: string; hint?: string }>; warnings?: string[]; instrument_family?: string; }; instrument_schema?: Record; consent_required?: boolean; grounding_mode?: string; }; }; export const postProjectFacts = async ( projectId: string, facts: Record, ) => { const { data } = await apiClient.post(`/api/projects/${projectId}/project-facts`, { facts }); return data; }; /** F1: explicit consent for structure-without-regulation generation. */ export const postGenerationConsent = async ( projectId: string, body: { mode?: string; ack: boolean } = { ack: true, mode: 'structure_without_regulation' }, ) => { const { data } = await apiClient.post(`/api/projects/${projectId}/generation-consent`, { mode: body.mode || 'structure_without_regulation', ack: body.ack, }); return data as { status: string; grounding_mode?: string; dossier_readiness?: string; consent_required?: boolean; generation_allowed?: boolean; generation_consent?: Record; }; }; export const deleteGenerationConsent = async (projectId: string) => { const { data } = await apiClient.delete(`/api/projects/${projectId}/generation-consent`); return data; }; export const getGrantDossierStats = async () => { const { data } = await apiClient.get('/api/grants/nabory/dossier-stats'); return data as { status: string; ready?: number; partial?: number; blind?: number; pct_ready?: number; pct_blind?: number; pct_primary_pdf?: number; avg_pack_size?: number; flags?: Record; }; }; export interface UserAnswer { question: string; answer: string; } export const matchGrantsForProject = async (projectId: string, userAnswers: UserAnswer[] = []) => { const { data } = await apiClient.post('/api/grants/match', { project_id: projectId, user_answers: userAnswers }); return data as { status: string; project_id: string; needs_more_info: boolean; clarifying_questions: string[]; matches: any[]; detail?: string; fallback_mode?: string }; }; // --- HEALTH --- export const getApiHealth = async () => { const { data } = await apiClient.get('/api/health'); return data; }; // --- COMPANY PROFILE ENDPOINTS --- export interface CompanyProfileData { nip: string; name?: string | null; pkd_codes: string[]; company_size?: string | null; location?: string | null; description?: string | null; is_verified?: boolean; } export const getCompanyProfile = async (): Promise => { const { data } = await apiClient.get('/api/company/profile'); return data; }; export const updateCompanyProfile = async (profileData: Partial): Promise => { const { data } = await apiClient.post('/api/company/profile', profileData); return data; }; // --- ELIGIBILITY SPINE (MŚP + de minimis 300k EUR) --- export interface EligibilityMsp { status: string; confidence?: number; source?: string; message?: string; is_sme?: boolean; is_large?: boolean; tree_summary?: unknown[]; } export interface EligibilityDeMinimis { used_eur: number | null; limit_eur: number; remaining_eur: number | null; exhausted: boolean; source: string; configured?: boolean; message?: string; checked_at?: string; } export interface EligibilityReport { nip?: string; krs?: string; msp: EligibilityMsp; de_minimis: EligibilityDeMinimis; eligible_instrument_kinds?: string[]; blockers: string[]; spine_enabled?: boolean; ready_for_full_autopilot?: boolean; version?: number; } export interface EligibilityResponse { status: string; eligibility: EligibilityReport; persisted?: boolean; persist_warning?: string; } export interface EligibilityRefreshPayload { nip: string; krs?: string; company_data?: Record; fetch_sudop?: boolean; de_minimis_manual_eur?: number | null; msp_status?: string; } export const getCompanyEligibility = async ( nip: string, krs?: string, ): Promise => { const { data } = await apiClient.get('/api/company/eligibility', { params: { nip, ...(krs ? { krs } : {}) }, }); return data as EligibilityResponse; }; export const refreshCompanyEligibility = async ( payload: EligibilityRefreshPayload, ): Promise => { const { data } = await apiClient.post('/api/company/eligibility/refresh', { fetch_sudop: true, ...payload, }); return data as EligibilityResponse; }; // --- STRATEGY CASCADE 2026 --- /** Canonical path ids from backend core/strategy/recommend.py */ export type StrategyPathId = | 'tax_psi_ulgi' | 'regional_fst' | 'national_feng' | 'debt_loan_guarantee' | 'direct_eu_prep'; export interface StrategyPath { id: StrategyPathId | string; label: string; score: number; reasons: string[]; primary?: boolean; de_minimis_blocked?: boolean; /** Tax path only — ids from core/strategy/tax_paths.py */ checklist_id?: string | null; checklist_ids?: string[]; /** PSI guardrail from tax_paths (true only on tax_psi_ulgi) */ never_say_submit_to_parp?: boolean; /** Full tax sub-path checklists when attached (psi / ulga_br / ip_box) */ tax_paths?: Array<{ id: string; label: string; checklist?: string[]; advisor_notes?: string[]; never_say_submit_to_parp?: boolean; }>; } export interface StrategyRecommendResult { version?: number; paths: StrategyPath[]; recommended_paths: StrategyPath[]; primary_path_id: StrategyPathId | string; cascade_order: Array; /** Mirrors ENABLE_STRATEGY_CASCADE on backend (default true). */ cascade_enforced?: boolean; eligibility_summary?: { msp?: string; de_minimis_exhausted?: boolean; de_minimis_used_eur?: number | null; }; notes?: string[]; } export interface StrategyRecommendResponse { status: string; strategy: StrategyRecommendResult; eligibility: EligibilityReport; } export interface StrategyRecommendParams { nip?: string; goal?: string; region?: string; } export interface StrategyRecommendBody { nip?: string | null; goal?: string; region?: string; company_data?: Record | null; eligibility?: EligibilityReport | Record | null; } /** GET /api/company/strategy/recommend — cascade from profile-backed eligibility. */ export const getStrategyRecommend = async ( params: StrategyRecommendParams = {}, ): Promise => { const { data } = await apiClient.get('/api/company/strategy/recommend', { params: { ...(params.nip ? { nip: params.nip } : {}), ...(params.goal != null && params.goal !== '' ? { goal: params.goal } : {}), ...(params.region != null && params.region !== '' ? { region: params.region } : {}), }, }); return data as StrategyRecommendResponse; }; /** POST /api/company/strategy/recommend — cascade with optional body eligibility/company_data. */ export const postStrategyRecommend = async ( body: StrategyRecommendBody, ): Promise => { const { data } = await apiClient.post('/api/company/strategy/recommend', body); return data as StrategyRecommendResponse; }; // --- TAX PATHS (F3) + BK2021 STUB (F8) --- export interface TaxPathChecklist { id: string; label: string; checklist: string[]; advisor_notes?: string[]; never_say_submit_to_parp?: boolean; } /** GET /api/tax/paths — plan-canonical tax checklists (PSI / B+R / IP Box). */ export const getTaxPaths = async (pathId?: string): Promise<{ status: string; paths: TaxPathChecklist[]; checklist_ids?: string[]; parent_strategy_path_id?: string; path?: TaxPathChecklist; }> => { const { data } = await apiClient.get('/api/tax/paths', { params: pathId ? { path_id: pathId } : undefined, }); return data; }; /** GET /api/company/tax-paths — compact tax surface with enabled flag. */ export const getCompanyTaxPaths = async (): Promise<{ status: string; enabled: boolean; tax_paths: TaxPathChecklist[]; }> => { const { data } = await apiClient.get('/api/company/tax-paths'); return data; }; /** GET /api/company/bk2021/status — F8 stub (default flag off, no live BK). */ export const getBk2021Status = async (): Promise<{ status: string; bk2021: Record; bk2021_stub_available?: boolean; sample_alert?: Record; }> => { const { data } = await apiClient.get('/api/company/bk2021/status'); return data; }; // --- REGIONAL CATALOG (F4.4) --- export interface RegionalProgram { id: string; name: string; program: string; operator: string; voivodeship: string; type?: string; status?: string; url?: string; topics?: string[]; source?: string; } export interface RegionalProgramsResponse { status: string; count: number; programs: RegionalProgram[]; voivodeships: string[]; bip_live_count?: number; bip_parser_voivodeships?: string[]; } export const getRegionalPrograms = async (voivodeship?: string) => { const { data } = await apiClient.get('/api/grants/regional/programs', { params: voivodeship ? { voivodeship } : {}, }); return data as RegionalProgramsResponse; }; export const createPipelineEntry = async (projectId: string, entry: Record) => { const { data } = await apiClient.post(`/api/projects/${projectId}/pipeline`, entry); return data; }; // --- POST-AWARD --- export interface ProjectMilestone { id: string; project_id: string; title: string; description?: string | null; due_date: string; status: string; completed_at?: string | null; } export const getPostAwardMilestones = async (projectId: string) => { const { data } = await apiClient.get(`/api/projects/${projectId}/post-award/milestones`); return data as { milestones: ProjectMilestone[]; count: number }; }; export const createPostAwardMilestone = async ( projectId: string, payload: { title: string; due_date: string; description?: string }, ) => { const { data } = await apiClient.post(`/api/projects/${projectId}/post-award/milestones`, payload); return data as ProjectMilestone; }; export const updatePostAwardMilestone = async ( projectId: string, milestoneId: string, payload: Partial<{ title: string; due_date: string; description: string; status: string }>, ) => { const { data } = await apiClient.patch( `/api/projects/${projectId}/post-award/milestones/${milestoneId}`, payload, ); return data as ProjectMilestone; }; export const deletePostAwardMilestone = async (projectId: string, milestoneId: string) => { const { data } = await apiClient.delete( `/api/projects/${projectId}/post-award/milestones/${milestoneId}`, ); return data; }; // --- NOTIFICATIONS --- export const getNotificationInbox = async (userId?: string, unreadOnly = false) => { const { data } = await apiClient.get('/api/notifications/inbox', { params: { user_id: userId, unread_only: unreadOnly }, }); return data; }; export const markNotificationRead = async (notificationId: string) => { const { data } = await apiClient.post(`/api/notifications/${notificationId}/read`); return data; }; // --- DEVELOPER API v2 --- export const createDeveloperApiKey = async (label: string) => { const { data } = await apiClient.post('/api/v2/developer/keys', { label }); return data as { api_key: string; key: { id: string; label: string; key_prefix: string } }; }; export const listDeveloperApiKeys = async () => { const { data } = await apiClient.get('/api/v2/developer/keys'); return data as { keys: Array<{ id: string; label: string; key_prefix: string; created_at: string }> }; }; export const revokeDeveloperApiKey = async (keyId: string) => { const { data } = await apiClient.delete(`/api/v2/developer/keys/${keyId}`); return data; };