This is a submission for the Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle
What I Built
Sawhorse is a quoting desk for a one-person custom furniture shop.
A maker gets a text like this: "We just bought a place in Catskill. Table for six, maybe eight with a leaf. Walnut if it's not crazy, otherwise white oak. My partner hates glossy finishes. Before Thanksgiving if that's even possible. Budget is around 4–5k. We're about 40 minutes from Hudson."
The tedious part is not writing a polite reply. It is turning that note into hours, board feet, and an honest no. At this shop's rate card, black walnut is $5,592. White oak is $4,438. Thanksgiving falls inside a 10 to 14 week lead time, so the letter says that date is not a promise.
The person who pays is an independent furniture maker, or the same kind of one-person shop in another trade: someone who already has a rate card, quotes from messy client texts, and does not want that text or those rates in a chatbot. A $40/month proposal tool still does not know a trestle base from a pedestal. Pasting the note into a general model produces a retail-shaped number the shop cannot build for.
The sample shop is Mara Ellison Studio in Hudson, New York. Her card is $85 an hour, white oak at $14 a board foot, walnut at $32, and delivery zones for town, the mid-Hudson, and New York City. Two other notes are built in: Jonah's hard-maple desk with a cable hole, which lands at $1,913 against a $2,000 ceiling, and Andre's four-foot bookshelf to Brooklyn, which comes in at $1,837 against "under fifteen hundred."
Demo
The sample desk is here: https://gabuters-sawhorse.surge.sh
Open Priya's note. The weights download into your browser, about 1.1 GB, once, and then the model reads the note on that machine. White oak is the one to send, because walnut is over the range, and the letter says Thanksgiving is inside the lead time. You can change any field before a dollar is calculated. Nothing in the note is uploaded.
A shop install is the same desk with the weights already on the laptop. pnpm dev serves it at http://127.0.0.1:43123. A short note usually reads in under a minute on a 4-core CPU with no GPU. The rate card and the glossary are editable. Saved tickets stay in a JSON file next to the app.
Code
Sawhorse
Sawhorse is a quoting desk for a one-person furniture shop. Paste a client note. An open-weight model on this machine reads it into a job ticket. The shop's rate card, not the model, sets every dollar.
The sample shop is Mara Ellison's, in Hudson, New York. Priya writes from Catskill about a dining table, walnut if it fits, otherwise white oak, no gloss, before Thanksgiving, four to five thousand dollars. Sawhorse prices both woods from Mara's hours and board feet, recommends the one that lands in the budget, and says plainly that Thanksgiving is inside a 10 to 14 week lead time.
Why the open model is the product
Qwen2.5-1.5B-Instruct (Q5_K_M, Apache-2.0) runs in llama.cpp through llama-cpp-python. No API key. The reply is constrained to a JSON schema that has no price field, and the prompt does not include the hourly rate or the lumber prices. A general…
The split that matters:
-
engine/reader.pyasks the model for a job ticket. The JSON schema has a client, a piece, woods, a finish, a delivery zone, a budget, and the client's own words for the deadline. It has no price field. -
engine/pricer.pyis ordinary arithmetic. Lumber is board feet times the species rate. Labor is hours times the shop rate. Delivery is a zone fee. A design drawing is added and, if the shop says so, credited back. -
engine/letter.pymay print a dollar amount only after that math. A test fails if the letter contains any dollar figure that is not on the ticket. -
data/pricebook.default.jsonis Mara's rate card. Edits and saved quotes stay indata/on the machine and are not part of the repo.
# engine/reader.py — the grammar the model is held to
properties = {
"client_name": {"type": "string"},
"piece": {"type": "string", "enum": pieces},
"species_primary": {"type": "string", "enum": species + ["unspecified"]},
"species_alternate": {"type": "string", "enum": species + ["none"]},
"finish": {"type": "string", "enum": finishes + ["unspecified"]},
"delivery": {"type": "string", "enum": zones + ["unspecified"]},
"budget_low": {"type": "integer"},
"budget_high": {"type": "integer"},
"deadline_raw": {"type": "string"},
# no price, no hours, no board feet
}
How I Built It
The open-weight model is Qwen2.5-1.5B-Instruct, quantized Q5_K_M, Apache-2.0, about 1.2 GB. Inference is llama.cpp through llama-cpp-python, on CPU. There is no API key.
create_chat_completion is called with response_format set to a JSON schema. llama.cpp compiles that schema into a grammar, so the reply cannot leave the ticket shape. The prompt lists piece ids, wood ids, finish ids, delivery zones, and the shop glossary. It does not include the hourly rate or the lumber prices. "Catskill" means the mid-Hudson zone. "Hates glossy" means hardwax oil. "Hard maple" means maple. The model cannot anchor on a number it was never shown.
A 1.5 billion parameter model still copies the shape of its examples. After the read, apply_note_guards fixes two high-confidence misses: if the word "leaf" is in the note, the ticket includes a leaf; if the note says the room is "9 feet by 11" and the model copies 9 and 11 into the furniture dimensions, those values are dropped, because that was the room. The maker still sees every field and can change it before pricing.
The desk is a Next.js app. A FastAPI process on localhost holds the weights. The browser talks only to Next, and Next proxies to that process, so a read never leaves the machine. The hosted sample keeps that split, only the process is the browser: llama.cpp runs there, and the rate card is the same Python. The glossary is the niche list a shop will actually edit: towns, finish phrases, species nicknames. The next note uses it. The same weights can take a LoRA later, trained on quotes the shop really sent.
Why Does Open Innovation Matter?
A frontier API would read one of these notes for less than a cent. That is not the bill that breaks a shop quoting fifteen small jobs in a month.
What breaks it is a wrong number, or a leaked one. A general chat model asked to "quote this walnut table" reaches for retail: a round number and a confident email, at a price the shop cannot build for. Sending that loses the job or gives away the margin. The same model does not know that Catskill is about forty minutes from this shop. You can paste a glossary into a hosted prompt. You also paste the client's town, their budget, and sometimes why they need the table by a certain Thursday. In a town this size that note is not anonymous, and the rate card is the business.
The open weight file is what makes the stricter design possible. llama.cpp will bind the reply to a grammar with no price field. The prompt is not given the rates, so there is nothing for the model to repeat. The weights are Apache-2.0, so the desk can be sold without renting the brain. They are small enough that a shop laptop with no GPU can run them. If the app disappeared, the rate card and the saved tickets would still be JSON files, and the model would still be a file beside them. Nobody gets to reprice the inference out from under a $4,000 job.
The glossary is not a fine-tune. It is the adjustment a one-person shop will keep. A closed API does not hand you the weights to train later, and it does not hand you a grammar that forbids a price.
Crashtoberfest? Happy Hacking
Top comments (0)