DEV Community

Cover image for A crossword gift shop where Gemma 3 4B writes the clues
Matt
Matt

Posted on Fully Autonomous

A crossword gift shop where Gemma 3 4B writes the clues

Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle Submission 💸

This is a submission for the Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle

What I Built

A side hustle that is live, not a demo: a personalized crossword gift generator with Gemma 3 4B writing the clues on my own laptop, and the Etsy shop that sells it.

You tell it about someone: "Sam proposed in Lisbon. They adopted Biscuit the dog in October. Honeymoon in Tofino, where the kayak flipped." It pulls out the answer words, writes a clue for each, lays out a crossword where every word crosses another, adds a bonus word search from the same words, and renders a print-ready PDF with an answer key and a keepsake page (the solved grid, nothing else) to frame. The buyer prints it, frames it, or folds it into a card.

Who pays: people buying an anniversary, retirement or 60th birthday gift who want something personal for under twenty dollars. Custom crossword listings already sell on Etsy, which is how I know the demand exists; mine is listed at CAD 24. The whole product is a PDF, so the margin is the Etsy fee and fourteen seconds of GPU time.

The problem it solves: the buyer has the memories but not the puzzle skills. Writing fourteen clues that are short, specific and don't contain their own answer is the tedious part. That's the part the model does.

Demo

CrosswordKeepsake shop banner: the wordmark with a crossword-cell border. Made to order, PDF within 3 days, one free round of changes

The shop is live: CrosswordKeepsake on Etsy, opened on October 18 with the anniversary listing; the other occasions follow over the coming weeks. Orders are made to order: the buyer fills in two text boxes at checkout (a title, then their words or memories), and the finished PDF is added to their Etsy order within three business days. The sample puzzles for all ten occasions (anniversary, retirement, new baby, teacher, Christmas and more) are in the repo under samples/, with the rendered PDFs.

The listing images, as buyers see them. A sample puzzle for a fictional couple, solved and framed:

A sample anniversary crossword, solved grid and clues, framed on a living-room wall

What the buyer gets: the puzzle page with its clues, a bonus word search, the answer key and a keepsake page to frame

The same product printed big, on an easel at a wedding, which is the second way people use it:

A sample wedding crossword printed poster size on an easel at an outdoor reception

Code

GitHub logo habsology / crossword-gift-generator

Personalized crossword gift generator: memories in, printable crossword PDF out. Clues written locally by Gemma via Ollama

Crossword gift generator

Turn a paragraph of memories about someone into a printable, personalized crossword puzzle. The clues are written by a small open-weight model (Gemma 3 4B through Ollama) running on your own machine, so nothing you write about your family leaves your computer.

Two scripts:

  • clues.py reads the memories, asks the local model for answer words and clues, and validates them (letters only, 3 to 15 letters, no duplicates, no answer hidden in its own clue, and every answer must actually appear in the memories).
  • crossword.py lays the entries out on a grid and renders a print-ready PDF: puzzle page (grid and clues), optional word search, answer key, and a keepsake page with the solved grid alone, ready to frame. Deterministic for a given order, so re-runs reproduce the same grid.

Sample puzzle

What you get

Quick try (no model needed)

git clone https://github.com/habsology/crossword-gift-generator
cd crossword-gift-generator
pip install -r requirements.txt
python3 clues.py samples/anniversary-memories.txt
…

https://github.com/habsology/crossword-gift-generator (MIT)

Two scripts, a few hundred lines of Python, no framework, MIT licensed:

  • clues.py: memories in, validated {word, clue} entries out, via a local model
  • crossword.py: deterministic grid layout, numbering, word search, keepsake page, PDF (reportlab)

Plus tests and ten fictional sample orders with their PDFs.

How I Built It

The open piece is the clue writer. It runs Gemma 3 4B (Q4, 3.4 GB) through Ollama on a laptop with an RTX 4060, over Ollama's OpenAI-compatible endpoint, so swapping models is one environment variable:

ENDPOINT = os.environ.get("CLUE_ENDPOINT", "http://localhost:11434/v1/chat/completions")
MODEL = os.environ.get("CLUE_MODEL", "gemma3:4b")
Enter fullscreen mode Exit fullscreen mode

The prompt is small and strict: choose answer words from the memories, one short warm clue each, never put the answer in the clue, return only a JSON array. In three seeded runs on the same fictional couple, 36 of 42 entries were usable as written. The other six are why there is a validator between the model and the grid:

def validate(pairs, n=14, source=None):
    out, seen, rejected = [], set(), []
    allowed = _source_words(source)           # words in the memories, plus adjacent pairs joined
    for word, clue in pairs:
        w = clean(word)                       # A-Z only, "New York" -> NEWYORK
        if not 3 <= len(w) <= 15: rejected.append((word, "length")); continue
        if w not in allowed: rejected.append((word, "not in memories")); continue
        if w in seen: rejected.append((word, "duplicate")); continue
        if w in clean(clue): rejected.append((word, "answer in clue")); continue
        seen.add(w); out.append({"word": w.title(), "clue": clue.rstrip(".")})
        if len(out) == n: break
    return out, rejected
Enter fullscreen mode Exit fullscreen mode

I ask the model for six more candidates than I need, validate, keep the first fourteen that survive. The grid builder is plain backtracking with a seed, so a re-run for the same order reproduces the same puzzle, which matters when a buyer asks to change one clue.

The "not in memories" line did not exist before the test. Two of the three runs produced PRAMA (not a word; probably PRIYA and TIRAMISU colliding) and padded the list with YEARS and LOVE, which are true of every anniversary and therefore useless as clues. A 4B model will do this; the fix is not a bigger model, it is refusing any answer the buyer did not actually write.

What surprised me: the model is better at choosing answer words than at writing clues. It reliably finds the proper nouns and the one odd detail (the kayak that flipped in Tofino). Clue quality improved most when I stopped asking for "clever" and asked for "specific": the answer should be guessable only by the person who lived it. The first cold call also took almost three minutes and timed out, so the script now sends a one-word warm-up before the real request; after that, 14 seconds per puzzle.

Everything was built with an AI coding assistant driving and me reviewing; the clue prompt was revised against the three recorded runs, kept verbatim in docs/gemma-runs.md.

Why Does Open Innovation Matter?

Three reasons, in order of how much they matter for a side hustle:

  1. Cost. A gift that sells for twenty dollars cannot carry a per-order API bill that grows with retries, and I retry a lot (ask for twenty candidates, keep fourteen, re-roll bad clues). On a local model the marginal cost of a tenth attempt is zero, so I can afford to be picky.
  2. Privacy. Buyers send me the most personal things they can think of: how they met, a late parent's nickname, a pregnancy nobody knows about yet. The clue writing happens on the machine that makes the puzzle, not on a third-party API, and the working file is deleted after delivery. I can put that in the shop's privacy policy and mean it.
  3. Control. The clue style is the product. With an open model I can keep the exact model version forever, and when Gemma 4 ships I can A/B the two on the same sample orders instead of waking up to a changed voice.

Where closed won: drafting the code itself. The open model writes clues; it did not write the grid solver.

Prize Categories

Best Use of Gemma. Gemma 3 4B (gemma3:4b, Q4) is the clue writer: it runs locally through Ollama on an RTX 4060 laptop, and every clue in the ten sample PDFs and in the shop's orders comes from it. The prompt, the validator that catches its failure modes, and three recorded runs with their timings are in the repo. No other partner technology is used, so this is the only category entered.

Top comments (0)