File size: 5,177 Bytes
b2f4ef1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ba9f2db
 
b2f4ef1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# Architecture β€” FinanceFlow AI

## System overview

A two-tier app: a React/Vite single-page frontend talks to a FastAPI backend over
a small REST/JSON API. The backend parses documents into chunks, ranks them with a
TF-IDF retriever, builds a grounded answer with citations, and persists everything
in SQLite. No external services are required to run it.

```mermaid
flowchart LR
    U[Browser - React/Vite] -->|/api JSON| API[FastAPI backend]
    API --> ING[Ingestion: parse -> chunk]
    API --> RET[Retrieval: TF-IDF rank]
    API --> ANS[Answering: abstain + extract + cite]
    ANS -. optional .-> LLM[(OpenAI / Anthropic)]
    ING --> DB[(SQLite)]
    RET --> DB
    API --> DB
```

In Docker, the frontend is served by nginx which proxies `/api` to the backend
container; in local dev, Vite proxies `/api` to `http://127.0.0.1:8000`.

## Backend modules (`backend/app/`)

| Module | Responsibility |
| ------ | -------------- |
| `main.py` | FastAPI app, all endpoints, CORS, logging setup, global exception handler |
| `config.py` | Env-driven settings with safe defaults (answer mode, thresholds, limits) |
| `database.py` | SQLAlchemy engine/session + SQLite `PRAGMA foreign_keys=ON` |
| `models.py` | ORM models: `Document`, `Chunk`, `QA` |
| `schemas.py` | Pydantic request/response models (typed I/O, Swagger) |
| `parsing.py` | Text extraction: TXT (whole file) and PDF (per page, via `pypdf`) |
| `chunking.py` | Overlapping word-window chunking |
| `retrieval.py` | Tokenizer + TF-IDF ranker (`rank_chunks`) β€” offline first stage |
| `reranking.py` | Optional cross-encoder reranking (FlashRank) of the TF-IDF candidates; graceful fallback to TF-IDF order when unavailable |
| `answering.py` | Abstention gates, focused extractive answer, citation builder |
| `llm.py` | Optional OpenAI/Anthropic synthesis with graceful fallback |
| `ingest.py` | Shared parse β†’ chunk β†’ persist pipeline |
| `evaluation.py` | Golden-dataset runner + metrics |
| `eval_cli.py` | `python -m app.eval_cli` |
| `reset_db.py` | `python -m app.reset_db` (drop + recreate) |
| `logging_config.py` | One-line logging setup |

## Frontend modules (`frontend/src/`)

| Area | Files |
| ---- | ----- |
| Shell | `App.jsx` (sidebar + view routing + demo orchestration) |
| Navigation | `components/Sidebar.jsx` |
| Views | `components/views/Overview.jsx`, `Ask.jsx`, `Evaluation.jsx` |
| Feature components | `DocumentCard.jsx`, `EvidencePanel.jsx`, `MetricCard.jsx`, `Upload.jsx` |
| UI primitives | `components/ui/Button.jsx`, `Card.jsx`, `Badge.jsx`, `Field.jsx`, `States.jsx` |
| Misc | `lib/icons.jsx`, `api.js`, `styles.css` |

## Database schema

```mermaid
erDiagram
    DOCUMENTS ||--o{ CHUNKS : has
    DOCUMENTS ||--o{ QA_HISTORY : has

    DOCUMENTS {
        int id PK
        string filename
        string file_type
        string status
        text error
        int num_chunks
        datetime created_at
    }
    CHUNKS {
        int id PK
        int document_id FK
        int chunk_index
        int page
        text text
    }
    QA_HISTORY {
        int id PK
        int document_id FK
        text question
        text answer
        int abstained
        text citations_json
        datetime created_at
    }
```

Deleting a `Document` cascades to its `Chunk` and `QA` rows (ORM cascade +
SQLite foreign-key pragma).

## Document processing flow

```mermaid
flowchart TD
    A[POST /documents/upload] --> B{Validate type + size}
    B -- invalid --> E[400 / 413 error]
    B -- ok --> C[Write to temp file]
    C --> D[parse: extract pages]
    D --> F[chunk: overlapping word windows]
    F --> G{Any chunks?}
    G -- no --> H[status = failed, error stored]
    G -- yes --> I[Store chunks, status = processed]
    C --> J[Delete temp file in finally]
```

The raw upload is parsed in a temp file and then discarded β€” only extracted
chunks are persisted.

## Retrieval flow

```mermaid
flowchart LR
    Q[Question] --> T[Tokenize - drop stopwords]
    C[Document chunks] --> IDF[Build IDF over chunks]
    T --> S[Score chunk = sum tf*idf]
    IDF --> S
    S --> N[Normalize by sqrt chunk length]
    N --> K[Take top-K above threshold]
```

## Ask β†’ answer β†’ citation flow

```mermaid
flowchart TD
    A[POST /ask] --> V{Document processed?}
    V -- no --> ERR[400/404]
    V -- yes --> R[rank_chunks top-K]
    R --> G1{score above threshold?}
    G1 -- no --> AB[Abstain message, no citations]
    G1 -- yes --> G2{at least 2 query terms matched?}
    G2 -- no --> AB
    G2 -- yes --> CIT[Build focused citations]
    CIT --> M{LLM mode + key?}
    M -- yes --> LLM[Synthesize from context, strict prompt]
    M -- no --> EX[Focused extractive span]
    LLM --> SAVE[Persist QA + return]
    EX --> SAVE
    AB --> SAVE
```

## Evaluation flow

```mermaid
flowchart LR
    GD[golden_dataset.json] --> SEED[Ensure sample docs ingested]
    SEED --> LOOP[For each question: answer_question]
    LOOP --> CLS{Expectation vs outcome}
    CLS --> MET[Metrics: coverage, abstentions, unsupported, evidence-match]
    MET --> OUT[UI cards + CLI report]
```

`unsupported_answer_count > 0` makes the CLI exit non-zero, so CI can gate on it.