I taught a local model to perform autopsies on dead repositories, then measured how often it lies
What I Built
There is a specific kind of repository that nobody writes about: the one with a README that describes a product, a package.json, two hundred commits, and a last commit from fourteen months ago. It is not archived. It is not deleted. The author moved on and the repository keeps sitting there, describing a world that ended.
I read the theme as giving those repositories a voice. Seance reads the git history of an abandoned project, extracts facts that can be verified, and lets a local open-weight model write about them. The model writes prose. The code decides what is true.
seance facts <repo> # deterministic evidence as JSON, no model
seance report <repo> # autopsy report, optional Markdown output
seance ask <repo> # interactive séance, with a tone flag
Demo
Real output, qwen3:4b running locally through Ollama, on a six-commit repository whose last commit is from August 2025:
AUTOPSY: gudang-tugas
Born : 2025-07-12 (C:16e5f46)
Died : 2025-08-18 (C:d287690) [last commit]
Gap : 14 months since the last commit
Size : 6 commits, 2 authors, 0 tags
What it was
A student task management platform with premium features for academic use.
[F:README.md@2a192ca]
Timeline
2025-07-12 Initial commit with login and registration forms [C:f719143]
2025-07-14 Creation of README.md [C:2e6a90a]
2025-08-18 Final commit creating SECURITY.md [C:d287690]
Cause of death (hypotheses, not facts)
1. The deletion of the config directory in 2025-08-09 may have been a critical
step that led to the project becoming unmanageable ... [C:91fba8f] confidence: medium
2 claims dropped for lacking evidence.
The séance, on the same repository:
you> When did work stop, and what was being changed at the end?
I remember the last commit was on 2025-08-18, creating SECURITY.md. Work stopped
after that, with no further changes for 14 months. [C:d287690, M:gap]
you> Which files mattered at the end?
The ghost does not remember which files mattered at the end. The evidence only
lists file counts by language at HEAD, not which files were important or mattered.
[M:languages]
The second answer is a refusal, and it is the feature. The tool would rather say it does not know than invent a reason.
Code
{% embed Bangkah/Seance %}
MIT, Python 3.12, Docker image and CI included. Real reports and the full 30-claim audit are in demo/README.md. The repository was created on 27 October 2026 for this challenge. Some design patterns come from my earlier entry, TidyMind (a loopback-only model client, bounded retries with varied sampling, terminal-safe output); the code in this repository was written this week.
How I Built It
Open-source AI used: qwen3:4b through Ollama 0.40.11, running locally. The runtime has no third-party Python dependencies; git is the only external tool.
The design bet: model speaks, code verifies
Before the model sees anything, the code builds an evidence pack. Every item has an id the code generated:
- C:<hash>: a commit, with author, date and subject
- F:<path>@<hash>: a file at a specific commit, with a short excerpt
- M:<name>: a measured metric: counts, dates, authors, tags, languages, the month a dependency manifest was last touched
The pack is sent as JSON data, never as instructions, and the model must answer with JSON matching a schema. Then a validator rejects a claim when:
- it cites an id that does not exist in the pack, or cites no id at all
- it quotes something that is not in the cited evidence
- it contains a hash, date or number that does not appear in that evidence
Rejected claims are removed and counted, and the count is printed. After three attempts with different sampling, the command exits with code 3 and prints nothing rather than half a report.
The rule is deliberately structural. I am not asking the model whether it is confident, and I am not asking a second model to grade the first. I am asking: can the sentence you wrote be traced to something this repository actually contains?
Safe to point at a stranger's repository
Repositories can contain instructions for git itself. .git/config can point core.fsmonitor, core.pager or diff.external at a script, and any tool that quietly runs git log in that directory will execute it.
Seance never touches the working directory. It makes a bare clone with git clone --bare --no-local into a temporary directory and reads only that clone. Git runs without a shell, with system and global config disabled, no terminal prompts, no pager, --no-ext-diff, --no-textconv, and a scrubbed environment.
The test suite has a hostile fixture that points those config keys at a canary script, plus a control test that proves the fixture really is hostile by running plain git against it. A fake Ollama that lies (unknown ids, fake quotes, broken JSON) checks that ungrounded output never reaches the terminal, and a snapshot check confirms the source repository is unchanged.
What it got wrong
I took 30 claims (26 from three real reports and 4 from ask answers) and checked every one by hand against git log, git show and the cited evidence. 18 were fully supported, 8 were defensible inferences, and 4 were wrong.
All four cited real ids, real dates and real commit text. The validator passed them because they were traceable. They were false because the model misread the evidence:
1. It called a February commit "the final commit" when the last commit is from October.
2. It said a CI workflow "was removed", pointing at the commit that adds it.
3. It suggested adding a FUNDING.yml file that the last commit had already added.
4. It named an organisation that the cited README never mentions.
This is the honest limit of structural grounding. It makes an invented hash or number impossible. It cannot make a wrong sentence impossible.
It also misjudges timing: on a repository that is only three weeks old it printed "Gap: 0 months" and then a hypothesis about long inactivity. It works when the silence is real.
Numbers, measured
Windows 11 laptop, Docker Desktop, Python 3.12.16, git 2.47.3 in the image, Ollama 0.40.11 with qwen3:4b on the host. Wall times include container start.
| What | Repository | Result |
| --- | --- | --- |
| facts | 2,006 commits (2,000 analysed, cap) | 14.2 s |
| report | 2,006 commits | 30.3 s, 70 evidence items omitted from the 10,000-character budget |
| report | 232 commits | 16.8 s |
| report | 6 commits | 13.2 s |
| tests | 49 tests | 21.0 s, 72% coverage |
The two-thousand-commit case is not slow because the evidence pack is budgeted by characters, not by commit count: the model sees a bounded prompt and the git work is the only thing that scales.
What I would do next
- A semantic check for the four failures above: whether a claim's subject matches the cited commit's subject, which is a much narrower question than "is this true".
- Deterministic handling of 0 months repositories: refuse with "this repository is not dead yet" instead of producing a low-confidence hypothesis.
- More manifests and a real dependency analysis, which needs parsing rather than file names.
Why Does Open Innovation Matter?
A repository's history is not always safe to send to someone else's server. Old commits can contain client names, internal paths, and secrets that were once committed and later removed. With an open-weight model running on my own machine, the history is read by a bare clone in a temporary directory and the only network connection the tool makes is to the Ollama endpoint, which is loopback unless you explicitly pass --allow-container. I did not run it with the network disconnected, so I am not claiming that.
Running the model myself also made the verification loop cheap. The retry logic and the 30-claim audit needed many runs, and none of them cost anything or hit a quota. The model is a command-line option on report and ask (--model), so a different local model can be tried without changing the code, though I only evaluated qwen3:4b.
The Spirit of It
The moment that landed was not a report. It was a refusal.
I asked an abandoned project why its author stopped working on it. It answered:
> I don't remember why the author stopped working on it. The only evidence I have is that Jofi was the sole author in history.
It had every ingredient for a story like the ones I had been half-expecting all week: a lone author, six months of silence, a last commit that only adds a funding file. It could have said burnout, or money, or a rewrite. Instead it said it did not know, and showed me the one fact it did have. That is the ghost I wanted: one that refuses to invent the ending.
1 reaction
StrukLokal: Local-First Receipt OCR for Freelancers
This is a submission for the Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle. I started building StrukLokal on October 12, 2026, the first day of the challenge.
What I Built
StrukLokal is a local-first receipt capture app for freelancers and small businesses that want to turn paper receipts into expense records without sending receipt contents to a hosted OCR service.
It reads receipt images or scanned PDFs and suggests a store name, transaction date, and total. A person can review and correct those fields, approve the transaction, and export approved records to CSV.
The likely users are freelancers, sole proprietors, and small teams who currently enter receipt details by hand. I have not yet tested willingness to pay with users or made a sale, so the commercial opportunity is still a hypothesis.
Demo
There is no hosted demo or video recording yet. The app is designed to run locally:
1. Clone the StrukLokal repository.
2. Follow the Windows setup steps in the README and install the requirements.
3. Run python prepare_models.py while connected to the internet to download and initialize the OCR model.
4. Run python app.py and open <127.0.0.1>.
The model cache is needed after setup. I could not initialize the cached model in my current Windows environment because access to its configuration file was denied, so I cannot provide a verified live OCR demo from this environment. The Flask upload → correction/approval → CSV route has been checked with OCR mocked; this verifies the app flow after OCR returns, not OCR inference itself.
Code
StrukLokal source code on GitHub
How I Built It
StrukLokal uses Flask for its local web interface, PaddleOCR configured for Indonesian text (PP-OCRv6), and a rules-based Python parser for the store name, date, and total.
OCR output is treated as a suggestion. Users can edit the extracted fields and must approve a transaction before it is included in the CSV. The app binds to 127.0.0.1 and keeps transaction data in process memory.
One parser regression test covers a receipt where the tax line is followed by an explicit total:
Subtotal Rp 100.000
Pajak Rp 10.000
TOTAL Rp 110.000
The explicit TOTAL label must take precedence over the Pajak label on the preceding line. Other parser tests cover mixed date formats and preserve the order of date candidates in the OCR text.
On the reviewed commit, all 14 parser unit tests passed. Ruff lint and format checks, Python syntax compilation, and repository hygiene checks passed as well. These checks do not measure OCR accuracy. I have not yet evaluated the app on a labeled set of real Indonesian receipts, so I am not claiming an accuracy percentage.
Why Does Open Innovation Matter?
Using open-source OCR makes local inference possible: the application does not need to upload receipt contents to a commercial OCR API during processing. This matters for financial documents, where users may prefer to keep transaction data on their own machines. It also lets developers inspect and adapt the parser instead of treating extraction as a black box.
There are trade-offs. The model must be downloaded during setup, the first run needs internet access, and model availability alone does not guarantee correct results. I have not yet verified a completely offline run or compared extraction quality and cost against a closed OCR API.
The next step is to test StrukLokal with consented Indonesian receipts, record expected values for each field in advance, and report both correct extractions and failures. I also want to put it in front of potential users and learn whether the workflow saves them enough time to pay for it.
EcoID: An Offline Plant Identifier That Gets You Outside
This is a submission for the Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass.
What I Built
EcoID is an offline-first plant identification tool for field observations.
The idea is simple: take a laptop or phone outside, photograph a real plant, and use a local AI model to get identification candidates. Then look at the plant yourself and verify the result.
EcoID is designed for:
- students learning about local plants
- field researchers and citizen scientists
- gardeners and small-scale farmers
- people who want to explore nature without sending their photos to a cloud service
The current model supports five plant classes:
- Mango — Mangifera indica
- Coconut — Cocos nucifera
- Banana — Musa acuminata
- Papaya — Carica papaya
- Cassava — Manihot esculenta
The AI does not present its output as a fact. It returns ranked identification candidates with confidence scores. The user then chooses whether the result is:
- Verified
- Rejected
- Uncertain
This human-in-the-loop flow matters because a model prediction is only a suggestion until somebody looks at the actual plant.
EcoID stores field observations locally, including the original photo, model prediction, alternative predictions, confidence score, verification status, notes, optional GPS coordinates, capture time from EXIF metadata, and user corrections.
The project is designed to make the computer useful for a short moment, then get the person back to observing the real world.
Demo
EcoID runs locally on a laptop or a phone-accessible local network. There is no public hosted demo because local execution is part of the project's privacy and offline design.
The trained model and downloaded dataset are not committed to the repository. Model weights and image data are kept outside Git because of their size and individual dataset licensing terms.
After training or obtaining the ONNX model locally:
python -m app --model models/ecoid-global-20261010.onnx --open
The app opens at:
http://127.0.0.1:8765
The main flow is:
Capture or upload a photo
↓
Run local inference
↓
Review possible identifications
↓
Verify, reject, or mark uncertain
↓
Save the field observation
↓
Review observations in history and on the local map
Code
The source code is available on GitHub:
github.com/Bangkah/EcoID/tree/main
The source code is released under the MIT License. Dataset images retain their original licenses and attribution requirements.
The main parts of the project are:
- app/ai/ — preprocessing, inference, model contracts, and result post-processing
- app/observation/ — drafts, verification, EXIF metadata, maps, statistics, and exports
- app/storage/ — local SQLite persistence and image storage
- app/ui/ — the local HTTP server and browser interface
- scripts/ — dataset preparation, training, export, evaluation, and benchmarking
- tests/ — unit, integration, offline, and browser tests
The architecture is:
!Image description
How I Built It
Local image preprocessing
Every image follows the same preprocessing path:
1. Decode the image
2. Apply EXIF orientation
3. Convert to RGB
4. Resize to 224x224
5. Normalize with ImageNet mean and standard deviation
6. Convert to a float32 tensor with shape (1, 3, 224, 224)
Keeping preprocessing in one shared module helps avoid train/serve skew between the training pipeline and the production inference path.
Open-source AI and local inference
EcoID uses a MobileNetV3-Small image classifier exported to ONNX and executed with ONNX Runtime on the CPU.
The UI and storage layers depend only on a small model contract, so the inference backend can be replaced without rewriting the rest of the application.
The output is converted into a Top-3 list of candidates. If the confidence score is below the configured threshold, the result is marked as LOW_CONFIDENCE.
The initial threshold was calibrated using validation data. The current configuration is:
threshold = 0.73
On the current validation set, this produced:
- 53.7% coverage
- 97.5% selective accuracy
- 80.0% negative rejection
These numbers are provisional and should be recalibrated when the field dataset grows.
Training results
The first real dataset download produced 2,447 usable licensed images, approximately 370 MB in total. The dataset was collected from iNaturalist using CC0, CC BY, and CC BY-SA images, with attribution metadata preserved.
The validated dataset contains approximately 2,000 training images across the five target classes, 300 validation images, and 149 negative samples across lookalike, non-plant, and low-quality groups.
The dataset pipeline includes:
- license metadata
- author attribution
- photographer-based split separation
- duplicate and near-duplicate checks
- validation and evaluation splits
- negative examples
- dataset structure validation
The initial MobileNetV3-Small training run achieved:
- best validation accuracy: 80.3%
- five target plant classes
- local CPU inference after ONNX export
On a separate 50-image evaluation set (10 images per class), the model achieved:
- Top-1 accuracy: 86.0% (43/50)
- Top-3 accuracy: 94.0% (47/50)
- negative rejection: 77.1% (84/109)
- selective accuracy at threshold 0.73: 94.1%
- coverage: 68.0%
This evaluation set was collected from licensed iNaturalist images and is useful as a reproducible benchmark. It is not a substitute for a field evaluation with locally captured phone photos.
The ONNX export was verified against the PyTorch model:
- maximum logit difference: approximately 1.9e-6
- Top-1 agreement: 20/20 samples
The repository also generates contact sheets for mandatory manual label review. Automated dataset validation passed with zero errors, but a complete human review of all contact sheets remains a dataset-quality task.
Human verification
A prediction is first stored as a temporary draft. It becomes a permanent observation only after the user makes a verification decision.
This prevents an abandoned or uncertain prediction from silently becoming part of the observation history.
The flow is:
photo → identification draft → human verification → saved observation
Draft photos that are abandoned are automatically removed after 24 hours.
Offline storage
The application stores everything locally:
~/.ecoid/
├── ecoid.db
├── images/
├── thumbs/
└── drafts/
The application does not require a cloud API for inference. The browser UI also uses a strict Content Security Policy and does not load external scripts, fonts, or images.
GPS is opt-in. A user can attach a photo's EXIF location, use device geolocation, or enter coordinates manually.
Performance
The model was benchmarked on a Windows laptop using CPU inference:
| Stage | Average |
|---|---:|
| Decode and resize | 6.3 ms |
| ONNX inference | 5.8 ms |
| Softmax and Top-K | 0.4 ms |
| Total | 12.4 ms |
The measured p95 total latency was 33.8 ms per image, below the project's target of 10 seconds per image.
Testing
The project includes:
- Python 3.11 and 3.12 test coverage
- Ruff linting
- byte-compilation checks
- tests with non-UTF-8 default encoding
- browser tests with real headless Chromium
- offline tests that block non-loopback sockets and DNS lookups
- UI tests for capture, verification, history, map, statistics, export, and deletion
The current local test result is:
222 tests passed
25 browser tests skipped unless browser testing is enabled
The CI workflow is defined in .github/workflows/ci.yml.
Why Does Open Innovation Matter?
Plant identification is a good example of why open innovation matters.
A closed cloud API could return a label quickly, but it would also introduce several limitations:
- photos would leave the device
- the application would depend on an internet connection
- the model's behavior would be difficult to inspect
- users would have less control over their field data
- the tool might not work in remote areas
Using open-source tools and local inference makes a different design possible. The complete pipeline can be inspected and adapted:
- image preprocessing is visible
- model inputs and outputs are explicit
- confidence thresholds can be calibrated
- observations remain on the user's device
- the backend can be replaced
- the application can run without a reliable internet connection
Open innovation also makes the project easier to learn from. Someone can inspect the training scripts, run the synthetic smoke test, replace the model, add new plant classes, or adapt the observation workflow for another field research problem.
The goal is not to pretend that a small classifier can replace botanical expertise. The goal is to build a transparent tool that helps people notice, record, and learn from the plants around them.
Limitations and Next Steps
This is an initial working model, not a finished botanical identification system.
The most important remaining limitation is domain coverage. The 50-image independent evaluation set is sourced from licensed iNaturalist images, while a dedicated evaluation set of locally captured Indonesian field photos is still needed.
The next steps are:
1. Add Indonesian field photos and compare them with the current 50-image iNaturalist benchmark.
2. Perform the manual visual quality-control pass on all contact sheets.
3. Remove or document near-duplicate training images.
4. Re-run calibration using the expanded validation set.
5. Re-run final evaluation on a fixed, independent test set.
6. Record a physical field demonstration with the trained model.
My Agent Session
This project was developed with an AI coding assistant using Copilot SDK in VS Code.
The agent helped inspect the repository, understand the architecture, connect the AI pipeline to the local observation workflow, run the dataset pipeline, train the initial model, verify ONNX parity, calibrate the confidence threshold, and validate the test suite.
The agent session is optional for this submission and is not linked here.
Prize Categories
I am entering the overall Hacktoberfest Open-Source AI Challenge.
I am not entering a partner category because EcoID does not currently use a partner-specific technology.
2 reactions · 2 comments
Mentions
I taught a local model to perform autopsies on dead repositories, then measured how often it lies What I Built There is a specific kind of repository that nobody writes about: the one with a README that describes a product, a package.json, two hundred commits, and a last commit from fourteen months ago. It is not archived. It is not deleted. The author moved on and the repository keeps sitting there, describing a world that ended. I read the theme as giving those repositories a voice. Seance reads the git history of an abandoned project, extracts facts that can be verified, and lets a local open-weight model write about them. The model writes prose. The code decides what is true. seance facts <repo> # deterministic evidence as JSON, no model seance report <repo> # autopsy report, optional Markdown output seance ask <repo> # interactive séance, with a tone flag Demo Real output, qwen3:4b running locally through Ollama, on a six-commit repository whose last commit is from August 2025: AUTOPSY: gudang-tugas Born : 2025-07-12 (C:16e5f46) Died : 2025-08-18 (C:d287690) [last commit] Gap : 14 months since the last commit Size : 6 commits, 2 authors, 0 tags What it was A student task management platform with premium features for academic use. [F:README.md@2a192ca] Timeline 2025-07-12 Initial commit with login and registration forms [C:f719143] 2025-07-14 Creation of README.md [C:2e6a90a] 2025-08-18 Final commit creating SECURITY.md [C:d287690] Cause of death (hypotheses, not facts) 1. The deletion of the config directory in 2025-08-09 may have been a critical step that led to the project becoming unmanageable ... [C:91fba8f] confidence: medium 2 claims dropped for lacking evidence. The séance, on the same repository: you> When did work stop, and what was being changed at the end? I remember the last commit was on 2025-08-18, creating SECURITY.md. Work stopped after that, with no further changes for 14 months. [C:d287690, M:gap] you> Which files mattered at the end? The ghost does not remember which files mattered at the end. The evidence only lists file counts by language at HEAD, not which files were important or mattered. [M:languages] The second answer is a refusal, and it is the feature. The tool would rather say it does not know than invent a reason. Code {% embed Bangkah/Seance %} MIT, Python 3.12, Docker image and CI included. Real reports and the full 30-claim audit are in demo/README.md. The repository was created on 27 October 2026 for this challenge. Some design patterns come from my earlier entry, TidyMind (a loopback-only model client, bounded retries with varied sampling, terminal-safe output); the code in this repository was written this week. How I Built It Open-source AI used: qwen3:4b through Ollama 0.40.11, running locally. The runtime has no third-party Python dependencies; git is the only external tool. The design bet: model speaks, code verifies Before the model sees anything, the code builds an evidence pack. Every item has an id the code generated: - C:<hash>: a commit, with author, date and subject - F:<path>@<hash>: a file at a specific commit, with a short excerpt - M:<name>: a measured metric: counts, dates, authors, tags, languages, the month a dependency manifest was last touched The pack is sent as JSON data, never as instructions, and the model must answer with JSON matching a schema. Then a validator rejects a claim when: - it cites an id that does not exist in the pack, or cites no id at all - it quotes something that is not in the cited evidence - it contains a hash, date or number that does not appear in that evidence Rejected claims are removed and counted, and the count is printed. After three attempts with different sampling, the command exits with code 3 and prints nothing rather than half a report. The rule is deliberately structural. I am not asking the model whether it is confident, and I am not asking a second model to grade the first. I am asking: can the sentence you wrote be traced to something this repository actually contains? Safe to point at a stranger's repository Repositories can contain instructions for git itself. .git/config can point core.fsmonitor, core.pager or diff.external at a script, and any tool that quietly runs git log in that directory will execute it. Seance never touches the working directory. It makes a bare clone with git clone --bare --no-local into a temporary directory and reads only that clone. Git runs without a shell, with system and global config disabled, no terminal prompts, no pager, --no-ext-diff, --no-textconv, and a scrubbed environment. The test suite has a hostile fixture that points those config keys at a canary script, plus a control test that proves the fixture really is hostile by running plain git against it. A fake Ollama that lies (unknown ids, fake quotes, broken JSON) checks that ungrounded output never reaches the terminal, and a snapshot check confirms the source repository is unchanged. What it got wrong I took 30 claims (26 from three real reports and 4 from ask answers) and checked every one by hand against git log, git show and the cited evidence. 18 were fully supported, 8 were defensible inferences, and 4 were wrong. All four cited real ids, real dates and real commit text. The validator passed them because they were traceable. They were false because the model misread the evidence: 1. It called a February commit "the final commit" when the last commit is from October. 2. It said a CI workflow "was removed", pointing at the commit that adds it. 3. It suggested adding a FUNDING.yml file that the last commit had already added. 4. It named an organisation that the cited README never mentions. This is the honest limit of structural grounding. It makes an invented hash or number impossible. It cannot make a wrong sentence impossible. It also misjudges timing: on a repository that is only three weeks old it printed "Gap: 0 months" and then a hypothesis about long inactivity. It works when the silence is real. Numbers, measured Windows 11 laptop, Docker Desktop, Python 3.12.16, git 2.47.3 in the image, Ollama 0.40.11 with qwen3:4b on the host. Wall times include container start. | What | Repository | Result | | --- | --- | --- | | facts | 2,006 commits (2,000 analysed, cap) | 14.2 s | | report | 2,006 commits | 30.3 s, 70 evidence items omitted from the 10,000-character budget | | report | 232 commits | 16.8 s | | report | 6 commits | 13.2 s | | tests | 49 tests | 21.0 s, 72% coverage | The two-thousand-commit case is not slow because the evidence pack is budgeted by characters, not by commit count: the model sees a bounded prompt and the git work is the only thing that scales. What I would do next - A semantic check for the four failures above: whether a claim's subject matches the cited commit's subject, which is a much narrower question than "is this true". - Deterministic handling of 0 months repositories: refuse with "this repository is not dead yet" instead of producing a low-confidence hypothesis. - More manifests and a real dependency analysis, which needs parsing rather than file names. Why Does Open Innovation Matter? A repository's history is not always safe to send to someone else's server. Old commits can contain client names, internal paths, and secrets that were once committed and later removed. With an open-weight model running on my own machine, the history is read by a bare clone in a temporary directory and the only network connection the tool makes is to the Ollama endpoint, which is loopback unless you explicitly pass --allow-container. I did not run it with the network disconnected, so I am not claiming that. Running the model myself also made the verification loop cheap. The retry logic and the 30-claim audit needed many runs, and none of them cost anything or hit a quota. The model is a command-line option on report and ask (--model), so a different local model can be tried without changing the code, though I only evaluated qwen3:4b. The Spirit of It The moment that landed was not a report. It was a refusal. I asked an abandoned project why its author stopped working on it. It answered: > I don't remember why the author stopped working on it. The only evidence I have is that Jofi was the sole author in history. It had every ingredient for a story like the ones I had been half-expecting all week: a lone author, six months of silence, a last commit that only adds a funding file. It could have said burnout, or money, or a rewrite. Instead it said it did not know, and showed me the one fact it did have. That is the ghost I wanted: one that refuses to invent the ending.
StrukLokal: Local-First Receipt OCR for Freelancers This is a submission for the Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle. I started building StrukLokal on October 12, 2026, the first day of the challenge. What I Built StrukLokal is a local-first receipt capture app for freelancers and small businesses that want to turn paper receipts into expense records without sending receipt contents to a hosted OCR service. It reads receipt images or scanned PDFs and suggests a store name, transaction date, and total. A person can review and correct those fields, approve the transaction, and export approved records to CSV. The likely users are freelancers, sole proprietors, and small teams who currently enter receipt details by hand. I have not yet tested willingness to pay with users or made a sale, so the commercial opportunity is still a hypothesis. Demo There is no hosted demo or video recording yet. The app is designed to run locally: 1. Clone the StrukLokal repository. 2. Follow the Windows setup steps in the README and install the requirements. 3. Run python prepare_models.py while connected to the internet to download and initialize the OCR model. 4. Run python app.py and open <127.0.0.1>. The model cache is needed after setup. I could not initialize the cached model in my current Windows environment because access to its configuration file was denied, so I cannot provide a verified live OCR demo from this environment. The Flask upload → correction/approval → CSV route has been checked with OCR mocked; this verifies the app flow after OCR returns, not OCR inference itself. Code StrukLokal source code on GitHub How I Built It StrukLokal uses Flask for its local web interface, PaddleOCR configured for Indonesian text (PP-OCRv6), and a rules-based Python parser for the store name, date, and total. OCR output is treated as a suggestion. Users can edit the extracted fields and must approve a transaction before it is included in the CSV. The app binds to 127.0.0.1 and keeps transaction data in process memory. One parser regression test covers a receipt where the tax line is followed by an explicit total: Subtotal Rp 100.000 Pajak Rp 10.000 TOTAL Rp 110.000 The explicit TOTAL label must take precedence over the Pajak label on the preceding line. Other parser tests cover mixed date formats and preserve the order of date candidates in the OCR text. On the reviewed commit, all 14 parser unit tests passed. Ruff lint and format checks, Python syntax compilation, and repository hygiene checks passed as well. These checks do not measure OCR accuracy. I have not yet evaluated the app on a labeled set of real Indonesian receipts, so I am not claiming an accuracy percentage. Why Does Open Innovation Matter? Using open-source OCR makes local inference possible: the application does not need to upload receipt contents to a commercial OCR API during processing. This matters for financial documents, where users may prefer to keep transaction data on their own machines. It also lets developers inspect and adapt the parser instead of treating extraction as a black box. There are trade-offs. The model must be downloaded during setup, the first run needs internet access, and model availability alone does not guarantee correct results. I have not yet verified a completely offline run or compared extraction quality and cost against a closed OCR API. The next step is to test StrukLokal with consented Indonesian receipts, record expected values for each field in advance, and report both correct extractions and failures. I also want to put it in front of potential users and learn whether the workflow saves them enough time to pay for it.
EcoID: An Offline Plant Identifier That Gets You Outside This is a submission for the Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass. What I Built EcoID is an offline-first plant identification tool for field observations. The idea is simple: take a laptop or phone outside, photograph a real plant, and use a local AI model to get identification candidates. Then look at the plant yourself and verify the result. EcoID is designed for: - students learning about local plants - field researchers and citizen scientists - gardeners and small-scale farmers - people who want to explore nature without sending their photos to a cloud service The current model supports five plant classes: - Mango — Mangifera indica - Coconut — Cocos nucifera - Banana — Musa acuminata - Papaya — Carica papaya - Cassava — Manihot esculenta The AI does not present its output as a fact. It returns ranked identification candidates with confidence scores. The user then chooses whether the result is: - Verified - Rejected - Uncertain This human-in-the-loop flow matters because a model prediction is only a suggestion until somebody looks at the actual plant. EcoID stores field observations locally, including the original photo, model prediction, alternative predictions, confidence score, verification status, notes, optional GPS coordinates, capture time from EXIF metadata, and user corrections. The project is designed to make the computer useful for a short moment, then get the person back to observing the real world. Demo EcoID runs locally on a laptop or a phone-accessible local network. There is no public hosted demo because local execution is part of the project's privacy and offline design. The trained model and downloaded dataset are not committed to the repository. Model weights and image data are kept outside Git because of their size and individual dataset licensing terms. After training or obtaining the ONNX model locally: python -m app --model models/ecoid-global-20261010.onnx --open The app opens at: http://127.0.0.1:8765 The main flow is: Capture or upload a photo ↓ Run local inference ↓ Review possible identifications ↓ Verify, reject, or mark uncertain ↓ Save the field observation ↓ Review observations in history and on the local map Code The source code is available on GitHub: github.com/Bangkah/EcoID/tree/main The source code is released under the MIT License. Dataset images retain their original licenses and attribution requirements. The main parts of the project are: - app/ai/ — preprocessing, inference, model contracts, and result post-processing - app/observation/ — drafts, verification, EXIF metadata, maps, statistics, and exports - app/storage/ — local SQLite persistence and image storage - app/ui/ — the local HTTP server and browser interface - scripts/ — dataset preparation, training, export, evaluation, and benchmarking - tests/ — unit, integration, offline, and browser tests The architecture is: !Image description How I Built It Local image preprocessing Every image follows the same preprocessing path: 1. Decode the image 2. Apply EXIF orientation 3. Convert to RGB 4. Resize to 224x224 5. Normalize with ImageNet mean and standard deviation 6. Convert to a float32 tensor with shape (1, 3, 224, 224) Keeping preprocessing in one shared module helps avoid train/serve skew between the training pipeline and the production inference path. Open-source AI and local inference EcoID uses a MobileNetV3-Small image classifier exported to ONNX and executed with ONNX Runtime on the CPU. The UI and storage layers depend only on a small model contract, so the inference backend can be replaced without rewriting the rest of the application. The output is converted into a Top-3 list of candidates. If the confidence score is below the configured threshold, the result is marked as LOW_CONFIDENCE. The initial threshold was calibrated using validation data. The current configuration is: threshold = 0.73 On the current validation set, this produced: - 53.7% coverage - 97.5% selective accuracy - 80.0% negative rejection These numbers are provisional and should be recalibrated when the field dataset grows. Training results The first real dataset download produced 2,447 usable licensed images, approximately 370 MB in total. The dataset was collected from iNaturalist using CC0, CC BY, and CC BY-SA images, with attribution metadata preserved. The validated dataset contains approximately 2,000 training images across the five target classes, 300 validation images, and 149 negative samples across lookalike, non-plant, and low-quality groups. The dataset pipeline includes: - license metadata - author attribution - photographer-based split separation - duplicate and near-duplicate checks - validation and evaluation splits - negative examples - dataset structure validation The initial MobileNetV3-Small training run achieved: - best validation accuracy: 80.3% - five target plant classes - local CPU inference after ONNX export On a separate 50-image evaluation set (10 images per class), the model achieved: - Top-1 accuracy: 86.0% (43/50) - Top-3 accuracy: 94.0% (47/50) - negative rejection: 77.1% (84/109) - selective accuracy at threshold 0.73: 94.1% - coverage: 68.0% This evaluation set was collected from licensed iNaturalist images and is useful as a reproducible benchmark. It is not a substitute for a field evaluation with locally captured phone photos. The ONNX export was verified against the PyTorch model: - maximum logit difference: approximately 1.9e-6 - Top-1 agreement: 20/20 samples The repository also generates contact sheets for mandatory manual label review. Automated dataset validation passed with zero errors, but a complete human review of all contact sheets remains a dataset-quality task. Human verification A prediction is first stored as a temporary draft. It becomes a permanent observation only after the user makes a verification decision. This prevents an abandoned or uncertain prediction from silently becoming part of the observation history. The flow is: photo → identification draft → human verification → saved observation Draft photos that are abandoned are automatically removed after 24 hours. Offline storage The application stores everything locally: ~/.ecoid/ ├── ecoid.db ├── images/ ├── thumbs/ └── drafts/ The application does not require a cloud API for inference. The browser UI also uses a strict Content Security Policy and does not load external scripts, fonts, or images. GPS is opt-in. A user can attach a photo's EXIF location, use device geolocation, or enter coordinates manually. Performance The model was benchmarked on a Windows laptop using CPU inference: | Stage | Average | |---|---:| | Decode and resize | 6.3 ms | | ONNX inference | 5.8 ms | | Softmax and Top-K | 0.4 ms | | Total | 12.4 ms | The measured p95 total latency was 33.8 ms per image, below the project's target of 10 seconds per image. Testing The project includes: - Python 3.11 and 3.12 test coverage - Ruff linting - byte-compilation checks - tests with non-UTF-8 default encoding - browser tests with real headless Chromium - offline tests that block non-loopback sockets and DNS lookups - UI tests for capture, verification, history, map, statistics, export, and deletion The current local test result is: 222 tests passed 25 browser tests skipped unless browser testing is enabled The CI workflow is defined in .github/workflows/ci.yml. Why Does Open Innovation Matter? Plant identification is a good example of why open innovation matters. A closed cloud API could return a label quickly, but it would also introduce several limitations: - photos would leave the device - the application would depend on an internet connection - the model's behavior would be difficult to inspect - users would have less control over their field data - the tool might not work in remote areas Using open-source tools and local inference makes a different design possible. The complete pipeline can be inspected and adapted: - image preprocessing is visible - model inputs and outputs are explicit - confidence thresholds can be calibrated - observations remain on the user's device - the backend can be replaced - the application can run without a reliable internet connection Open innovation also makes the project easier to learn from. Someone can inspect the training scripts, run the synthetic smoke test, replace the model, add new plant classes, or adapt the observation workflow for another field research problem. The goal is not to pretend that a small classifier can replace botanical expertise. The goal is to build a transparent tool that helps people notice, record, and learn from the plants around them. Limitations and Next Steps This is an initial working model, not a finished botanical identification system. The most important remaining limitation is domain coverage. The 50-image independent evaluation set is sourced from licensed iNaturalist images, while a dedicated evaluation set of locally captured Indonesian field photos is still needed. The next steps are: 1. Add Indonesian field photos and compare them with the current 50-image iNaturalist benchmark. 2. Perform the manual visual quality-control pass on all contact sheets. 3. Remove or document near-duplicate training images. 4. Re-run calibration using the expanded validation set. 5. Re-run final evaluation on a fixed, independent test set. 6. Record a physical field demonstration with the trained model. My Agent Session This project was developed with an AI coding assistant using Copilot SDK in VS Code. The agent helped inspect the repository, understand the architecture, connect the AI pipeline to the local observation workflow, run the dataset pipeline, train the initial model, verify ONNX parity, calibrate the confidence threshold, and validate the test suite. The agent session is optional for this submission and is not linked here. Prize Categories I am entering the overall Hacktoberfest Open-Source AI Challenge. I am not entering a partner category because EcoID does not currently use a partner-specific technology.