The shortest path from a Turkish PDF table to an answer
Tablo2SQL-TR is an application that runs on your own computer and opens in your browser. It reads the tables in Turkish PDF and HTML documents, turns them into a clean database, and lets you ask that database questions in everyday language. You do not need to know SQL, Excel formulas or programming. You type a question and get an answer taken from the actual values in the tables.
In short: it turns numbers locked inside reports into information you can query.
Public bodies, municipalities and companies publish much of their information as PDF reports: activity reports, budget execution tables, financial statements, statistical bulletins, fee schedules. Getting information out of these documents is still largely manual work.
An example: you have an 80-page municipal activity report. The question is simple: “What share of the 2024 personnel budget was actually spent?” The answer sits in a table somewhere in that report. But:
1.234.567,89. Tools that assume English formatting either
cannot read them or silently turn them into a different number.%4,5, dates like 15.03.2025.Anyone who has pasted such a table into Excel knows how it ends: shifted columns, numbers stuck as text, hours of clean-up. General-purpose AI chat tools, on the other hand, often answer such questions with confident-sounding but invented numbers, and do not show where the answer came from.
We measured it: when the same tables are loaded without handling the Turkish number format, about three quarters of the answers break even when the SQL queries themselves are perfect. Tablo2SQL-TR was built to close exactly this gap.
Tablo2SQL-TR turns the job into three steps:
You can download the resulting database as Excel, CSV, SQLite or a SQL dump and use it in other tools.
1.234,56, (12,5), %3,4,
Ocak 2024, the dotted/dotless İ/ı, and
suffixed names such as “İzmir’in” are handled correctly.This guide walks you from the first launch to advanced use. Everything works without AI. Connect Claude, OpenAI or a local Ollama model and almost every corner of the app gets smarter.
About the screenshots: the AI screenshots were taken against a test server that imitates the real APIs, so AI-generated text starts with
[MOCK]. A real model will word things differently, and in more detail.The sample documents are Turkish, so table contents, column names and sample questions stay in Turkish even when the interface is in English.
Download the project and open the folder. Double-click the launcher for your operating system:
| System | File | Note |
|---|---|---|
| Windows | baslat.bat |
If SmartScreen warns you: More info → Run anyway |
| macOS | baslat.command |
The first time: right-click → Open (Gatekeeper) |
| Linux | baslat.sh |
Run chmod +x baslat.sh if needed |
The launcher takes care of the rest:
.venv) and installs
any missing packages.kisayol_olustur.* files.The first launch can take a few minutes while packages install. Later launches take seconds.
The screen has two parts:
The app ships with six synthetic sample documents: a municipal activity report, provincial energy consumption, a company’s financial report, water statistics, a price-index bulletin and a fee schedule. The knowledge base starts pre-loaded with them, so you can go straight to tab ③ and ask a question without uploading anything.
Language / Theme. Türkçe ⇄ English and Light ⇄ Dark. Your choices are remembered.
AI engine. The top shows the status of the three providers (Ollama, OpenAI, Claude: Connected · N models or Not connected). 🔌 Model connections opens the connections panel (see Section 6). The Default model list holds the rule-based engine plus every synced model of the connected providers. The model you pick is used in every tab that has no choice of its own. The badge below shows whether AI is on and the session’s usage counter. Where AI is used lists every place the AI touches.
Agent.
The last two switches are only active when an AI provider is selected.
Knowledge base. Shows how many tables there are and where the database file lives. The Reset with samples, Empty and download buttons in four formats are here too.
auto (recommended): tries pdfplumber for ruled
tables first and falls back to Camelot-stream.pdfplumber: ruled tables.camelot-lattice: ruled tables, alternative engine.camelot-stream: unruled, whitespace-aligned
tables.tr (the correct one):
1.234,56 → 1234.56, (12,5) → −12.5,
%3,4 → 3.4.en: assumes English number format. For comparison.text: keeps numbers as text. For comparison.Each table opens in its own panel. You see the raw grid, with merged cells highlighted, next to the resolved headers, each column’s type and unit, and the schema that will be created:
What to look for here:
butce_2024 and
gerceklesme_2024.toplam_satiri = 1, so
SUM() does not double-count.If everything looks right, click ⬇️ Import all into the database. Importing the same document again replaces the old copy, so tables are never duplicated.
This tab shows each table in the database: its source, columns, types, units and data. It also shows the schema description that is sent to the model. With AI on, the description also includes a table summary and column descriptions.
If you know SQL, the SQL console (read-only) lets
you run your own SELECT queries. It rejects anything that
would modify the database.
Downloading the database. You can export the finished knowledge base in four formats:
| Format | When to use it |
|---|---|
| SQLite (.db) | For DB Browser, DBeaver, Python or R. Includes the metadata tables
(_tablolar, _sutunlar). |
| Excel (.xlsx) | One sheet per table. Numbers come through as real numbers, not text. |
| CSV (.zip) | One CSV per table. Saved as UTF-8 with BOM, so Turkish characters survive in Excel. |
| SQL dump (.sql) | For loading into another database or putting under version control. |
Tip: a value that a Turkish document shows as
1.234,56is stored as the number1234.56. Excel displays it in your locale’s format, and sums and formulas just work.
Go to tab ③ and type your question:
İzmir’in 2024 elektrik tüketimi ne kadar? (“What was İzmir’s electricity consumption in 2024?”)
Small badges above each answer show the status (answered / unanswerable), the number of attempts, the engine used and the time taken. The generated SQL sits below. Expand Agent steps to follow what the system did:
'15.03.2025', a number such as '1.234,56', or
a column that is not in the schema.Perhaps the most important feature is that the system knows when it does not know. The sample documents do not contain Mersin:
The system looks up the value from the question (“Mersin”) in the database. If it is missing, or the query returns nothing, the system does not invent a number. It abstains and says why. On the test set it correctly abstains on 90.9% of unanswerable questions. Its false-alarm rate on answerable questions is 5.7%.
15.03.2025 or 1.250; the system converts
them.The app works fully without AI. The rule-based engine reaches 92.9% execution accuracy on the test set. Turning AI on adds:
🔌 Model connections in the sidebar opens one panel that manages all three providers:
| Provider | Needs | Where does the data go? | Cost |
|---|---|---|---|
| Ollama (local) | Ollama installed and running | Never leaves your computer | Free |
| OpenAI (cloud) | API key (+ optional server URL) | OpenAI or an OpenAI-compatible server you choose (OpenRouter, vLLM, LM Studio…) | Pay per use |
| Claude (cloud) | API key | Anthropic API | Pay per use |
Connecting a cloud provider. Paste the key into
API key and click Save and sync. The app saves
the key and immediately fetches the available models from the provider’s
API. The card turns to Connected · N models and shows only a
masked form of the saved key (e.g. sk-…F1cA). Get a key
↗ opens the provider’s key page. To use another OpenAI-compatible
server, enter its address under Server URL (optional).
Disconnect deletes the key and the model list; model
choices for that provider fall back to the rule-based engine.
The model list updates itself. You never type a model name. At every launch the app checks the connected providers and re-syncs if the list is empty or older than 12 hours. Sync models refreshes it right away. Show models lists what is available now.
Then 🔌 Test connection in the sidebar sends one tiny request to confirm everything works.
A different model per tab. Every tab starts with a Model for this tab picker. By default it uses the sidebar model; you can, for example, use a fast, cheap model for table suggestions and a stronger one for asking questions.
The settings file. All settings (API keys, Ollama
address and device, language, theme, model and tab choices) live in
ayarlar/tablo2sql-ayarlar.json in the app folder. To move
them to another computer, copy the file to the same place in the app
folder there, or use Download settings file and
Load settings file… in the panel. The file holds your
keys in plain text: do not share it or email it. If you put the key in
.env instead (ANTHROPIC_API_KEY,
OPENAI_API_KEY), the app reads it from there and the card
shows “(environment variable)”.
http://localhost:11434 automatically; use Change server
address for anything else.Which model? Start with
qwen2.5-coder:7b(about 4.7 GB, runs in 8 GB of memory). With 16 GB on the graphics card,qwen2.5-coder:14borgpt-oss:20bis noticeably more accurate. For free-form Turkish questions trygemma3:12borqwen3:8b. Measure which one works best on your documents with Model comparison in the Evaluation tab (Section 8).
A summary of Where AI is used:
① Extraction: AI suggestions. For each table, the AI proposes:
il_elektrik_tuketimi)Applying them is optional, controlled by Apply AI suggestions on import. Deterministic extraction always remains the primary source. Applied summaries and descriptions go into the schema description, which directly helps SQL generation.
③ Ask: suggested questions. The AI suggests questions that use the actual values in your database. Click one to ask it right away, or use 🔄 New suggestions for another set.
③ Ask: self-correcting SQL. In the example below the
model wrote the date in Turkish format on its first try
('15.03.2025'). The static check flagged it as a
TARIH_BICIMI (date format) error and fed back the hint
“dates are stored in ISO format”. The second attempt ran correctly:
Other AI features:
Cost control. The sidebar status card shows the number of requests and tokens used in the session. AI responses are cached on disk, so you never pay twice for the same table or error.
With a cloud provider (Claude/OpenAI), the following data goes to that provider:
| Feature | What is sent |
|---|---|
| SQL generation | The question + the schema: table/column names, descriptions, up to 25 distinct values per text column, and sample values |
| Table suggestions | Table caption, column information, the first 5 rows |
| Phrased answers | The question, the SQL and the first 20 result rows |
| Error explanation | The error record: question, SQL, error message |
| Evaluation analysis | Question/SQL/answer of the failed examples |
If you work with confidential documents, use Ollama. Your data stays on your machine.
Every mistake the system makes is logged with its type. This tab shows those records as cards:
| Type | Meaning | Typical example |
|---|---|---|
SEMA_ESLEME (schema linking) |
Table/column does not exist | tuketim instead of tuketim_2024 |
DEGER_ESLEME (value linking) |
The value is written differently in the database | “izmir” ↔︎ “İzmir”, “Istanbul” ↔︎ “İstanbul” |
BIRIM (unit) |
Unit confusion | Asked in million TL, answered in thousand TL |
TARIH_BICIMI (date format) |
Turkish-format date | '15.03.2025' instead of '2025-03-15' |
SAYI_BICIMI (number format) |
Turkish-format number | '1.234,56' compared as text |
SAYISAL_AKIL (numeric reasoning) |
Calculation error | Wrong denominator in a percentage change, counting the “Toplam” row twice |
SOZDIZIMI / YURUTME (syntax /
execution) |
The SQL did not run | Missing parenthesis, wrong function |
BOS_SONUC (empty result) |
The query returned nothing | Wrong filter value |
CEVAPSIZLIK (abstention) |
Wrong abstention decision | Rejecting an answerable question, or the reverse |
With AI on, each card gets an 🤖 Explain this error with AI button. The explanation comes under three short headings: What happened? Why? How to fix it?
This tab measures the system on the TR-TabloSQL test set. The set has 99 questions: 64 for development, 35 held out for testing, 11 unanswerable, across 20 categories. The metric is execution accuracy (EX): the result of the generated SQL is compared with the result of the gold SQL, within a small numeric tolerance.
tr, en
and text side by side to see the effect of Turkish number
format for yourself. With the rule-based engine, EX is
92.9% for tr, 29.3% for en and 13.1% for text.Model comparison at the top of the tab measures several models side by side on the same question set. Accuracy here is checked against the expected answer, not a gold SQL, so you can use it with your own documents too.
The screenshot was taken with the test server; the “mock” models’ results do not reflect real model performance.
The heavy set has five levels:
| Level | Measures | Example |
|---|---|---|
| L1 | Single-value lookup | “Akköprü Belediyesinde 2024 yılında Emek Mahallesinin nüfusu ne kadardır?” |
| L2 | Aggregation, filters, ranking, unit conversion (with total-row traps) | “… tüm gider türleri için toplam ödenek ne kadardır?” |
| L3 | Multi-step computation: ratios, percentage change, shares, rank, second highest | “… nüfusu 2023’ten 2024’e en fazla oranda artan mahalle hangisidir?” |
| L4 | Cross-document: comparing organisations, differences across units | “2024 yılında Yeşilvadi ile Bademli arasında toplam gerçekleşme farkı kaç TL’dir?” |
| L5 | Traps and unanswerable: entity of another organisation, missing year, organisation or indicator | “Kartaltepe Belediyesinde Orhangazi Mahallesinin nüfusu ne kadardır?” (Orhangazi belongs to another municipality) |
About 20% of the questions are in English, Turkish without diacritics
(kayali ilcesinin nufusu) or contain typos. The table shows
Accuracy %, accuracy per level, Unanswerable→abstain %
and Silent wrong % (questions the system answered
confidently but wrongly). The rule-based engine scores 13% on this set;
that is by design, the set is built to make the differences between LLMs
visible.
From the command line:
python pilot/pilot_calistir.py --kume agir_hizli --claude # newest Claude model
python pilot/pilot_calistir.py --kume agir --ollama qwen2.5-coder:7b --cihaz gpu --sinir 100Keys and addresses are read from the settings file. To regenerate the
set or make it larger:
python agir_kume/uret.py --tekrar 6.
The whole interface is available in Turkish and
English. Switching the language also switches the menus, agent
steps, error explanations and number formats (1.234,56 ⇄
1,234.56). AI responses come back in the selected language.
The database content stays in the language of the source documents
(Turkish).
The dark theme is easier on the eyes in long sessions:
Your language, theme, provider and model choices are saved in
outputs/.arayuz_ayarlari.json and restored at the next
launch. API keys are never written to this file.
“Python not found”. The launcher offers to install it. If that fails, install 3.10 or later from python.org. On Windows, tick “Add python.exe to PATH” in the installer.
The browser did not open / the port is busy. The
launcher searches for a free port starting at 8501. The address is
printed in the terminal window, for example
http://localhost:8502. To use a specific port, run
python scripts/baslat.py --port 8600.
No tables were found in my PDF. - The document may
be scanned (images only). Add a text layer with an OCR
tool first, for example ocrmypdf. - If the table has no
ruling lines, switch the extractor to
camelot-stream.
Numbers stayed as text. Make sure the number-parsing
mode is tr. If the raw grid shows “unparsed” warnings,
those cells use an unusual notation (e.g. 1 234,5 or
—).
The model list is empty. - Claude/OpenAI:
the key may be wrong (401). The card in the Model connections panel
shows the error; save the key again and click Sync
models. - Corporate network: a proxy may be blocking
the API address. - Ollama: is Ollama running? Try
ollama list. The default address is
http://localhost:11434; if yours differs, use Change
server address on the Ollama card. If the app runs inside Docker
the address is usually
http://host.docker.internal:11434.
I chose GPU and the model will not load. The graphics card does not have enough memory for the model. Switch the device to Auto or pick a smaller model.
My English question was not understood. The rule-based engine is built for Turkish questions. Turn on an AI provider for English.
The answer is wrong but the system sounds confident.
Expand the agent steps and look at the generated SQL. Usually there is a
mismatch between a phrase in the question and a table or column name.
With AI on, the explanation in the error catalog helps too. Errors from
questions asked in the UI are logged to
outputs/arayuz_hatalari.jsonl, and evaluation errors to
outputs/hata_gunlugu.jsonl. Both make good bug reports.
All experiments also run without the interface. Use the virtual
environment’s Python: .venv\Scripts\python on Windows,
.venv/bin/python elsewhere.
# End to end: samples → databases → test set → evaluation → HTML report
python scripts/run_all.py
python scripts/run_all.py --hizli # tr mode only ("hızlı" = quick)
python scripts/run_all.py --claude # + Claude (model picked automatically)
python scripts/run_all.py --openai gpt-4o-mini # + a specific OpenAI model
python scripts/run_all.py --ollama # + the first installed Ollama model
python scripts/hata_enjeksiyonu.py # error-catalog detection rate on 159 perturbations
python scripts/makale_sayilari.py # every number in the paper, with confidence intervals
python -m pytest tests/ -q # 60 tests
python agir_kume/uret.py # builds the heavy LLM test set (52 documents, 767 questions)
python pilot/pilot_calistir.py --kume agir_hizli --ollama qwen2.5-coder:7b # measure one model
# A test server for trying the AI features without a key
python tests/mock_llm_server.py # imitates the Anthropic/OpenAI/Ollama APIs (port 8765)
# In the app: Model connections → Ollama address http://localhost:8765,
# OpenAI server URL http://localhost:8765/v1 and key "test"
# (for Claude, in .env: ANTHROPIC_BASE_URL=http://localhost:8765 ANTHROPIC_API_KEY=test)Results are written to outputs/:
basarim_raporu.html: the performance report; open it in
a browser.ozet.jsonsonuclar/: per-question results.hata_gunlugu.jsonl: the error log.Tablo2SQL-TR is an open research prototype. The sample documents are synthetic and do not represent real institutions. For important decisions, always check the answer against the source table. The SQL and result rows in the agent steps are there for exactly that.