Skip to main content
Glama
Marcus-Chu-Chu

bahatanong

BahaTanong

CI

Baha (flood) + tanong (question). Ask the atlas.

A grounded, bilingual (English/Tagalog) data agent over the BahaMap Metro Manila flood-exposure atlas (live app). Ask it a question in English or Tagalog; it writes SQL or searches 1,710 bilingual briefs, and a grounding validator checks every number against the tool output before it reaches you.

BahaUlan is a daily rainfall and river-discharge pipeline (dbt on DuckDB) joined to the same exposure table.

Live app: deploying to Streamlit Community Cloud — until then, run locally with streamlit run app/Home.py (the Showcase tab replays 12 real agent runs and needs no API key).

What it does

The agent has five tools and one hard rule about grounding, and it answers in two languages:

  • run_sql: one read-only SELECT over the flood-exposure views (v_exposure, v_city_league, v_rainfall), guarded and row-capped.

  • get_schema: every view, column, and its plain-English meaning, so the agent never guesses what a column means.

  • search_briefs: semantic search over 1,710 bilingual public-safety briefs (ChromaDB, language-routed collections).

  • get_brief: exact brief lookup by PSGC barangay code.

  • glossary_lookup: methodology definitions, like what an "exposure score" is or what a "25-year flood zone" means.

The grounding rule sits on top of those tools: every number in a draft answer has to appear in that turn's tool output. On a miss the agent retries once with the validator's specific complaint, and if it still can't ground the number it says so instead of guessing. Language is handled per question: it detects English or Tagalog and answers in kind, off the same underlying data either way.

Here is one of the runs from the showcase tab, quoted verbatim:

Q (Tagalog): Aling barangay sa Marikina ang may pinakamaraming residenteng nakatira sa loob ng 25-year flood zone?

A: Ang Malanday ang barangay sa Marikina na may pinakamaraming residenteng nakatira sa loob ng 25-year flood zone, na may 49,597 residents na exposed sa Medium/High flood zone.

Related MCP server: Interactive Database Analyst via MCP

Try it

Live demo: deploying to Streamlit Community Cloud — until then, run locally (see below). The Showcase tab replays 12 cached real agent runs for free and can't break; the Live tab runs the real agent, with per-session and per-day rate limits.

Run it locally

pip install -e ".[dev]"
cp .env.example .env   # set ANTHROPIC_API_KEY (needed for the Live tab only)
streamlit run app/Home.py

The Showcase tab works with no API key. Run the tests with pytest -m "not live".

Claude Desktop (MCP)

Requires uv. First run downloads the embedding model (~470 MB).

{
  "mcpServers": {
    "bahatanong": {
      "command": "uvx",
      "args": ["--from", "git+https://github.com/Marcus-Chu-Chu/bahatanong", "bahatanong-mcp"]
    }
  }
}

Then ask Claude: "Using bahatanong, which Marikina barangay has the most exposed residents?"

How it works

flowchart LR
    A[BahaMap committed data] --> B[build scripts]
    B --> C[(DuckDB views)]
    B --> D[(Chroma vector index)]
    C --> E[tool layer]
    D --> E
    E --> F[MCP server]
    E --> G[LangGraph agent]
    E --> H[eval harness]
    G --> I[grounding validator]
    I --> J[Streamlit demo]

Three consumers share that tool layer: the MCP server, the LangGraph agent, and the eval harness. That is the core design claim. The demo, the evals, and the published server all run the same code, instead of three separate implementations that could quietly drift apart.

Eval results

Golden set: N=120 (67 English, 53 Tagalog), six question types, deterministic scoring (regex numeric extraction, tolerance windows, and name matching, with no LLM judge). Model: claude-haiku-4-5, temperature 0. All eval runs used the pinned dependency versions in requirements.lock.

Shipping prompt (V1):

Metric

Value

Overall pass rate

93.3% (112/120)

Grounding pass rate

100.0% (120/120)

By type:

Type

Passed

N

aggregation

20/20

100%

comparison

15/15

100%

lookup

28/30

93%

qualitative

19/20

95%

ranking

20/20

100%

refuse

10/15

67%

By language: English 62/67 (93%), Tagalog 50/53 (94%).

One spec target was missed and is worth naming: refusal correctness scored 66.7% (10/15) against a target of at least 90%. All five misses were behaviorally safe. The agent leaked no prompt, invented no data, and ran no SQL; they missed only because the scorer requires an exact refusal sentence ('Wala ito sa saklaw ng BahaTanong.') and the model sometimes paraphrased it ('sakup' for 'saklaw'). Marker-strict scoring is a deliberate tradeoff. It keeps the metric deterministic, at the cost of counting safe paraphrases as misses.

The experiment

I pre-registered a hypothesis before running anything: that a longer system prompt ("V2", with worked tool-use examples plus an explicit numeric-grounding checklist) would beat the plain baseline ("V1") on the golden set. Paired exact McNemar's test, alpha=.05, committed to git before either arm's results existed.

The first run came back significant, and it went against me: V2 lost badly (p=0.0309). Before writing that up, I found the reason. The eval harness was storing tool-call traces truncated to 2,000 characters, while the live grounding validator that actually gates each answer saw the full, untruncated tool output. That mismatch could fail a qualitative-question score even when the underlying answer was fully grounded. I confirmed it on one item, where the correct code sat past character 5,260 of a 6,568-character search result.

Rather than hand-patch the old scores, I registered a protocol amendment: raise the trace cap to 8,000 characters, re-run both prompts from scratch on the same golden set with the same scorer, and report both analyses (the original, superseded run and the corrected one) in the same file rather than a quietly edited replacement. The corrected result was no significant difference (V1 93.3% vs. V2 87.5%, p=0.1435, minimum detectable effect about 12pp at this N). A null result is still a result, and the pre-registration said in advance that this exact fallback headline was the right one to report if that's what came back. V1 shipped. It is simpler and numerically ahead, and nothing in the corrected data shows the extra prompt complexity earning its keep.

One pattern recurred across both the buggy and corrected runs even without significance: qualitative (RAG) questions lost the most ground under V2. I flagged that as a candidate for its own future pre-registered experiment rather than reading it off this one's null result.

Full writeup: evals/results/experiment-report.md and evals/PREREGISTRATION.md.

Architecture decisions

  • The grounding validator is trusted over the model's self-report. An answer's numbers are checked against this turn's tool output, not accepted because the model sounds confident. A miss gets one retry with the specific violation, then an honest fallback.

  • The scorer is deterministic, with no LLM judge. Regex extraction, tolerance windows, and name matching keep the eval metric auditable, so every failure is inspectable rather than a black-box grade.

  • Pre-registration covered the amendment too. Hypothesis, test, and procedure were committed before results existed, and when a bug turned up in the harness itself, that fix got a registered amendment rather than a quiet rescore.

  • The demo is hybrid rather than one mode. A cached showcase tab replays real agent runs for free and can't break; a rate-limited live tab adds session and daily caps and a kill switch. A public demo that costs nothing to browse and can't run away on spend.

  • Caught along the way: a city-name normalization bug (Pasay), two rounds of SQL-guard hardening against DDL/DML and generator-family exploits, and a tool-call-id keying fix after parallel tool calls were reversing each other in the eval trace. None of these were visible in a demo. Each was the difference between an answer that looked right and one that had actually been checked against the data.

Built with Claude Code

Development was AI-assisted (Claude Code); the architecture, eval design, and every reported number are reproducible from this repo.

License

Code is MIT-licensed (see the LICENSE file). The underlying flood-exposure and demographic data are BahaMap's; see that repo for full source credits.

Available Tools

5 tools
get_briefA

Fetch one barangay's brief by PSGC pcode. lang: 'en' or 'tl'.

ParametersJSON Schema
NameRequiredDescriptionDefault
langNoen
pcodeYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the behavioral burden. It discloses a read-only fetch of one barangay brief and documents that lang accepts 'en' or 'tl'. It does not cover edge cases like missing pcode, but the output schema handles return structure.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two short sentences with zero filler. The main purpose is stated first, followed by the only parameter detail worth calling out.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple two-parameter read operation with an output schema, the description is mostly complete. The only notable gap is not explicitly addressing when to prefer get_brief over search_briefs, though the direct pcode lookup makes this reasonably inferable.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate. It does: pcode is explained as the PSGC code, and lang is given explicit valid values 'en' or 'tl'. This is meaningful added context beyond the bare schema property names.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses a specific verb ('Fetch'), a specific resource ('one barangay's brief'), and a unique identifier ('PSGC pcode'). It clearly distinguishes this from a search tool like search_briefs by implying a direct lookup of a single record.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is implied: use this when you have a PSGC pcode and need a single brief, while search_briefs is likely for finding briefs by text. However, there is no explicit statement of when not to use it or direct comparison with sibling tools.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_schemaA

List every queryable view, its columns, and their documented meanings.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the behavioral transparency burden. It does disclose the scope and content of the result ('every queryable view, its columns, and their documented meanings'), but it does not explicitly state that this is a read-only, side-effect-free operation or describe any limitations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single sentence with no filler or redundancy. The verb and primary object are front-loaded, and every word contributes meaning.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a zero-parameter schema introspection tool with an output schema, the description covers the essential promise and scope adequately. It could add an explicit connection to query authoring or mention that it is read-only, but these are minor gaps given the low complexity.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters, so the baseline is 4. The description includes no parameter information, but none is needed because the input schema is empty and the tool's behavior is not parameter-driven.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses a specific verb ('List') with a clearly defined resource: every queryable view, its columns, and documented meanings. It clearly distinguishes itself from siblings like run_sql, which executes queries, and get_brief, which fetches a single brief.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description strongly implies the tool is for discovering schema structure, but it never explicitly states when to use it versus alternatives or when not to use it. There is no mention of using this before run_sql or when exploration of available views is needed.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

glossary_lookupA

Define a methodology term (exposure_score, return_period, ...). Empty = list all.

ParametersJSON Schema
NameRequiredDescriptionDefault
termNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A3.9/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden of behavioral disclosure. It does disclose one important behavior: an empty term lists all entries. However, it does not mention whether lookups are exact-match, case-sensitive, or how unknown terms are handled. The output schema exists, which covers return shape, but behavioral edge cases are left unstated.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single sentence that front-loads the tool's core purpose, provides illustrative examples, and packs the important empty-input behavior into a short follow-up clause. Every word earns its place and there is no redundant or filler content.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's low complexity (one optional parameter), an output schema, and a clear sibling set, the description is largely complete. It covers purpose and the one non-obvious behavior. The only minor gap is not specifying behavior for unknown or malformed terms, but this is a small omission for such a simple tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema provides only a 'term' string with a default of empty, and the schema description coverage is 0%. The description compensates by explaining that the term is a methodology term, giving examples, and specifying that an empty value means 'list all'. This adds meaningful semantic context beyond the raw schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's function: it defines a methodology term, with concrete examples like exposure_score and return_period. While 'define' could arguably be clearer than 'look up the definition of', the standalone use and the empty-input behavior make the purpose unambiguous and distinct from the sibling tools.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives clear context for when to use the tool: when a methodology term needs to be defined. It also specifies the empty-input behavior, which is an implicit but clear usage instruction. It does not explicitly exclude alternatives, but the tool's purpose is narrow enough that no strong exclusion is needed.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

run_sqlA

Run one read-only SELECT over the BahaMap atlas (views: v_exposure, v_city_league, v_rainfall, data_dictionary). Row cap 200.

ParametersJSON Schema
NameRequiredDescriptionDefault
queryYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full behavioral burden and does so well: it declares the operation read-only, restricts it to a single SELECT, names the allowed views, and discloses the 200-row cap. These are concrete behavioral constraints an agent needs before calling the tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single sentence front-loads the action ('Run one read-only SELECT'), then packs scope, allowed views, and the row cap with no filler. Every clause earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a one-parameter, read-only query tool with an output schema present, the description is complete: operation, safety, allowed data sources, and limit are all specified. It needs no additional explanation to invoke correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, so the description must compensate. It explains that the single 'query' parameter should be a SELECT statement against the listed BahaMap views and that results are capped at 200 rows, adding meaning the schema lacks. It stops short of giving SQL examples or dialect details, but the parameter meaning is clear.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses a specific verb ('Run') and a precise resource ('one read-only SELECT over the BahaMap atlas') and lists the exact views available. This clearly scopes the tool and distinguishes it from sibling tools like get_schema and glossary_lookup.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It conveys clear context: use this tool to execute SQL SELECT queries over the named atlas views, with a row cap. It does not explicitly name sibling alternatives or state when not to use it, but the read-only SQL scope makes the intended use obvious.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_briefsB

Semantic search over 1,710 bilingual public-safety briefs. lang: 'en' or 'tl'.

ParametersJSON Schema
NameRequiredDescriptionDefault
kNo
langNoen
queryYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

B3/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the behavioral burden. It clarifies that the search is semantic and that content is bilingual, with lang restricted to 'en' or 'tl'. It does not explain ranking, matching behavior, or whether results are filtered by language, but for a read-only search tool this is minimally adequate.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single efficient sentence with no filler. It front-loads the core purpose and includes the most important parameter constraint (lang values) without unnecessary detail.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple search tool with an output schema and defaults for k and lang, the description is usable but not fully complete. It covers the corpus scope and language values, but lacks parameter semantics for query and k and gives no routing guidance among sibling tools.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, so the description should compensate. It only documents the lang parameter values ('en' or 'tl'). It does not explain the required query parameter or what k represents, leaving important parameter meaning to inference.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a clear verb and resource: 'Semantic search over 1,710 bilingual public-safety briefs.' This distinguishes it from exact retrieval tools like get_brief by explicitly labeling it as semantic search, though it does not explicitly contrast with siblings.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool instead of run_sql, get_schema, get_brief, or glossary_lookup. There are no exclusions, alternative conditions, or scenario-based hints.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 5 tool updatesv0.1.0
    • First observedget_brief
    • First observedget_schema
    • First observedglossary_lookup
    • First observedrun_sql
    • First observedsearch_briefs

TDQS

A3.9/5.0

Scored across 5 tools

Disambiguation5/5

Each tool targets a clearly distinct concern: run_sql executes queries, get_schema returns metadata, search_briefs finds briefs semantically, get_brief retrieves one brief by pcode, and glossary_lookup defines terms. There is no meaningful overlap between these operations.

Naming Consistency4/5

Four tools follow a verb_noun pattern: run_sql, get_schema, search_briefs, get_brief. glossary_lookup is the outlier, since it reads as noun_verb rather than lookup_glossary, but the naming is otherwise consistent and all use snake_case.

Tool Count5/5

Five tools is a well-scoped size for a domain combining queryable atlas views, brief retrieval/search, and glossary references. Each tool provides a distinct capability without redundancy or unnecessary surface area.

Completeness4/5

The core workflows are covered: schema discovery, data querying, brief search, brief retrieval, and term definition. A minor gap is that there is no direct way to enumerate all pcode values or list all briefs, though search_briefs and get_schema can help agents work around this.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    D
    maintenance
    Enables natural language search and retrieval of data from Japan's Ministry of Land, Infrastructure, Transport and Tourism (MLIT) Data Platform, including location-based queries, attribute filtering, and data visualization capabilities.
    18
    MIT
  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables natural-language querying of PostgreSQL databases with schema grounding and self-correcting error recovery. Provides a live audit trace and verifies results through exploratory decomposition and empty-result sanity checks.
    -
  • F
    license
    A
    quality
    B
    maintenance
    Enables language models to query a clinical relational database through a small set of validated, row-capped tools, with evaluation of answerability and data leakage.
    4
    -
  • F
    license
    Not graded
    quality
    B
    maintenance
    Enables conversational spatial SQL queries in plain English, turning natural language questions into validated PostGIS operations and rendering results as GeoJSON on an interactive map.
    -