DEV Community

Tharun
Tharun

Posted on

Gemma Reads the Chat. Python Refuses to Guess.

Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady Submission 🦥

What I Built

A class WhatsApp group is a notice board that rearranges itself every few seconds.

Someone announces an assignment. Three people reply. Someone sends a meme. Another person repeats the announcement. The deadline is still there, somewhere between the good mornings and the messages nobody needs to read twice.

Then there is a message like this:

26/10/2026, 8:05 pm - Ravi (CR): Guys repu 10 ki DBMS lab record submit cheyyali

In Telugu/Tenglish, repu means tomorrow, ki means at, and submit cheyyali means the work needs to be submitted.

The sentence is easy enough to understand. Turning it into a calendar event is not quite so easy. Does 10 mean AM or PM? Is the assignment for me or another student? Is this a new deadline or just someone repeating an announcement?

I built Repu (రేపు = tomorrow) to handle the tedious reading without pretending those questions always have safe answers.

Repu takes an exported WhatsApp .txt chat, written in Telugu script, Romanized Telugu (Tenglish), or English, and turns it into candidate tasks. Each suggestion includes evidence from the original message, so I can check why it was extracted.

The workflow can identify possible assignments, lab records, exams, payments, meetings, and other actionable items. It resolves supported date phrases, flags ambiguous times and ownership, suggests possible duplicates, and lets me review, edit, dismiss, confirm, and export the tasks.

The model proposes the work. Deterministic Python code checks the evidence and handles date calculations. I make the final decision before anything reaches my calendar.

When Repu refuses to guess

The included synthetic WhatsApp conversation contains this message:

Guys repu 10 ki DBMS lab record submit cheyyali

The message was sent on October 26, 2026. Repu resolves repu to October 27, 2026, but it does not silently decide whether 10 means 10:00 AM or 10:00 PM.

Now consider another message:

next Monday fee payment last date, don't forget

Repu can identify the possible deadline, but it flags the meaning of next Monday for human review instead of treating the interpretation as certain.

These are small decisions, but they matter. A reminder at the wrong time is not useful automation. It is another mistake waiting to happen.

Each task has four separate confirmation checks:

  • The task is real.
  • It belongs to me.
  • The deadline is correct.
  • I approve the export.

The .ics export remains unavailable until the required task details are supplied and all four confirmations are true. The backend enforces these checks again when generating the calendar file.

That is the difference between a list generated by an AI model and a workflow I am willing to trust with my calendar.

Demo

Live demo:

The hosted demo configuration is designed to accept only the included fictional sample conversation. It blocks private file uploads and AI inference. Running the actual Gemma extraction pipeline requires a local Repu installation with Ollama.

Code

Source code:

Repu (రేపు)

Local-first task extraction for WhatsApp class groups, in Telugu, Tenglish and English.

CI Python License

Repu (రేపు) means "tomorrow" in Telugu. Repu reads an exported WhatsApp chat, proposes deadlines and tasks with a local open-weight model, verifies every suggestion against the original message, and exports to a calendar (.ics) only the tasks you explicitly confirm. Chat content is never sent to a cloud AI service.

A message like "repu 10 ki DBMS lab record submit cheyyali" ("submit the DBMS lab record tomorrow at 10") is ambiguous: is "10" AM or PM, and is the task yours? Repu does not guess. It asks.

Table of contents

Features

  • Private by design. Inference runs through Ollama on your machine (default model: Gemma 3 4B). Sessions live in memory only, and logs never…

The project in five lines

backend/app/main.py
    FastAPI endpoints, sessions, jobs and demo restrictions

backend/app/services/chat_parser.py
    Parse WhatsApp exports and preserve message timestamps

backend/app/services/extraction_service.py
    Ask Gemma for structured task candidates and validate output

backend/app/services/evidence_verifier.py
backend/app/services/date_resolver.py
    Verify source quotes and resolve dates in Python

backend/app/services/calendar_exporter.py
    Generate calendar files only for confirmed tasks
Enter fullscreen mode Exit fullscreen mode

The application uses a dependency-free HTML/CSS/JavaScript frontend in frontend/index.html. There is no React, Vite, or frontend build step required to run the main application.

The architecture is deliberately a modular monolith: the parser, model adapter, extraction pipeline, date resolver, and calendar exporter have separate responsibilities, but they work together as one local application.

The pipeline

WhatsApp .txt export
        |
        v
Parse messages and timestamps
        |
        v
Gemma 3 4B through Ollama
        |
        v
Validate the model's JSON output
        |
        v
Verify evidence against the original message
        |
        v
Resolve dates and times in Python
        |
        v
Check ownership and suggest duplicates
        |
        v
Review, edit and confirm
        |
        v
Confirmed tasks --> .ics / CSV
Enter fullscreen mode Exit fullscreen mode

The important part is the boundary between the model and the application. Repu never treats a model-generated task as a calendar event simply because the model returned valid JSON.

How I Built It

The stack is Python, FastAPI, Ollama, Gemma 3 4B, and plain HTML/CSS/JavaScript. I used standard-library Python HTTP handling for the Ollama client rather than adding another HTTP client dependency.

Step 1: Parse the chat before asking AI to read it

WhatsApp exports are not always clean, structured data. They can contain different timestamp formats, multiline messages, system notifications, and attachment placeholders.

Repu's parser handles common Android and iOS text exports, UTF-8 and UTF-16 input, different timestamp formats, multiline messages, and system or attachment lines. It also reports parsing warnings.

The default limits are 10 MB per import and 5,000 messages, with messages sent for extraction in batches of 20.

Repu works with the text in the export. It does not analyse the contents of attached images, audio, videos, or documents.

Step 2: Give Gemma one job

I use the open-weight Gemma 3 4B model through Ollama, configured as gemma3:4b.

The extraction service sends timestamped messages to the model in bounded batches and requests structured JSON. Each candidate can contain its title, action type, evidence quote, date phrase, time phrase, ownership estimate, and uncertainty notes.

The model's instructions establish the most important boundary:

- For each task copy evidence_quote EXACTLY from the message text
  (a contiguous substring) and give its message_id.
- Copy date_phrase and time_phrase exactly as written
  (e.g. "repu", "10 ki"). Never compute dates.
  Never invent a time or date: use null.
Enter fullscreen mode Exit fullscreen mode

These are excerpts from the actual extraction prompt in backend/app/services/extraction_service.py.

The distinction is intentional. Gemma can recognise that repu indicates tomorrow and that 10 ki refers to a time. It is not asked to calculate a calendar date or choose AM or PM.

The application validates the response against a strict schema and retries once when the output is invalid. If an individual batch still fails validation, Repu records that failure and can continue with other batches. If every batch fails, the analysis returns an error instead of pretending it succeeded.

Chat messages are also treated as untrusted data, not instructions. A message that asks the model to reveal its system prompt or change its rules should not become an instruction to the application.

Step 3: Verify the evidence in actual Python code

A model can return well-formed JSON and still invent a quotation. A valid response format does not prove that the task exists in the original conversation.

That is why I added evidence_verifier.py.

Here is the core verification function from the project:

def verify_evidence(quote: str | None, source_text: str | None) -> str:
    if not quote or not quote.strip() or source_text is None:
        return MISSING

    if quote in source_text:
        return VERIFIED_EXACT

    q = normalize(quote)
    if q and q in normalize(source_text):
        return VERIFIED_NORMALIZED

    return UNVERIFIED
Enter fullscreen mode Exit fullscreen mode

The function distinguishes an exact match, a match after normalisation, and evidence that cannot be verified.

If the quote does not match the message initially associated with a candidate, the extraction service also tries to locate it in the same batch. A fabricated or unverifiable quote is flagged for review rather than trusted as evidence.

This is a small piece of code, but it changes the role of the model. Instead of asking whether the model sounds convincing, Repu checks whether its proposed evidence exists.

Step 4: Let Python calculate the deadline

Date arithmetic belongs in deterministic code, not in a language model.

The resolver supports phrases such as today, ivala, ఈరోజు, tomorrow, repu, రేపు, ellundi, ఎల్లుండి, and supported weekday names in English and Telugu. It also handles certain numeric dates and relative-day expressions.

The reference point is the timestamp of the message containing the task, not the time at which the user imports the chat.

Part of the resolver's actual logic looks like this:

if relative is not None:
    if ref is None:
        return None, "missing_reference_time", (
            "Message has no valid timestamp.",
        )

    d = ref.date() + timedelta(days=relative)

    if relative >= 1 and ref.hour < 4:
        return d, "ambiguous", (
            "Message sent after midnight; the relative day may mean something different.",
        )

    return d, "resolved", ()
Enter fullscreen mode Exit fullscreen mode

Here, relative has already been determined from the recognised date phrase, and ref is the original message timestamp.

The after-midnight check is important because people do not always use tomorrow in the same way when sending a message at 2 AM. Repu flags that uncertainty rather than blindly applying the arithmetic.

Time resolution follows the same principle. A clear 10 PM can be converted to 22:00. An unsupported time phrase is flagged. A time such as 10 ki without an AM/PM indicator or a clear day-part remains ambiguous.

The interface then lets the user correct missing or uncertain deadline information. Repu would rather ask for clarification than create a confident but incorrect reminder.

Step 5: Keep ownership and duplicates visible

Class-group announcements are not always personal assignments. Some are for the entire class, others are directed at a named student, and some have no clear owner.

Repu distinguishes explicit references to the current user, tasks assigned to someone else, general announcements, and unknown ownership. The optional display-name field helps identify explicit references to the user, but uncertain ownership still requires confirmation.

The same philosophy applies to duplicates. Repu can suggest that two similar tasks might refer to the same deadline, but it does not silently delete one.

Both tasks remain available for inspection. The user decides whether they are duplicates.

Step 6: Make the review screen do the boring work

The interface groups tasks into four tabs:

  • Needs your review
  • Ready to confirm
  • Confirmed
  • Dismissed

Each task displays the extracted title, source quote, sender, message timestamp, evidence-verification status, ownership, deadline, and reasons why it needs review. The date and time phrases are highlighted inside the evidence quote.

The user can edit the title and deadline, select AM or PM when necessary, dismiss an irrelevant task, and restore a dismissed task.

After review, Repu supports:

  • .ics calendar export for timed and all-day events
  • CSV export of confirmed tasks
  • A copyable morning brief
  • A chronological “Coming up” timeline

The export code independently enforces the confirmation checks. The relevant validation is straightforward:

for flag in (
    "task_confirmed",
    "owner_confirmed",
    "deadline_confirmed",
    "export_approved",
):
    if t.get(flag) is not True:
        raise export_rejected(
            f"Task {i} is missing confirmation: {flag}."
        )
Enter fullscreen mode Exit fullscreen mode

This is from backend/app/services/calendar_exporter.py.

The generated calendar contains the approved task title and date/time, not the original chat message. Timed events use floating local time so that the event appears at the same wall-clock time in the user's calendar. When the time is genuinely unknown, Repu does not invent one.

Finding the deadline is only part of the job. Being able to verify it and approve it without re-reading the entire conversation is what makes the workflow practical.

Step 7: Add a morning brief and optional overnight processing

Repu's morning brief includes a deterministic headline counting candidate tasks, dated tasks, and items needing review. An optional two-line summary can be generated by the local model. Those lines are explicitly labelled “AI summary · unverified” so they are not confused with verified task data.

There is also an optional command-line watcher for people who prefer dropping text exports into a folder.

First, create the input directory. The watcher expects an existing folder; it does not create that input folder automatically.

From the repository root in Bash:

mkdir -p ~/repu-inbox

cp sample_data/synthetic_whatsapp_chat.txt \
   ~/repu-inbox/classgroup.txt

python -m backend.app.watch ~/repu-inbox --once
Enter fullscreen mode Exit fullscreen mode

This processes the sample file and writes a Markdown brief at:

~/repu-inbox/repu-briefs/brief-classgroup.md
Enter fullscreen mode Exit fullscreen mode

For continuous monitoring, leave out --once:

python -m backend.app.watch ~/repu-inbox
Enter fullscreen mode Exit fullscreen mode

The watcher checks the top-level .txt files periodically. Press Ctrl+C to stop it.

This is an optional feature; the main web application does not depend on it. The watcher produces suggestions only. It never creates calendar events or .ics files.

One privacy detail matters: overnight briefs deliberately write task titles and source quotes to Markdown files on the user's disk. Keep that output folder private.

Step 8: Run the main application locally

Install Ollama first and make sure it is running. Then, from the repository root:

ollama pull gemma3:4b

python -m venv .venv
source .venv/bin/activate

pip install -r requirements.txt

python -m uvicorn backend.app.main:app \
    --host 127.0.0.1 \
    --port 8000
Enter fullscreen mode Exit fullscreen mode

Open http://127.0.0.1:8000, load the fictional sample chat, preview the import, and start the extraction.

For Windows PowerShell, the repository also includes scripts/run.ps1, which creates the virtual environment, installs the Python dependencies, checks whether Ollama is reachable, and starts Repu.

The first model inference can take time while the model loads. Repu reports batch-by-batch progress and provides actionable errors if Ollama is unavailable, the model has not been downloaded, the output is invalid, or memory is insufficient.

The server binds to 127.0.0.1 intentionally. This is a local application, not a private-chat service intended to be exposed directly to the public internet.

Why Does Open Innovation Matter?

Class WhatsApp groups contain personal information: names, phone numbers, conversations, and assignments people never intended to send to a third-party AI service.

In local mode, Repu routes imported chat text through the local FastAPI process to the configured Ollama endpoint. The Repu application code does not send the chat to a hosted AI API.

The main application keeps session data in process memory, expires sessions after 60 minutes by default, and offers a Clear session action. Its operational logs contain metadata such as message counts, processing duration, error types, and model name, rather than the actual chat content. The main application does not persist imported chats to disk.

Once the model is downloaded, local inference does not require an internet connection to a remote AI provider, an API key, or a per-token payment. The model can also be changed through configuration without replacing the parser, evidence verifier, date resolver, or confirmation workflow.

But I want to be precise about what this proves.

I have not run a controlled comparison against a closed model, so I cannot claim that Gemma is more accurate at Telugu/Tenglish extraction than a hosted alternative. Open weights give me control over where inference runs and how the result is handled. They do not automatically make the model correct.

I have also not independently audited Ollama's own networking or telemetry behaviour. Anyone requiring a strict air-gapped guarantee should verify that separately.

For Repu, open innovation makes the architecture inspectable and the model replaceable. The model can interpret the conversation, but the safety-critical decisions remain in code and with the user.

What I Have and Haven't Measured

The project includes automated tests for parsing, date resolution, evidence verification, extraction, API behaviour, export validation, failure handling, and privacy-sensitive behaviour.

The backend test run currently reports 74 passing pytest tests with one opt-in real-model test skipped. The separate core unit-test suite reports 38 passing tests.

The skipped test requires a real Ollama installation with the model downloaded. These test results therefore do not establish that Gemma's real-world Telugu/Tenglish extraction is accurate. The Node-based frontend smoke tests are also separate and require the frontend development dependencies to be installed.

The project includes a synthetic evaluation dataset with 40 development examples and 110 test examples. On the deterministic resolver evaluation, the code matched all 83 actionable test examples against the expected date, time, and ambiguity labels.

That result needs context. The dataset is template-generated, and the same author wrote the rules and the expected outputs. It demonstrates consistency on the constructed examples, not 100% accuracy on real WhatsApp conversations.

The limitations are just as important as the passing tests:

  • Real-model end-to-end extraction accuracy has not been established.
  • Accuracy on representative real Telugu/Tenglish conversations has not been measured.
  • RAM usage, inference latency, and actual time saved have not been benchmarked.
  • Images, audio, video, and document contents are outside the current scope.
  • The redesigned interface has not been validated through a real-browser Playwright test suite.

The next step is to evaluate the full pipeline on a carefully anonymised, representative collection of messages, measuring missed tasks, false positives, evidence verification, and ambiguous deadlines.

I would rather publish an honest limitation than turn a synthetic test into a misleading accuracy claim.

Prize Categories

  • Best Use of Gemma: Repu uses the open-weight Gemma 3 4B model through Ollama to interpret Telugu, Tenglish, and English class-group messages. Evidence verification, date calculation, ambiguity handling, and calendar approval remain in deterministic code and under the user's control.

Thanks for reading. The goal is simple: let Repu do the tedious reading, keep uncertainty visible, and leave the final confirmation to the person who actually has to submit the assignment.

Top comments (0)