Metadata-Version: 2.4
Name: finanfut-sdk
Version: 1.3.6
Summary: Official FinanFut Intelligence Python SDK
Author-email: FinanFut <support@finanfut.com>
License: MIT License
        
        Copyright (c) 2025 FinanFut
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
        
Project-URL: Homepage, https://finanfut.com
Project-URL: Repository, https://github.com/<EL-TEU-REPO>
Project-URL: Documentation, https://finanfut.com/docs
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: requests>=2.31.0
Requires-Dist: pydantic>=2.6
Requires-Dist: pydantic-settings>=2
Dynamic: license-file

# FinanFut Intelligence Python SDK

SDK oficial de **FinanFut Intelligence** per integrar agents, intents, memòria i fluxos d'orquestració des de Python.

## Índex
- [Instal·lació](#instal·lació)
- [Configuració](#configuració)
- [Quickstart](#quickstart)
- [Capacitats del client](#capacitats-del-client)
- [Interact API (`/api/v1/public/requests`)](#interact-api-apiv1publicrequests)
- [Aplicacions i manifest SDK](#aplicacions-i-manifest-sdk)
- [Catàleg de models i model-settings](#catàleg-de-models-i-model-settings)
- [Documents i document QA](#documents-i-document-qa)
- [Contexts i context sessions](#contexts-i-context-sessions)
- [OCR mapping](#ocr-mapping)
- [Data import (CSV/PDF → JSON)](#data-import-csvpdf--json)
- [Billing, analytics i access tokens](#billing-analytics-i-access-tokens)
- [Gestió d'errors](#gestió-derrors)
- [CLI](#cli)
- [Llicència](#llicència)

---

## Instal·lació

```bash
pip install finanfut-sdk
```

Requisit: **Python >= 3.12**.

---

## Configuració

Pots inicialitzar el client amb paràmetres directes o bé mitjançant fitxer de configuració + variables d'entorn.

### Fitxer local (`~/.finanfut/config.json`)

```bash
mkdir -p ~/.finanfut
```

```json
{
  "api_key": "YOUR_API_KEY",
  "application_id": "YOUR_APPLICATION_ID",
  "api_url": "https://finanfut-intelligence.onrender.com"
}
```

### Variables d'entorn suportades

- `FINANFUT_API_KEY`
- `FINANFUT_APPLICATION_ID`
- `FINANFUT_API_URL`

`FINANFUT_API_URL` accepta host base o URL amb `/api` o `/api/v1`; l'SDK la normalitza per evitar segments duplicats.

---

## Quickstart

```python
from finanfut_sdk import FinanFutClient

client = FinanFutClient(
    api_key="YOUR_API_KEY",
    application_id="YOUR_APPLICATION_ID",
)

response = client.interact.query(
    query="Hola! Programa una reunió demà a les 10.",
    application_agent_id="YOUR_APPLICATION_AGENT_ID",
    intent_id="YOUR_INTENT_ID",
)

print(response.answer)
print(response.model, response.ai_model_id)
```

---

## Capacitats del client

`FinanFutClient` exposa aquests mòduls:

- `client.interact`
- `client.agents`
- `client.intents`
- `client.applications`
- `client.application_agents`
- `client.memory`
- `client.knowledge`
- `client.documents`
- `client.data_import`
- `client.ocr`
- `client.models`
- `client.contexts`
- `client.context_sessions`
- `client.billing`
- `client.analytics`
- `client.access_tokens`

També pots activar mode sandbox amb `FinanFutClient.for_sandbox(...)` o `dry_run=True`.

---

## Interact API (`/api/v1/public/requests`)

`client.interact.query(...)` requereix:

- `query`
- `intent_id`
- **una** referència d'agent: `application_agent_id` o `agent_slug` o `agent_id`

Paràmetres opcionals rellevants:

- `intent`: etiqueta textual de l'intent (p. ex. `knowledge.answer`) per mantenir traçabilitat humana al lifecycle del request.
- `parameters`: claus addicionals per a routing/execució (incloent compatibilitat amb `intent_name`).
- `parameters.knowledge_source`: controla on cerca el runtime per intents de coneixement. `auto` és el valor per defecte: usa `knowledge_records` de l'aplicació quan no s'envia cap document/context, i usa chunks quan s'envia `document_id` o `context_id`. També accepta `application`, `document`, `context` i `mixed`.
- `knowledge_source`, `knowledge_record_type`, `max_knowledge_records` i `knowledge_min_score`: dreceres tipades de `client.interact.query(...)` per no haver de construir manualment `parameters`.

Exemple amb context i document:

```python
response = client.interact.query(
    query="Respon segons aquest context",
    intent_id="INTENT_UUID",
    intent="knowledge.answer",
    application_agent_id="APP_AGENT_UUID",
    context_id="CONTEXT_UUID",
    document_id="DOCUMENT_UUID",
    parameters={"lang": "ca"},
)

print(response.answer)
print(response.tokens)
```

Exemple amb knowledge d'aplicació per defecte:

```python
response = client.interact.query(
    query="Com es crea un torneig?",
    intent_id="INTENT_UUID",
    intent="knowledge.answer",
    application_agent_id="APP_AGENT_UUID",
)
```

Exemple combinant knowledge d'aplicació i chunks documentals:

```python
response = client.interact.query(
    query="Com es crea un torneig segons el contracte carregat?",
    intent_id="INTENT_UUID",
    intent="knowledge.answer",
    application_agent_id="APP_AGENT_UUID",
    document_id="DOCUMENT_UUID",
    parameters={"knowledge_source": "mixed"},
)
```

Quan el backend retorna una resposta estructurada dins `envelope.user_output.structured_json`, l'SDK la promou automàticament com a `response.answer` i també la deixa disponible a:

- `response.structured_answer`
- `response.envelope.user_output.structured_json`

Exemple per a `content.generate`:

```python
response = client.interact.query(
    query="Prepara una publicació de 100 paraules sobre la jornada",
    intent_id="INTENT_UUID",
    application_agent_id="APP_AGENT_UUID",
)

if isinstance(response.answer, dict):
    print(response.answer["post_text"])
    print(response.answer.get("image_url"))
    print(response.answer.get("freshness_status"))
```

---

## Aplicacions i manifest SDK

```python
apps = client.applications.list()
app = client.applications.get("APP_UUID")

manifest = client.applications.get_manifest("APP_UUID")
if manifest:
    print(manifest.manifest_version)

bootstrap = client.applications.bootstrap_manifest("APP_UUID")
if bootstrap:
    print(bootstrap.agents_by_id.keys())
```

Pots fer peticions condicionals amb ETag via `if_none_match`; si el backend respon `304`, el client retorna `None`.

---

## Catàleg de models i model-settings

```python
catalog = client.models.list(category="chat", only_active=True)

settings = client.models.list_application_settings(
    application_id=client.application_id,
    category="chat",
)

updated = client.models.update_application_setting(
    application_id=client.application_id,
    agent_ref="default",
    payload={
        "ai_model_id": catalog[0].id,
        "category": "chat",
        "temperature": 0.2,
    },
)

print(updated.ai_model_id)
```

---

## Knowledge incremental

`client.knowledge` permet enviar canvis de coneixement sense pujar documents complets cada vegada. El backend desa events pendents i `sync()` els consolida en records versionats per alimentar cerca, FAQ i agents documentals.

```python
event = client.knowledge.record(
    event_type="backend.endpoint.updated",
    record_key="sports.tournaments.create",
    record_type="backend.endpoint",
    title="Creació de tornejos",
    content="POST /tournaments crea un torneig i accepta registration_deadline.",
    metadata={"domain": "sports", "module": "tournaments"},
)

sync = client.knowledge.sync(async_=True)
results = client.knowledge.search(
    "Com creo un torneig?",
    filters={"domain": "sports"},
    min_score=0.1,
)
print(event.id, sync.updated_records, results.results[0].record.title)
```

Discovery i Knowledge Graph:

```python
discovery = client.knowledge.discover(
    frontend_routes=[
        {
            "path": "/config/tournaments",
            "component": "TournamentConfig",
            "api_paths": [{"method": "POST", "path": "/api/v1/tournaments"}],
        }
    ],
    sync_records=True,
)

graph = client.knowledge.graph(relation_type="USES_CONNECTOR")
print(discovery.nodes, len(graph.edges))
```

---

## Documents i document QA

`client.documents.upload(...)` admet `file_path`, `content` o `text`.

```python
from pathlib import Path

# Fitxer físic
pdf_doc = client.documents.upload(
    file_path=Path("knowledge.pdf"),
    document_type="context_general",
)

# Text lliure
schema_doc = client.documents.upload(
    text='{"name": "Ada", "role": "engineer"}',
    file_name="profile.json",
    mime_type="application/json",
    document_type="schema_contract",
)

print(pdf_doc.document_type, schema_doc.document_type_version)
```

Altres operacions habituals:

```python
items = client.documents.list(limit=20)
detail = client.documents.get(str(pdf_doc.id))
answer = client.documents.ask(str(pdf_doc.id), "Resumeix els punts clau")
```

Upload asíncron amb polling:

```python
accepted = client.documents.upload(
    text="# FAQ async",
    file_name="faq.md",
    mime_type="text/markdown",
    async_mode=True,
)

if hasattr(accepted, "job_id"):
    while True:
        job = client.documents.get_upload_job(str(accepted.job_id))
        if job.status == "completed":
            print("Document llest:", job.document.id if job.document else accepted.document_id)
            break
        if job.status == "failed":
            raise RuntimeError(job.error or "job_failed")
```

---

## Contexts i context sessions

### Contexts persistents

```python
ctx = client.contexts.create(
    name="Context operatiu",
    description="Documents base per al flux",
    documents=[str(pdf_doc.id)],
)

ctx = client.contexts.add_documents(ctx.id, [str(schema_doc.id)])
print(ctx.id, len(ctx.documents))
```

### Sessions de context

```python
session = client.context_sessions.create(
    name="Sessió de proves",
    context_id=ctx.id,
)

client.context_sessions.add_message(
    session.id,
    agent_name="sports_operations_agent",
    query="Quina és la següent acció recomanada?",
    answer="Revisar el calendari i validar la disponibilitat.",
)
```

---

## OCR mapping

`client.ocr.map(...)` crida `POST /api/v1/ocr/mappings` i transforma una imatge, un PDF amb text extractable, un fitxer de text o un document ja persistit en un JSON alineat amb el contracte que li passes.

Exemple amb una imatge local:

```python
from finanfut_sdk import FinanFutClient

client = FinanFutClient()

contract = {
    "type": "object",
    "additionalProperties": False,
    "required": ["competition", "home_team", "away_team", "score"],
    "properties": {
        "competition": {"type": ["string", "null"]},
        "home_team": {"type": ["string", "null"]},
        "away_team": {"type": ["string", "null"]},
        "score": {
            "type": "object",
            "additionalProperties": False,
            "required": ["home", "away"],
            "properties": {
                "home": {"type": ["integer", "null"]},
                "away": {"type": ["integer", "null"]},
            },
        },
    },
}

result = client.ocr.map(
    file_path="acta.jpg",
    profile_key="sports.match_report",
    output_contract=contract,
    instructions="Extreu només dades visibles a l'acta. No inventis valors.",
    confidence_threshold=0.78,
)

print(result.answer.mapped_json)
print(result.answer.validation.status)
print(result.answer.extraction.confidence)
```

Fonts acceptades:

- `file_path`: camí local; l'SDK llegeix el fitxer i l'envia en base64.
- `content`: bytes ja carregats en memòria.
- `content_base64`: contingut base64 o data URL ja preparat.
- `document_id`: reutilitza un document existent de la mateixa aplicació.

Opcions útils:

- `profile_key`: `generic.document_mapping` o `sports.match_report`.
- `include_raw_text_excerpt`: demana un extracte textual quan és operativament necessari.
- `persist_source`: desa la font com a document intern i retorna `document_id`.
- `timeout`: timeout HTTP per a documents grans o models més lents.

La resposta tipada és `OCRMappingResponse`. El resultat útil viu a `result.answer.mapped_json`; valida `result.answer.validation.status == "valid"` abans d'automatitzar accions. Si rep `"needs_review"`, revisa `missing_fields`, `errors`, `warnings` i `field_evidence`.

Els PDFs escanejats no s'han d'enviar com a PDF en v1. Cal convertir la pàgina a imatge i enviar-la amb `file_path`, `content` o `content_base64`.

---

## Data import (CSV/PDF → JSON)

```python
preview = client.data_import.upload_transient_csv(file_path="ingesta.csv")

result = client.data_import.transform_data(
    application_id=client.application_id,
    intent_id="UUID_INTENT_TRANSFORM_DATA",
    transient_csv=preview,
    contract_document_id="UUID_DATA_IMPORT_CONTRACT",
)

print(result.items[0] if result.items else result.response)
```

Per PDF:

```python
preview = client.data_import.upload_transient_pdf(file_path="factura.pdf")
result = client.data_import.transform_document(
    application_id=client.application_id,
    intent_id="UUID_INTENT_TRANSFORM_DOCUMENT",
    transient_document=preview,
    contract_document_id="UUID_DATA_IMPORT_CONTRACT",
)
```

---

## Billing, analytics i access tokens

```python
usage = client.billing.get_usage(period="month")
print(usage.tokens_used, usage.cost)

logs = client.analytics.logs()
requests_trace = client.analytics.requests()
print(len(logs), len(requests_trace))

tokens = client.access_tokens.list()
print(len(tokens.items))
```

---

## Gestió d'errors

L'SDK eleva excepcions tipades (`ValidationError`, `AuthenticationError`, `RateLimitError`, etc.) segons l'estat HTTP i el payload del backend.

Per errors de contracte (`UNPROCESSABLE_OUTPUT_CONTRACT`) retornats com `{"error": "<text>"}`, el missatge complet es propaga en una `ValidationError` perquè no es perdi el detall del contracte fallit.

---

## CLI

```bash
finanfut init
finanfut interact "Hola" --agent-id "APP_AGENT_UUID" --intent-id "INTENT_UUID"
finanfut usage --period month
finanfut agents list
```

Flags globals disponibles: `--api-key`, `--application-id`, `--api-url`, `--config`, `--dry-run`.

---

## Llicència

Aquest projecte es distribueix sota llicència MIT.
