Demo: form answers to an AI-written PDF report by Prabhudutt MishraDemo: form answers to an AI-written PDF report by Prabhudutt Mishra

Demo: form answers to an AI-written PDF report

Prabhudutt Mishra

Prabhudutt Mishra

A demo I built with made-up answers for a sample bakery. It is my own sample, not client work.
What it does, end to end:
Reads one saved set of form answers from a file.
Runs them through a rules table, with short rules like q8 > 14, to pick the recommendations, benchmarks and quick wins that apply.
Sends the answers and the picked items to Google Gemini, which writes a short personal analysis.
Checks the AI's reply in code. Every number in it has to appear in the answers or in the picked items, numbers written as words (like "twelve" or "half") are rejected, and the AI can only use the items the rules picked. If the reply fails, the AI is asked again with the reasons, up to 2 more times. If it still fails, no report is made.
Builds a 3-page PDF with a chart. The numbers in the tiles, chart and cards come from the data, and the one total in the report is added up by the code, not the AI.
Emails the PDF as an attachment over Gmail.
On 22 October 2026 I ran it with gemini-2.5-flash, and the report arrived by email. Each run also feeds the checker a reply with a made-up number, to make sure it still rejects it. The check covers numbers, not meaning, so a real number can still end up in the wrong sentence. That is why a person reads the sample reports.
In this demo the rules and content are CSV files. In a client build they can sit in a Google Sheet you edit.
I use AI tools to speed up the routine parts of the coding, then test everything myself.
The number check, from the demo's code:
_NUMBER = re.compile(r"\d+(?:\.\d+)?")
# Figures written as words slip past the digit check, so they are not allowed at all.
# "one" is left out: it is everyday English ("one cause", "one at a time"), not a figure.
_NUMBER_WORD = re.compile(
r"\b(two|three|four|five|six|seven|eight|nine|ten|eleven|twelve|thirteen|fourteen|fifteen|"
r"sixteen|seventeen|eighteen|nineteen|twenty|thirty|forty|fifty|sixty|seventy|eighty|ninety|"
r"hundred|thousand|million|billion|half|halve|halves|quarter|third|double|doubles|twice|triple|"
r"thrice|dozen)\b", re.IGNORECASE)


def allowed_numbers(sub: dict, blocks: list[dict]) -> set[str]:
"""Every number the AI may use: the answers, plus the selected blocks' values and text."""
allowed = set()
sources = [str(v) for v in sub["values"].values()]
for b in blocks:
sources += [fmt_num(b["value"]), b["title"], b["text"]]
for text in sources:
allowed.update(fmt_num(n) for n in _NUMBER.findall(text))
return allowed


def ai_text_fields(reply: dict):
yield "summary", reply.get("summary", "")
yield "links_between_issues", reply.get("links_between_issues", "")
for i, p in enumerate(reply.get("priorities", []), start=1):
yield f"priorities[{i}].headline", p.get("headline", "")
yield f"priorities[{i}].why", p.get("why", "")


def validate_ai(reply: dict, sub: dict, blocks: list[dict]) -> list[str]:
problems = []
for key, typ in (("summary", str), ("priorities", list),
("links_between_issues", str), ("chosen_block_ids", list)):
if not isinstance(reply.get(key), typ):
problems.append(f"'{key}' is missing or not a {typ.__name__}")
if problems:
return problems

selected = {b["id"] for b in blocks}
for bid in reply["chosen_block_ids"]:
if bid not in selected:
problems.append(f"chosen_block_ids: '{bid}' was not selected by the rules")
if len(set(reply["chosen_block_ids"])) != len(reply["chosen_block_ids"]):
problems.append("chosen_block_ids: contains duplicates")
if not 1 <= len(reply["priorities"]) <= 3:
problems.append(f"priorities: expected 1-3, got {len(reply['priorities'])}")
for i, p in enumerate(reply["priorities"], start=1):
if p.get("block_id") not in reply["chosen_block_ids"]:
problems.append(f"priorities[{i}]: block '{p.get('block_id')}' is not in chosen_block_ids")

allowed = allowed_numbers(sub, blocks)
for field, text in ai_text_fields(reply):
if not str(text).strip():
problems.append(f"{field}: empty")
for num in _NUMBER.findall(str(text)):
if fmt_num(num) not in allowed:
problems.append(f"{field}: number {num} is not in the answers or the content sheet")
for word in _NUMBER_WORD.findall(str(text)):
problems.append(f"{field}: '{word}' is a figure written as a word; use the digits "
"from the answers or the content sheet, or leave it out")
return problems
Like this project

Posted Oct 23, 2026

My own demo with made-up bakery data: form answers, a rules sheet and Gemini become a checked 3-page PDF, sent by email.

Likes

0

Views

0

Timeline

Oct 3, 2026 - Oct 22, 2026