WildProof: Go Outside With a Question, Come Back With Evidence
This is a submission for the Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass What I Built Most “go outside” apps stop at reminders, routes, or motivational messages. I built WildProof: an offline-
This is a submission for the Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass
What I Built
Most “go outside” apps stop at reminders, routes, or motivational messages.
I built WildProof: an offline-first field observation app that turns a short walk into a small investigation.
Go outside with a question. Come back with evidence.
WildProof is for hikers, students, citizen scientists, and anyone who wants to spend less time scrolling and more time paying attention to the world around them.
The experience is intentionally simple:
- Choose a mission such as Habitat Comparison or Pattern Hunt.
- Leave the screen and collect observations.
- Capture written notes and photo evidence references.
- Save the expedition locally, even without connectivity.
- Return to analysis when a backend is available.
- Review a structured report that makes uncertainty visible.
WildProof is not a “guess the species from this photo” app. It does not pretend that uncertain AI guesses are facts.
Every report separates:
- Observed — directly supported by the submitted evidence.
- Inferred — a reasoned deduction from the available evidence.
- Unknown — a claim that needs more evidence.
The product rule is:
Never let a successful-looking report hide an unsuccessful analysis.
Demo
Live demo: wildproof.onrender.com
The main walkthrough is:
- Start a Pattern Hunt mission.
- Add a field note and photo evidence.
- Generate a report through the Temporal-backed path.
- Review observed, inferred, and unknown claims.
- Inspect missing evidence, the suggested next step, and the evidence-quality score.
The repository contains the working MVP and setup instructions:
JaniDhruv
/
wildproof
WildProof is an offline-first open-AI app that turns short walks into meaningful field missions. Capture photos, sounds, and notes, then use Gemma to create evidence-linked observations with clear confidence levels. TabPFN scores report quality while durable workflows handle retries, helping people notice and understand the world around them.
WildProof
Go outside with a question. Come back with evidence.
An offline-first, uncertainty-aware field observation app for meaningful time outdoors
Why WildProof · How It Works · Features · Quick Start · Challenge Evidence
🧠 TL;DR
WildProof turns a short walk into a small field investigation:
Choose a mission → collect notes and photo evidence → save locally → generate a structured report → see what is observed, inferred, or still unknown.
The app is built for the Hacktoberfest Open-Source AI Challenge: Week 1 - Touch Grass. It uses local Gemma 2B inference through Ollama, IndexedDB for offline evidence capture, and Temporal for durable analysis.
View the repository · View the challenge
MVP status: working and honest about its boundaries. The current Gemma path analyzes written notes and photo metadata. It does not send image pixels to a vision model.
🌿 Why WildProof?
Most outdoor apps optimize for routes…
Code
Repository: github.com/JaniDhruv/wildproof
The project is released under the MIT License.
How I Built It
WildProof is a React and TypeScript progressive web app backed by FastAPI, Ollama, Gemma 2B, and Temporal.
Offline-first evidence capture
The frontend stores expeditions using IndexedDB through the idb package.
That means a user can:
- Start a mission without signing in.
- Capture notes and photo evidence references without a network connection.
- Refresh the page without losing the expedition.
- Return to analysis later when the backend is available.
Photos are currently stored locally as evidence references. The current text-only Gemma path receives written notes and photo metadata; it does not receive image pixels or claim to visually identify the contents of a photo.
Local open-weight AI
The core analysis uses Gemma 2B through Ollama.
The backend sends the mission type and collected field notes to Gemma and requests a strict JSON report:
{
"summary": "A concise expedition summary.",
"observations": [
{
"claim": "A claim grounded in the submitted evidence.",
"confidence": "observed",
"supportingEvidenceIds": ["note-1"]
}
],
"missing_evidence": ["What would strengthen the conclusion."],
"suggested_next_step": "One practical next action."
}
The response is validated against a Pydantic schema before it reaches the frontend. If Gemma returns malformed JSON, the API makes one repair attempt. If that also fails, the application surfaces an explicit failure instead of manufacturing a successful report.
If the local model is unavailable, the expedition remains preserved locally and the UI shows a clearly labeled fallback state.
Durable analysis with Temporal
Local model inference can fail because of timeouts, unavailable services, or temporary infrastructure problems. WildProof uses a Temporal workflow to coordinate the analysis stages:
Normalize evidence
↓
Run Gemma analysis
↓
Validate the report
↓
Calculate evidence quality
↓
Return the durable report
The frontend uses the Temporal-backed endpoint by default and includes a switch for the direct FastAPI path.
The value of Temporal here is not just adding another service. A failed activity can be retried without discarding the evidence collected during the walk or restarting the entire expedition.
Evidence-quality scoring
WildProof calculates a transparent quality score from structured evidence features such as:
- Number of photos.
- Number of notes.
- Total note length.
- Expedition duration.
The repository includes a reproducible baseline-versus-TabPFN experiment for small tabular datasets in api/tabpfn_experiment.py.
The production endpoint currently uses an explicitly labeled baseline heuristic when the installed TabPFN/Torch combination is unavailable. That limitation is visible in the API and UI; the app does not claim that TabPFN is running when it is not.
Observability
The frontend and backend include Sentry instrumentation for:
- Request failures.
- Model-analysis errors.
- Request and analysis timing.
- Frontend errors.
- Durable workflow failures.
Sentry events require a configured SENTRY_DSN, so the project can run locally without secrets or a hosted observability account.
Why Does Open Innovation Matter?
WildProof is about trust, privacy, and control over personal observations.
Outdoor evidence can include photos from private locations, movement patterns, and notes about a person’s home or routine. Connectivity is also unreliable in exactly the places where the app is meant to be used.
A closed, cloud-only AI API would make it harder to:
- Capture evidence offline.
- Keep field notes under the user’s control.
- Inspect and change the model workflow.
- Reproduce the output format.
- Make uncertainty and failure visible.
Using an open-weight model through local inference makes the core analysis inspectable and adaptable. The model can be replaced, the prompt can be audited, and the report schema can enforce explicit distinctions between observation and inference.
Open innovation also makes failure easier to discuss honestly. WildProof can show when Gemma is unavailable, when the app falls back to a baseline score, and when an image was stored as evidence without being sent to a vision model.
For this project, open AI is not just a cheaper way to add a chatbot. It is what makes a privacy-aware, offline-first, uncertainty-aware field tool practical.
What I Learned
The hardest part was not sending a prompt to a model.
The harder problems were:
- Designing an output schema that does not reward confident hallucinations.
- Preserving evidence when the network disappears.
- Making AI failures visible without losing the user’s work.
- Making a durable workflow useful instead of merely adding infrastructure.
- Being honest when a dependency such as TabPFN cannot run in the current environment.
The biggest change in the project was narrowing the claims to match the evidence. Photos are currently evidence references, not visual input to Gemma. TabPFN is a reproducible experiment with a production-safe baseline fallback. These constraints make the MVP less flashy, but more trustworthy.
My Agent Session
I used GitHub Copilot to explore the product direction, review the implementation against the challenge requirements, identify overstated claims, and tighten the final MVP narrative.
We got any great ideas for hacktoberfest dev challenge week 1 touch grass?
Also see current submissions and find ideas that will beat them and select prize categories wisely and select one if there's v low competition
WE CAN ADD AS MANY PRIZE CATEGORIES AS WE WANT, JUST MAKE SURE WE SELECT A V GOOD PROJ IDEA THAT REALLY WINS.
{
"skill": "devrelay-challenges"
}
Skill "devrelay-challenges" loaded successfully. Follow the instructions in the skill context.
{
"skill": "devrelay-community-wisdom"
}
Skill "devrelay-community-wisdom" loaded successfully. Follow the instructions in the skill context.
I’ll first verify the live Week 1 prompt, rubric, deadline, and current public submissions, then compare their patterns against a few buildable concepts and choose the strongest low-competition prize angles.
{
"url": "https://dev.to/events",
"max_length": 12000
}
Contents of https://dev.to/events:
We're a place where coders share, stay up-to-date and grow their careers.
{
"url": "https://www.google.com/search?q=site%3Adev.to+Hacktoberfest+%22Touch+Grass%22+Week+1",
"max_length": 8000
}
Contents of https://www.google.com/search?q=site%3Adev.to+Hacktoberfest+%22Touch+Grass%22+Week+1:
Google Search
{
"url": "https://www.bing.com/search?q=site%3Adev.to+Hacktoberfest+%22Touch+Grass%22+Week+1",
"max_length": 8000
}
Contents of https://www.bing.com/search?q=site%3Adev.to+Hacktoberfest+%22Touch+Grass%22+Week+1:
Sep 28, 2026 · If you remember Hacktoberfest as "open four pull requests, get a T-shirt," that version is over. Hacktoberfest 2026 is …
{
"url": "https://www.bing.com/search?q=%22Touch+Grass%22+DEV+Challenge+2026",
"max_length": 12000
}
Contents of https://www.bing.com/search?q=%22Touch+Grass%22+DEV+Challenge+2026:
TOUCH definition: 1. to put your hand or another part of your body lightly onto and off something or someone: 2. (of…. Learn more.
{
"url": "https://www.bing.com/search?q=site%3Adev.to%2Fchallenges+touch+grass+hacktoberfest",
"max_length": 12000
}
Contents of https://www.bing.com/search?q=site%3Adev.to%2Fchallenges+touch+grass+hacktoberfest:
TOUCH definition: 1. to put your hand or another part of your body lightly onto and off something or someone: 2. (of…. Learn more.
{
"url": "https://www.bing.com/search?q=%22Hacktoberfest+2026%22+%22Touch+Grass%22",
"max_length": 12000
}
Contents of https://www.bing.com/search?q=%22Hacktoberfest+2026%22+%22Touch+Grass%22:
Sep 28, 2026 · If you remember Hacktoberfest as "open four pull requests, get a T-shirt," that version is over. Hacktoberfest 2026 is …
{
"url": "https://www.bing.com/search?q=%22Touch+Grass%22+%22DEV+Challenge%22"
}
{
"url": "https://dev.to/search?q=touch%20grass"
}
We got any great ideas for hacktoberfest dev challenge week 1 touch grass https://dev.to/challenges/hacktoberfest-week1-2026-10-05?
Also see current submissions and find ideas that will beat them and select prize categories wisely and select one if there's v low competition
WE CAN ADD AS MANY PRIZE CATEGORIES AS WE WANT, JUST MAKE SURE WE SELECT A V GOOD PROJ IDEA THAT REALLY WINS.
{
"url": "https://dev.to/challenges/hacktoberfest-week1-2026-10-05",
"max_length": 20000
}
Contents of https://dev.to/challenges/hacktoberfest-week1-2026-10-05:
### The Five Challenges
#### Do pull requests count this year?
No. This year's Hacktoberfest is not about open source pull requests. There's no PR count to hit and no repos to hunt for. Instead, you build a brand-new project with open-source AI at its core, and write about it on DEV. [Here's everything you need to know about Hacktoberfest 2026.](https://dev.to/mlh/everything-you-need-to-know-about-hacktoberfest-2026-ai-belongs-to-everyone-mdk)
#### How do the Hacktoberfest challenges work?
We're running five DEV Challenges back to back through October: a Weekend Challenge to start (October 2 to 5), then a new challenge every Monday (October 5, 12, 19 and 26). Every challenge uses the same prompt, and each one gets its own theme. We reveal each theme the day its challenge launches. See all five on the [HF26 DEV Challenge Hub](https://dev.to/challenges/hf26).
#### What's the prompt?
The same for all five challenges: build something with open-source AI at its core (an open-weight model, an open-source agent harness or framework, local inference, or any mix), and tell us why open innovation matters for what you built.
#### Can I enter more than one challenge?
Yes, enter as many as you like. You can publish **one submission per challenge**, so that's up to five in October. Each submission has to be a new project built during that challenge's window.
#### Can one submission win more than once?
No. Each submission is automatically in the running for the overall prize and every partner category it qualifies for, but you can win once per challenge, and we'll aim to celebrate as many different builders as possible across the month.
#### I'm joining late. Is it worth it?
Absolutely. Every challenge is a fresh start, so someone who finds us in week four competes on the same footing as someone who's been here since day one.
### Prizes and Partner Categories
#### What can I win?
Every challenge has its own winners, and we'll share each challenge's prizes when it launches. Here's what's up for grabs in the [Weekend Challenge](https://dev.to/challenges/hacktoberfest-weekend-2026-10-01):
* **Overall winner:** $250, a [DEV++](https://dev.to/++) membership, and an exclusive DEV badge.
* **Featured partner categories:** $200 for each winner.
* **Partner categories:** $100 for each winner.
Every winner also gets an exclusive DEV badge, and **everyone** with a valid submission gets a completion badge.
#### How do partner categories work?
You opt in by using the partner's technology in your project and listing each category you're entering in the Prize Categories section of your post. One project can enter as many categories as it genuinely uses. You don't need to use any partner technology to win the overall prize.
#### Which partner categories are open?
Featured categories ($200):
Partner categories ($100):
Partner categories may change from challenge to challenge as new partners join. Each challenge page lists the categories open for that challenge.
#### Who can enter Best Use of Arduino?
Anyone building with an Arduino UNO Q, whether you got one at an in-person Fest or have your own.
#### How do I claim partner credits?
Several partners are giving participants credits and promo codes, including Tinker, Render, Backboard, and ElevenLabs. Claim yours at [hacktoberfest.com/my/promos](https://hacktoberfest.com/my/promos/?utm_source=dev.to&utm_medium=challenge-page&utm_campaign=hacktoberfest-2026&utm_content=faq-claim-credits).
### Submission
#### Can I submit a pull request to an existing project?
No. **New projects, not pull requests:** each submission is a new project built during that challenge's window. Pull requests to existing projects don't count this year.
#### Can I work on an old project?
No, all submissions and their respective repositories must be started and completed within the challenge window. Commits made after the submission deadline must be noted in the project's readme. Failure to do so may result in disqualification.
#### Can I work on a team?
Yes, you can work on teams of up to four people.
* If you collaborate with anyone, you'll need to list their DEV handles in your submission post so we can award a badge to your entire team! **Please only publish one submission per team.**
* DEV does not handle prize-splitting, so in the event that your submission wins, you will need to split the prize amongst yourselves. Thank you for understanding!
#### Should I include my agent session?
**Show your work.** We'd love to see how you built it. Save your agent session with [DevRelay](https://devrelay.com) and embed it in your post, or link to it. It's optional, but it helps the judges understand your process.
#### Can my submission include open source code?
Riffing on open source code and borrowing and improving on previous work/ideas is encouraged but it's important your changes are significant enough to ensure your submission is valid.
#### What happens if my submission is considered plagiarized or invalid?
Anything deemed to be plagiarism will not be eligible for prizes. Incidental plagiarism may simply result in your disqualification from the challenge (regardless of the number of other valid submissions you have published). Egregious plagiarism will result in your suspension from DEV entirely. Any non-generic, non-trivial usage of prior work, including open source code must be credited in your submission.
#### Do submissions have to be in English?
Non-english submissions are eligible for a completion badge but not eligible for prizes due to the current limitations of our judges. We will not be judging on mastery of the English language, so please don't let this deter you from submitting if you are not a native English speaker! We hope to evolve this in the future to be more accommodating.
#### Can I use AI?
Use of AI is allowed as long as all other rules are followed. We want to give you a chance to show off your skills in realistic scenarios. If you use AI tools to help you achieve your submission, all the power to you.
### Eligibility
#### How old do I have to be to participate?
Participants need to be 18+ in order to participate.
#### If I live in X, am I eligible to participate?
For eligibility rules, see our [official challenge rules](https://dev.to/page/official-hackathon-rules).
### Judging and Prizing
#### How are submissions judged?
This is DEV, so **your write-up matters most**. A great project with a thin write-up won't win. We judge on Writing Quality (weighted most heavily), Relevance to the Prompt and Theme, Creativity, Technical Execution, and, if you're entering a partner category, Use of Partner Technology.
#### When are winners announced?
Winners are announced the week after each challenge closes. Each challenge's page lists the week under Key Dates.
#### Can there be ties?
In the event of a tie in scoring between judges, the judges will select the entry that received the highest number of positive reactions on their DEV post to determine the winner.
#### When will I receive my DEV badge?
Both participation and winner badges will be awarded, in most cases, the same day as the winner announcement.
#### When will I receive my prizes?
The DEV Team will contact you via the email associated with your DEV profile within, at most, 10 business days of the announcement date to share the details of claiming your prizes.
#### What steps do I need to take to receive my cash prize?
The winner (including each member of a team) may be required to sign and return an affidavit of eligibility and publicity/liability release, and provide any additional tax filing information (such as a W-9, social security number or Federal tax ID number) within seven (7) business days following the date of your first email notification.
### Questions
#### Where can I ask questions?
Drop them in the comments on the challenge's announcement post, and follow the [#hf26challenge](https://dev.to/t/hf26challenge) tag so you don't miss a theme reveal.
{
"url": "https://dev.to/api/articles?tag=hacktoberfest&per_page=100",
"max_length": 20000
}
Content type application/json; charset=utf-8 cannot be simplified to markdown. Here is the raw content:
Contents of https://dev.to/api/articles?tag=hacktoberfest&per_page=100:
[{"type_of":"article","id":4826102,"title":"I Taught Local AI to Help Me Notice the World.","description":"Your World Is More Interesting Than Your Feed. This is a submission for the Hacktoberfest...","readable_publish_date":"Oct 10","slug":"i-taught-local-ai-to-help-me-notice-the-world-5a45","path":"/ishagautam504/i-taught-local-ai-to-help-me-notice-the-world-5a45","url":"https://dev.to/ishagautam504/i-taught-local-ai-to-help-me-notice-the-world-5a45","comments_count":0,"public_reactions_count":0,"collection_id":null,"published_timestamp":"2026-10-10T01:54:46Z","language":"en","subforem_id":1,"ai_disclosure_level":"not_disclosed","ai_disclosure_label":"Not Disclosed","positive_reactions_count":0,"cover_image":null,"social_image":"https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzlo656u088u66liiygb2.png","canonical_url":"https://dev.to/ishagautam504/i-taught-local-ai-to-help-me-notice-the-world-5a45","created_at":"2026-10-10T01:54:46Z","edited_at":null,"crossposted_at":null,"published_at":"2026-10-10T01:54:46Z","last_comment_at":"2026-10-10T01:54:46Z","reading_time_minutes":4,"tag_list":["ai","devchallenge","hacktoberfest","opensource"],"tags":"ai, devchallenge, hacktoberfest, opensource","user":{"name":"Isha Gautam","username":"ishagautam504","twitter_username":null,"github_username":"shiriei","user_id":4153893,"website_url":null,"profile_image":"https://media2.dev.to/dynamic/image/width=640,height=640,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4153893%2Fe4ab1c15-f2af-4d93-adf9-57a4efa2b410.jpg","profile_image_90":"https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4153893%2Fe4ab1c15-f2af-4d93-adf9-57a4efa2b410.jpg"}},{"type_of":"article","id":4826090,"title":"Thirsty Leaf: A Tiny Plant Reminder App That Gets You Off Your Screen 🪴","description":"This is a submission for the Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass ...","readable_publish_date":"Oct 10","slug":"thirsty-leaf-a-tiny-plant-reminder-app-that-gets-you-off-your-screen-787","path":"/ayushjhawar/thirsty-leaf-a-tiny-plant-reminder-app-that-gets-you-off-your-screen-787","url":"https://dev.to/ayushjhawar/thirsty-leaf-a-tiny-plant-reminder-app-that-gets-you-off-your-screen-787","comments_count":0,"public_reactions_count":0,"collection_id":null,"published_timestamp":"2026-10-10T01:50:17Z","language":"en","subforem_id":1,"ai_disclosure_level":"not_disclosed","ai_disclosure_label":"Not Disclosed","positive_reactions_count":0,"cover_image":null,"social_image":"https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frb5b3eqz4ile0fhpuw0a.png","canonical_url":"https://dev.to/ayushjhawar/thirsty-leaf-a-tiny-plant-reminder-app-that-gets-you-off-your-screen-787","created_at":"2026-10-10T01:50:17Z","edited_at":"2026-10-10T01:51:12Z","crossposted_at":null,"published_at":"2026-10-10T01:50:17Z","last_comment_at":"2026-10-10T01:50:17Z","reading_time_minutes":2,"tag_list":["devchallenge","hf26challenge","hacktoberfest","webdev"],"tags":"devchallenge, hf26challenge, hacktoberfest, webdev","user":{"name":"Ayush Jhawar","username":"ayushjhawar","twitter_username":null,"github_username":null,"user_id":1991366,"website_url":null,"profile_image":"https://media2.dev.to/dynamic/image/width=640,height=640,fit=cover,gravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1991366%2Fdbb2df41-09e5-4a23-b475-553f09cfa987.gif","profile_image_90":"https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1991366%2Fdbb2df41-09e5-4a23-b475-553f09cfa987.gif"}},{"type_of":"article","id":4825380,"title":"EcoID: An Offline Plant Identifier That Gets You Outside","description":"This is a submission for the Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass. ...","readable_publish_date":"Oct 9","slug":"ecoid-an-offline-plant-identifier-that-gets-you-outside-4b6e","path":"/bangkah/ecoid-an-offline-plant-identifier-that-gets-you-outside-4b6e","url":"https://dev.to/bangkah/ecoid-an-offline-plant-identifier-that-gets-you-outside-4b6e","comments_count":0,"public_reactions_count":2,"collection_id":null,"published_timestamp":"2026-10-09T20:55:09Z","language":"en","subforem_id":1,"ai_disclosure_level":"not_disclosed","ai_disclosure_label":"Not Disclosed","positive_reactions_count":
[Truncated — full output (19.8 KB) temporarily saved to C:\Users\Lenovo\AppData\Local\Temp\1791598621844-copilot-tool-output-d768962d67f24ebe8de920a19f789ed0.txt]
{
"url": "https://dev.to/api/articles?tag=devchallenge&per_page=100",
"max_length": 20000
}
Content type application/json; charset=utf-8 cannot be simplified to markdown. Here is the raw content:
Contents of https://dev.to/api/articles?tag=devchallenge&per_page=100:
[{"type_of":"article","id":4776935,"title":"To Retry or Not to Retry? That Is the Question.","description":"This is a submission for the Kaggle Benchmarking Challenge Those who read my articles know that a...","readable_publish_date":"Oct 8","slug":"to-retry-or-not-to-retry-that-is-the-question-1j2l","path":"/gramli/to-retry-or-not-to-retry-that-is-the-question-1j2l","url":"https://dev.to/gramli/to-retry-or-not-to-retry-that-is-the-question-1j2l","comments_count":60,"public_reactions_count":55,"collection_id":null,"published_timestamp":"2026-10-08T07:00:15Z","language":"en","subforem_id":1,"ai_disclosure_level":"not_disclosed","ai_disclosure_label":"Not Disclosed","positive_reactions_count":55,"cover_image":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkwmpr58hbcmut3j173us.jpg","social_image":"https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkwmpr58hbcmut3j173us.jpg","canonical_url":"https://dev.to/gramli/to-retry-or-not-to-retry-that-is-the-question-1j2l","created_at":"2026-09-30T10:35:25Z","edited_at":null,"crossposted_at":null,"published_at":"2026-10-08T07:00:15Z","last_comment_at":"2026-10-10T01:27:53Z","reading_time_minutes":9,"tag_list":["devchallenge","kagglechallenge","ai","machinelearning"],"tags":"devchallenge, kagglechallenge, ai, machinelearning","user":{"name":"Daniel Balcarek","username":"gramli","twitter_username":"dbalcare","github_username":"Gramli","user_id":3669374,"website_url":"https://gramli.github.io/","profile_image":"https://media2.dev.to/dynamic/image/width=640,height=640,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3669374%2F0ad6f20b-8faa-45a4-a8ef-ef83e702d37b.png","profile_image_90":"https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3669374%2F0ad6f20b-8faa-45a4-a8ef-ef83e702d37b.png"}},{"type_of":"article","id":4821589,"title":"Super-Intelligent Yes-Men: Are We Training AI to Ignore the Truth?","description":"This is a submission for the Kaggle Benchmarking Challenge What I Benchmarked A published...","readable_publish_date":"Oct 9","slug":"super-intelligent-yes-men-are-we-training-ai-to-ignore-the-truth-epp","path":"/dannwaneri/super-intelligent-yes-men-are-we-training-ai-to-ignore-the-truth-epp","url":"https://dev.to/dannwaneri/super-intelligent-yes-men-are-we-training-ai-to-ignore-the-truth-epp","comments_count":11,"public_reactions_count":34,"collection_id":null,"published_timestamp":"2026-10-09T12:26:09Z","language":"en","subforem_id":1,"ai_disclosure_level":"not_disclosed","ai_disclosure_label":"Not Disclosed","positive_reactions_count":34,"cover_image":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzbfo80f8ybw9hnissfdp.jpg","social_image":"https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzbfo80f8ybw9hnissfdp.jpg","canonical_url":"https://dev.to/dannwaneri/super-intelligent-yes-men-are-we-training-ai-to-ignore-the-truth-epp","created_at":"2026-10-09T06:35:58Z","edited_at":null,"crossposted_at":null,"published_at":"2026-10-09T12:26:09Z","last_comment_at":"2026-10-10T01:21:41Z","reading_time_minutes":10,"tag_list":["devchallenge","kagglechallenge","ai","machinelearning"],"tags":"devchallenge, kagglechallenge, ai, machinelearning","user":{"name":"Daniel Nwaneri","username":"dannwaneri","twitter_username":"dannwaneri","github_username":"dannwaneri","user_id":3606168,"website_url":"https://dannwaneri.com/","profile_image":"https://media2.dev.to/dynamic/image/width=640,height=640,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3606168%2F7684e1e1-b986-4ee3-ae5b-56db2b97d286.jpg","profile_image_90":"https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3606168%2F7684e1e1-b986-4ee3-ae5b-56db2b97d286.jpg"}},{"type_of":"article","id":4825130,"title":"Congrats to the Hacktoberfest Weekend Challenge: Build for a Friend Winners!","description":"We are so excited to announce the winners of the Hacktoberfest Weekend Challenge: Build for a Friend,...","readable_publish_date":"Oct 9","slug":"congrats-to-the-
[Truncated — full output (19.8 KB) temporarily saved to C:\Users\Lenovo\AppData\Local\Temp\1791598621839-copilot-tool-output-8d2230d0e7e8452892f4d9a0499627a8.txt]
{"pattern":"Touch|touch|Prize|Category|Submission|title|published_at","paths":"C:\\Users\\Lenovo\\AppData\\Local\\Temp\\1791598621839-copilot-tool-output-8d2230d0e7e8452892f4d9a0499627a8.txt","output_...
{"pattern":"Touch|touch|Prize|Category|Submission|title|published_at","paths":"C:\\Users\\Lenovo\\AppData\\Local\\Temp\\1791598621839-copilot-tool-output-d768962d67f24ebe8de920a19f789ed0.txt","output_...
{"command":"Get-ChildItem 'C:\\Users\\Lenovo\\AppData\\Local\\Temp' -Filter '*copilot-tool-output*' | Sort-Object LastWriteTime -Descending | Select-Object -First 10 FullName,Length","description":"Lo...
FullName Len
gth
-------- ---
C:\Users\Lenovo\AppData\Local\Temp\1791598621844-copilot-tool-output-d768962d67f24ebe8de920a19f789ed0.txt 286
C:\Users\Lenovo\AppData\Local\Temp\1791598621839-copilot-tool-output-8d2230d0e7e8452892f4d9a0499627a8.txt 283
C:\Users\Lenovo\AppData\Local\Temp\1791598366201-copilot-tool-output-d73927d96e604da29fd38587a9b7d1fb.txt 283
C:\Users\Lenovo\AppData\Local\Temp\1791598366199-copilot-tool-output-de274956969d44bdb7e7d6869e1c4378.txt 285
C:\Users\Lenovo\AppData\Local\Temp\1791598314030-copilot-tool-output-11456-42a55670-ce77-48e0-b5dc-8740a0eaeded.txt 753
C:\Users\Lenovo\AppData\Local\Temp\1791598307043-copilot-tool-output-345579aebf5f434a9048e157168fa9e7.txt 299
C:\Users\Lenovo\AppData\Local\Temp\1791598307016-copilot-tool-output-e3400f8195c64107af99e10c8efa4f78.txt 261
C:\Users\Lenovo\AppData\Local\Temp\1791598306749-copilot-tool-output-51ccdb36d6f541b3b1c945b925d684a0.txt 275
C:\Users\Lenovo\AppData\Local\Temp\1791598306516-copilot-tool-output-ea61a72d6c4349559ed0d354d992aa15.txt 925
C:\Users\Lenovo\AppData\Local\Temp\1791598297492-copilot-tool-output-11456-36d1678a-99e7-4503-b67b-d5c490d86218.txt 695
<shellId: 10 completed with exit code 0>
{
"url": "https://dev.to/api/articles?tag=hacktoberfest&per_page=100&page=2",
"max_length": 20000
}
Content type application/json; charset=utf-8 cannot be simplified to markdown. Here is the raw content:
Contents of https://dev.to/api/articles?tag=hacktoberfest&per_page=100&page=2:
[{"type_of":"article","id":4799331,"title":"Introduction to Python for AI and Machine Learning","description":"Python is one of the most popular programming languages in the world. It is simple to learn, easy to...","readable_publish_date":"Oct 5","slug":"introduction-to-python-for-ai-and-machine-learning-397o","path":"/arya10/introduction-to-python-for-ai-and-machine-learning-397o","url":"https://dev.to/arya10/introduction-to-python-for-ai-and-machine-learning-397o","comments_count":0,"public_reactions_count":0,"collection_id":null,"published_timestamp":"2026-10-05T05:12:10Z","language":"en","subforem_id":1,"ai_disclosure_level":"not_disclosed","ai_disclosure_label":"Not Disclosed","positive_reactions_count":0,"cover_image":null,"social_image":"https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq36ixk8xe5kd2o8vs6dc.png","canonical_url":"https://dev.to/arya10/introduction-to-python-for-ai-and-machine-learning-397o","created_at":"2026-10-05T05:12:10Z","edited_at":null,"crossposted_at":null,"published_at":"2026-10-05T05:12:10Z","last_comment_at":"2026-10-05T05:12:10Z","reading_time_minutes":2,"tag_list":["python","ai","beginners","hacktoberfest"],"tags":"python, ai, beginners, hacktoberfest","user":{"name":"Arya Joshi","username":"arya10","twitter_username":null,"github_username":"arya179","user_id":4155443,"website_url":null,"profile_image":"https://media2.dev.to/dynamic/image/width=640,height=640,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F99mvlsfu5tfj9m7ku25d.png","profile_image_90":"https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F99mvlsfu5tfj9m7ku25d.png"}},{"type_of":"article","id":4797027,"title":"JobiFC: A Finishing Coach Built for My Friend","description":"This is a submission for the Hacktoberfest Weekend Challenge: Build for a Friend. What I...","readable_publish_date":"Oct 4","slug":"jobifc-a-finishing-coach-built-for-my-friend-3cd3","path":"/rudraksh_karmakar_28/jobifc-a-finishing-coach-built-for-my-friend-3cd3","url":"https://dev.to/rudraksh_karmakar_28/jobifc-a-finishing-coach-built-for-my-friend-3cd3","comments_count":0,"public_reactions_count":0,"collection_id":null,"published_timestamp":"2026-10-04T18:20:04Z","language":"en","subforem_id":1,"ai_disclosure_level":"not_disclosed","ai_disclosure_label":"Not Disclosed","positive_reactions_count":0,"cover_image":null,"social_image":"https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdaulvmowwg0zwnnyz7gz.png","canonical_url":"https://dev.to/rudraksh_karmakar_28/jobifc-a-finishing-coach-built-for-my-friend-3cd3","created_at":"2026-10-04T18:20:04Z","edited_at":null,"crossposted_at":null,"published_at":"2026-10-04T18:20:04Z","last_comment_at":"2026-10-04T18:20:04Z","reading_time_minutes":2,"tag_list":["devchallenge","weekendchallenge","hf26challenge","hacktoberfest"],"tags":"devchallenge, weekendchallenge, hf26challenge, hacktoberfest","user":{"name":"Rudraksh Karmakar","username":"rudraksh_karmakar_28","twitter_username":null,"github_username":null,"user_id":4161725,"website_url":null,"profile_image":"https://media2.dev.to/dynamic/image/width=640,height=640,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4161725%2Fad4ce813-46d9-4422-840f-6424cf09c7d8.jpeg","profile_image_90":"https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4161725%2Fad4ce813-46d9-4422-840f-6424cf09c7d8.jpeg"}},{"type_of":"article","id":4797128,"title":"I Built My Friend an AI-Powered Allergy-Safe Meal Planner 🍳🤖","description":"This is a submission for the Hacktoberfest Weekend Challenge: Build for a Friend What I Built I...","readable_publish_date":"Oct 4","slug":"i-built-my-friend-an-ai-powered-allergy-safe-meal-planner-1c9m","path":"/meet_rathod_494ebb83b3a7d/i-built-my-friend-an-ai-powered-allergy-safe-meal-planner-1c9m","url":"https://dev.to/meet_rathod_494ebb83b3a7d/i-built-my-friend-an-ai-powered-allergy-safe-meal-planner-1c9m","comments_count":0,"public_reactions_count":0,"collection_id":null,"published_timestamp":"2026-10-04T18:45:03Z","language":"en","subforem_id":1,"ai_disclosure_level":"not_disclosed","ai_disclosure_label":"Not Disclosed","positive_reactions_count":0,"cover_image":null,"social_image":"https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%
[Truncated — full output (19.8 KB) temporarily saved to C:\Users\Lenovo\AppData\Local\Temp\1791598633294-copilot-tool-output-4fe51446e1ea4ca59eae8088b363fb50.txt]
{
"path": "C:\\Users\\Lenovo\\AppData\\Local\\Temp\\1791598621839-copilot-tool-output-8d2230d0e7e8452892f4d9a0499627a8.txt"
}
File too large to read at once (19.8 KB). Consider using the grep tool to search within the file, or view with view_range to read specific sections.
{
"path": "C:\\Users\\Lenovo\\AppData\\Local\\Temp\\1791598621844-copilot-tool-output-d768962d67f24ebe8de920a19f789ed0.txt"
}
File too large to read at once (19.8 KB). Consider using the grep tool to search within the file, or view with view_range to read specific sections.
{
"path": "C:\\Users\\Lenovo\\AppData\\Local\\Temp\\1791598633294-copilot-tool-output-4fe51446e1ea4ca59eae8088b363fb50.txt"
}
File too large to read at once (19.8 KB). Consider using the grep tool to search within the file, or view with view_range to read specific sections.
{"command":"$files = Get-ChildItem 'C:\\Users\\Lenovo\\AppData\\Local\\Temp' -Filter '*copilot-tool-output*.txt' | Where-Object { $_.Length -gt 10000 } | Sort-Object LastWriteTime -Descending | Select...
Output too large to read at once (20.3 KB). Saved to: C:\Users\Lenovo\AppData\Local\Temp\1791598643852-copilot-tool-output-11456-e3973522-3e97-4907-ad4b-b6ed152575e0.txt
Consider using tools like rg (for searching), head/tail (for viewing start/end), view with view_range (for specific sections), or jq (for JSON) to examine portions of the output.
Preview (first 500 chars):
FILE 1791598633294-copilot-tool-output-4fe51446e1ea4ca59eae8088b363fb50.txt
ConvertFrom-Json : Unterminated string passed in. (20108): [{"type_of":"article","id":4799331,"title":"Introduction to
Python for AI and Machine Learning","description":"Python is one of the most popular programming languages in the
world. It is simple to learn, easy to...","readable_publish_date":"Oct 5","slug":"introduction-to-python-for-ai-and-mac
hine-learning-397o","path":"/arya10/introduction-to-python-for-ai-and
<shellId: 11 completed with exit code 1>
{
"url": "https://dev.to/api/articles?tag=hacktoberfest&per_page=20",
"max_length": 20000
}
Content type application/json; charset=utf-8 cannot be simplified to markdown. Here is the raw content:
Contents of https://dev.to/api/articles?tag=hacktoberfest&per_page=20:
[{"type_of":"article","id":4826102,"title":"I Taught Local AI to Help Me Notice the World.","description":"Your World Is More Interesting Than Your Feed. This is a submission for the Hacktoberfest...","readable_publish_date":"Oct 10","slug":"i-taught-local-ai-to-help-me-notice-the-world-5a45","path":"/ishagautam504/i-taught-local-ai-to-help-me-notice-the-world-5a45","url":"https://dev.to/ishagautam504/i-taught-local-ai-to-help-me-notice-the-world-5a45","comments_count":0,"public_reactions_count":0,"collection_id":null,"published_timestamp":"2026-10-10T01:54:46Z","language":"en","subforem_id":1,"ai_disclosure_level":"not_disclosed","ai_disclosure_label":"Not Disclosed","positive_reactions_count":0,"cover_image":null,"social_image":"https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzlo656u088u66liiygb2.png","canonical_url":"https://dev.to/ishagautam504/i-taught-local-ai-to-help-me-notice-the-world-5a45","created_at":"2026-10-10T01:54:46Z","edited_at":null,"crossposted_at":null,"published_at":"2026-10-10T01:54:46Z","last_comment_at":"2026-10-10T01:54:46Z","reading_time_minutes":4,"tag_list":["ai","devchallenge","hacktoberfest","opensource"],"tags":"ai, devchallenge, hacktoberfest, opensource","user":{"name":"Isha Gautam","username":"ishagautam504","twitter_username":null,"github_username":"shiriei","user_id":4153893,"website_url":null,"profile_image":"https://media2.dev.to/dynamic/image/width=640,height=640,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4153893%2Fe4ab1c15-f2af-4d93-adf9-57a4efa2b410.jpg","profile_image_90":"https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4153893%2Fe4ab1c15-f2af-4d93-adf9-57a4efa2b410.jpg"}},{"type_of":"article","id":4826090,"title":"Thirsty Leaf: A Tiny Plant Reminder App That Gets You Off Your Screen 🪴","description":"This is a submission for the Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass ...","readable_publish_date":"Oct 10","slug":"thirsty-leaf-a-tiny-plant-reminder-app-that-gets-you-off-your-screen-787","path":"/ayushjhawar/thirsty-leaf-a-tiny-plant-reminder-app-that-gets-you-off-your-screen-787","url":"https://dev.to/ayushjhawar/thirsty-leaf-a-tiny-plant-reminder-app-that-gets-you-off-your-screen-787","comments_count":0,"public_reactions_count":0,"collection_id":null,"published_timestamp":"2026-10-10T01:50:17Z","language":"en","subforem_id":1,"ai_disclosure_level":"not_disclosed","ai_disclosure_label":"Not Disclosed","positive_reactions_count":0,"cover_image":null,"social_image":"https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frb5b3eqz4ile0fhpuw0a.png","canonical_url":"https://dev.to/ayushjhawar/thirsty-leaf-a-tiny-plant-reminder-app-that-gets-you-off-your-screen-787","created_at":"2026-10-10T01:50:17Z","edited_at":"2026-10-10T01:51:12Z","crossposted_at":null,"published_at":"2026-10-10T01:50:17Z","last_comment_at":"2026-10-10T01:50:17Z","reading_time_minutes":2,"tag_list":["devchallenge","hf26challenge","hacktoberfest","webdev"],"tags":"devchallenge, hf26challenge, hacktoberfest, webdev","user":{"name":"Ayush Jhawar","username":"ayushjhawar","twitter_username":null,"github_username":null,"user_id":1991366,"website_url":null,"profile_image":"https://media2.dev.to/dynamic/image/width=640,height=640,fit=cover,gravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1991366%2Fdbb2df41-09e5-4a23-b475-553f09cfa987.gif","profile_image_90":"https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1991366%2Fdbb2df41-09e5-4a23-b475-553f09cfa987.gif"}},{"type_of":"article","id":4825380,"title":"EcoID: An Offline Plant Identifier That Gets You Outside","description":"This is a submission for the Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass. ...","readable_publish_date":"Oct 9","slug":"ecoid-an-offline-plant-identifier-that-gets-you-outside-4b6e","path":"/bangkah/ecoid-an-offline-plant-identifier-that-gets-you-outside-4b6e","url":"https://dev.to/bangkah/ecoid-an-offline-plant-identifier-that-gets-you-outside-4b6e","comments_count":0,"public_reactions_count":2,"collection_id":null,"published_timestamp":"2026-10-09T20:55:09Z","language":"en","subforem_id":1,"ai_disclosure_level":"not_disclosed","ai_disclosure_label":"Not Disclosed","positive_reactions_count":2
[Truncated — full output (19.8 KB) temporarily saved to C:\Users\Lenovo\AppData\Local\Temp\1791598649703-copilot-tool-output-0d2e9e72551a4e7d903495768cbd0b82.txt]
{
"url": "https://dev.to/api/articles?tag=hf26challenge&per_page=20",
"max_length": 20000
}
Content type application/json; charset=utf-8 cannot be simplified to markdown. Here is the raw content:
Contents of https://dev.to/api/articles?tag=hf26challenge&per_page=20:
[{"type_of":"article","id":4825130,"title":"Congrats to the Hacktoberfest Weekend Challenge: Build for a Friend Winners!","description":"We are so excited to announce the winners of the Hacktoberfest Weekend Challenge: Build for a Friend,...","readable_publish_date":"Oct 9","slug":"congrats-to-the-hacktoberfest-weekend-challenge-build-for-a-friend-winners-pgc","path":"/devteam/congrats-to-the-hacktoberfest-weekend-challenge-build-for-a-friend-winners-pgc","url":"https://dev.to/devteam/congrats-to-the-hacktoberfest-weekend-challenge-build-for-a-friend-winners-pgc","comments_count":13,"public_reactions_count":46,"collection_id":null,"published_timestamp":"2026-10-09T19:32:27Z","language":"en","subforem_id":1,"ai_disclosure_level":"some_ai","ai_disclosure_label":"AI-assisted","positive_reactions_count":46,"cover_image":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu5wpcby840hb2v82xxkn.png","social_image":"https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu5wpcby840hb2v82xxkn.png","canonical_url":"https://dev.to/devteam/congrats-to-the-hacktoberfest-weekend-challenge-build-for-a-friend-winners-pgc","created_at":"2026-10-09T19:23:07Z","edited_at":null,"crossposted_at":null,"published_at":"2026-10-09T19:32:27Z","last_comment_at":"2026-10-10T00:12:33Z","reading_time_minutes":4,"tag_list":["devchallenge","weekendchallenge","hf26challenge"],"tags":"devchallenge, weekendchallenge, hf26challenge","user":{"name":"Jem","username":"heyitsjem","twitter_username":null,"github_username":"GemHell","user_id":1875101,"website_url":null,"profile_image":"https://media2.dev.to/dynamic/image/width=640,height=640,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1875101%2Ffed41ffa-3228-4a95-8381-2562d87d01ba.jpg","profile_image_90":"https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1875101%2Ffed41ffa-3228-4a95-8381-2562d87d01ba.jpg"},"organization":{"name":"The DEV Team","username":"devteam","slug":"devteam","profile_image":"https://media2.dev.to/dynamic/image/width=640,height=640,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F1%2Fd908a186-5651-4a5a-9f76-15200bc6801f.jpg","profile_image_90":"https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F1%2Fd908a186-5651-4a5a-9f76-15200bc6801f.jpg"}},{"type_of":"article","id":4812457,"title":"I built an offline AI that knows your last frost date, no internet, no API","description":"An open-weight tabular model forecasts your last spring frost and a local Gemma writes the planting advice. Fully offline, no account, $0 to run.","readable_publish_date":"Oct 9","slug":"i-built-an-offline-ai-that-knows-your-last-frost-date-no-internet-no-api-3b8e","path":"/sarvar_04/i-built-an-offline-ai-that-knows-your-last-frost-date-no-internet-no-api-3b8e","url":"https://dev.to/sarvar_04/i-built-an-offline-ai-that-knows-your-last-frost-date-no-internet-no-api-3b8e","comments_count":0,"public_reactions_count":14,"collection_id":null,"published_timestamp":"2026-10-09T12:44:55Z","language":"en","subforem_id":1,"ai_disclosure_level":"some_ai","ai_disclosure_label":"AI-assisted","positive_reactions_count":14,"cover_image":null,"social_image":"https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fduwuvmt1n9j9mnsa92h8.png","canonical_url":"https://dev.to/sarvar_04/i-built-an-offline-ai-that-knows-your-last-frost-date-no-internet-no-api-3b8e","created_at":"2026-10-07T13:11:46Z","edited_at":null,"crossposted_at":null,"published_at":"2026-10-09T12:44:55Z","last_comment_at":"2026-10-09T12:44:55Z","reading_time_minutes":9,"tag_list":["devchallenge","hf26challenge","ai","opensource"],"tags":"devchallenge, hf26challenge, ai, opensource","user":{"name":"Sarvar Nadaf","username":"sarvar_04","twitter_username":"SarvarN_04","github_username":"simplynadaf","user_id":1163149,"website_url":"https://sarvarnadaf.com/","profile_image":"https://media2.dev.to/dynamic/image/width=640,height=640,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1163149%2F5786d6f1-a0b7-46bb-ae3d-7ce0ff849e30.jpg","profile_imag
[Truncated — full output (19.8 KB) temporarily saved to C:\Users\Lenovo\AppData\Local\Temp\1791598649661-copilot-tool-output-531e97863a074e80896b362e1c292c61.txt]
{
"url": "https://dev.to/api/articles?tag=hacktoberfest&top=7&per_page=20",
"max_length": 20000
}
Content type application/json; charset=utf-8 cannot be simplified to markdown. Here is the raw content:
Contents of https://dev.to/api/articles?tag=hacktoberfest&top=7&per_page=20:
[{"type_of":"article","id":4803571,"title":"Build a Tiny Virtual Pet on Arduino in 60 Minutes 🐾","description":"Turn an Arduino Uno Q into Kiku, a Tamagotchi-style virtual pet with an animated LED face, moods, and keyboard controls.","readable_publish_date":"Oct 6","slug":"build-a-tiny-virtual-pet-on-arduino-in-60-minutes-2cf","path":"/heraistudio/build-a-tiny-virtual-pet-on-arduino-in-60-minutes-2cf","url":"https://dev.to/heraistudio/build-a-tiny-virtual-pet-on-arduino-in-60-minutes-2cf","comments_count":15,"public_reactions_count":40,"collection_id":null,"published_timestamp":"2026-10-06T00:47:44Z","language":"en","subforem_id":1,"ai_disclosure_level":"not_disclosed","ai_disclosure_label":"Not Disclosed","positive_reactions_count":40,"cover_image":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdvrnvtfr1l854y335hld.png","social_image":"https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdvrnvtfr1l854y335hld.png","canonical_url":"https://dev.to/heraistudio/build-a-tiny-virtual-pet-on-arduino-in-60-minutes-2cf","created_at":"2026-10-05T22:35:06Z","edited_at":"2026-10-06T00:56:19Z","crossposted_at":null,"published_at":"2026-10-06T00:47:44Z","last_comment_at":"2026-10-08T16:12:29Z","reading_time_minutes":5,"tag_list":["arduino","beginners","programming","hacktoberfest"],"tags":"arduino, beginners, programming, hacktoberfest","user":{"name":"Jen Looper","username":"jenlooper","twitter_username":"jenlooper","github_username":"jlooper","user_id":9819,"website_url":"http://www.jenlooper.com","profile_image":"https://media2.dev.to/dynamic/image/width=640,height=640,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F9819%2Fd0d0dc5d-031b-43fe-85a4-469def492123.jpg","profile_image_90":"https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F9819%2Fd0d0dc5d-031b-43fe-85a4-469def492123.jpg"},"organization":{"name":"Her AI Studio","username":"heraistudio","slug":"heraistudio","profile_image":"https://media2.dev.to/dynamic/image/width=640,height=640,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F14625%2Fc587ad01-6b1b-4e97-b620-9e19a4a8a2cb.png","profile_image_90":"https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F14625%2Fc587ad01-6b1b-4e97-b620-9e19a4a8a2cb.png"}},{"type_of":"article","id":4803194,"title":"Touch Grass: 6 Project Ideas for People Who Live in a Terminal (Hacktoberfest DEV Challenge, Week 1)","description":"The theme for Week 1 of the Hacktoberfest DEV Challenge is out, and it is a little personal for...","readable_publish_date":"Oct 5","slug":"touch-grass-6-project-ideas-for-people-who-live-in-a-terminal-hacktoberfest-dev-challenge-week-1-1e7n","path":"/devopsdaily/touch-grass-6-project-ideas-for-people-who-live-in-a-terminal-hacktoberfest-dev-challenge-week-1-1e7n","url":"https://dev.to/devopsdaily/touch-grass-6-project-ideas-for-people-who-live-in-a-terminal-hacktoberfest-dev-challenge-week-1-1e7n","comments_count":1,"public_reactions_count":18,"collection_id":null,"published_timestamp":"2026-10-05T20:01:41Z","language":"en","subforem_id":1,"ai_disclosure_level":"not_disclosed","ai_disclosure_label":"Not Disclosed","positive_reactions_count":18,"cover_image":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjw2eiz14syipjdx6ltm8.webp","social_image":"https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjw2eiz14syipjdx6ltm8.webp","canonical_url":"https://dev.to/devopsdaily/touch-grass-6-project-ideas-for-people-who-live-in-a-terminal-hacktoberfest-dev-challenge-week-1-1e7n","created_at":"2026-10-05T20:01:32Z","edited_at":null,"crossposted_at":null,"published_at":"2026-10-05T20:01:41Z","last_comment_at":"2026-10-08T14:06:39Z","reading_time_minutes":9,"tag_list":["hacktoberfest","ai","devops","opensource"],"tags":"hacktoberfest, ai, devops, opensource","user":{"name":"DevOps Daily","username":"devopsdaily","twitter_username":"thedevopsdaily","github_username":"thedevopsdaily","user_id":382434,"website_url":"https://devops-daily.com","profile_image":"https://
[Truncated — full output (19.8 KB) temporarily saved to C:\Users\Lenovo\AppData\Local\Temp\1791598649749-copilot-tool-output-353e3078e5734b789b1db77b082ab6b9.txt]
{
"url": "https://dev.to/api/articles?tag=hacktoberfest&per_page=5",
"max_length": 12000
}
Content type application/json; charset=utf-8 cannot be simplified to markdown. Here is the raw content:
Contents of https://dev.to/api/articles?tag=hacktoberfest&per_page=5:
[{"type_of":"article","id":4826102,"title":"I Taught Local AI to Help Me Notice the World.","description":"Your World Is More Interesting Than Your Feed. This is a submission for the Hacktoberfest...","readable_publish_date":"Oct 10","slug":"i-taught-local-ai-to-help-me-notice-the-world-5a45","path":"/ishagautam504/i-taught-local-ai-to-help-me-notice-the-world-5a45","url":"https://dev.to/ishagautam504/i-taught-local-ai-to-help-me-notice-the-world-5a45","comments_count":0,"public_reactions_count":0,"collection_id":null,"published_timestamp":"2026-10-10T01:54:46Z","language":"en","subforem_id":1,"ai_disclosure_level":"not_disclosed","ai_disclosure_label":"Not Disclosed","positive_reactions_count":0,"cover_image":null,"social_image":"https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzlo656u088u66liiygb2.png","canonical_url":"https://dev.to/ishagautam504/i-taught-local-ai-to-help-me-notice-the-world-5a45","created_at":"2026-10-10T01:54:46Z","edited_at":null,"crossposted_at":null,"published_at":"2026-10-10T01:54:46Z","last_comment_at":"2026-10-10T01:54:46Z","reading_time_minutes":4,"tag_list":["ai","devchallenge","hacktoberfest","opensource"],"tags":"ai, devchallenge, hacktoberfest, opensource","user":{"name":"Isha Gautam","username":"ishagautam504","twitter_username":null,"github_username":"shiriei","user_id":4153893,"website_url":null,"profile_image":"https://media2.dev.to/dynamic/image/width=640,height=640,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4153893%2Fe4ab1c15-f2af-4d93-adf9-57a4efa2b410.jpg","profile_image_90":"https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4153893%2Fe4ab1c15-f2af-4d93-adf9-57a4efa2b410.jpg"}},{"type_of":"article","id":4826090,"title":"Thirsty Leaf: A Tiny Plant Reminder App That Gets You Off Your Screen 🪴","description":"This is a submission for the Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass ...","readable_publish_date":"Oct 10","slug":"thirsty-leaf-a-tiny-plant-reminder-app-that-gets-you-off-your-screen-787","path":"/ayushjhawar/thirsty-leaf-a-tiny-plant-reminder-app-that-gets-you-off-your-screen-787","url":"https://dev.to/ayushjhawar/thirsty-leaf-a-tiny-plant-reminder-app-that-gets-you-off-your-screen-787","comments_count":0,"public_reactions_count":0,"collection_id":null,"published_timestamp":"2026-10-10T01:50:17Z","language":"en","subforem_id":1,"ai_disclosure_level":"not_disclosed","ai_disclosure_label":"Not Disclosed","positive_reactions_count":0,"cover_image":null,"social_image":"https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frb5b3eqz4ile0fhpuw0a.png","canonical_url":"https://dev.to/ayushjhawar/thirsty-leaf-a-tiny-plant-reminder-app-that-gets-you-off-your-screen-787","created_at":"2026-10-10T01:50:17Z","edited_at":"2026-10-10T01:51:12Z","crossposted_at":null,"published_at":"2026-10-10T01:50:17Z","last_comment_at":"2026-10-10T01:50:17Z","reading_time_minutes":2,"tag_list":["devchallenge","hf26challenge","hacktoberfest","webdev"],"tags":"devchallenge, hf26challenge, hacktoberfest, webdev","user":{"name":"Ayush Jhawar","username":"ayushjhawar","twitter_username":null,"github_username":null,"user_id":1991366,"website_url":null,"profile_image":"https://media2.dev.to/dynamic/image/width=640,height=640,fit=cover,gravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1991366%2Fdbb2df41-09e5-4a23-b475-553f09cfa987.gif","profile_image_90":"https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1991366%2Fdbb2df41-09e5-4a23-b475-553f09cfa987.gif"}},{"type_of":"article","id":4825380,"title":"EcoID: An Offline Plant Identifier That Gets You Outside","description":"This is a submission for the Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass. ...","readable_publish_date":"Oct 9","slug":"ecoid-an-offline-plant-identifier-that-gets-you-outside-4b6e","path":"/bangkah/ecoid-an-offline-plant-identifier-that-gets-you-outside-4b6e","url":"https://dev.to/bangkah/ecoid-an-offline-plant-identifier-that-gets-you-outside-4b6e","comments_count":0,"public_reactions_count":2,"collection_id":null,"published_timestamp":"2026-10-09T20:55:09Z","language":"en","subforem_id":1,"ai_disclosure_level":"not_disclosed","ai_disclosure_label":"Not Disclosed","positive_reactions_count":2,
[Truncated — full output (10.3 KB) temporarily saved to C:\Users\Lenovo\AppData\Local\Temp\1791598654613-copilot-tool-output-7226ec43225f4444bc22954afb959ee5.txt]
{
"url": "https://dev.to/api/articles?tag=hacktoberfest&top=7&per_page=5",
"max_length": 12000
}
Content type application/json; charset=utf-8 cannot be simplified to markdown. Here is the raw content:
Contents of https://dev.to/api/articles?tag=hacktoberfest&top=7&per_page=5:
[{"type_of":"article","id":4803571,"title":"Build a Tiny Virtual Pet on Arduino in 60 Minutes 🐾","description":"Turn an Arduino Uno Q into Kiku, a Tamagotchi-style virtual pet with an animated LED face, moods, and keyboard controls.","readable_publish_date":"Oct 6","slug":"build-a-tiny-virtual-pet-on-arduino-in-60-minutes-2cf","path":"/heraistudio/build-a-tiny-virtual-pet-on-arduino-in-60-minutes-2cf","url":"https://dev.to/heraistudio/build-a-tiny-virtual-pet-on-arduino-in-60-minutes-2cf","comments_count":15,"public_reactions_count":40,"collection_id":null,"published_timestamp":"2026-10-06T00:47:44Z","language":"en","subforem_id":1,"ai_disclosure_level":"not_disclosed","ai_disclosure_label":"Not Disclosed","positive_reactions_count":40,"cover_image":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdvrnvtfr1l854y335hld.png","social_image":"https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdvrnvtfr1l854y335hld.png","canonical_url":"https://dev.to/heraistudio/build-a-tiny-virtual-pet-on-arduino-in-60-minutes-2cf","created_at":"2026-10-05T22:35:06Z","edited_at":"2026-10-06T00:56:19Z","crossposted_at":null,"published_at":"2026-10-06T00:47:44Z","last_comment_at":"2026-10-08T16:12:29Z","reading_time_minutes":5,"tag_list":["arduino","beginners","programming","hacktoberfest"],"tags":"arduino, beginners, programming, hacktoberfest","user":{"name":"Jen Looper","username":"jenlooper","twitter_username":"jenlooper","github_username":"jlooper","user_id":9819,"website_url":"http://www.jenlooper.com","profile_image":"https://media2.dev.to/dynamic/image/width=640,height=640,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F9819%2Fd0d0dc5d-031b-43fe-85a4-469def492123.jpg","profile_image_90":"https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F9819%2Fd0d0dc5d-031b-43fe-85a4-469def492123.jpg"},"organization":{"name":"Her AI Studio","username":"heraistudio","slug":"heraistudio","profile_image":"https://media2.dev.to/dynamic/image/width=640,height=640,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F14625%2Fc587ad01-6b1b-4e97-b620-9e19a4a8a2cb.png","profile_image_90":"https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F14625%2Fc587ad01-6b1b-4e97-b620-9e19a4a8a2cb.png"}},{"type_of":"article","id":4803194,"title":"Touch Grass: 6 Project Ideas for People Who Live in a Terminal (Hacktoberfest DEV Challenge, Week 1)","description":"The theme for Week 1 of the Hacktoberfest DEV Challenge is out, and it is a little personal for...","readable_publish_date":"Oct 5","slug":"touch-grass-6-project-ideas-for-people-who-live-in-a-terminal-hacktoberfest-dev-challenge-week-1-1e7n","path":"/devopsdaily/touch-grass-6-project-ideas-for-people-who-live-in-a-terminal-hacktoberfest-dev-challenge-week-1-1e7n","url":"https://dev.to/devopsdaily/touch-grass-6-project-ideas-for-people-who-live-in-a-terminal-hacktoberfest-dev-challenge-week-1-1e7n","comments_count":1,"public_reactions_count":18,"collection_id":null,"published_timestamp":"2026-10-05T20:01:41Z","language":"en","subforem_id":1,"ai_disclosure_level":"not_disclosed","ai_disclosure_label":"Not Disclosed","positive_reactions_count":18,"cover_image":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjw2eiz14syipjdx6ltm8.webp","social_image":"https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjw2eiz14syipjdx6ltm8.webp","canonical_url":"https://dev.to/devopsdaily/touch-grass-6-project-ideas-for-people-who-live-in-a-terminal-hacktoberfest-dev-challenge-week-1-1e7n","created_at":"2026-10-05T20:01:32Z","edited_at":null,"crossposted_at":null,"published_at":"2026-10-05T20:01:41Z","last_comment_at":"2026-10-08T14:06:39Z","reading_time_minutes":9,"tag_list":["hacktoberfest","ai","devops","opensource"],"tags":"hacktoberfest, ai, devops, opensource","user":{"name":"DevOps Daily","username":"devopsdaily","twitter_username":"thedevopsdaily","github_username":"thedevopsdaily","user_id":382434,"website_url":"https://devops-daily.com","profile_image":"https://m
[Truncated — full output (11.7 KB) temporarily saved to C:\Users\Lenovo\AppData\Local\Temp\1791598654679-copilot-tool-output-d1ccd37ff65a492ba3df70dbbda8dc56.txt]
{
"url": "https://dev.to/api/articles?tag=hf26challenge&per_page=5",
"max_length": 12000
}
Content type application/json; charset=utf-8 cannot be simplified to markdown. Here is the raw content:
Contents of https://dev.to/api/articles?tag=hf26challenge&per_page=5:
[{"type_of":"article","id":4825130,"title":"Congrats to the Hacktoberfest Weekend Challenge: Build for a Friend Winners!","description":"We are so excited to announce the winners of the Hacktoberfest Weekend Challenge: Build for a Friend,...","readable_publish_date":"Oct 9","slug":"congrats-to-the-hacktoberfest-weekend-challenge-build-for-a-friend-winners-pgc","path":"/devteam/congrats-to-the-hacktoberfest-weekend-challenge-build-for-a-friend-winners-pgc","url":"https://dev.to/devteam/congrats-to-the-hacktoberfest-weekend-challenge-build-for-a-friend-winners-pgc","comments_count":13,"public_reactions_count":46,"collection_id":null,"published_timestamp":"2026-10-09T19:32:27Z","language":"en","subforem_id":1,"ai_disclosure_level":"some_ai","ai_disclosure_label":"AI-assisted","positive_reactions_count":46,"cover_image":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu5wpcby840hb2v82xxkn.png","social_image":"https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu5wpcby840hb2v82xxkn.png","canonical_url":"https://dev.to/devteam/congrats-to-the-hacktoberfest-weekend-challenge-build-for-a-friend-winners-pgc","created_at":"2026-10-09T19:23:07Z","edited_at":null,"crossposted_at":null,"published_at":"2026-10-09T19:32:27Z","last_comment_at":"2026-10-10T00:12:33Z","reading_time_minutes":4,"tag_list":["devchallenge","weekendchallenge","hf26challenge"],"tags":"devchallenge, weekendchallenge, hf26challenge","user":{"name":"Jem","username":"heyitsjem","twitter_username":null,"github_username":"GemHell","user_id":1875101,"website_url":null,"profile_image":"https://media2.dev.to/dynamic/image/width=640,height=640,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1875101%2Ffed41ffa-3228-4a95-8381-2562d87d01ba.jpg","profile_image_90":"https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1875101%2Ffed41ffa-3228-4a95-8381-2562d87d01ba.jpg"},"organization":{"name":"The DEV Team","username":"devteam","slug":"devteam","profile_image":"https://media2.dev.to/dynamic/image/width=640,height=640,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F1%2Fd908a186-5651-4a5a-9f76-15200bc6801f.jpg","profile_image_90":"https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F1%2Fd908a186-5651-4a5a-9f76-15200bc6801f.jpg"}},{"type_of":"article","id":4812457,"title":"I built an offline AI that knows your last frost date, no internet, no API","description":"An open-weight tabular model forecasts your last spring frost and a local Gemma writes the planting advice. Fully offline, no account, $0 to run.","readable_publish_date":"Oct 9","slug":"i-built-an-offline-ai-that-knows-your-last-frost-date-no-internet-no-api-3b8e","path":"/sarvar_04/i-built-an-offline-ai-that-knows-your-last-frost-date-no-internet-no-api-3b8e","url":"https://dev.to/sarvar_04/i-built-an-offline-ai-that-knows-your-last-frost-date-no-internet-no-api-3b8e","comments_count":0,"public_reactions_count":14,"collection_id":null,"published_timestamp":"2026-10-09T12:44:55Z","language":"en","subforem_id":1,"ai_disclosure_level":"some_ai","ai_disclosure_label":"AI-assisted","positive_reactions_count":14,"cover_image":null,"social_image":"https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fduwuvmt1n9j9mnsa92h8.png","canonical_url":"https://dev.to/sarvar_04/i-built-an-offline-ai-that-knows-your-last-frost-date-no-internet-no-api-3b8e","created_at":"2026-10-07T13:11:46Z","edited_at":null,"crossposted_at":null,"published_at":"2026-10-09T12:44:55Z","last_comment_at":"2026-10-09T12:44:55Z","reading_time_minutes":9,"tag_list":["devchallenge","hf26challenge","ai","opensource"],"tags":"devchallenge, hf26challenge, ai, opensource","user":{"name":"Sarvar Nadaf","username":"sarvar_04","twitter_username":"SarvarN_04","github_username":"simplynadaf","user_id":1163149,"website_url":"https://sarvarnadaf.com/","profile_image":"https://media2.dev.to/dynamic/image/width=640,height=640,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1163149%2F5786d6f1-a0b7-46bb-ae3d-7ce0ff849e30.jpg","profile_image
[Truncated — full output (11.8 KB) temporarily saved to C:\Users\Lenovo\AppData\Local\Temp\1791598654869-copilot-tool-output-0f098409515247b28ee20f7f3586aef2.txt]
{"command":"$urls=@('https://dev.to/api/articles?tag=hacktoberfest&per_page=100','https://dev.to/api/articles?tag=hacktoberfest&top=7&per_page=100'); foreach($u in $urls){$r=Invoke-RestMethod -Uri $u;...
Output too large to read at once (19.7 KB). Saved to: C:\Users\Lenovo\AppData\Local\Temp\1791598660238-copilot-tool-output-11456-7ecbfed0-dd19-49db-91cf-77c8d243fd84.txt
Consider using tools like rg (for searching), head/tail (for viewing start/end), view with view_range (for specific sections), or jq (for JSON) to examine portions of the output.
Preview (first 500 chars):
id title
-- -----
4826102 I Taught Local AI to Help Me Notice the World.
4826090 Thirsty Leaf: A Tiny Plant Reminder App That Gets You Off Your Screen 🪴
4825380 EcoID: An O
<shellId: 12 completed with exit code 0>
{"command":"$r=Invoke-RestMethod -Uri 'https://dev.to/api/articles?tag=hacktoberfest&per_page=100'; $r | Where-Object {$_.published_at -ge '2026-10-05' -and $_.published_at -lt '2026-10-11'} | Select-...
Output too large to read at once (19.4 KB). Saved to: C:\Users\Lenovo\AppData\Local\Temp\1791598667873-copilot-tool-output-11456-e2d00850-7246-416e-b8c2-220d57ac8170.txt
Consider using tools like rg (for searching), head/tail (for viewing start/end), view with view_range (for specific sections), or jq (for JSON) to examine portions of the output.
Preview (first 500 chars):
"id","title","path","published_at","positive_reactions_count","comments_count","tags"
"4826102","I Taught Local AI to Help Me Notice the World.","/ishagautam504/i-taught-local-ai-to-help-me-notice-the-world-5a45","2026-10-10T01:54:46Z","0","0","ai,devchallenge,hacktoberfest,opensource"
"4826090","Thirsty Leaf: A Tiny Plant Reminder App That Gets You Off Your Screen ??","/ayushjhawar/thirsty-leaf-a-tiny-plant-reminder-app-that-gets-you-off-your-screen-787","2026-10-10T01:50:17Z","0","0","devchall
<shellId: 13 completed with exit code 0>
{"command":"Import-Csv 'C:\\Users\\Lenovo\\.copilot\\session-state\\36a364a0-4d59-4fe3-8349-b59433759a92\\files\\submissions.csv' | Select-Object -First 40 id,title,path,published_at,positive_reaction...
id title
-- -----
4826102 I Taught Local AI to Help Me Notice the World.
4826090 Thirsty Leaf: A Tiny Plant Reminder App That Gets You Off Your Screen ??
4825380 EcoID: An Offline Plant Identifier That Gets You Outside
4824064 Plain Sight Walk is a walking game that lives in your pocket.
4825711 ?? Ruta Viva AI: Turning Everyday Walks into AI-Powered Adventures
4825776 Nikal Pado: An offline-first outdoor adventure planner for the Touch Grass challenge
4825097 Comparing AI Models in Cursor: Opus 5.5 vs. Grok 4.7
4825056 Outbound ?? An AI Quest App That Wants You to Close It
4824813 your first open-source contribution in 2026: what to hand the agent, what to keep for yourself
4822262 Trailside: an open-weight bird call identifier that talks back
4823882 I Built an Offline AI That Tells You What to Plant in One of the Hottest Climates on Earth ??
4824446 How to build a Biomedical Literature Search Agent
4822843 Shabdasakhi (???????): A Gemma-Powered Marathi Expression Coach
4821556 RaahSaathi AI: An Offline Companion for Touching Grass
4821642 RaahSaathi AI: An Offline Companion for Touching Grass
4822447 Building an AI Code Visualizer for Hacktoberfest 2026
4825033 My First Hacktoberfest: Starting My Open Source Journey ??
4820459 Touch Grass: Verified Field Notes From Your Hike (Hacktoberfest Week 1)
4823898 First Dev Post
4819133 Local Micro-Garden & Frost Date Advisor | Touch Grass Challenge
4819120 NQT Saathi: An Offline Hinglish Prep Buddy I Built for My Friend
4817565 Building SortSense: A Local AI File Organizer Built in a Single Hack Day
4817238 Prompt Heist: a game where you talk your way past Gemma-powered guards (and learn how to defend AI apps)
4817413 ClaimPilot: Building an AI Evidence Intelligence System with Gemma 4
4817441 ArchGuard: The Visual Architecture Linter for Open-Source Repositories
4817160 DebugBuddy: an AI mentor that teaches beginners to fix their own bugs
4817191 Campus Lost & Found
4817062 GitHygiene: Does This Vulnerability Actually Matter in My Code?
4817067 Our Experience at the Hacktober Fest
4817054 PCSense - an AI technician that proves it fixed your PC before it tells you so
4816912 Chronicles of Hacktoberfest Amrita
4817073 We built a second brain that fact-checks itself, running Gemma 4 locally, in one hackathon day Connectore
-Hacktoberfest hackathon - Team WInDaChat
4817327 HACKTOBER FEST
4817088 Red-Team AI: Stress-Test Your Ideas Before They Fail
4816079 ?? Starting My Hacktoberfest 2026 Journey
4814396 I Built DevDrop to Help My Friend Turn an Idea Into a Website
4813234 NaturePulse: An Offline AI Companion to Get You Outside
4816636 ?? TouchGrass AI: Turning Screen Time Into Outdoor Time with Local Gemma
4810215 TouchGrass AI � An Open-Source AI Outdoor Challenge Generator ??
4813037 [Boost]
<shellId: 14 completed with exit code 0>
{"command":"$h=(Invoke-WebRequest -Uri 'https://dev.to/challenges/hacktoberfest-week1-2026-10-05').Content; $h | Select-String -Pattern 'Featured|Partner|Prize|Touch Grass|Week 1|categories' -AllMatch...
Invoke-WebRequest : Windows PowerShell is in NonInteractive mode. Read and Prompt functionality is not available.
At line:2 char:5
+ $h=(Invoke-WebRequest -Uri 'https://dev.to/challenges/hacktoberfest-w ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [Invoke-WebRequest], PSInvalidOperationException
+ FullyQualifiedErrorId : InvalidOperation,Microsoft.PowerShell.Commands.InvokeWebRequestCommand
<shellId: 15 completed with exit code 1>
{"command":"curl.exe -L 'https://dev.to/challenges/hacktoberfest-week1-2026-10-05' -o 'C:\\Users\\Lenovo\\.copilot\\session-state\\36a364a0-4d59-4fe3-8349-b59433759a92\\files\\challenge.html'; Select-...
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 0
100 361.3k 100 361.3k 0 0 0.98M 0 0
100 361.3k 100 361.3k 0 0 0.98M 0 0
100 361.3k 100 361.3k 0 0 0.98M 0 0
C:\Users\Lenovo\.copilot\session-state\36a364a0-4d59-4fe3-8349-b59433759a92\files\challenge.html:909: /* Hub prompt
panel: no prize box, so the judging criteria sit in a compact card
C:\Users\Lenovo\.copilot\session-state\36a364a0-4d59-4fe3-8349-b59433759a92\files\challenge.html:1156: /* A heading
that groups prize categories, with a one-line prize summary. */
C:\Users\Lenovo\.copilot\session-state\36a364a0-4d59-4fe3-8349-b59433759a92\files\challenge.html:1157: #custom
.challenge-prize-group {
C:\Users\Lenovo\.copilot\session-state\36a364a0-4d59-4fe3-8349-b59433759a92\files\challenge.html:1165: #custom
.challenge-panel + .challenge-prize-group {
C:\Users\Lenovo\.copilot\session-state\36a364a0-4d59-4fe3-8349-b59433759a92\files\challenge.html:1169: #custom
.challenge-prize-group h3 {
C:\Users\Lenovo\.copilot\session-state\36a364a0-4d59-4fe3-8349-b59433759a92\files\challenge.html:1175: #custom
.challenge-prize-group p {
C:\Users\Lenovo\.copilot\session-state\36a364a0-4d59-4fe3-8349-b59433759a92\files\challenge.html:1180: #custom
.challenge-prize-categories-note {
C:\Users\Lenovo\.copilot\session-state\36a364a0-4d59-4fe3-8349-b59433759a92\files\challenge.html:1186: /* Prize
category panels with a partner logo tile on the left (Hacktoberfest 2026),
C:\Users\Lenovo\.copilot\session-state\36a364a0-4d59-4fe3-8349-b59433759a92\files\challenge.html:1188: #custom
.challenge-prize-category {
C:\Users\Lenovo\.copilot\session-state\36a364a0-4d59-4fe3-8349-b59433759a92\files\challenge.html:1194: #custom
.challenge-prize-category + .challenge-prize-category {
C:\Users\Lenovo\.copilot\session-state\36a364a0-4d59-4fe3-8349-b59433759a92\files\challenge.html:1198: #custom
.challenge-prize-category__main {
C:\Users\Lenovo\.copilot\session-state\36a364a0-4d59-4fe3-8349-b59433759a92\files\challenge.html:1205: #custom
.challenge-prize-category__logos {
C:\Users\Lenovo\.copilot\session-state\36a364a0-4d59-4fe3-8349-b59433759a92\files\challenge.html:1217: #custom
.challenge-prize-category__logos img {
C:\Users\Lenovo\.copilot\session-state\36a364a0-4d59-4fe3-8349-b59433759a92\files\challenge.html:1225: #custom
.challenge-prize-category h3 {
C:\Users\Lenovo\.copilot\session-state\36a364a0-4d59-4fe3-8349-b59433759a92\files\challenge.html:1230: #custom
.challenge-prize-category p {
C:\Users\Lenovo\.copilot\session-state\36a364a0-4d59-4fe3-8349-b59433759a92\files\challenge.html:1234: #custom
.challenge-prize-category .challenge-panel__aside {
C:\Users\Lenovo\.copilot\session-state\36a364a0-4d59-4fe3-8349-b59433759a92\files\challenge.html:1241: #custom
.challenge-prize-category .challenge-panel__aside h4 {
C:\Users\Lenovo\.copilot\session-state\36a364a0-4d59-4fe3-8349-b59433759a92\files\challenge.html:1413: #custom
.challenge-prize-category .challenge-panel__aside {
C:\Users\Lenovo\.copilot\session-state\36a364a0-4d59-4fe3-8349-b59433759a92\files\challenge.html:1428: #custom
.challenge-prize-category__main {
C:\Users\Lenovo\.copilot\session-state\36a364a0-4d59-4fe3-8349-b59433759a92\files\challenge.html:1453: #custom
.challenge-prize-category__main {
<shellId: 16 completed with exit code 0>
{"command":"Select-String -Path 'C:\\Users\\Lenovo\\.copilot\\session-state\\36a364a0-4d59-4fe3-8349-b59433759a92\\files\\challenge.html' -Pattern 'Prize Categories|Featured partner|Partner categories...
Output too large to read at once (45.4 KB). Saved to: C:\Users\Lenovo\AppData\Local\Temp\1791598689361-copilot-tool-output-11456-4f8b19e2-0b19-45d6-98da-85b41dfc2e8c.txt
Consider using tools like rg (for searching), head/tail (for viewing start/end), view with view_range (for specific sections), or jq (for JSON) to examine portions of the output.
Preview (first 500 chars):
<h3>Best Use of Render</h3>
<p class="tw-text-black dark:tw-text-zinc-50">Use Render as your project's AI runtime, to host an agent's front end, or to run Hermes or OpenClaw.</p>
<div class="challenge-panel challenge-prompt challenge-prize-category" id="best-use-of-tinker">
<h3>Best Use of Tinker</h3>
<p class="tw-text-black dark:tw-text-zinc-50">Use Thinking Machines' Tinker to fine-tune a model for a specific task, and show a clear improvement in performance, latency, or cost over a baseline.<
<shellId: 17 completed with exit code 0>
{"command":"$lines=Get-Content 'C:\\Users\\Lenovo\\.copilot\\session-state\\36a364a0-4d59-4fe3-8349-b59433759a92\\files\\challenge.html'; for($i=0;$i -lt $lines.Count;$i++){if($lines[$i] -match '<h3>(...
Featured categories -- $200 for each winner
Best Use of Render -- Use Render as your project's AI runtime, to host an agent's front end, or to run Hermes or OpenClaw.
Best Use of TabPFN -- Use TabPFN, Prior Labs' tabular foundation model, to forecast, predict, classify, or spot anomalies from historical data like a CSV. Use it inside an agent tool (with or without the MCP server) or on its own.
Best Use of Tinker -- Use Thinking Machines' Tinker to fine-tune a model for a specific task, and show a clear improvement in performance, latency, or cost over a baseline.
Best Use of Arduino -- Build with an Arduino UNO Q: run a model on the board, build a physical agent that senses and acts, or optimize a model for it with Qualcomm AI Hub. Open to anyone building with an UNO Q, whether you got one at an in-person Fest or have your own.
Best Use of DigitalOcean -- Build or deploy your project on DigitalOcean: host your app or agent, run an open-weight model on a GPU Droplet (1-Click Models make this quick), or build an agent on the Gradient AI Platform.
Best Use of Gemma -- Use Gemma, Google's open-weight model, in building your project: run it locally, fine-tune it, or serve it through Google Cloud or another provider.
Partner categories -- $100 for each winner
Best Use of Backboard -- Build with R-CLI, Backboard's open-source terminal coding agent, compare open-weight models through a single Backboard API key, or give an open-source project's assistant memory and RAG.
Best Use of ElevenLabs -- Give an open-source agent a voice, transcribe audio for a local model, or generate narration for your demo.
Best Use of Entire -- Share the agent sessions behind your project in the write-up, or search past agent work to explain why code exists.
Best Use of GitHub Copilot -- Build with Copilot's coding agent, the Copilot CLI, or the new Copilot app, automate your project with GitHub Actions, or review contributors' PRs with Copilot.
Best Use of Mastra -- Orchestrate an agent over open models, add memory and tools to a chat agent, or build a workflow that triages GitHub issues.
Best Use of MongoDB Atlas -- Use Atlas as your data layer: Atlas Vector Search for retrieval, long-term memory for an agent, or the database behind an app built on an open-weight model.
Best Use of Sentry Agent Tracing -- Show your agent's work: how it's set up, how it performs (latency, tokens, cost), what you found, and how you debugged it. Include traces or screenshots in your write-up.
Best Use of SerpApi -- Give an agent live web search, ground a RAG app in fresh results, or track news for an open-source project.
Best Use of Temporal -- Make your agent durable. Wrap it in a Temporal workflow so it survives failures, retries flaky tool calls, and picks up where it left off.
Best Use of Tiger Data -- Store embeddings with pgvector, run hybrid keyword and vector search for an agent, or let an agent manage a database through Tiger MCP.
<shellId: 18 completed with exit code 0>
{"command":"$lines=Get-Content 'C:\\Users\\Lenovo\\.copilot\\session-state\\36a364a0-4d59-4fe3-8349-b59433759a92\\files\\challenge.html'; for($i=0;$i -lt $lines.Count;$i++){if($lines[$i] -match 'Key D...
1619 <h2>Key Dates</h2>
1631 <span class="tw-italic">Week of October 12</span>
2349 <p>We're running five DEV Challenges back to back through October: a Weekend Challenge to start (October 2 to 5), then a new challenge every Monday (October 5, 12, 19 and 26). Every challenge uses the same prompt, and each one gets its own theme. We reveal each theme the day its challenge launches. See all five on the <a href="https://dev.to/challenges/hf26">HF26 DEV Challenge Hub</a>.</p>
2439 <p>No, all submissions and their respective repositories must be started and completed within the challenge window. Commits made after the submission deadline must be noted in the project's readme. Failure to do so may result in disqualification.</p>
2469 <p>Winners are announced the week after each challenge closes. Each challenge's page lists the week under Key Dates.</p>
2491 <p>NO PURCHASE NECESSARY. Open only to 18+. Contest entry period ends October 11, 2026 at 11:59 PM PDT. Contest is void where prohibited or restricted by law or regulation. All entries must be submitted during the contest period. For Official Rules, see <a href="https://dev.to/page/hacktoberfest-week1-2026-10-05-contest-rules">Hacktoberfest Open-Source AI Challenge: Week 1 Contest Rules</a> and <a href="https://dev.to/page/official-hackathon-rules">General Contest Official Rules</a>.</p>
<shellId: 19 completed with exit code 0>
{
"url": "https://dev.to/api/articles/4826102",
"max_length": 12000
}
Content type application/json; charset=utf-8 cannot be simplified to markdown. Here is the raw content:
Contents of https://dev.to/api/articles/4826102:
{"type_of":"article","id":4826102,"title":"I Taught Local AI to Help Me Notice the World.","description":"Your World Is More Interesting Than Your Feed. This is a submission for the Hacktoberfest...","readable_publish_date":"Oct 10","slug":"i-taught-local-ai-to-help-me-notice-the-world-5a45","path":"/ishagautam504/i-taught-local-ai-to-help-me-notice-the-world-5a45","url":"https://dev.to/ishagautam504/i-taught-local-ai-to-help-me-notice-the-world-5a45","comments_count":0,"public_reactions_count":0,"collection_id":null,"published_timestamp":"2026-10-10T01:54:46Z","language":"en","subforem_id":1,"ai_disclosure_level":"not_disclosed","ai_disclosure_label":"Not Disclosed","positive_reactions_count":0,"cover_image":null,"social_image":"https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzlo656u088u66liiygb2.png","canonical_url":"https://dev.to/ishagautam504/i-taught-local-ai-to-help-me-notice-the-world-5a45","created_at":"2026-10-10T01:54:46Z","edited_at":null,"crossposted_at":null,"published_at":"2026-10-10T01:54:46Z","last_comment_at":"2026-10-10T01:54:46Z","reading_time_minutes":4,"tag_list":"ai, devchallenge, hacktoberfest, opensource","tags":["ai","devchallenge","hacktoberfest","opensource"],"body_html":"\u003ch1\u003e\n \u003ca name=\"your-world-is-more-interesting-than-your-feed\" href=\"#your-world-is-more-interesting-than-your-feed\"\u003e\n \u003c/a\u003e\n Your World Is More Interesting Than Your Feed.\n\u003c/h1\u003e\n\n\u003cp\u003e\u003cem\u003eThis is a submission for the \u003ca href=\"https://dev.to/challenges/hacktoberfest-week1-2026-10-05\"\u003eHacktoberfest Open-Source AI Challenge Week 1: Touch Grass\u003c/a\u003e.\u003c/em\u003e\u003c/p\u003e\n\n\u003cp\u003eWhat if the things you noticed on a walk mattered more than the posts you scrolled past?\u003c/p\u003e\n\n\u003cp\u003eA strange-looking plant. An interesting rock. The sound of birds from a place you've never stopped to notice. Tiny details we usually walk past without a second thought.\u003c/p\u003e\n\n\u003cp\u003e\u003cstrong\u003eThat's the idea behind TRACE.\u003c/strong\u003e\u003c/p\u003e\n\n\u003cp\u003eTRACE is a map-based exploration app that turns real-world observations into a personal, AI-assisted record of the world around you. It encourages you to step outside, pay attention, capture what you find, and slowly build a map of your own discoveries.\u003c/p\u003e\n\n\u003cp\u003eThe goal isn't to spend more time in an app. It's to give you a reason to spend more time noticing the world outside it.\u003c/p\u003e\n\n\u003ch2\u003e\n \u003ca name=\"what-i-built\" href=\"#what-i-built\"\u003e\n \u003c/a\u003e\n 🌍 What I Built\n\u003c/h2\u003e\n\n\u003cp\u003eTRACE combines real-world exploration, multimedia capture, local AI, and a little gamification to make curiosity a habit.\u003c/p\u003e\n\n\u003cp\u003eHere's what you can do with it:\u003c/p\u003e\n\n\u003cul\u003e\n\u003cli\u003e\n\u003cstrong\u003e🗺️ Trace Map:\u003c/strong\u003e Explore observations on an interactive map and see the world through the things people notice.\u003c/li\u003e\n\u003cli\u003e\n\u003cstrong\u003e📍 Trace Capture:\u003c/strong\u003e Create observations with text, photos, or audio. Attach a location using GPS or place it manually on the map.\u003c/li\u003e\n\u003cli\u003e\n\u003cstrong\u003e🧠 Local AI Processing:\u003c/strong\u003e Turn captured observations into more structured records using a locally running AI model.\u003c/li\u003e\n\u003cli\u003e\n\u003cstrong\u003e🌱 Sticker Garden:\u003c/strong\u003e Earn daily stickers for qualifying observations, build exploration streaks, and collect sticker artwork as you keep exploring.\u003c/li\u003e\n\u003cli\u003e\n\u003cstrong\u003e✨ A Personal Exploration Record:\u003c/strong\u003e Revisit your saved traces and build a growing record of the places, sights, and sounds you've encountered.\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003cp\u003eThe small details matter. A photo captures what you saw. Audio captures what you heard. A location gives that moment a place on the map. Together, they help turn an ordinary walk into something worth remembering.\u003c/p\u003e\n\n\u003ch2\u003e\n \u003ca name=\"demo\" href=\"#demo\"\u003e\n \u003c/a\u003e\n 🎥 Demo\n\u003c/h2\u003e\n\n\u003cp\u003e\u003cstrong\u003eWatch TRACE in action:\u003c/strong\u003e\u003c/p\u003e\n\n\n\u003cdiv class=\"crayons-card c-embed text-styles text-styles--secondary\"\u003e\n \u003cdiv class=\"c-embed__content\"\u003e\n \u003cdiv class=\"c-embed__body\"\u003e\n \u003ch2 class=\"fs-xl lh-tight\"\u003e\n \u003ca href=\"https://drive.google.com/file/d/1zdN7hmWnpcH1dYPVikhVnQPVfflRq4Xy/view?usp=drive_link\" target=\"_blank\" rel=\"noop
[Truncated — full output (12.0 KB) temporarily saved to C:\Users\Lenovo\AppData\Local\Temp\1791598710839-copilot-tool-output-42463d56be8843a2acf9dfd002d6f1b4.txt]
{
"url": "https://dev.to/api/articles/4826090",
"max_length": 12000
}
Content type application/json; charset=utf-8 cannot be simplified to markdown. Here is the raw content:
Contents of https://dev.to/api/articles/4826090:
{"type_of":"article","id":4826090,"title":"Thirsty Leaf: A Tiny Plant Reminder App That Gets You Off Your Screen 🪴","description":"This is a submission for the Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass ...","readable_publish_date":"Oct 10","slug":"thirsty-leaf-a-tiny-plant-reminder-app-that-gets-you-off-your-screen-787","path":"/ayushjhawar/thirsty-leaf-a-tiny-plant-reminder-app-that-gets-you-off-your-screen-787","url":"https://dev.to/ayushjhawar/thirsty-leaf-a-tiny-plant-reminder-app-that-gets-you-off-your-screen-787","comments_count":0,"public_reactions_count":0,"collection_id":null,"published_timestamp":"2026-10-10T01:50:17Z","language":"en","subforem_id":1,"ai_disclosure_level":"not_disclosed","ai_disclosure_label":"Not Disclosed","positive_reactions_count":0,"cover_image":null,"social_image":"https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frb5b3eqz4ile0fhpuw0a.png","canonical_url":"https://dev.to/ayushjhawar/thirsty-leaf-a-tiny-plant-reminder-app-that-gets-you-off-your-screen-787","created_at":"2026-10-10T01:50:17Z","edited_at":"2026-10-10T01:51:12Z","crossposted_at":null,"published_at":"2026-10-10T01:50:17Z","last_comment_at":"2026-10-10T01:50:17Z","reading_time_minutes":2,"tag_list":"devchallenge, hf26challenge, hacktoberfest, webdev","tags":["devchallenge","hf26challenge","hacktoberfest","webdev"],"body_html":"\u003cp\u003e\u003cem\u003eThis is a submission for the \u003ca href=\"https://dev.to/challenges/hacktoberfest-week1-2026-10-05\"\u003eHacktoberfest Open-Source AI Challenge Week 1: Touch Grass\u003c/a\u003e\u003c/em\u003e\u003c/p\u003e\n\n\u003ch2\u003e\n \u003ca name=\"what-i-built\" href=\"#what-i-built\"\u003e\n \u003c/a\u003e\n What I Built\n\u003c/h2\u003e\n\n\u003cp\u003eThirstyLeaf is a tiny plant-watering reminder app. Add a plant, pick a date and time, and it tells you when it’s thirsty. It’s a nudge to look up from the screen, walk over to your plants, and actually touch some (potted) grass.\u003c/p\u003e\n\n\u003cp\u003eIt’s for anyone who keeps killing houseplants because they forget, which includes me.\u003c/p\u003e\n\n\u003cp\u003eQuick dates: Today / Tomorrow / In 3 days / Next week chips set the date in one tap.\u003cbr\u003e\nRepeats: daily through monthly. Marking a plant watered reschedules from now, so a late watering still gets a full interval.\u003cbr\u003e\nNeeds water: everything that’s due is collected in one place, and the count shows in the browser tab title so you can spot it from another tab.\u003cbr\u003e\nWatered / +1 hour / +1 day: mark a plant done or snooze it. One-time reminders move to Done, where Undo brings them back.\u003cbr\u003e\nNo account, no backend: everything lives in your browser’s localStorage.\u003c/p\u003e\n\u003ch2\u003e\n \u003ca name=\"demo\" href=\"#demo\"\u003e\n \u003c/a\u003e\n Demo\n\u003c/h2\u003e\n\n\u003cp\u003e\u003ca href=\"https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F798f4bp3u7u3vr6id7ei.png\" class=\"article-body-image-wrapper\"\u003e\u003cimg src=\"https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F798f4bp3u7u3vr6id7ei.png\" alt=\" \" loading=\"lazy\" width=\"800\" height=\"375\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003ch2\u003e\n \u003ca name=\"code\" href=\"#code\"\u003e\n \u003c/a\u003e\n Code\n\u003c/h2\u003e\n\n\n\u003cdiv class=\"ltag-github-readme-tag\"\u003e\n \u003cdiv class=\"readme-overview\"\u003e\n \u003ch2\u003e\n \u003cimg src=\"https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg\" alt=\"GitHub logo\"\u003e\n \u003ca href=\"https://github.com/Ayushjhawar8\" target=\"_blank\" rel=\"noopener noreferrer\"\u003e\n Ayushjhawar8\n \u003c/a\u003e / \u003ca style=\"font-weight: 600;\" href=\"https://github.com/Ayushjhawar8/ThirstyLeaf\" target=\"_blank\" rel=\"noopener noreferrer\"\u003e\n ThirstyLeaf\n \u003c/a\u003e\n \u003c/h2\u003e\n \u003ch3\u003e\n Set a reminder to water any plant on any day and time. No account, no backend — reminders are saved in your browser's `localStorage`.\n \u003c/h3\u003e\n \u003c/div\u003e\n \u003cdiv class=\"ltag-github-body\"\u003e\n \n\u003cdiv id=\"readme\" class=\"md\" data-path=\"README.md\"\u003e\u003carticle class=\"markdown-body entry-content container-lg\" itemprop=\"text\"\u003e\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\n\u003ch1 class=\"heading-element\" dir=\"auto\"\u003e🪴 Plant Re
[Truncated — full output (12.0 KB) temporarily saved to C:\Users\Lenovo\AppData\Local\Temp\1791598710857-copilot-tool-output-bf250b6b147d4c8abe3db1216311dda5.txt]
{
"url": "https://dev.to/api/articles/4825380",
"max_length": 12000
}
Content type application/json; charset=utf-8 cannot be simplified to markdown. Here is the raw content:
Contents of https://dev.to/api/articles/4825380:
{"type_of":"article","id":4825380,"title":"EcoID: An Offline Plant Identifier That Gets You Outside","description":"This is a submission for the Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass. ...","readable_publish_date":"Oct 9","slug":"ecoid-an-offline-plant-identifier-that-gets-you-outside-4b6e","path":"/bangkah/ecoid-an-offline-plant-identifier-that-gets-you-outside-4b6e","url":"https://dev.to/bangkah/ecoid-an-offline-plant-identifier-that-gets-you-outside-4b6e","comments_count":0,"public_reactions_count":2,"collection_id":null,"published_timestamp":"2026-10-09T20:55:09Z","language":"en","subforem_id":1,"ai_disclosure_level":"not_disclosed","ai_disclosure_label":"Not Disclosed","positive_reactions_count":2,"cover_image":null,"social_image":"https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhalcp7to4rknldjvi6ze.png","canonical_url":"https://dev.to/bangkah/ecoid-an-offline-plant-identifier-that-gets-you-outside-4b6e","created_at":"2026-10-09T20:55:09Z","edited_at":"2026-10-09T21:02:14Z","crossposted_at":null,"published_at":"2026-10-09T20:55:09Z","last_comment_at":"2026-10-09T20:55:09Z","reading_time_minutes":6,"tag_list":"hacktoberfest, opensource, devchallenge, hf26challenge","tags":["hacktoberfest","opensource","devchallenge","hf26challenge"],"body_html":"\u003cp\u003eThis is a submission for the \u003ca href=\"https://dev.to/challenges/hacktoberfest-week1-2026-10-05\"\u003eHacktoberfest Open-Source AI Challenge Week 1: Touch Grass\u003c/a\u003e.\u003c/p\u003e\n\n\u003ch2\u003e\n \u003ca name=\"what-i-built\" href=\"#what-i-built\"\u003e\n \u003c/a\u003e\n What I Built\n\u003c/h2\u003e\n\n\u003cp\u003eEcoID is an offline-first plant identification tool for field observations.\u003c/p\u003e\n\n\u003cp\u003eThe 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.\u003c/p\u003e\n\n\u003cp\u003eEcoID is designed for:\u003c/p\u003e\n\n\u003cul\u003e\n\u003cli\u003estudents learning about local plants\u003c/li\u003e\n\u003cli\u003efield researchers and citizen scientists\u003c/li\u003e\n\u003cli\u003egardeners and small-scale farmers\u003c/li\u003e\n\u003cli\u003epeople who want to explore nature without sending their photos to a cloud service\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003cp\u003eThe current model supports five plant classes:\u003c/p\u003e\n\n\u003cul\u003e\n\u003cli\u003eMango — \u003ccode\u003eMangifera indica\u003c/code\u003e\n\u003c/li\u003e\n\u003cli\u003eCoconut — \u003ccode\u003eCocos nucifera\u003c/code\u003e\n\u003c/li\u003e\n\u003cli\u003eBanana — \u003ccode\u003eMusa acuminata\u003c/code\u003e\n\u003c/li\u003e\n\u003cli\u003ePapaya — \u003ccode\u003eCarica papaya\u003c/code\u003e\n\u003c/li\u003e\n\u003cli\u003eCassava — \u003ccode\u003eManihot esculenta\u003c/code\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003cp\u003eThe 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:\u003c/p\u003e\n\n\u003cul\u003e\n\u003cli\u003eVerified\u003c/li\u003e\n\u003cli\u003eRejected\u003c/li\u003e\n\u003cli\u003eUncertain\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003cp\u003eThis human-in-the-loop flow matters because a model prediction is only a suggestion until somebody looks at the actual plant.\u003c/p\u003e\n\n\u003cp\u003eEcoID 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.\u003c/p\u003e\n\n\u003cp\u003eThe project is designed to make the computer useful for a short moment, then get the person back to observing the real world.\u003c/p\u003e\n\n\u003ch2\u003e\n \u003ca name=\"demo\" href=\"#demo\"\u003e\n \u003c/a\u003e\n Demo\n\u003c/h2\u003e\n\n\u003cp\u003eEcoID 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.\u003c/p\u003e\n\n\u003cp\u003eThe 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.\u003c/p\u003e\n\n\u003cp\u003eAfter training or obtaining the ONNX model locally:\u003cbr\u003e\n\u003c/p\u003e\n\n\u003cdiv class=\"highlight js-code-highlight\"\u003e\n\u003cpre class=\"highlight shell\"\u003e\u003ccode\u003epython \u003cspan class=\"nt\"\u003e-m\u003c/span\u003e app \u003cspan class=\"nt\"\u003e--model\u003c/span\u003e models/e
[Truncated — full output (12.0 KB) temporarily saved to C:\Users\Lenovo\AppData\Local\Temp\1791598710855-copilot-tool-output-2129ba8eedde40d5a44d3e348f5bb4af.txt]
{
"url": "https://dev.to/api/articles/4824064",
"max_length": 12000
}
Content type application/json; charset=utf-8 cannot be simplified to markdown. Here is the raw content:
Contents of https://dev.to/api/articles/4824064:
{"type_of":"article","id":4824064,"title":"Plain Sight Walk is a walking game that lives in your pocket.","description":"This is a submission for the Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass ...","readable_publish_date":"Oct 9","slug":"plain-sight-walk-is-a-walking-game-that-lives-in-your-pocket-gjo","path":"/maitrivv/plain-sight-walk-is-a-walking-game-that-lives-in-your-pocket-gjo","url":"https://dev.to/maitrivv/plain-sight-walk-is-a-walking-game-that-lives-in-your-pocket-gjo","comments_count":0,"public_reactions_count":5,"collection_id":null,"published_timestamp":"2026-10-09T15:18:26Z","language":"en","subforem_id":1,"ai_disclosure_level":"not_disclosed","ai_disclosure_label":"Not Disclosed","positive_reactions_count":5,"cover_image":null,"social_image":"https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8gthn1l61indujppiyh6.png","canonical_url":"https://dev.to/maitrivv/plain-sight-walk-is-a-walking-game-that-lives-in-your-pocket-gjo","created_at":"2026-10-09T15:18:27Z","edited_at":"2026-10-09T15:35:12Z","crossposted_at":null,"published_at":"2026-10-09T15:18:26Z","last_comment_at":"2026-10-09T15:18:26Z","reading_time_minutes":5,"tag_list":"devchallenge, hf26challenge, hacktoberfest","tags":["devchallenge","hf26challenge","hacktoberfest"],"body_html":"\u003cp\u003e\u003cem\u003eThis is a submission for the \u003ca href=\"https://dev.to/challenges/hacktoberfest-week1-2026-10-05\"\u003eHacktoberfest Open-Source AI Challenge Week 1: Touch Grass\u003c/a\u003e\u003c/em\u003e\u003c/p\u003e\n\n\u003ch2\u003e\n \u003ca name=\"what-i-built\" href=\"#what-i-built\"\u003e\n \u003c/a\u003e\n What I Built\n\u003c/h2\u003e\n\n\u003cp\u003ePlain Sight Walk is a walking game that lives in your pocket.\u003c/p\u003e\n\n\u003cp\u003e\u003ca href=\"https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faj58p5tskg98baczo7wy.png\" class=\"article-body-image-wrapper\"\u003e\u003cimg src=\"https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faj58p5tskg98baczo7wy.png\" alt=\"plain-sight-walk powered by Gemma\" loading=\"lazy\" width=\"800\" height=\"400\"\u003e\u003c/a\u003e\u003c/p\u003e\n\n\u003cp\u003eEvery walk has a secret word, say WANDER. You spell it one letter at a time with things you actually find outside: W is for wall, A is for acorn, N is for nest. Gemma writes you a riddle for each letter and can read it aloud, so the phone stays away. When you find the thing, you photograph it, and an open vision model checks the photo. When the word is complete, the app turns your walk into a photo carousel: each slide is one of your photos, stamped with its letter, the object, and how many kilometres into the walk you took it. The secret word picks the look of the whole set. WANDER becomes \"Roadside Almanac\", a warm sepia. SHADOW becomes \"Chiaroscuro\", black and white.\u003c/p\u003e\n\n\u003cp\u003eThe screen is only for the start and the end. The middle of the experience is looking at things.\u003c/p\u003e\n\n\u003ch2\u003e\n \u003ca name=\"demo\" href=\"#demo\"\u003e\n \u003c/a\u003e\n Demo\n\u003c/h2\u003e\n\n\u003cp\u003eLive app: \u003ca href=\"\"\u003eplain-sight-walk.vercel.app\u003c/a\u003e (best in recent Chrome or Edge; it needs WebGPU for Gemma)\u003c/p\u003e\n\n\u003ch2\u003e\n \u003ca name=\"code\" href=\"#code\"\u003e\n \u003c/a\u003e\n Code\n\u003c/h2\u003e\n\n\u003cp\u003eCode (MIT):\u003c/p\u003e\n\n\n\u003cdiv class=\"ltag-github-readme-tag\"\u003e\n \u003cdiv class=\"readme-overview\"\u003e\n \u003ch2\u003e\n \u003cimg src=\"https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg\" alt=\"GitHub logo\"\u003e\n \u003ca href=\"https://github.com/maitri-vv\" target=\"_blank\" rel=\"noopener noreferrer\"\u003e\n maitri-vv\n \u003c/a\u003e / \u003ca style=\"font-weight: 600;\" href=\"https://github.com/maitri-vv/plain-sight-walk\" target=\"_blank\" rel=\"noopener noreferrer\"\u003e\n plain-sight-walk\n \u003c/a\u003e\n \u003c/h2\u003e\n \u003ch3\u003e\n \n \u003c/h3\u003e\n \u003c/div\u003e\n \u003cdiv class=\"ltag-github-body\"\u003e\n \n\u003cdiv id=\"readme\" class=\"md\" data-path=\"README.md\"\u003e\u003carticle class=\"markdown-body entry-content container-lg\" itemprop=\"text\"\u003e\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\n\u003ch1 class=\"heading-element\" dir=\"auto\"\u003ePlain Sight Walk\u003c/h1\u003e\n\u003c/div\u003e\n\u
[Truncated — full output (12.0 KB) temporarily saved to C:\Users\Lenovo\AppData\Local\Temp\1791598711101-copilot-tool-output-ef2d02c30d8d4c98aeb0a2b44ef9703e.txt]
{
"url": "https://dev.to/api/articles/4825711",
"max_length": 12000
}
Content type application/json; charset=utf-8 cannot be simplified to markdown. Here is the raw content:
Contents of https://dev.to/api/articles/4825711:
{"type_of":"article","id":4825711,"title":"🌿 Ruta Viva AI: Turning Everyday Walks into AI-Powered Adventures","description":"What if AI could help us spend less time staring at screens and more time exploring the...","readable_publish_date":"Oct 9","slug":"ruta-viva-ai-turning-everyday-walks-into-ai-powered-adventures-1apa","path":"/ezequie1sc/ruta-viva-ai-turning-everyday-walks-into-ai-powered-adventures-1apa","url":"https://dev.to/ezequie1sc/ruta-viva-ai-turning-everyday-walks-into-ai-powered-adventures-1apa","comments_count":0,"public_reactions_count":0,"collection_id":null,"published_timestamp":"2026-10-09T22:43:26Z","language":"en","subforem_id":1,"ai_disclosure_level":"not_disclosed","ai_disclosure_label":"Not Disclosed","positive_reactions_count":0,"cover_image":null,"social_image":"https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgq4xouzkh8cbb8cjzzmk.png","canonical_url":"https://dev.to/ezequie1sc/ruta-viva-ai-turning-everyday-walks-into-ai-powered-adventures-1apa","created_at":"2026-10-09T22:43:26Z","edited_at":"2026-10-09T22:47:10Z","crossposted_at":null,"published_at":"2026-10-09T22:43:26Z","last_comment_at":"2026-10-09T22:43:26Z","reading_time_minutes":5,"tag_list":"devchallenge, hf26challenge, hacktoberfest, programming","tags":["devchallenge","hf26challenge","hacktoberfest","programming"],"body_html":"\u003cp\u003eWhat if AI could help us spend less time staring at screens and more time exploring the world?\u003c/p\u003e\n\n\u003cp\u003eThat's the idea behind Ruta Viva AI, an AI-powered outdoor adventure platform that transforms ordinary walks into interactive missions. Instead of spending more time scrolling, users can discover their surroundings, complete real-world challenges, and earn XP while exploring nature and local culture.\u003c/p\u003e\n\n\u003cp\u003eThe goal is simple: make the screen the shortest part of the adventure.\u003c/p\u003e\n\n\u003cp\u003e\u003ca href=\"https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F75tovh37q4cmparxflor.png\" class=\"article-body-image-wrapper\"\u003e\u003cimg src=\"https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F75tovh37q4cmparxflor.png\" alt=\" \" loading=\"lazy\" width=\"800\" height=\"376\"\u003e\u003c/a\u003e\u003c/p\u003e\n\n\u003cp\u003eWhat I Built\u003c/p\u003e\n\n\u003cp\u003eRuta Viva AI turns going outside into a game.\u003c/p\u003e\n\n\u003cp\u003eUsers choose their preferred adventure type, difficulty, and duration. The application then selects an adventure containing missions designed to encourage real-world exploration.\u003c/p\u003e\n\n\u003cp\u003eAdventure categories include:\u003c/p\u003e\n\n\u003cp\u003e🌳 Nature: Observe plants, discover patterns, identify colors, and listen to the sounds around you.\u003c/p\u003e\n\n\u003cp\u003e🏛️ Culture: Explore architecture, public art, and interesting details in your community.\u003c/p\u003e\n\n\u003cp\u003e🚶 Walking: Turn an ordinary walk into a sequence of small challenges.\u003c/p\u003e\n\n\u003cp\u003e🎲 Surprise: Let the adventure system choose an unexpected exploration experience.\u003c/p\u003e\n\n\u003cp\u003eEach adventure includes missions, descriptions, and experience points (XP). The idea is to give people a reason to step outside, pay attention to their surroundings, and turn everyday activities into memorable experiences.\u003c/p\u003e\n\n\u003cp\u003eThe project is designed for curious people who want to explore their communities, enjoy the outdoors, and make their walks more engaging.\u003c/p\u003e\n\n\u003cp\u003e\u003ca href=\"https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjynmqtb5ey8d08q1hlua.png\" class=\"article-body-image-wrapper\"\u003e\u003cimg src=\"https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjynmqtb5ey8d08q1hlua.png\" alt=\" \" loading=\"lazy\" width=\"800\" height=\"391\"\u003e\u003c/a\u003e\u003c/p\u003e\n\n\u003cp\u003e\u003ca href=\"https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2e3i119ktbc2op5o6d2p.png\" class=\"article-body-image-wrapper\"\u003e\u003cimg src=\"https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-do
[Truncated — full output (12.0 KB) temporarily saved to C:\Users\Lenovo\AppData\Local\Temp\1791598710823-copilot-tool-output-f94209cbf6984ba6965feaecc8088a90.txt]
The live page confirms the key constraint: Week 1 closes Oct 11, 2026 at 11:59 PM PDT, and judging weights the write-up most. I’m now narrowing the idea around gaps in the visible submission field and the categories that appear least crowded.
{"items":[{"type":"website","label":"Hacktoberfest Week 1 challenge","isArtifact":false,"link":"https://dev.to/challenges/hacktoberfest-week1-2026-10-05"},{"type":"file","label":"Current submission sn...
Added reference: 345b6238-bf33-4c86-a077-4bd3d0a5e7e2
Added artifact: 8671077e-fd5f-4e24-a63e-e8454fe5100d
Best bet: FieldProof — an offline AI “micro-expedition” app
A mobile-first PWA that turns a 20-minute walk into structured, evidence-backed field research.
Instead of another “go outside” reminder or walking route generator, FieldProof gives users a mission:
“Find three different leaf shapes within 500m.”
“Record two bird/insect sounds.”
“Compare shaded and sunny ground temperatures.”
“Document one change in your local habitat.”
The user collects photos, short audio clips, notes, and optional sensor data. Local/open-source AI then:
- Converts observations into structured field notes.
- Suggests follow-up observations without pretending to identify species with certainty.
- Scores the quality and diversity of the evidence.
- Produces a shareable “field report.”
- Builds a personal map of repeated observations over time.
This is meaningfully different from the current wave of walking planners, plant identifiers, reminders, and AI adventure generators.
What the current submissions are already doing
The visible submission field is heavily concentrated around:
- Walking/adventure planners:
- Plant and nature identification:
- Generic “reduce screen time” tools:
The strongest strategic observation is that “help me walk outside” is crowded, while “help me collect useful, repeatable evidence about my environment” is much less represented.
The current winner-oriented differentiation should be:
Not an AI that tells you where to go.
An AI that helps you notice, measure, and understand the place you already are.
Why FieldProof has a better chance
1. It fits the theme without being a superficial wellness app
The outdoor action is essential to the product. The app only becomes useful after the user returns with real observations.
2. It creates a strong demo
A compelling 90-second demo:
- Start a “shaded vs sunny habitat” mission.
- Capture two photos, one short audio clip, and a note.
- Run local AI processing.
- Show the generated evidence card.
- Display a field report with confidence levels and missing evidence.
- Compare today’s observation with a previous walk.
That is more memorable than displaying another route map or motivational quote.
3. It has a credible open-AI story
The open-source model is not decorative. It performs the core transformation from messy field input into a structured observation record, ideally with:
- Local Gemma inference.
- Explicit uncertainty.
- No cloud upload required for the basic workflow.
- Exportable JSON/CSV field notes.
4. It gives you excellent writing material
The article can discuss:
- Why local AI is useful when collecting sensitive location data.
- How the app avoids hallucinated species identification.
- How structured evidence is better than confident guesses.
- What failed when the model was given poor photos or noisy audio.
- A before/after comparison against a cloud-only or rules-only baseline.
That directly supports DEV’s stated emphasis on writing quality.
Prize-category strategy
The challenge page currently lists:
Featured categories — $200
- Render
- TabPFN
- Tinker
- Arduino
- DigitalOcean
- Gemma
Partner categories — $100
- Backboard
- ElevenLabs
- Entire
- GitHub Copilot
- Mastra
- MongoDB Atlas
- Sentry Agent Tracing
- SerpApi
- Temporal
- Tiger Data
One submission can opt into multiple categories, but it can only win once, so categories increase eligibility rather than multiplying the payout.
Recommended categories
| Category | Recommendation | How FieldProof genuinely qualifies |
|---|---|---|
| Overall winner | Definitely enter | Strong theme fit, original concept, offline AI, compelling evidence-based demo |
| Best Use of Gemma | Definitely enter | Local Gemma structures observations and generates missions/follow-ups |
| Best Use of TabPFN | Best low-competition bet | Predict observation quality or likely habitat findings from structured walk data |
| Best Use of Temporal | Strong low-competition bet | Durable workflow for processing media, retrying inference, and resuming interrupted walks |
| Best Use of Sentry Agent Tracing | Strong | Show latency, model failures, token/cost behavior, and processing traces |
| Best Use of Render | Good | Deploy the PWA/API and host the demo reliably |
| Best Use of Entire | Good if used properly | Include the actual agent/build session and explain key implementation decisions |
| Best Use of ElevenLabs | Optional | Narrate the field report or provide an audio mission guide |
| Best Use of MongoDB Atlas | Optional | Store observation history and vector-search similar past observations |
| Best Use of Tiger Data | Optional | Use pgvector for semantic search over field notes |
My low-competition pick: Best Use of TabPFN
Based on the visible submissions, there are many walking, plant, and local-AI entries, but very few obvious TabPFN projects.
Use TabPFN for a small but defensible prediction task:
Given habitat type, time of day, temperature, weather, walk duration, season, and prior observations, predict which observation mission is most likely to produce useful evidence.
Or:
Predict whether a submitted field observation is likely to be “high quality” based on image count, audio duration, lighting, habitat metadata, and completeness.
Do not use TabPFN as a cosmetic add-on. Include a small evaluation:
- Baseline: rule-based score.
- TabPFN score.
- Accuracy/F1 or calibration comparison.
- One chart in the article.
- Explanation of where the model fails.
That is substantially more credible than simply adding a partner SDK and displaying its output.
Second low-competition category: Best Use of Temporal
This is also promising because most submissions will likely be simple web apps.
Use Temporal for:
Create expedition
-> upload evidence
-> run local/model analysis
-> validate confidence
-> generate field report
-> retry failed steps
-> sync when connection returns
Demonstrate an interrupted workflow resuming rather than restarting. That directly matches Temporal’s category wording and gives you a technically interesting article section.
Scope for the remaining time
You have approximately 52 hours until the Oct 11, 2026 11:59 PM PDT deadline.
Build only this MVP:
Must-have
- PWA interface with “Start mission.”
- Three mission types:
- Compare two habitats.
- Find three distinct visual patterns.
- Record one natural sound and describe the environment.
- Photo upload and text notes.
- Local Gemma-powered structured field report.
- Confidence labels:
observed,inferred,unknown. - TabPFN prediction using a small tabular dataset.
- One deployed demo.
- A polished article with screenshots and benchmark results.
Nice-to-have
- Audio transcription.
- ElevenLabs narration.
- Temporal retry/resume demonstration.
- Sentry trace screenshots.
- Export to JSON/CSV.
- Map of historical observations.
Avoid
- Full species identification.
- A complex social network.
- User accounts unless absolutely necessary.
- Real-time collaboration.
- Building a generic chatbot.
- Supporting every model provider.
- Pretending that low-quality images produce scientific certainty.
Suggested project positioning
Name
FieldProof
Tagline
An offline-first open-AI companion for turning ordinary walks into trustworthy field notes.
Strong article title
I Built an Offline AI That Turns a 20-Minute Walk Into Evidence
Article structure
- The problem: most “touch grass” apps tell people to go outside but do not help them notice anything.
- The idea: structured micro-expeditions.
- Why local AI matters for location and personal data.
- Architecture diagram.
- Gemma workflow.
- TabPFN baseline comparison.
- Temporal failure/recovery demo.
- Sentry performance traces.
- What the model got wrong.
- What I would improve with more field data.
- Open-source setup instructions and live demo.
Be very transparent that this is observation assistance, not authoritative species identification. That restraint will improve trust and the quality of the write-up.
Final category selection
I would enter FieldProof in:
- Overall winner
- Best Use of Gemma
- Best Use of TabPFN
- Best Use of Temporal
- Best Use of Sentry Agent Tracing
- Best Use of Render
- Best Use of Entire
- Best Use of ElevenLabs, only if narration is genuinely integrated
- Best Use of MongoDB Atlas, only if historical observation retrieval is real
The highest-leverage combination is:
Gemma + TabPFN + Temporal + Sentry + Render
It gives you a clear open-model story, one rare technical category, one durable-agent category, measurable observability, and a reliable public demo.
Community Wisdom
🌐 Community Wisdom: I Taught Local AI to Help Me Notice the World
Source: ishagautam504
This is the closest visible concept to FieldProof, which validates the emotional direction but also shows that a generic “local AI helps me notice nature” pitch is no longer enough. FieldProof should differentiate through structured evidence, uncertainty handling, and measurable field-data quality.
🌐 Community Wisdom: EcoID: An Offline Plant Identifier That Gets You Outside
Source: bangkah
Offline nature identification is already represented, so competing directly as another plant identifier would be strategically weak. The opportunity is to make the AI useful even when identification is uncertain: collect evidence, compare environments, and improve future observations.
The important caveat: DEV does not appear to publish an official live count of entries per partner category, so “low competition” here is an evidence-based inference from the current public submission titles and visible project descriptions—not a guaranteed leaderboard count.
Can you come up with an implementation plan?
I’ll tailor the plan to the existing workspace first, then turn FieldProof into a deadline-safe build sequence with explicit MVP boundaries, category evidence, and submission checks.
{
"command": "Get-ChildItem -Force | Select-Object Mode,Length,LastWriteTime,Name",
"description": "Inspect workspace structure"
}
<shellId: 20 completed with exit code 0>
{
"command": "Get-ChildItem -Recurse -File -ErrorAction SilentlyContinue | Select-Object -First 80 FullName",
"description": "Identify project files and existing app"
}
<shellId: 21 completed with exit code 0>
{
"command": "Get-ChildItem -Force -Name README*,package.json,pyproject.toml,requirements*.txt,*.sln,*.csproj,Cargo.toml 2>$null",
"description": "Detect project stack and entrypoints"
}
<shellId: 22 completed with exit code 1>
{"command":"Write-Output \"PWD: $(Get-Location)\"; Get-ChildItem -Force | Format-List Name,FullName,Mode,Length; Get-ChildItem -Recurse -Force -File -ErrorAction SilentlyContinue | Select-Object -Firs...
PWD: E:\HACKTOBERFEST WEEK 1 CHALLENGE
<shellId: 23 completed with exit code 0>
FieldProof implementation plan
The workspace is currently empty, so this should be treated as a greenfield MVP. The deadline is approximately 28.5 hours away: Oct 11, 2026 at 11:59 PM PDT, which is Oct 12 at about 12:29 PM IST.
The priority is not building every feature. It is producing a polished, credible demo and a strong DEV write-up.
1. Product definition
Project
FieldProof
One-sentence pitch
FieldProof is an offline-first open-AI app that turns a short walk into structured, uncertainty-aware field notes.
Core user flow
Choose a mission
↓
Walk outside
↓
Capture photos + notes
↓
Run local/open AI analysis
↓
Review evidence and confidence
↓
Export/share a field report
MVP missions
Implement only three:
-
Habitat comparison
- Capture one sunny-area observation and one shaded-area observation.
- Generate a comparison report.
-
Pattern hunt
- Find three visually different natural objects or textures.
- AI groups them into broad categories without claiming exact species.
-
Sound and scene
- Capture a short audio clip or text description.
- Generate a structured environmental note.
Avoid building a general-purpose nature identification app.
2. Recommended stack
Use a stack optimized for speed and demo quality:
| Layer | Choice |
|---|---|
| Frontend | React + Vite + TypeScript |
| Styling | Tailwind CSS |
| Local persistence | IndexedDB via idb
|
| AI | Gemma through Ollama or a hosted open-weight endpoint |
| Backend | Small Python FastAPI service |
| Tabular model | TabPFN |
| Durable workflow | Temporal, only for the analysis pipeline |
| Observability | Sentry |
| Deployment | Render |
| Storage | Local browser storage for MVP; MongoDB/Tiger Data only if time remains |
| Testing | Vitest + Playwright smoke test |
If the environment already has a preferred stack installed, preserve it. Otherwise, React/Vite/TypeScript is the fastest reliable route.
3. Architecture
React PWA
├─ Mission selection
├─ Camera/file/audio capture
├─ Evidence review
├─ Field report
└─ Offline queue
│
▼
FastAPI API
├─ /missions
├─ /analyze
├─ /predict-quality
└─ /health
│
├─ Gemma inference
├─ TabPFN quality prediction
├─ Temporal workflow
└─ Sentry tracing
Important architecture decision
The app should still function without the backend:
- Mission templates are bundled locally.
- Evidence is saved in IndexedDB.
- The user can complete a walk offline.
- AI analysis runs when the backend is available.
- Failed analysis is queued for retry.
This gives FieldProof a genuine offline-first story rather than merely adding “offline” to the README.
4. Data model
Start with simple typed objects.
type MissionType =
| "habitat-comparison"
| "pattern-hunt"
| "sound-and-scene";
type EvidenceKind = "photo" | "audio" | "note";
type Confidence = "observed" | "inferred" | "unknown";
interface EvidenceItem {
id: string;
kind: EvidenceKind;
localUrl?: string;
text?: string;
capturedAt: string;
latitude?: number;
longitude?: number;
}
interface Expedition {
id: string;
missionType: MissionType;
startedAt: string;
completedAt?: string;
evidence: EvidenceItem[];
status: "draft" | "queued" | "processing" | "complete" | "failed";
}
interface FieldReport {
summary: string;
observations: Array<{
claim: string;
confidence: Confidence;
supportingEvidenceIds: string[];
}>;
missingEvidence: string[];
suggestedNextStep: string;
qualityScore?: number;
}
The observed / inferred / unknown distinction is central to the product and should appear visibly in the UI.
5. Build sequence
Phase 1 — Project foundation
Time box: 45 minutes
Create:
- Vite React TypeScript app.
- Tailwind setup.
- Basic routing or screen-state navigation.
- ESLint and formatter.
-
.env.example. - MIT license.
- README skeleton.
Initial screens:
Home
Mission selection
Active expedition
Evidence review
Field report
About / methodology
Do not spend time on authentication.
Phase 2 — Mission and evidence capture
Time box: 3 hours
Implement:
- Mission cards.
- Start mission button.
- Photo upload using file input.
- Optional camera capture on mobile.
- Text observation input.
- Optional audio upload.
- Evidence list with delete/reorder.
- Save draft to IndexedDB.
- Resume unfinished expedition after refresh.
Acceptance criteria:
- A user can start a mission without an account.
- Refreshing the page does not lose the expedition.
- A user can complete a mission with photos and notes even while offline.
Phase 3 — Report generation without AI
Time box: 1.5 hours
Before connecting a model, build a deterministic mock report generator.
This allows the complete UI flow to work immediately:
Evidence review
↓
Generate report
↓
Report page with confidence labels
↓
Export JSON
This is important because the AI integration may be the part most likely to fail under deadline pressure.
The fallback must be explicit in the UI:
“AI analysis unavailable. Showing a locally generated evidence summary.”
Do not silently present mock output as AI output.
Phase 4 — Gemma integration
Time box: 3 hours
Create a small FastAPI service with:
GET /health
POST /analyze
POST /predict-quality
POST /analyze should receive:
- Mission type.
- Text observations.
- Image references or compressed images.
- Optional audio transcript.
- Location metadata only if the user opts in.
Prompt Gemma to return strict JSON:
{
"summary": "...",
"observations": [
{
"claim": "...",
"confidence": "observed",
"evidence_ids": ["..."]
}
],
"missing_evidence": ["..."],
"suggested_next_step": "..."
}
Add validation with Pydantic. If the model returns invalid JSON:
- Log the failure.
- Retry once with a repair prompt.
- If it still fails, return an explicit analysis error.
- Preserve the evidence so the user can retry.
Do not let malformed AI output break the whole expedition.
Phase 5 — TabPFN category implementation
Time box: 2 hours
Use TabPFN for a narrow, measurable task:
Predict whether an expedition contains enough evidence for a useful field report.
Create a small structured feature set:
- Number of photos.
- Number of notes.
- Note length.
- Number of distinct mission locations.
- Audio duration.
- Time spent on mission.
- Lighting/habitat metadata.
- Evidence completeness.
Create a baseline:
quality = evidence_count >= 3 && note_length >= 40
Compare the baseline against TabPFN using a small labeled fixture dataset.
The article should show:
- Dataset size.
- Feature list.
- Baseline performance.
- TabPFN performance.
- One limitation.
If real model integration becomes unstable, keep the TabPFN experiment as a standalone reproducible script and do not fake live predictions in the UI.
Phase 6 — Temporal workflow
Time box: 2 hours
Use Temporal only around the analysis pipeline:
analyzeExpedition
├─ normalizeEvidence
├─ runGemmaAnalysis
├─ validateReport
├─ runTabPFNQualityPrediction
└─ persistReport
Demonstrate one failure scenario:
- Start processing.
- Force one activity to fail.
- Show retry/resumption.
- Complete the report without restarting the expedition.
This gives you a strong Best Use of Temporal story.
If Temporal setup threatens the deadline, retain the workflow interface and document the durable pipeline separately rather than delaying the entire app.
Phase 7 — Sentry instrumentation
Time box: 45 minutes
Instrument:
- Expedition creation.
- Evidence upload.
- Gemma analysis duration.
- Model parse failures.
- TabPFN prediction duration.
- Workflow retries.
Capture screenshots showing:
- A successful analysis trace.
- A failed/retried analysis.
- Latency or error information.
This supports Best Use of Sentry Agent Tracing.
Never include API keys or private location data in traces.
Phase 8 — UI polish and demo path
Time box: 3 hours
Polish only the main path:
- Home.
- Mission selection.
- Evidence capture.
- Report.
- Export.
Add:
- Strong empty states.
- Loading state with clear progress.
- Offline indicator.
- Retry button.
- Confidence badges.
- “Why this claim?” evidence links.
- Mobile layout.
- One sample expedition for judges.
The report screen is the most important screen. Make the AI’s uncertainty visually obvious.
6. Deployment plan
Render deployment
Deploy:
- Frontend as a static site.
- FastAPI as a web service.
- Model service separately only if required.
Required production checks:
/health returns 200
frontend can call production API
analysis errors display correctly
no localhost URLs remain
README setup instructions work
If local Gemma cannot run reliably on Render, use:
- Local Gemma for the development/demo evidence.
- A clearly documented open-weight inference endpoint for the hosted demo.
- Or a deterministic demo fixture with an explicit “demo mode” label.
Do not imply that a hosted fallback is local inference if it is not.
7. Testing checklist
Automated tests
Minimum:
- Mission creation.
- IndexedDB save/resume.
- Report schema validation.
- Invalid model JSON handling.
- TabPFN feature generation.
- Offline queue behavior.
- API health endpoint.
Manual smoke test
Run this exact script:
- Open deployed app on mobile viewport.
- Start Pattern Hunt.
- Upload three images.
- Add a 50-word note.
- Disconnect network.
- Refresh page.
- Confirm evidence remains.
- Reconnect network.
- Run analysis.
- Confirm every report claim links to evidence.
- Export JSON.
- Confirm no secrets appear in output.
8. Prize category evidence
Add a section to the README called Prize Category Evidence.
| Category | Evidence to include |
|---|---|
| Overall | Live demo, screenshots, architecture, polished write-up |
| Gemma | Model name, prompt, local/open inference explanation, sample output |
| TabPFN | Dataset, features, baseline comparison, result chart |
| Temporal | Workflow diagram and retry/resume recording |
| Sentry | Trace screenshots and performance findings |
| Render | Production URL and deployment architecture |
| Entire | Link/embed to the actual development session |
| ElevenLabs | Only if narration is a meaningful feature |
| MongoDB Atlas | Only if historical observation search is implemented |
Do not enter a category just because a library was imported. Every category needs a visible feature and an explanation in the article.
9. Submission article plan
The article is crucial because the challenge explicitly weights writing quality heavily.
Suggested title
I Built an Offline AI That Turns a 20-Minute Walk Into Evidence
Article structure
-
The problem
- Most outdoor apps give reminders or routes.
- They do not help users notice and record their environment.
-
The product
- Explain one expedition from start to finish.
-
Why open AI
- Privacy.
- Offline operation.
- Inspectable behavior.
- Lower dependence on proprietary APIs.
-
Architecture
- Include the system diagram.
-
Gemma
- Show structured output and uncertainty handling.
-
TabPFN
- Show the prediction task and baseline comparison.
-
Temporal
- Show failure recovery.
-
Sentry
- Show what you measured and what you fixed.
-
Limitations
- Small dataset.
- No authoritative species claims.
- Model performance varies by image/audio quality.
-
How to run it
- Repository.
- Environment variables.
- Local model setup.
- Demo URL.
-
Prize categories
- List only categories genuinely supported.
10. Time budget from now
| Time remaining | Deliverable |
|---|---|
| 0–4 hours | Foundation, mission flow, IndexedDB |
| 4–8 hours | Evidence capture and complete mock report flow |
| 8–12 hours | Gemma API and validated reports |
| 12–14 hours | TabPFN experiment |
| 14–16 hours | Temporal retry demo |
| 16–17 hours | Sentry instrumentation |
| 17–20 hours | UI polish and deployment |
| 20–24 hours | Tests, screenshots, article |
| Final 4 hours | Buffer, submission checks, publish before cutoff |
If behind schedule, cut in this order:
- ElevenLabs.
- MongoDB/Tiger Data.
- Map/history features.
- Audio capture.
- Complex Temporal deployment.
Do not cut:
- Evidence-linked reports.
- Confidence labels.
- Gemma integration.
- TabPFN comparison.
- Deployment.
- Article quality.
Definition of done
FieldProof is ready when:
- A new user can complete one mission in under five minutes.
- Evidence survives refresh and temporary disconnection.
- The report distinguishes observed, inferred, and unknown claims.
- Gemma is used for the central AI task.
- TabPFN has a real baseline comparison.
- At least one Temporal retry is demonstrable.
- Sentry contains useful traces.
- The app is deployed.
- The repository has a license and reproducible setup.
- The DEV article includes screenshots, architecture, results, limitations, and category evidence.
- The submission is published before Oct 11, 2026 at 11:59 PM PDT.
Community Wisdom: I Taught Local AI to Help Me Notice the World
The implementation should not compete as another generic “AI helps you notice nature” app. The differentiation needs to be the structured evidence workflow, explicit uncertainty, and measurable analysis quality.
Community Wisdom: EcoID: An Offline Plant Identifier That Gets You Outside
An offline plant-identification angle is already represented. FieldProof should instead remain useful when identification is uncertain, making evidence collection and comparison the core product rather than exact recognition.
Now a better name for the repo and MVP, and a 350 chars desc for it.
Recommended name: WildProof
Short, memorable, and more emotionally compelling than “FieldProof.” It communicates the core idea: turning real-world exploration into trustworthy evidence.
Repository name
wildproof
MVP name
WildProof: AI Field Missions
350-character description
WildProof is an offline-first open-AI app that turns short walks into meaningful field missions. Capture photos, sounds, and notes, then use Gemma to create evidence-linked observations with clear confidence levels. TabPFN scores report quality while durable workflows handle retries, helping people notice and understand the world around them.
Character count: 348 chars
Other strong alternatives:
noticelyfieldnote-aigroundtruthwalkwiseoutside-inproof-of-walk
My pick remains WildProof because it is distinctive, easy to remember, and gives the project room to grow beyond plants or walking routes.
What do you think about current implementation till phase 8
I’ll review the current repository against the Phase 1–8 plan, check what is actually implemented versus only scaffolded, and run the smallest relevant validation before giving a candid readiness assessment.
{"command":"Get-ChildItem -Force | Select-Object Mode,Length,Name; Get-ChildItem -Recurse -File -ErrorAction SilentlyContinue | Select-Object -First 120 FullName","description":"Inspect current reposi...
Mode Length Name
---- ------ ----
d--h-- .git
d----- api
d----- node_modules
d----- public
d----- src
d----- venv
-a---- 285 .env
-a---- 95 .env.example
-a---- 531 .gitignore
-a---- 245 .oxlintrc.json
-a---- 400 index.html
-a---- 1089 LICENSE
-a---- 77014 package-lock.json
-a---- 790 package.json
-a---- 3544 README.md
-a---- 655 tsconfig.app.json
-a---- 119 tsconfig.json
-a---- 558 tsconfig.node.json
-a---- 220 vite.config.ts
<shellId: 0 completed with exit code 0>
{
"command": "git status --short; git log --oneline -5",
"description": "Check current git state and recent work"
}
f51f1a9 chore: rename FieldProof to WildProof across entire stack
98ae635 docs: update README with correct working directory for backend
ae889c1 feat: Phase 8 UI Polish and README
fab4ab1 chore: ensure venv is ignored
52dcef8 fix: load .env using python-dotenv for Sentry
<shellId: 1 completed with exit code 0>
{
"pattern": "**/*",
"paths": "E:\\HACKTOBERFEST WEEK 1 CHALLENGE"
}
{
"command": "Get-ChildItem src -Recurse -File | Select-Object FullName; Get-ChildItem api -Recurse -File | Select-Object FullName",
"description": "List implementation files"
}
FullName
--------
E:\HACKTOBERFEST WEEK 1 CHALLENGE\src\App.css
E:\HACKTOBERFEST WEEK 1 CHALLENGE\src\App.tsx
E:\HACKTOBERFEST WEEK 1 CHALLENGE\src\index.css
E:\HACKTOBERFEST WEEK 1 CHALLENGE\src\main.tsx
E:\HACKTOBERFEST WEEK 1 CHALLENGE\src\assets\hero.png
E:\HACKTOBERFEST WEEK 1 CHALLENGE\src\assets\react.svg
E:\HACKTOBERFEST WEEK 1 CHALLENGE\src\assets\vite.svg
E:\HACKTOBERFEST WEEK 1 CHALLENGE\src\lib\db.ts
E:\HACKTOBERFEST WEEK 1 CHALLENGE\api\main.py
E:\HACKTOBERFEST WEEK 1 CHALLENGE\api\requirements.txt
E:\HACKTOBERFEST WEEK 1 CHALLENGE\api\tabpfn_experiment.py
E:\HACKTOBERFEST WEEK 1 CHALLENGE\api\temporal_worker.py
E:\HACKTOBERFEST WEEK 1 CHALLENGE\api\temporal_workflow.py
E:\HACKTOBERFEST WEEK 1 CHALLENGE\api\__pycache__\main.cpython-311.pyc
E:\HACKTOBERFEST WEEK 1 CHALLENGE\api\__pycache__\temporal_workflow.cpython-311.pyc
<shellId: 2 completed with exit code 0>
{
"command": "Get-Content package.json; Get-Content README.md",
"description": "Read project configuration and documentation"
}
{
"name": "hacktoberfest-week-1-challenge",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "oxlint",
"preview": "vite preview"
},
"dependencies": {
"@sentry/react": "^11.6.0",
"idb": "^8.0.4",
"lucide-react": "^1.54.0",
"react": "^19.2.8",
"react-dom": "^19.2.8",
"react-router-dom": "^7.18.4"
},
"devDependencies": {
"@tailwindcss/vite": "^4.3.3",
"@types/node": "^24.13.3",
"@types/react": "^19.2.18",
"@types/react-dom": "^19.2.7",
"@vitejs/plugin-react": "^6.1.1",
"autoprefixer": "^10.6.1",
"oxlint": "^1.81.0",
"postcss": "^8.5.29",
"tailwindcss": "^4.3.3",
"typescript": "~6.0.2",
"vite": "^8.3.0"
}
}
# WildProof 🌿
**WildProof** is an offline-first, uncertainty-aware field observation app designed to help researchers, hikers, and citizen scientists confidently log outdoor evidence. Built for the **Hacktoberfest DEV Challenge (Week 1: Touch Grass)**.
Unlike typical AI identification apps that confidently guess incorrect species, WildProof embraces uncertainty. It acts as an offline journal for your expedition (capturing photos, notes, and locations via IndexedDB). When you return to civilization, a local AI (Gemma 2B) analyzes your evidence, generating a structured report that explicitly separates *observed facts* from *inferred deductions*.
## How it works
1. **Mission Selection**: Choose a mission type (e.g., Habitat Comparison, Pattern Hunt).
2. **Offline Evidence Capture**: Go outside! Log field notes and snap photos. Everything is saved locally via IndexedDB, meaning it works deep in the woods without cell service.
3. **Durable Analysis pipeline**: Back on Wi-Fi, the app submits your evidence to a local FastAPI backend. A Temporal workflow coordinates the AI analysis, ensuring no data is lost even if the AI model crashes or times out.
4. **Structured Report**: The local Gemma model structures the raw evidence into a rigid JSON schema, highlighting what was directly observed, what is missing, and suggesting your next actionable step in the field.
## Challenge Categories Targeted
We built WildProof to specifically target three prize categories:
### 1. Best Use of TabPFN
We integrated TabPFN to predict "Expedition Quality" based on tabular features (number of photos, note length, mission duration). Because TabPFN is optimized for small tabular datasets, it perfectly captures the non-linear relationships in small field-data sets.
*See `api/tabpfn_experiment.py` for the reproducible baseline-vs-TabPFN experiment script!*
### 2. Best Use of Temporal
The AI analysis pipeline is notoriously flaky (GPU OOMs, local model timeouts). We wrapped the entire backend analysis in a **Temporal Workflow** (`api/temporal_workflow.py`). If the Gemma analysis step fails or times out, Temporal durably retries the specific activity without losing the user's hard-earned field data or restarting the entire expedition process.
### 3. Best Use of Sentry
We instrumented the entire stack for deep performance tracing and error tracking:
- **React Frontend**: `@sentry/react` captures browser interactions and session replays.
- **FastAPI Backend & Temporal Worker**: `sentry-sdk[fastapi]` tracks request latency and catches model timeouts.
*(You can simulate a crash and generate a trace link using the `GET /sentry-debug` endpoint!)*
## Local Setup
### Prerequisites
- Node.js (v18+)
- Python 3.10+
- Local [Ollama](https://ollama.com/) with `gemma:2b` pulled (`ollama run gemma:2b`)
- (Optional) Temporal CLI for running the durable workflow locally.
### 1. Frontend Setup
```bash
npm install
npm run dev
```
The React app will be available at `http://localhost:5173`.
### 2. Backend Setup
Create a `.env` file from `.env.example` and add your Sentry DSN if you wish to track errors.
```bash
python -m venv venv
# Windows:
.\venv\Scripts\activate
# Mac/Linux:
# source venv/bin/activate
pip install -r api/requirements.txt
```
### 3. Running the Backend
To start the standard FastAPI server:
```bash
cd api
uvicorn main:app --reload
```
*(Optional)* To run the Temporal worker (requires Temporal running locally `temporal server start-dev`):
```bash
cd api
python temporal_worker.py
```
## License
MIT
<shellId: 3 completed with exit code 0>
{
"command": "Get-Content src\\App.tsx; Get-Content src\\main.tsx",
"description": "Read main frontend entrypoints"
}
Output too large to read at once (15.5 KB). Saved to: C:\Users\Lenovo\AppData\Local\Temp\1791603007322-copilot-tool-output-7892-d2f6babe-1fcc-4e42-9d49-9b280f369dc6.txt
Consider using tools like rg (for searching), head/tail (for viewing start/end), view with view_range (for specific sections), or jq (for JSON) to examine portions of the output.
Preview (first 500 chars):
import { useState, useEffect } from 'react';
import { BrowserRouter as Router, Routes, Route, Link, useParams, useNavigate } from 'react-router-dom';
import { Leaf, Map, ClipboardList, Info, Camera, Trash, Plus, Check } from 'lucide-react';
import { getExpedition, saveExpedition } from './lib/db';
import type { Expedition, EvidenceItem, MissionType, FieldReport } from './lib/db';
function generateId() {
return Math.random().toString(36).substr(2, 9);
}
function Home() {
return (
<div c
<shellId: 5 completed with exit code 0>
{
"command": "Get-Content api\\main.py; Get-Content api\\requirements.txt",
"description": "Read backend entrypoint and dependencies"
}
from fastapi import FastAPI, HTTPException, Request
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, ValidationError, Field
from typing import List, Optional, Literal
import httpx
import json
import logging
from temporalio.client import Client
from temporal_workflow import EvidenceData
import sentry_sdk
import os
from dotenv import load_dotenv
load_dotenv()
sentry_dsn = os.environ.get("SENTRY_DSN")
if sentry_dsn:
sentry_sdk.init(
dsn=sentry_dsn,
traces_sample_rate=1.0,
profiles_sample_rate=1.0,
)
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
app = FastAPI(title="WildProof API")
# Allow frontend to access the API
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # In production, restrict to frontend URL
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
OLLAMA_API_URL = "http://localhost:11434/api/generate"
MODEL_NAME = "gemma:2b" # Or "gemma", "llama3.2-vision", depending on what is installed locally
class EvidencePayload(BaseModel):
id: str
kind: str
text: Optional[str] = None
capturedAt: str
localUrl: Optional[str] = None # Base64 representation if sent
class AnalyzeRequest(BaseModel):
missionType: str
evidence: List[EvidencePayload]
class Observation(BaseModel):
claim: str
confidence: Literal["observed", "inferred", "unknown"]
supportingEvidenceIds: List[str]
class FieldReport(BaseModel):
summary: str
observations: List[Observation]
missingEvidence: List[str] = Field(alias="missing_evidence")
suggestedNextStep: str = Field(alias="suggested_next_step")
class Config:
populate_by_name = True
@app.get("/health")
def health_check():
return {"status": "ok"}
@app.get("/sentry-debug")
def trigger_error():
logger.error("Simulated performance issue / timeout for Sentry Tracing.")
raise Exception("Simulated timeout for Sentry!")
async def generate_from_ollama(prompt: str) -> str:
async with httpx.AsyncClient(timeout=60.0) as client:
try:
response = await client.post(
OLLAMA_API_URL,
json={
"model": MODEL_NAME,
"prompt": prompt,
"stream": False,
"format": "json"
}
)
response.raise_for_status()
return response.json().get("response", "")
except Exception as e:
logger.error(f"Failed to communicate with Ollama: {e}")
raise HTTPException(status_code=503, detail="Local AI model is unavailable.")
@app.post("/analyze", response_model=FieldReport)
async def analyze_expedition(request: AnalyzeRequest):
logger.info(f"Analyzing expedition: {request.missionType} with {len(request.evidence)} items.")
# Formulate prompt from evidence
text_evidence = []
for ev in request.evidence:
if ev.kind == "note" and ev.text:
text_evidence.append(f"Note ID {ev.id}: {ev.text}")
elif ev.kind == "photo":
text_evidence.append(f"Photo Evidence ID {ev.id} captured.")
evidence_str = "\n".join(text_evidence)
prompt = f"""
You are an expert field researcher analyzing data from an outdoor expedition.
Mission Type: {request.missionType}
Here is the collected evidence:
{evidence_str}
Analyze this evidence and provide a structured JSON response. Do NOT output markdown, ONLY valid JSON.
Your JSON must match this structure exactly:
{{
"summary": "A 1-2 sentence overall summary of the expedition.",
"observations": [
{{
"claim": "Specific observation here.",
"confidence": "observed" | "inferred" | "unknown",
"supportingEvidenceIds": ["id1", "id2"]
}}
],
"missing_evidence": ["What evidence is missing to make a stronger conclusion?"],
"suggested_next_step": "A single actionable next step."
}}
Rule for confidence:
- "observed": Only if directly stated in a note or directly visible in a photo (if provided).
- "inferred": A logical deduction based on the evidence.
- "unknown": Something guessed or speculative.
Ensure the "supportingEvidenceIds" match the Note IDs or Photo IDs provided above.
"""
raw_response = await generate_from_ollama(prompt)
try:
data = json.loads(raw_response)
report = FieldReport(**data)
return report
except (json.JSONDecodeError, ValidationError) as e:
logger.warning(f"First parse attempt failed: {e}. Attempting repair.")
repair_prompt = f"""
You previously returned invalid JSON. Fix the following JSON string so it matches the expected schema perfectly.
Schema:
{{
"summary": "...",
"observations": [
{{
"claim": "...",
"confidence": "observed" | "inferred" | "unknown",
"supportingEvidenceIds": ["id1"]
}}
],
"missing_evidence": ["..."],
"suggested_next_step": "..."
}}
Invalid JSON:
{raw_response}
"""
repair_response = await generate_from_ollama(repair_prompt)
try:
data = json.loads(repair_response)
report = FieldReport(**data)
return report
except Exception as e:
logger.error(f"Repair attempt failed: {e}")
raise HTTPException(status_code=500, detail="AI returned malformed data. Please try again.")
@app.post("/analyze-durable")
async def analyze_expedition_durable(request: AnalyzeRequest):
logger.info("Triggering durable analysis via Temporal...")
try:
client = await Client.connect("localhost:7233")
# Convert Pydantic request to workflow dataclass
evidence_data = EvidenceData(
mission_type=request.missionType,
evidence_items=[ev.model_dump() for ev in request.evidence]
)
# Execute workflow
result = await client.execute_workflow(
"ExpeditionAnalysisWorkflow",
evidence_data,
id=f"expedition-analysis-{request.missionType}-{len(request.evidence)}",
task_queue="wildproof-analysis-queue",
)
return {"status": "Workflow completed", "result": result}
except Exception as e:
logger.error(f"Temporal execution failed: {e}")
raise HTTPException(status_code=500, detail="Temporal workflow failed or server is not running.")
class QualityPredictionRequest(BaseModel):
num_photos: int
num_notes: int
total_note_length: int
duration_seconds: int
@app.post("/predict-quality")
def predict_quality(features: QualityPredictionRequest):
# This uses the baseline heuristic.
# For the TabPFN experiment (which can be memory-heavy), see tabpfn_experiment.py
evidence_count = features.num_photos + features.num_notes
is_high_quality = (evidence_count >= 3) and (features.total_note_length >= 40)
return {
"is_high_quality": is_high_quality,
"score": evidence_count + (features.total_note_length / 10),
"model": "baseline_heuristic"
}
fastapi==0.111.0
uvicorn==0.30.1
pydantic==2.8.2
httpx==0.27.0
tabpfn==0.1.10
scikit-learn==1.5.0
pandas==2.2.2
numpy==1.26.4
temporalio==1.6.0
sentry-sdk[fastapi]==2.13.0
python-dotenv==1.0.1
<shellId: 4 completed with exit code 0>
{
"command": "Get-Content src\\lib\\db.ts; Get-Content src\\main.tsx; Get-Content src\\App.css",
"description": "Inspect persistence and UI styling"
}
import { openDB } from 'idb';
import type { DBSchema, IDBPDatabase } from 'idb';
export type MissionType = 'habitat-comparison' | 'pattern-hunt' | 'sound-and-scene';
export type EvidenceKind = 'photo' | 'audio' | 'note';
export type Confidence = 'observed' | 'inferred' | 'unknown';
export type ExpeditionStatus = 'draft' | 'queued' | 'processing' | 'complete' | 'failed';
export interface EvidenceItem {
id: string;
kind: EvidenceKind;
localUrl?: string; // object URL or base64
text?: string;
capturedAt: string;
latitude?: number;
longitude?: number;
}
export interface Observation {
claim: string;
confidence: Confidence;
supportingEvidenceIds: string[];
}
export interface FieldReport {
summary: string;
observations: Observation[];
missingEvidence: string[];
suggestedNextStep: string;
qualityScore?: number;
}
export interface Expedition {
id: string;
missionType: MissionType;
startedAt: string;
completedAt?: string;
evidence: EvidenceItem[];
status: ExpeditionStatus;
report?: FieldReport;
}
interface WildProofDB extends DBSchema {
expeditions: {
key: string;
value: Expedition;
indexes: { 'by-status': string };
};
}
let dbPromise: Promise<IDBPDatabase<WildProofDB>> | null = null;
export async function getDb() {
if (!dbPromise) {
dbPromise = openDB<WildProofDB>('wildproof-db', 1, {
upgrade(db) {
const store = db.createObjectStore('expeditions', { keyPath: 'id' });
store.createIndex('by-status', 'status');
},
});
}
return dbPromise;
}
export async function saveExpedition(exp: Expedition) {
const db = await getDb();
await db.put('expeditions', exp);
}
export async function getExpedition(id: string) {
const db = await getDb();
return db.get('expeditions', id);
}
export async function getAllExpeditions() {
const db = await getDb();
return db.getAll('expeditions');
}
export async function deleteExpedition(id: string) {
const db = await getDb();
await db.delete('expeditions', id);
}
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx'
import * as Sentry from '@sentry/react'
if (import.meta.env.VITE_SENTRY_DSN) {
Sentry.init({
dsn: import.meta.env.VITE_SENTRY_DSN,
integrations: [
Sentry.browserTracingIntegration(),
Sentry.replayIntegration(),
],
tracesSampleRate: 1.0,
tracePropagationTargets: ["localhost"],
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
});
}
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
)
.counter {
font-size: 16px;
padding: 5px 10px;
border-radius: 5px;
color: var(--accent);
background: var(--accent-bg);
border: 2px solid transparent;
transition: border-color 0.3s;
margin-bottom: 24px;
&:hover {
border-color: var(--accent-border);
}
&:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
}
.hero {
position: relative;
.base,
.framework,
.vite {
inset-inline: 0;
margin: 0 auto;
}
.base {
width: 170px;
position: relative;
z-index: 0;
}
.framework,
.vite {
position: absolute;
}
.framework {
z-index: 1;
top: 34px;
height: 28px;
transform: perspective(2000px) rotateZ(300deg) rotateX(44deg) rotateY(39deg)
scale(1.4);
}
.vite {
z-index: 0;
top: 107px;
height: 26px;
width: auto;
transform: perspective(2000px) rotateZ(300deg) rotateX(40deg) rotateY(39deg)
scale(0.8);
}
}
#center {
display: flex;
flex-direction: column;
gap: 25px;
place-content: center;
place-items: center;
flex-grow: 1;
@media (max-width: 1024px) {
padding: 32px 20px 24px;
gap: 18px;
}
}
#next-steps {
display: flex;
border-top: 1px solid var(--border);
text-align: left;
& > div {
flex: 1 1 0;
padding: 32px;
@media (max-width: 1024px) {
padding: 24px 20px;
}
}
.icon {
margin-bottom: 16px;
width: 22px;
height: 22px;
}
@media (max-width: 1024px) {
flex-direction: column;
text-align: center;
}
}
#docs {
border-right: 1px solid var(--border);
@media (max-width: 1024px) {
border-right: none;
border-bottom: 1px solid var(--border);
}
}
#next-steps ul {
list-style: none;
padding: 0;
display: flex;
gap: 8px;
margin: 32px 0 0;
.logo {
height: 18px;
}
a {
color: var(--text-h);
font-size: 16px;
border-radius: 6px;
background: var(--social-bg);
display: flex;
padding: 6px 12px;
align-items: center;
gap: 8px;
text-decoration: none;
transition: box-shadow 0.3s;
&:hover {
box-shadow: var(--shadow);
}
.button-icon {
height: 18px;
width: 18px;
}
}
@media (max-width: 1024px) {
margin-top: 20px;
flex-wrap: wrap;
justify-content: center;
li {
flex: 1 1 calc(50% - 8px);
}
a {
width: 100%;
justify-content: center;
box-sizing: border-box;
}
}
}
#spacer {
height: 88px;
border-top: 1px solid var(--border);
@media (max-width: 1024px) {
height: 48px;
}
}
.ticks {
position: relative;
width: 100%;
&::before,
&::after {
content: '';
position: absolute;
top: -4.5px;
border: 5px solid transparent;
}
&::before {
left: 0;
border-left-color: var(--border);
}
&::after {
right: 0;
border-right-color: var(--border);
}
}
<shellId: 6 completed with exit code 0>
{
"command": "Get-Content api\\temporal_workflow.py; Get-Content api\\temporal_worker.py; Get-Content api\\tabpfn_experiment.py",
"description": "Inspect Temporal and TabPFN implementation"
}
from datetime import timedelta
import asyncio
from temporalio import activity, workflow
from typing import List, Dict, Any
# Define data classes used in the workflow
from dataclasses import dataclass
@dataclass
class EvidenceData:
mission_type: str
evidence_items: List[Dict[str, Any]]
@dataclass
class ReportData:
summary: str
observations: List[Dict[str, Any]]
missing_evidence: List[str]
suggested_next_step: str
# ---------------------------------------------------------
# ACTIVITIES
# ---------------------------------------------------------
@activity.defn
async def normalize_evidence(evidence: EvidenceData) -> EvidenceData:
# Simulates preparing/cleaning local evidence payload for AI
activity.logger.info("Normalizing evidence payload...")
# Add dummy location normalization logic or size reduction
return evidence
# A mock counter to simulate a transient failure for demonstration purposes
gemma_failures = 0
@activity.defn
async def run_gemma_analysis(evidence: EvidenceData) -> ReportData:
global gemma_failures
activity.logger.info(f"Running Gemma Analysis for {evidence.mission_type}...")
# DEMONSTRATION OF TEMPORAL'S DURABLE RETRY
# We force the activity to fail the first time to show how Temporal resumes it
# without restarting the entire expedition process.
if gemma_failures < 1:
gemma_failures += 1
activity.logger.error("Simulated GPU OOM or API Timeout error in Gemma.")
raise RuntimeError("Gemma model failed to respond (Simulated Timeout)")
# If we get here, it succeeded (on the retry)
return ReportData(
summary="A durable field report generated after a simulated failure recovery.",
observations=[
{"claim": "Evidence recovered seamlessly.", "confidence": "observed", "supportingEvidenceIds": []}
],
missing_evidence=[],
suggested_next_step="Review the Sentry tracing logs."
)
@activity.defn
async def validate_report(report: ReportData) -> ReportData:
activity.logger.info("Validating AI JSON Schema...")
# Simulates Pydantic validation
return report
@activity.defn
async def run_tabpfn_quality_prediction(report: ReportData) -> int:
activity.logger.info("Running TabPFN tabular prediction on expedition quality...")
return 95 # Dummy high quality score
@activity.defn
async def persist_report(report: ReportData) -> str:
activity.logger.info("Persisting durable report to Database...")
return "Report fully processed and saved!"
# ---------------------------------------------------------
# WORKFLOW
# ---------------------------------------------------------
@workflow.defn
class ExpeditionAnalysisWorkflow:
@workflow.run
async def analyze_expedition(self, evidence: EvidenceData) -> str:
# Step 1: Normalize
normalized = await workflow.execute_activity(
normalize_evidence,
evidence,
start_to_close_timeout=timedelta(seconds=10),
)
# Step 2: Run Gemma Analysis
# This activity is prone to "failure", but Temporal will automatically retry it
# based on the default retry policy, ensuring the walk data isn't lost.
raw_report = await workflow.execute_activity(
run_gemma_analysis,
normalized,
start_to_close_timeout=timedelta(seconds=60),
)
# Step 3: Validate
validated_report = await workflow.execute_activity(
validate_report,
raw_report,
start_to_close_timeout=timedelta(seconds=10),
)
# Step 4: Quality Prediction
quality_score = await workflow.execute_activity(
run_tabpfn_quality_prediction,
validated_report,
start_to_close_timeout=timedelta(seconds=30),
)
# Step 5: Persist
result = await workflow.execute_activity(
persist_report,
validated_report,
start_to_close_timeout=timedelta(seconds=10),
)
return f"{result} (Quality Score: {quality_score})"
import asyncio
import logging
from temporalio.client import Client
from temporalio.worker import Worker
# Import our workflow and activities
from temporal_workflow import (
ExpeditionAnalysisWorkflow,
normalize_evidence,
run_gemma_analysis,
validate_report,
run_tabpfn_quality_prediction,
persist_report,
)
import sentry_sdk
import os
from dotenv import load_dotenv
load_dotenv()
sentry_dsn = os.environ.get("SENTRY_DSN")
if sentry_dsn:
sentry_sdk.init(
dsn=sentry_dsn,
traces_sample_rate=1.0,
profiles_sample_rate=1.0,
)
async def main():
logging.basicConfig(level=logging.INFO)
# Connect to local Temporal server (requires Temporal CLI or Docker running locally)
# E.g., `temporal server start-dev`
try:
client = await Client.connect("localhost:7233")
except Exception as e:
logging.error("Failed to connect to Temporal Server. Make sure it is running on localhost:7233.")
return
# Run the worker
worker = Worker(
client,
task_queue="wildproof-analysis-queue",
workflows=[ExpeditionAnalysisWorkflow],
activities=[
normalize_evidence,
run_gemma_analysis,
validate_report,
run_tabpfn_quality_prediction,
persist_report,
],
)
logging.info("Starting Temporal Worker for WildProof Analysis...")
await worker.run()
if __name__ == "__main__":
asyncio.run(main())
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from tabpfn import TabPFNClassifier
# 1. Generate Synthetic Dataset
# Features: num_photos, num_notes, total_note_length, duration_seconds
np.random.seed(42)
n_samples = 150
num_photos = np.random.randint(0, 5, n_samples)
num_notes = np.random.randint(0, 5, n_samples)
total_note_length = np.random.randint(0, 200, n_samples)
duration_seconds = np.random.randint(60, 3600, n_samples)
# A bit of noise
X = pd.DataFrame({
'num_photos': num_photos,
'num_notes': num_notes,
'total_note_length': total_note_length,
'duration_seconds': duration_seconds
})
# True labels (Is it a high-quality report?)
# Real world: determined by human review. Here: synthetically generated based on complex underlying rules.
y = ((num_photos >= 1) & (total_note_length >= 20) & (duration_seconds > 300)).astype(int)
# Add some random noise to the labels to make it non-deterministic
flip_mask = np.random.rand(n_samples) < 0.1
y = np.where(flip_mask, 1 - y, y)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 2. Baseline Model
# Rule: evidence_count >= 3 && note_length >= 40
def baseline_predict(df):
evidence_count = df['num_photos'] + df['num_notes']
return ((evidence_count >= 3) & (df['total_note_length'] >= 40)).astype(int)
baseline_preds = baseline_predict(X_test)
baseline_acc = accuracy_score(y_test, baseline_preds)
# 3. TabPFN Model
# TabPFN is a pre-trained transformer for tabular data that needs no hyperparameter tuning for small datasets
classifier = TabPFNClassifier(device='cpu', N_ensemble_configurations=32)
classifier.fit(X_train, y_train)
tabpfn_preds = classifier.predict(X_test)
tabpfn_acc = accuracy_score(y_test, tabpfn_preds)
print("=== WildProof TabPFN Quality Prediction Experiment ===")
print(f"Dataset Size: {n_samples} samples")
print(f"Features: {list(X.columns)}")
print(f"Baseline Accuracy: {baseline_acc * 100:.2f}%")
print(f"TabPFN Accuracy: {tabpfn_acc * 100:.2f}%")
print("\nLimitations: TabPFN is optimized for small tabular datasets (<10k samples) and can be memory intensive for inference in a lightweight API compared to a simple heuristic. However, it perfectly captures non-linear relationships in the field data.")
<shellId: 7 completed with exit code 0>
{
"command": "npm run build; npm run lint",
"description": "Validate frontend build and lint",
"initial_wait": 120,
"mode": "sync"
}
> [email protected] build
> tsc -b && vite build
vite v8.3.4 building client environment for production...
transforming...
✓ 2189 modules transformed.
rendering chunks...
computing gzip size...
dist/index.html 0.49 kB │ gzip: 0.33 kB
dist/assets/index-BVzDAGDx.css 23.10 kB │ gzip: 5.13 kB
dist/assets/index-CPZGUSMf.js 564.68 kB │ gzip: 182.73 kB
[plugin builtin:vite-reporter]
(!) Some chunks are larger than 500 kB after minification. Consider:
- Using dynamic import() to code-split the application
- Use build.rolldownOptions.output.codeSplitting to improve chunking: https://rolldown.rs/reference/OutputOptions.codeSplitting
- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.
✓ built in 8.35s
[PLUGIN_TIMINGS] JavaScript callbacks ran for 4.6s of this 8.2s build (56%).
The slowest callbacks, timed inside each callback (the wait before a callback starts is excluded, the time it awaits is included):
- plugin @tailwindcss/vite:generate:build transform (40%, 3.3s, 1 call)
Additional callback time came from callbacks under 1s.
See https://rolldown.rs/reference/InputOptions.checks#bundlertimings for more details.
> [email protected] lint
> oxlint
<shellId: 8 completed with exit code 0>
{
"filePaths": [
"E:\\HACKTOBERFEST WEEK 1 CHALLENGE"
]
}
No errors found.
{"command":"$i=0; Get-Content src\\App.tsx | ForEach-Object { $i++; if($_ -match 'function |fetch\\(|analyze|saveExpedition|MediaRecorder|input|mission|report|predict|durable|quality|offline|navigator...
4: import { getExpedition, saveExpedition } from './lib/db';
5: import type { Expedition, EvidenceItem, MissionType, FieldReport } from './lib/db';
7: function generateId() {
11: function Home() {
19: An offline-first open-AI companion for turning ordinary walks into trustworthy field notes.
22: to="/missions"
31: function Missions() {
34: const startMission = async (id: MissionType) => {
40: missionType: id,
45: await saveExpedition(newExp);
62: <h2 className="text-2xl font-bold mb-4 text-neutral-800">Select Mission</h2>
71: onClick={() => startMission(m.id as MissionType)}
84: function ActiveExpedition() {
100: const addPhoto = (e: React.ChangeEvent<HTMLInputElement>) => {
112: await saveExpedition(updated);
128: await saveExpedition(updated);
135: await saveExpedition(updated);
141: await saveExpedition(updated);
142: navigate(`/report/${expedition.id}`);
149: <h2 className="text-3xl font-extrabold capitalize text-neutral-800 tracking-tight">{expedition.missionType.replace('-', ' ')}</h2>
155: <Check className="w-5 h-5"/> Generate Report
186: <input type="file" accept="image/*" capture="environment" className="hidden" onChange={addPhoto} />
190: <input
208: function Report() {
218: if (!exp.report) {
220: const res = await fetch('http://localhost:8000/analyze', {
223: body: JSON.stringify({ missionType: exp.missionType, evidence: exp.evidence })
227: const updated = { ...exp, status: 'complete' as const, report: data };
228: await saveExpedition(updated);
232: const mockReport: FieldReport = {
241: const updated = { ...exp, status: 'complete' as const, report: mockReport };
242: await saveExpedition(updated);
253: if (!expedition || !expedition.report) return <div className="p-4 text-center mt-10">Generating report...</div>;
255: const { report } = expedition;
259: <h2 className="text-3xl font-bold mb-2">Field Report</h2>
270: <p className="text-emerald-900 leading-relaxed">{report.summary}</p>
275: {report.observations.map((obs: any, i: number) => (
293: {report.missingEvidence.map((e: string, i: number) => <li key={i}>{e}</li>)}
298: <p className="text-sm text-neutral-600">{report.suggestedNextStep}</p>
305: function About() {
319: export default function App() {
330: <Link to="/missions" className="hover:text-emerald-600 flex items-center gap-1"><ClipboardList className="w-4 h-4 hidden sm:block"/> Missions</Link>
339: <Route path="/missions" element={<Missions />} />
341: <Route path="/report/:id" element={<Report />} />
<shellId: 9 completed with exit code 0>
{
"command": "$i=0; Get-Content src\\App.tsx | ForEach-Object { $i++; '{0}: {1}' -f $i,$_ } | Select-Object -Last 220",
"description": "Inspect lower frontend implementation"
}
Output too large to read at once (11.1 KB). Saved to: C:\Users\Lenovo\AppData\Local\Temp\1791603040604-copilot-tool-output-7892-30b1f507-073b-44fa-90a9-8e45dcdb65bc.txt
Consider using tools like rg (for searching), head/tail (for viewing start/end), view with view_range (for specific sections), or jq (for JSON) to examine portions of the output.
Preview (first 500 chars):
129: setExpedition(updated);
130: setNoteText('');
131: };
132:
133: const deleteEvidence = async (evId: string) => {
134: const updated = { ...expedition, evidence: expedition.evidence.filter(e => e.id !== evId) };
135: await saveExpedition(updated);
136: setExpedition(updated);
137: };
138:
139: const completeExpedition = async () => {
140: const updated = { ...expedition, status: 'queued' as const, completedAt: new Date().toISOString() };
141: await saveE
<shellId: 11 completed with exit code 0>
{
"command": "$i=0; Get-Content src\\App.tsx | ForEach-Object { $i++; '{0}: {1}' -f $i,$_ } | Select-Object -First 220",
"description": "Inspect upper frontend implementation"
}
Output too large to read at once (10.0 KB). Saved to: C:\Users\Lenovo\AppData\Local\Temp\1791603040755-copilot-tool-output-7892-8e79c925-9c3c-4022-bc4b-d37f83d42ef9.txt
Consider using tools like rg (for searching), head/tail (for viewing start/end), view with view_range (for specific sections), or jq (for JSON) to examine portions of the output.
Preview (first 500 chars):
1: import { useState, useEffect } from 'react';
2: import { BrowserRouter as Router, Routes, Route, Link, useParams, useNavigate } from 'react-router-dom';
3: import { Leaf, Map, ClipboardList, Info, Camera, Trash, Plus, Check } from 'lucide-react';
4: import { getExpedition, saveExpedition } from './lib/db';
5: import type { Expedition, EvidenceItem, MissionType, FieldReport } from './lib/db';
6:
7: function generateId() {
8: return Math.random().toString(36).substr(2, 9);
9: }
10:
11: func
<shellId: 10 completed with exit code 0>
{
"command": "$lines=Get-Content src\\App.tsx; $lines[205..252]",
"description": "Inspect report generation and fallback behavior"
}
}
function Report() {
const { id } = useParams<{ id: string }>();
const [expedition, setExpedition] = useState<Expedition | null>(null);
const [errorMsg, setErrorMsg] = useState<string | null>(null);
useEffect(() => {
if (id) {
getExpedition(id).then(async exp => {
if (exp) {
if (!exp.report) {
try {
const res = await fetch('http://localhost:8000/analyze', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ missionType: exp.missionType, evidence: exp.evidence })
});
if (!res.ok) throw new Error('Backend unavailable');
const data = await res.json();
const updated = { ...exp, status: 'complete' as const, report: data };
await saveExpedition(updated);
setExpedition(updated);
} catch {
setErrorMsg("AI analysis unavailable. Showing a locally generated evidence summary.");
const mockReport: FieldReport = {
summary: "A brief walk yielding partial insights into the local habitat (Mocked).",
observations: [
{ claim: "Found broadleaf patterns near the pathway.", confidence: "observed", supportingEvidenceIds: [] },
{ claim: "Likely a moderately humid micro-climate.", confidence: "inferred", supportingEvidenceIds: [] },
],
missingEvidence: ["Audio capturing ambient bird sounds.", "Temperature readings."],
suggestedNextStep: "Return at dusk to capture nocturnal insect patterns."
};
const updated = { ...exp, status: 'complete' as const, report: mockReport };
await saveExpedition(updated);
setExpedition(updated);
}
} else {
setExpedition(exp);
}
}
});
}
}, [id]);
if (!expedition || !expedition.report) return <div className="p-4 text-center mt-10">Generating report...</div>;
<shellId: 12 completed with exit code 0>
{
"command": "git ls-files .env .env.example; Get-Content .gitignore",
"description": "Check environment secret tracking"
}
.env.example
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# Python
__pycache__/
*.py[cod]
*$py.class
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Env vars
.env
.env.*
<shellId: 13 completed with exit code 0>
{
"command": "$lines=Get-Content src\\App.tsx; $lines[80..205]",
"description": "Inspect capture and completion flow"
}
);
}
function ActiveExpedition() {
const { id } = useParams<{ id: string }>();
const navigate = useNavigate();
const [expedition, setExpedition] = useState<Expedition | null>(null);
const [noteText, setNoteText] = useState('');
useEffect(() => {
if (id) {
getExpedition(id).then(exp => {
if (exp) setExpedition(exp);
});
}
}, [id]);
if (!expedition) return <div className="p-4 text-center mt-10">Loading expedition...</div>;
const addPhoto = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files && e.target.files[0]) {
const file = e.target.files[0];
const reader = new FileReader();
reader.onload = async (ev) => {
const newEv: EvidenceItem = {
id: generateId(),
kind: 'photo',
localUrl: ev.target?.result as string,
capturedAt: new Date().toISOString(),
};
const updated = { ...expedition, evidence: [...expedition.evidence, newEv] };
await saveExpedition(updated);
setExpedition(updated);
};
reader.readAsDataURL(file);
}
};
const addNote = async () => {
if (!noteText.trim()) return;
const newEv: EvidenceItem = {
id: generateId(),
kind: 'note',
text: noteText,
capturedAt: new Date().toISOString(),
};
const updated = { ...expedition, evidence: [...expedition.evidence, newEv] };
await saveExpedition(updated);
setExpedition(updated);
setNoteText('');
};
const deleteEvidence = async (evId: string) => {
const updated = { ...expedition, evidence: expedition.evidence.filter(e => e.id !== evId) };
await saveExpedition(updated);
setExpedition(updated);
};
const completeExpedition = async () => {
const updated = { ...expedition, status: 'queued' as const, completedAt: new Date().toISOString() };
await saveExpedition(updated);
navigate(`/report/${expedition.id}`);
};
return (
<div className="p-4 max-w-2xl mx-auto pb-24">
<div className="mb-6 flex justify-between items-center">
<div>
<h2 className="text-3xl font-extrabold capitalize text-neutral-800 tracking-tight">{expedition.missionType.replace('-', ' ')}</h2>
<span className="bg-amber-100 text-amber-800 px-3 py-1 rounded-full text-xs font-bold tracking-wider shadow-sm border border-amber-200 uppercase mt-2 inline-block">
Status: {expedition.status}
</span>
</div>
<button onClick={completeExpedition} className="bg-emerald-600 text-white px-5 py-3 rounded-xl shadow-lg hover:shadow-xl font-bold flex items-center gap-2 hover:bg-emerald-700 transition transform hover:-translate-y-0.5">
<Check className="w-5 h-5"/> Generate Report
</button>
</div>
<div className="space-y-4 mb-8">
{expedition.evidence.length === 0 && (
<div className="text-center p-8 border-2 border-dashed border-neutral-200 rounded-xl text-neutral-500">
No evidence yet. Start capturing your surroundings!
</div>
)}
{expedition.evidence.map(ev => (
<div key={ev.id} className="flex items-start justify-between bg-white p-4 rounded-xl shadow-sm border border-neutral-200">
<div className="flex-1">
<span className="text-xs uppercase font-bold text-neutral-400 mb-1 block">{ev.kind}</span>
{ev.kind === 'photo' && ev.localUrl && (
<img src={ev.localUrl} alt="Evidence" className="w-full h-48 object-cover rounded mt-2 bg-neutral-100" />
)}
{ev.kind === 'note' && <p className="text-neutral-800 mt-1">{ev.text}</p>}
<p className="text-xs text-neutral-400 mt-2">{new Date(ev.capturedAt).toLocaleTimeString()}</p>
</div>
<button onClick={() => deleteEvidence(ev.id)} className="text-red-500 p-2 ml-4 hover:bg-red-50 rounded">
<Trash className="w-5 h-5"/>
</button>
</div>
))}
</div>
<div className="fixed bottom-0 left-0 right-0 bg-white border-t border-neutral-200 p-4 shadow-[0_-4px_6px_-1px_rgba(0,0,0,0.05)]">
<div className="max-w-2xl mx-auto flex gap-2">
<label className="flex-1 flex justify-center items-center gap-2 bg-neutral-100 hover:bg-neutral-200 text-neutral-700 py-3 rounded-lg cursor-pointer font-medium transition">
<Camera className="w-5 h-5" /> Add Photo
<input type="file" accept="image/*" capture="environment" className="hidden" onChange={addPhoto} />
</label>
<div className="flex-[2] flex gap-2">
<input
type="text"
placeholder="Add a field note..."
className="flex-1 bg-neutral-100 border-none rounded-lg px-4 focus:ring-2 focus:ring-emerald-500"
value={noteText}
onChange={(e) => setNoteText(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && addNote()}
/>
<button onClick={addNote} className="bg-neutral-800 hover:bg-neutral-900 text-white px-4 rounded-lg flex items-center justify-center transition">
<Plus className="w-5 h-5" />
</button>
</div>
</div>
</div>
</div>
);
}
<shellId: 15 completed with exit code 0>
{
"command": "git status --short; git show --stat --oneline HEAD",
"description": "Check working tree and latest change"
}
f51f1a9 chore: rename FieldProof to WildProof across entire stack
README.md | 8 ++++----
api/main.py | 4 ++--
api/tabpfn_experiment.py | 2 +-
api/temporal_worker.py | 4 ++--
index.html | 2 +-
src/App.tsx | 10 +++++-----
src/lib/db.ts | 6 +++---
7 files changed, 18 insertions(+), 18 deletions(-)
<shellId: 14 completed with exit code 0>
Overall assessment: good demo foundation, not yet Phase 8 complete
I’d rate the current implementation 6/10 for MVP readiness and 4/10 for prize-category credibility.
The frontend is visually presentable and the core “capture → persist → report” path exists. However, several features are currently simulated or incomplete, so the README overstates what judges will be able to verify.
What is genuinely working
Strong foundation
- React/Vite/TypeScript app builds successfully.
-
npm run lintpasses. - IndexedDB persistence is implemented in db.ts.
- Mission selection works.
- Photo capture/upload works.
- Field notes work.
- Evidence survives refresh through IndexedDB.
- Report screen has
observed,inferred, andunknownconcepts. - Backend validates the report schema with Pydantic.
- Gemma/Ollama integration has a JSON repair attempt.
- Sentry initialization exists on frontend and backend.
- Temporal workflow and worker files exist.
- TabPFN experiment script exists.
- README and MIT license are present.
The frontend build is currently healthy:
npm run build ✅
npm run lint ✅
The most important gaps
1. The report fallback hides backend failure too aggressively
In App.tsx, any backend failure falls back to a mock report.
That is useful for a demo, but it creates a judging risk:
- A network failure looks like a completed report.
- The app does not visibly distinguish “real Gemma report” from “demo fallback” strongly enough.
- The error state is stored only in React state and is not displayed in the visible report flow.
Fix before submission:
Add a prominent report badge:
AI source: Gemma / Local fallback
And show a retry action instead of silently treating the fallback as complete.
2. Images are not actually sent to Gemma
The frontend stores images as localUrl data URLs, but the backend only converts photo evidence into:
Photo Evidence ID ... captured.
The model receives no image pixels. Therefore this currently is a text-and-metadata analysis, not visual field analysis.
This is the biggest mismatch between the product claim and implementation.
You have two realistic choices:
- Fastest: explicitly position photos as evidence references and make the model analyze notes only.
- Better: send compressed image data and use a vision-capable open model.
For the deadline, I recommend the first option unless you already have a working local vision model. Do not claim Gemma identified or interpreted the image contents unless it actually received them.
3. Audio capture is missing
The plan included audio, but ActiveExpedition only supports:
- Photos
- Text notes
The “sound-and-scene” mission therefore cannot fulfill its intended workflow.
Recommendation: add audio only if it can be implemented cleanly in under an hour. Otherwise remove or hide that mission and keep only:
- Habitat comparison
- Pattern hunt
A smaller honest MVP is better than a visibly incomplete mission.
4. Location is defined but not collected
latitude and longitude exist in the data model, but no geolocation capture is implemented.
Do not describe the current app as collecting locations. Either:
- Add an explicit opt-in geolocation button, or
- Remove location claims from the README.
5. TabPFN is not integrated into the API or UI
The API endpoint /predict-quality currently returns:
model: "baseline_heuristic"
The Temporal workflow returns a hardcoded quality score:
return 95
The real TabPFN work is only in tabpfn_experiment.py.
This means Best Use of TabPFN is not yet honestly demonstrated in the product.
Minimum fix:
- Add a “Report quality” section to the report screen.
- Call
/predict-quality. - Display whether the result came from
baseline_heuristicortabpfn. - Include the actual experiment output in the README/article.
- Remove the hardcoded Temporal score.
6. Temporal currently runs a fake workflow
The Temporal workflow in temporal_workflow.py is a convincing scaffold, but it does not yet perform the actual product pipeline:
-
run_gemma_analysisreturns a hardcoded report. - It intentionally fails once using a module-level counter.
-
validate_reportdoes not validate. -
run_tabpfn_quality_predictionreturns95. -
persist_reportdoes not persist anything.
That is acceptable as a demo of Temporal retry mechanics, but not as the actual analysis pipeline.
Critical submission wording change:
Describe this as:
“A Temporal prototype demonstrating durable retry behavior for the planned analysis pipeline.”
Do not say the full Gemma → validation → TabPFN → persistence pipeline is production-integrated yet.
7. Sentry has instrumentation, but not proof
Sentry setup exists, and /sentry-debug intentionally throws an error. However, the project still needs evidence:
- A real trace screenshot.
- A model latency trace.
- A failed request or retry trace.
- A short explanation of what was learned from the trace.
Also, allow_origins=["*"] and allow_credentials=True should not be used in the deployed version. Restrict CORS to the frontend URL before publishing.
8. The UI still has leftover starter CSS
App.css contains unused Vite starter styles such as .hero, .framework, .vite, #next-steps, and .ticks.
It does not block the demo, but it makes the project look less finished and suggests the polish phase is incomplete.
9. No automated tests exist
The build and lint pass, but there are no visible tests for:
- IndexedDB save/resume.
- Report schema parsing.
- Invalid AI output.
- Fallback behavior.
- Quality scoring.
- API health.
For the challenge, at least add a small backend smoke test or a frontend test for the most important behavior.
Phase-by-phase status
| Phase | Status | Assessment |
|---|---|---|
| 1. Foundation | ✅ Complete | Good |
| 2. Mission/evidence capture | ⚠️ Partial | Photos and notes only; no audio/location |
| 3. Mock report flow | ⚠️ Partial | Works, but fallback can mask failures |
| 4. Gemma integration | ⚠️ Partial | Text-only; image contents are not sent |
| 5. TabPFN | ⚠️ Partial | Experiment exists, product uses heuristic |
| 6. Temporal | ⚠️ Prototype | Retry demo exists, real pipeline is mocked |
| 7. Sentry | ⚠️ Partial | Instrumentation exists, evidence/screenshots missing |
| 8. UI polish/deployment | ⚠️ Partial | Build passes, but no verified production deployment |
What I would fix first
Priority 1 — Make claims match reality
Update the README immediately:
- Remove claims that Gemma analyzes image contents.
- Remove claims that locations are captured.
- Label Temporal and TabPFN as prototypes until integrated.
- Explain the local fallback clearly.
This prevents credibility problems if judges inspect the repository.
Priority 2 — Integrate quality scoring visibly
This is the highest-value prize fix:
- Add
/predict-qualitycall after report generation. - Display the score in the report.
- Run the TabPFN experiment.
- Add baseline-vs-TabPFN results to the article.
Without this, the TabPFN category is weak.
Priority 3 — Replace hardcoded Temporal outputs
At minimum:
- Pass the actual evidence into the activity.
- Call the same Gemma analysis helper used by
/analyze. - Validate the returned report.
- Attach the real quality score.
- Return structured workflow output.
Priority 4 — Add judge-visible proof
Capture:
- App home screen.
- Active expedition with evidence.
- Real AI report.
- TabPFN comparison.
- Temporal retry.
- Sentry trace.
- Deployed URL.
Priority 5 — Fix production configuration
Before deployment:
- Restrict CORS.
- Move
http://localhost:8000intoVITE_API_URL. - Verify Sentry DSNs are not committed.
- Add backend/frontend health checks.
- Confirm the deployed app does not fall back silently.
Category recommendation after reviewing the implementation
Keep targeting
- Overall winner
- Best Use of Gemma
- Best Use of Sentry Agent Tracing
- Best Use of Render, once deployed
Target only after implementation fixes
- Best Use of TabPFN
- Best Use of Temporal
Do not enter yet
- ElevenLabs: no audio feature.
- MongoDB Atlas: no MongoDB integration.
- Tiger Data: no pgvector integration.
- Entire: only after linking a real agent session.
The strongest honest category combination right now is:
Overall + Gemma + Sentry + Render
After the next fixes:
Overall + Gemma + TabPFN + Temporal + Sentry + Render
Bottom line
You have a good-looking functional prototype, not yet a fully evidenced prize submission. The app’s core UX is ahead of its backend integration.
The most important work is not adding more screens. It is making these three claims real:
- The report is genuinely generated by Gemma.
- The quality score is genuinely produced by TabPFN or clearly labeled as baseline.
- Temporal genuinely wraps the analysis pipeline rather than returning demo data.
Once those are fixed and documented with screenshots, WildProof becomes substantially more competitive.
Community Wisdom
🌐 Community Wisdom: EcoID: An Offline Plant Identifier That Gets You Outside
Source: bangkah
Since offline nature-AI concepts are already visible in the field, WildProof’s advantage must come from trustworthy evidence handling rather than another identification claim. The current implementation is strongest where it emphasizes uncertainty, but that advantage needs to be reflected consistently in the actual backend output.
🌐 Community Wisdom: I Taught Local AI to Help Me Notice the World
Source: ishagautam504
This neighboring project reinforces that the emotional pitch is attractive, but the differentiator cannot remain “local AI helps me notice nature.” WildProof needs the stronger technical proof: evidence-linked claims, measured quality, and recoverable processing.
How about now?
I’ll re-check the latest implementation and validation results against the specific gaps from the last review, focusing on whether the prize claims are now backed by real runtime behavior.
{
"command": "git status --short; git log --oneline -8",
"description": "Inspect latest changes"
}
a4a1f43 fix: address MVP judging gaps (TabPFN api, Temporal real integration, Fallback UI, CORS, CSS cleanup)
f51f1a9 chore: rename FieldProof to WildProof across entire stack
98ae635 docs: update README with correct working directory for backend
ae889c1 feat: Phase 8 UI Polish and README
fab4ab1 chore: ensure venv is ignored
52dcef8 fix: load .env using python-dotenv for Sentry
d0d9645 fix: IDE error in main.py and update gitignore
91699fa feat: Phase 7 Sentry Tracing integration across full stack
<shellId: 0 completed with exit code 0>
{
"command": "Get-ChildItem src -Recurse -File | Select-Object FullName; Get-ChildItem api -Recurse -File | Select-Object FullName",
"description": "List current implementation files"
}
FullName
--------
E:\HACKTOBERFEST WEEK 1 CHALLENGE\src\App.css
E:\HACKTOBERFEST WEEK 1 CHALLENGE\src\App.tsx
E:\HACKTOBERFEST WEEK 1 CHALLENGE\src\index.css
E:\HACKTOBERFEST WEEK 1 CHALLENGE\src\main.tsx
E:\HACKTOBERFEST WEEK 1 CHALLENGE\src\assets\hero.png
E:\HACKTOBERFEST WEEK 1 CHALLENGE\src\assets\react.svg
E:\HACKTOBERFEST WEEK 1 CHALLENGE\src\assets\vite.svg
E:\HACKTOBERFEST WEEK 1 CHALLENGE\src\lib\db.ts
E:\HACKTOBERFEST WEEK 1 CHALLENGE\api\main.py
E:\HACKTOBERFEST WEEK 1 CHALLENGE\api\requirements.txt
E:\HACKTOBERFEST WEEK 1 CHALLENGE\api\tabpfn_experiment.py
E:\HACKTOBERFEST WEEK 1 CHALLENGE\api\temporal_worker.py
E:\HACKTOBERFEST WEEK 1 CHALLENGE\api\temporal_workflow.py
E:\HACKTOBERFEST WEEK 1 CHALLENGE\api\__pycache__\main.cpython-311.pyc
E:\HACKTOBERFEST WEEK 1 CHALLENGE\api\__pycache__\temporal_workflow.cpython-311.pyc
<shellId: 1 completed with exit code 0>
{
"command": "Get-Content package.json; Get-Content README.md",
"description": "Review current project claims"
}
{
"name": "hacktoberfest-week-1-challenge",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "oxlint",
"preview": "vite preview"
},
"dependencies": {
"@sentry/react": "^11.6.0",
"idb": "^8.0.4",
"lucide-react": "^1.54.0",
"react": "^19.2.8",
"react-dom": "^19.2.8",
"react-router-dom": "^7.18.4"
},
"devDependencies": {
"@tailwindcss/vite": "^4.3.3",
"@types/node": "^24.13.3",
"@types/react": "^19.2.18",
"@types/react-dom": "^19.2.7",
"@vitejs/plugin-react": "^6.1.1",
"autoprefixer": "^10.6.1",
"oxlint": "^1.81.0",
"postcss": "^8.5.29",
"tailwindcss": "^4.3.3",
"typescript": "~6.0.2",
"vite": "^8.3.0"
}
}
# WildProof 🌿
**WildProof** is an offline-first, uncertainty-aware field observation app designed to help researchers, hikers, and citizen scientists confidently log outdoor evidence. Built for the **Hacktoberfest DEV Challenge (Week 1: Touch Grass)**.
Unlike typical AI identification apps that confidently guess incorrect species, WildProof embraces uncertainty. It acts as an offline journal for your expedition (capturing photos, notes, and metadata via IndexedDB). When you return to civilization, a local AI (Gemma 2B) analyzes your evidence—currently processing the text notes and photographic metadata—generating a structured report that explicitly separates *observed facts* from *inferred deductions*.
## How it works
1. **Mission Selection**: Choose a mission type (e.g., Habitat Comparison, Pattern Hunt).
2. **Offline Evidence Capture**: Go outside! Log field notes and snap photos. Everything is saved locally via IndexedDB, meaning it works deep in the woods without cell service.
3. **Durable Analysis pipeline**: Back on Wi-Fi, the app submits your evidence to a local FastAPI backend. A Temporal workflow coordinates the AI analysis, ensuring no data is lost even if the AI model crashes or times out.
4. **Structured Report**: The local Gemma model structures the raw evidence into a rigid JSON schema, highlighting what was directly observed, what is missing, and suggesting your next actionable step in the field.
## Challenge Categories Targeted
We built WildProof to specifically target three prize categories:
### 1. Best Use of TabPFN
We integrated TabPFN directly into the API to predict "Expedition Quality" based on tabular features (number of photos, note length, mission duration). Because TabPFN is optimized for small tabular datasets, it perfectly captures the non-linear relationships in small field-data sets. It is fully wired up in the `/predict-quality` endpoint and displays the score dynamically in the UI.
*See `api/tabpfn_experiment.py` for the reproducible baseline-vs-TabPFN experiment script!*
### 2. Best Use of Temporal
The AI analysis pipeline is notoriously flaky (GPU OOMs, local model timeouts). We wrapped the entire backend analysis in a **Temporal Workflow** (`api/temporal_workflow.py`). If the Gemma analysis step fails or times out, Temporal durably retries the specific activity without losing the user's hard-earned field data or restarting the entire expedition process.
### 3. Best Use of Sentry
We instrumented the entire stack for deep performance tracing and error tracking:
- **React Frontend**: `@sentry/react` captures browser interactions and session replays.
- **FastAPI Backend & Temporal Worker**: `sentry-sdk[fastapi]` tracks request latency and catches model timeouts.
*(You can simulate a crash and generate a trace link using the `GET /sentry-debug` endpoint!)*
## Local Setup
### Prerequisites
- Node.js (v18+)
- Python 3.10+
- Local [Ollama](https://ollama.com/) with `gemma:2b` pulled (`ollama run gemma:2b`)
- (Optional) Temporal CLI for running the durable workflow locally.
### 1. Frontend Setup
```bash
npm install
npm run dev
```
The React app will be available at `http://localhost:5173`.
### 2. Backend Setup
Create a `.env` file from `.env.example` and add your Sentry DSN if you wish to track errors.
```bash
python -m venv venv
# Windows:
.\venv\Scripts\activate
# Mac/Linux:
# source venv/bin/activate
pip install -r api/requirements.txt
```
### 3. Running the Backend
To start the standard FastAPI server:
```bash
cd api
uvicorn main:app --reload
```
*(Optional)* To run the Temporal worker (requires Temporal running locally `temporal server start-dev`):
```bash
cd api
python temporal_worker.py
```
## License
MIT
<shellId: 3 completed with exit code 0>
{
"command": "npm run build; npm run lint",
"description": "Run frontend validation",
"initial_wait": 120,
"mode": "sync"
}
> [email protected] build
> tsc -b && vite build
vite v8.3.4 building client environment for production...
transforming...
✓ 2189 modules transformed.
rendering chunks...
computing gzip size...
dist/index.html 0.49 kB │ gzip: 0.32 kB
dist/assets/index-Dt_fwG0i.css 23.84 kB │ gzip: 5.28 kB
dist/assets/index-IsZ2nT4l.js 566.10 kB │ gzip: 183.13 kB
[plugin builtin:vite-reporter]
(!) Some chunks are larger than 500 kB after minification. Consider:
- Using dynamic import() to code-split the application
- Use build.rolldownOptions.output.codeSplitting to improve chunking: https://rolldown.rs/reference/OutputOptions.codeSplitting
- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.
✓ built in 8.37s
[PLUGIN_TIMINGS] JavaScript callbacks ran for 6.1s of this 8.3s build (74%).
The slowest callbacks, timed inside each callback (the wait before a callback starts is excluded, the time it awaits is included):
- plugin @tailwindcss/vite:generate:build transform (53%, 4.4s, 1 call)
- plugin vite:css-post renderChunk (17%, 1.4s, 1 call)
Additional callback time came from callbacks under 1s.
See https://rolldown.rs/reference/InputOptions.checks#bundlertimings for more details.
> [email protected] lint
> oxlint
<shellId: 2 completed with exit code 0>
{
"command": "git show --stat --oneline a4a1f43; git show --format= --name-only a4a1f43",
"description": "Inspect latest judging-gap fix"
}
a4a1f43 fix: address MVP judging gaps (TabPFN api, Temporal real integration, Fallback UI, CORS, CSS cleanup)
README.md | 4 +-
api/main.py | 57 ++++++++++++---
api/temporal_workflow.py | 52 +++++++------
src/App.css | 185 +----------------------------------------------
src/App.tsx | 103 ++++++++++++++++++--------
5 files changed, 154 insertions(+), 247 deletions(-)
README.md
api/main.py
api/temporal_workflow.py
src/App.css
src/App.tsx
<shellId: 4 completed with exit code 0>
{
"command": "$i=0; Get-Content api\\main.py | ForEach-Object { $i++; '{0}: {1}' -f $i,$_ } | Select-Object -First 280",
"description": "Review updated backend integration"
}
Output too large to read at once (9.3 KB). Saved to: C:\Users\Lenovo\AppData\Local\Temp\1791603496696-copilot-tool-output-19664-03c11a24-754a-4384-a7cb-7a1f38be5178.txt
Consider using tools like rg (for searching), head/tail (for viewing start/end), view with view_range (for specific sections), or jq (for JSON) to examine portions of the output.
Preview (first 500 chars):
1: from fastapi import FastAPI, HTTPException, Request
2: from fastapi.middleware.cors import CORSMiddleware
3: from pydantic import BaseModel, ValidationError, Field
4: from typing import List, Optional, Literal
5: import httpx
6: import json
7: import logging
8: from temporalio.client import Client
9: from temporal_workflow import EvidenceData
10: import sentry_sdk
11: import os
12: from dotenv import load_dotenv
13:
14: # Optional TabPFN import
15: try:
16: from tabpfn import TabPFNClass
<shellId: 5 completed with exit code 0>
{"command":"$i=0; Get-Content api\\temporal_workflow.py | ForEach-Object { $i++; '{0}: {1}' -f $i,$_ }; $i=0; Get-Content api\\tabpfn_experiment.py | ForEach-Object { $i++; '{0}: {1}' -f $i,$_ }","des...
1: from datetime import timedelta
2: import asyncio
3: from temporalio import activity, workflow
4: from typing import List, Dict, Any
5:
6: # Define data classes used in the workflow
7: from dataclasses import dataclass
8:
9: @dataclass
10: class EvidenceData:
11: mission_type: str
12: evidence_items: List[Dict[str, Any]]
13:
14: @dataclass
15: class ReportData:
16: summary: str
17: observations: List[Dict[str, Any]]
18: missing_evidence: List[str]
19: suggested_next_step: str
20:
21: # ---------------------------------------------------------
22: # ACTIVITIES
23: # ---------------------------------------------------------
24:
25: @activity.defn
26: async def normalize_evidence(evidence: EvidenceData) -> EvidenceData:
27: # Simulates preparing/cleaning local evidence payload for AI
28: activity.logger.info("Normalizing evidence payload...")
29: # Add dummy location normalization logic or size reduction
30: return evidence
31:
32: import json
33: import httpx
34:
35: @activity.defn
36: async def run_gemma_analysis(evidence: EvidenceData) -> ReportData:
37: activity.logger.info(f"Running REAL Gemma Analysis for {evidence.mission_type}...")
38:
39: prompt = f"Mission: {evidence.mission_type}. Analyze this field evidence:\n"
40: for e in evidence.evidence_items:
41: if e.get("kind") == "note":
42: prompt += f"- Note: {e.get('text')}\n"
43: elif e.get("kind") == "photo":
44: prompt += "- Photo: [Image metadata omitted for text-only model]\n"
45:
46: prompt += "\nOutput JSON with summary, observations (with claim, confidence), missing_evidence, and suggested_next_step."
47:
48: # This will fail (and therefore trigger Temporal's durable retries) if Ollama is not running!
49: async with httpx.AsyncClient(timeout=60.0) as client:
50: response = await client.post(
51: "http://localhost:11434/api/generate",
52: json={"model": "gemma:2b", "prompt": prompt, "stream": False, "format": "json"}
53: )
54: response.raise_for_status()
55: result = response.json()
56:
57: try:
58: data = json.loads(result["response"])
59: return ReportData(
60: summary=data.get("summary", "Fallback summary"),
61: observations=data.get("observations", []),
62: missing_evidence=data.get("missing_evidence", []),
63: suggested_next_step=data.get("suggested_next_step", "")
64: )
65: except Exception:
66: raise RuntimeError("Failed to parse Gemma output into valid JSON")
67:
68: @activity.defn
69: async def validate_report(report: ReportData) -> ReportData:
70: activity.logger.info("Validating AI JSON Schema...")
71: # Simulates Pydantic validation
72: return report
73:
74: @activity.defn
75: async def run_tabpfn_quality_prediction(report: ReportData) -> int:
76: activity.logger.info("Running TabPFN tabular prediction on expedition quality...")
77: return 95 # Dummy high quality score
78:
79: @activity.defn
80: async def persist_report(report: ReportData) -> str:
81: activity.logger.info("Persisting durable report to Database...")
82: return "Report fully processed and saved!"
83:
84: # ---------------------------------------------------------
85: # WORKFLOW
86: # ---------------------------------------------------------
87:
88: @workflow.defn
89: class ExpeditionAnalysisWorkflow:
90: @workflow.run
91: async def analyze_expedition(self, evidence: EvidenceData) -> str:
92: # Step 1: Normalize
93: normalized = await workflow.execute_activity(
94: normalize_evidence,
95: evidence,
96: start_to_close_timeout=timedelta(seconds=10),
97: )
98:
99: # Step 2: Run Gemma Analysis
100: # This activity is prone to "failure", but Temporal will automatically retry it
101: # based on the default retry policy, ensuring the walk data isn't lost.
102: raw_report = await workflow.execute_activity(
103: run_gemma_analysis,
104: normalized,
105: start_to_close_timeout=timedelta(seconds=60),
106: )
107:
108: # Step 3: Validate
109: validated_report = await workflow.execute_activity(
110: validate_report,
111: raw_report,
112: start_to_close_timeout=timedelta(seconds=10),
113: )
114:
115: # Step 4: Quality Prediction
116: quality_score = await workflow.execute_activity(
117: run_tabpfn_quality_prediction,
118: validated_report,
119: start_to_close_timeout=timedelta(seconds=30),
120: )
121:
122: # Step 5: Persist
123: result = await workflow.execute_activity(
124: persist_report,
125: validated_report,
126: start_to_close_timeout=timedelta(seconds=10),
127: )
128:
129: return f"{result} (Quality Score: {quality_score})"
1: import pandas as pd
2: import numpy as np
3: from sklearn.model_selection import train_test_split
4: from sklearn.metrics import accuracy_score
5: from tabpfn import TabPFNClassifier
6:
7: # 1. Generate Synthetic Dataset
8: # Features: num_photos, num_notes, total_note_length, duration_seconds
9: np.random.seed(42)
10: n_samples = 150
11:
12: num_photos = np.random.randint(0, 5, n_samples)
13: num_notes = np.random.randint(0, 5, n_samples)
14: total_note_length = np.random.randint(0, 200, n_samples)
15: duration_seconds = np.random.randint(60, 3600, n_samples)
16:
17: # A bit of noise
18: X = pd.DataFrame({
19: 'num_photos': num_photos,
20: 'num_notes': num_notes,
21: 'total_note_length': total_note_length,
22: 'duration_seconds': duration_seconds
23: })
24:
25: # True labels (Is it a high-quality report?)
26: # Real world: determined by human review. Here: synthetically generated based on complex underlying rules.
27: y = ((num_photos >= 1) & (total_note_length >= 20) & (duration_seconds > 300)).astype(int)
28: # Add some random noise to the labels to make it non-deterministic
29: flip_mask = np.random.rand(n_samples) < 0.1
30: y = np.where(flip_mask, 1 - y, y)
31:
32: X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
33:
34: # 2. Baseline Model
35: # Rule: evidence_count >= 3 && note_length >= 40
36: def baseline_predict(df):
37: evidence_count = df['num_photos'] + df['num_notes']
38: return ((evidence_count >= 3) & (df['total_note_length'] >= 40)).astype(int)
39:
40: baseline_preds = baseline_predict(X_test)
41: baseline_acc = accuracy_score(y_test, baseline_preds)
42:
43: # 3. TabPFN Model
44: # TabPFN is a pre-trained transformer for tabular data that needs no hyperparameter tuning for small datasets
45: classifier = TabPFNClassifier(device='cpu', N_ensemble_configurations=32)
46: classifier.fit(X_train, y_train)
47: tabpfn_preds = classifier.predict(X_test)
48: tabpfn_acc = accuracy_score(y_test, tabpfn_preds)
49:
50: print("=== WildProof TabPFN Quality Prediction Experiment ===")
51: print(f"Dataset Size: {n_samples} samples")
52: print(f"Features: {list(X.columns)}")
53: print(f"Baseline Accuracy: {baseline_acc * 100:.2f}%")
54: print(f"TabPFN Accuracy: {tabpfn_acc * 100:.2f}%")
55: print("\nLimitations: TabPFN is optimized for small tabular datasets (<10k samples) and can be memory intensive for inference in a lightweight API compared to a simple heuristic. However, it perfectly captures non-linear relationships in the field data.")
<shellId: 7 completed with exit code 0>
{"command":"$i=0; Get-Content src\\App.tsx | ForEach-Object { $i++; if($_ -match 'AI|quality|predict|API|fallback|error|report|localhost|VITE|Temporal') { '{0}: {1}' -f $i,$_ } }","description":"Revie...
5: import type { Expedition, EvidenceItem, MissionType, FieldReport } from './lib/db';
19: An offline-first open-AI companion for turning ordinary walks into trustworthy field notes.
45: await saveExpedition(newExp);
59: <p className="text-emerald-200/90 text-lg max-w-md">Uncertainty-aware field logging for the rigorous outdoor researcher.</p>
112: await saveExpedition(updated);
128: await saveExpedition(updated);
135: await saveExpedition(updated);
141: await saveExpedition(updated);
142: navigate(`/report/${expedition.id}`);
149: <h2 className="text-3xl font-extrabold capitalize text-neutral-800 tracking-tight">{expedition.missionType.replace('-', ' ')}</h2>
155: <Check className="w-5 h-5"/> Generate Report
208: function Report() {
212: const [errorMsg, setErrorMsg] = useState<string | null>(null);
213: const [quality, setQuality] = useState<{score: number, source: string} | null>(null);
215: const fetchReport = async (exp: Expedition) => {
216: setErrorMsg(null);
218: const baseUrl = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000';
219: const res = await fetch(`${baseUrl}/analyze`, {
224: if (!res.ok) throw new Error('Backend unavailable');
225: const data = await res.json();
227: // Fetch quality score
230: const qRes = await fetch(`${baseUrl}/predict-quality`, {
239: if (qRes.ok) qData = await qRes.json();
240: } catch (e) { console.warn("Quality endpoint failed", e); }
242: const updated = { ...exp, status: 'complete' as const, report: data };
243: await saveExpedition(updated);
245: setQuality({ score: qData.score, source: qData.model });
247: setErrorMsg("Local AI Offline. Viewing Mock Fallback Data.");
248: const mockReport: FieldReport = {
249: summary: "[MOCK REPORT] A brief walk yielding partial insights into the local habitat.",
251: { claim: "Found broadleaf patterns near the pathway.", confidence: "observed", supportingEvidenceIds: [] },
252: { claim: "Likely a moderately humid micro-climate.", confidence: "inferred", supportingEvidenceIds: [] },
257: setExpedition({ ...exp, report: mockReport });
265: if (!exp.report || exp.status !== 'complete') {
266: await fetchReport(exp);
275: if (!expedition || !expedition.report) return <div className="p-4 text-center mt-10">Generating report...</div>;
277: const { report } = expedition;
281: <h2 className="text-3xl font-bold mb-2">Field Report</h2>
284: {errorMsg && (
289: <p className="font-bold uppercase text-xs tracking-wider mb-1">Mock Fallback Mode</p>
290: <p className="text-sm">{errorMsg}</p>
294: onClick={() => fetchReport(expedition)}
297: Retry AI Analysis
302: {quality && !errorMsg && (
305: <p className="text-xs font-bold text-blue-500 uppercase tracking-wide">Data Quality Score</p>
306: <p className="text-sm text-blue-800">Scored via {quality.source === 'tabpfn' ? 'TabPFN Model' : 'Baseline Heuristic'}</p>
308: <div className="text-3xl font-extrabold text-blue-600">{quality.score.toFixed(0)}/100</div>
313: <p className="text-emerald-900 leading-relaxed">{report.summary}</p>
318: {report.observations.map((obs: any, i: number) => (
327: <p className="text-neutral-800 flex-1">{obs.claim}</p>
336: {report.missingEvidence.map((e: string, i: number) => <li key={i}>{e}</li>)}
341: <p className="text-sm text-neutral-600">{report.suggestedNextStep}</p>
353: WildProof turns a 20-minute walk into structured, evidence-backed field research using local AI.
379: <main className="flex-1 w-full max-w-4xl mx-auto">
384: <Route path="/report/:id" element={<Report />} />
387: </main>
<shellId: 6 completed with exit code 0>
{
"command": "$lines=Get-Content api\\main.py; $lines[1..180]",
"description": "Inspect API quality implementation"
}
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, ValidationError, Field
from typing import List, Optional, Literal
import httpx
import json
import logging
from temporalio.client import Client
from temporal_workflow import EvidenceData
import sentry_sdk
import os
from dotenv import load_dotenv
# Optional TabPFN import
try:
from tabpfn import TabPFNClassifier
import pandas as pd
import numpy as np
# Train dummy model on startup for demo purposes
np.random.seed(42)
n_samples = 150
X = pd.DataFrame({
'num_photos': np.random.randint(0, 5, n_samples),
'num_notes': np.random.randint(0, 5, n_samples),
'total_note_length': np.random.randint(0, 200, n_samples),
'duration_seconds': np.random.randint(60, 3600, n_samples)
})
y = ((X['num_photos'] >= 1) & (X['total_note_length'] >= 20) & (X['duration_seconds'] > 300)).astype(int)
tabpfn_model = TabPFNClassifier(device='cpu', N_ensemble_configurations=8)
tabpfn_model.fit(X, y)
TABPFN_AVAILABLE = True
except ImportError:
TABPFN_AVAILABLE = False
load_dotenv()
sentry_dsn = os.environ.get("SENTRY_DSN")
if sentry_dsn:
sentry_sdk.init(
dsn=sentry_dsn,
traces_sample_rate=1.0,
profiles_sample_rate=1.0,
)
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
app = FastAPI(title="WildProof API")
# Allow frontend to access the API
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:5173", "https://wildproof.onrender.com"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
OLLAMA_API_URL = "http://localhost:11434/api/generate"
MODEL_NAME = "gemma:2b" # Or "gemma", "llama3.2-vision", depending on what is installed locally
class EvidencePayload(BaseModel):
id: str
kind: str
text: Optional[str] = None
capturedAt: str
localUrl: Optional[str] = None # Base64 representation if sent
class AnalyzeRequest(BaseModel):
missionType: str
evidence: List[EvidencePayload]
class Observation(BaseModel):
claim: str
confidence: Literal["observed", "inferred", "unknown"]
supportingEvidenceIds: List[str]
class FieldReport(BaseModel):
summary: str
observations: List[Observation]
missingEvidence: List[str] = Field(alias="missing_evidence")
suggestedNextStep: str = Field(alias="suggested_next_step")
class Config:
populate_by_name = True
@app.get("/health")
def health_check():
return {"status": "ok"}
@app.get("/sentry-debug")
def trigger_error():
logger.error("Simulated performance issue / timeout for Sentry Tracing.")
raise Exception("Simulated timeout for Sentry!")
async def generate_from_ollama(prompt: str) -> str:
async with httpx.AsyncClient(timeout=60.0) as client:
try:
response = await client.post(
OLLAMA_API_URL,
json={
"model": MODEL_NAME,
"prompt": prompt,
"stream": False,
"format": "json"
}
)
response.raise_for_status()
return response.json().get("response", "")
except Exception as e:
logger.error(f"Failed to communicate with Ollama: {e}")
raise HTTPException(status_code=503, detail="Local AI model is unavailable.")
@app.post("/analyze", response_model=FieldReport)
async def analyze_expedition(request: AnalyzeRequest):
logger.info(f"Analyzing expedition: {request.missionType} with {len(request.evidence)} items.")
# Formulate prompt from evidence
text_evidence = []
for ev in request.evidence:
if ev.kind == "note" and ev.text:
text_evidence.append(f"Note ID {ev.id}: {ev.text}")
elif ev.kind == "photo":
text_evidence.append(f"Photo Evidence ID {ev.id} captured.")
evidence_str = "\n".join(text_evidence)
prompt = f"""
You are an expert field researcher analyzing data from an outdoor expedition.
Mission Type: {request.missionType}
Here is the collected evidence:
{evidence_str}
Analyze this evidence and provide a structured JSON response. Do NOT output markdown, ONLY valid JSON.
Your JSON must match this structure exactly:
{{
"summary": "A 1-2 sentence overall summary of the expedition.",
"observations": [
{{
"claim": "Specific observation here.",
"confidence": "observed" | "inferred" | "unknown",
"supportingEvidenceIds": ["id1", "id2"]
}}
],
"missing_evidence": ["What evidence is missing to make a stronger conclusion?"],
"suggested_next_step": "A single actionable next step."
}}
Rule for confidence:
- "observed": Only if directly stated in a note or directly visible in a photo (if provided).
- "inferred": A logical deduction based on the evidence.
- "unknown": Something guessed or speculative.
Ensure the "supportingEvidenceIds" match the Note IDs or Photo IDs provided above.
"""
raw_response = await generate_from_ollama(prompt)
try:
data = json.loads(raw_response)
report = FieldReport(**data)
return report
except (json.JSONDecodeError, ValidationError) as e:
logger.warning(f"First parse attempt failed: {e}. Attempting repair.")
repair_prompt = f"""
You previously returned invalid JSON. Fix the following JSON string so it matches the expected schema perfectly.
Schema:
{{
"summary": "...",
"observations": [
{{
"claim": "...",
"confidence": "observed" | "inferred" | "unknown",
"supportingEvidenceIds": ["id1"]
}}
],
"missing_evidence": ["..."],
<shellId: 8 completed with exit code 0>
{
"command": "git diff HEAD~1..HEAD -- api\\main.py src\\App.tsx README.md",
"description": "Review latest code diff"
}
Output too large to read at once (11.0 KB). Saved to: C:\Users\Lenovo\AppData\Local\Temp\1791603502888-copilot-tool-output-19664-d47e7c99-0c59-41ba-bb68-4837d59d46d3.txt
Consider using tools like rg (for searching), head/tail (for viewing start/end), view with view_range (for specific sections), or jq (for JSON) to examine portions of the output.
Preview (first 500 chars):
diff --git a/README.md b/README.md
index e759aa0..9e68c3f 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
**WildProof** is an offline-first, uncertainty-aware field observation app designed to help researchers, hikers, and citizen scientists confidently log outdoor evidence. Built for the **Hacktoberfest DEV Challenge (Week 1: Touch Grass)**.
-Unlike typical AI identification apps that confidently guess incorrect species, WildProof embraces uncertainty. It acts as an offline journal
<shellId: 9 completed with exit code 0>
{
"filePaths": [
"E:\\HACKTOBERFEST WEEK 1 CHALLENGE\\src",
"E:\\HACKTOBERFEST WEEK 1 CHALLENGE\\api"
]
}
No errors found.
{
"resourcePath": "E:\\HACKTOBERFEST WEEK 1 CHALLENGE",
"pythonPath": "E:\\HACKTOBERFEST WEEK 1 CHALLENGE\\venv\\Scripts\\python.exe"
}
A Python Environment has been configured.
Following is the information about the Python environment:
1. Environment Type: venv
2. Version: 3.11.9
3. Command Prefix to run Python in a terminal is: `"e:/HACKTOBERFEST WEEK 1 CHALLENGE/venv/Scripts/python.exe"`
Instead of running `Python sample.py` in the terminal, you will now run: `"e:/HACKTOBERFEST WEEK 1 CHALLENGE/venv/Scripts/python.exe" sample.py`
Similarly instead of running `Python -c "import sys;...."` in the terminal, you will now run: `"e:/HACKTOBERFEST WEEK 1 CHALLENGE/venv/Scripts/python.exe" -c "import sys;...."`
{"command":"& 'E:\\HACKTOBERFEST WEEK 1 CHALLENGE\\venv\\Scripts\\python.exe' -m py_compile api\\main.py api\\temporal_workflow.py api\\temporal_worker.py api\\tabpfn_experiment.py","description":"Val...
<shellId: 10 completed with exit code 0>
{
"command": "$lines=Get-Content api\\main.py; $lines[180..300]",
"description": "Inspect quality endpoint implementation"
}
"missing_evidence": ["..."],
"suggested_next_step": "..."
}}
Invalid JSON:
{raw_response}
"""
repair_response = await generate_from_ollama(repair_prompt)
try:
data = json.loads(repair_response)
report = FieldReport(**data)
return report
except Exception as e:
logger.error(f"Repair attempt failed: {e}")
raise HTTPException(status_code=500, detail="AI returned malformed data. Please try again.")
@app.post("/analyze-durable")
async def analyze_expedition_durable(request: AnalyzeRequest):
logger.info("Triggering durable analysis via Temporal...")
try:
client = await Client.connect("localhost:7233")
# Convert Pydantic request to workflow dataclass
evidence_data = EvidenceData(
mission_type=request.missionType,
evidence_items=[ev.model_dump() for ev in request.evidence]
)
# Execute workflow
result = await client.execute_workflow(
"ExpeditionAnalysisWorkflow",
evidence_data,
id=f"expedition-analysis-{request.missionType}-{len(request.evidence)}",
task_queue="wildproof-analysis-queue",
)
return {"status": "Workflow completed", "result": result}
except Exception as e:
logger.error(f"Temporal execution failed: {e}")
raise HTTPException(status_code=500, detail="Temporal workflow failed or server is not running.")
class QualityPredictionRequest(BaseModel):
num_photos: int
num_notes: int
total_note_length: int
duration_seconds: int
@app.post("/predict-quality")
def predict_quality(features: QualityPredictionRequest):
if TABPFN_AVAILABLE:
try:
df = pd.DataFrame({
'num_photos': [features.num_photos],
'num_notes': [features.num_notes],
'total_note_length': [features.total_note_length],
'duration_seconds': [features.duration_seconds]
})
probs = tabpfn_model.predict_proba(df)[0]
score = int(probs[1] * 100) # Convert probability to 0-100 score
return {"score": score, "model": "tabpfn"}
except Exception as e:
logger.error(f"TabPFN prediction failed: {e}")
# Baseline heuristic fallback
evidence_count = features.num_photos + features.num_notes
if evidence_count >= 3 and features.total_note_length >= 40:
score = 85
elif evidence_count > 0:
score = 50
else:
score = 10
return {"score": score, "model": "baseline_heuristic"}
<shellId: 11 completed with exit code 0>
{
"command": "$lines=Get-Content src\\App.tsx; $lines[208..365]",
"description": "Inspect report UI and quality wiring"
}
const { id } = useParams<{ id: string }>();
const [expedition, setExpedition] = useState<Expedition | null>(null);
const [errorMsg, setErrorMsg] = useState<string | null>(null);
const [quality, setQuality] = useState<{score: number, source: string} | null>(null);
const fetchReport = async (exp: Expedition) => {
setErrorMsg(null);
try {
const baseUrl = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000';
const res = await fetch(`${baseUrl}/analyze`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ missionType: exp.missionType, evidence: exp.evidence })
});
if (!res.ok) throw new Error('Backend unavailable');
const data = await res.json();
// Fetch quality score
let qData = { score: 85, model: 'baseline' };
try {
const qRes = await fetch(`${baseUrl}/predict-quality`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
num_photos: exp.evidence.filter(e => e.kind === 'photo').length,
num_notes: exp.evidence.filter(e => e.kind === 'note').length,
duration_seconds: 1200
})
});
if (qRes.ok) qData = await qRes.json();
} catch (e) { console.warn("Quality endpoint failed", e); }
const updated = { ...exp, status: 'complete' as const, report: data };
await saveExpedition(updated);
setExpedition(updated);
setQuality({ score: qData.score, source: qData.model });
} catch {
setErrorMsg("Local AI Offline. Viewing Mock Fallback Data.");
const mockReport: FieldReport = {
summary: "[MOCK REPORT] A brief walk yielding partial insights into the local habitat.",
observations: [
{ claim: "Found broadleaf patterns near the pathway.", confidence: "observed", supportingEvidenceIds: [] },
{ claim: "Likely a moderately humid micro-climate.", confidence: "inferred", supportingEvidenceIds: [] },
],
missingEvidence: ["Audio capturing ambient bird sounds.", "Temperature readings."],
suggestedNextStep: "Return at dusk to capture nocturnal insect patterns."
};
setExpedition({ ...exp, report: mockReport });
}
};
useEffect(() => {
if (id) {
getExpedition(id).then(async exp => {
if (exp) {
if (!exp.report || exp.status !== 'complete') {
await fetchReport(exp);
} else {
setExpedition(exp);
}
}
});
}
}, [id]);
if (!expedition || !expedition.report) return <div className="p-4 text-center mt-10">Generating report...</div>;
const { report } = expedition;
return (
<div className="p-4 max-w-2xl mx-auto mt-4">
<h2 className="text-3xl font-bold mb-2">Field Report</h2>
<p className="text-neutral-500 mb-6">{new Date(expedition.completedAt || expedition.startedAt).toLocaleString()}</p>
{errorMsg && (
<div className="bg-amber-100 border border-amber-300 text-amber-900 p-4 rounded-xl mb-6 flex items-start justify-between gap-4 shadow-sm">
<div className="flex items-start gap-2">
<Info className="w-5 h-5 flex-shrink-0 mt-0.5 text-amber-600" />
<div>
<p className="font-bold uppercase text-xs tracking-wider mb-1">Mock Fallback Mode</p>
<p className="text-sm">{errorMsg}</p>
</div>
</div>
<button
onClick={() => fetchReport(expedition)}
className="bg-amber-200 hover:bg-amber-300 text-amber-900 px-4 py-2 rounded-lg text-sm font-bold shadow-sm transition"
>
Retry AI Analysis
</button>
</div>
)}
{quality && !errorMsg && (
<div className="bg-blue-50 border border-blue-100 p-4 rounded-xl mb-6 flex items-center justify-between shadow-sm">
<div>
<p className="text-xs font-bold text-blue-500 uppercase tracking-wide">Data Quality Score</p>
<p className="text-sm text-blue-800">Scored via {quality.source === 'tabpfn' ? 'TabPFN Model' : 'Baseline Heuristic'}</p>
</div>
<div className="text-3xl font-extrabold text-blue-600">{quality.score.toFixed(0)}/100</div>
</div>
)}
<div className="bg-emerald-50 border border-emerald-100 p-6 rounded-xl mb-6">
<p className="text-emerald-900 leading-relaxed">{report.summary}</p>
</div>
<h3 className="text-xl font-semibold mb-4">Observations</h3>
<div className="space-y-3 mb-8">
{report.observations.map((obs: any, i: number) => (
<div key={i} className="flex items-start gap-3 bg-white p-4 border border-neutral-200 rounded-lg shadow-sm">
<span className={`text-xs px-2 py-1 rounded font-bold uppercase tracking-wide mt-0.5
${obs.confidence === 'observed' ? 'bg-blue-100 text-blue-700' : ''}
${obs.confidence === 'inferred' ? 'bg-amber-100 text-amber-700' : ''}
${obs.confidence === 'unknown' ? 'bg-neutral-100 text-neutral-600' : ''}
`}>
{obs.confidence}
</span>
<p className="text-neutral-800 flex-1">{obs.claim}</p>
</div>
))}
</div>
<div className="grid md:grid-cols-2 gap-4">
<div className="bg-white p-5 border border-neutral-200 rounded-xl shadow-sm">
<h4 className="font-semibold text-neutral-900 mb-2">Missing Evidence</h4>
<ul className="list-disc pl-5 text-sm text-neutral-600 space-y-1">
{report.missingEvidence.map((e: string, i: number) => <li key={i}>{e}</li>)}
</ul>
</div>
<div className="bg-white p-5 border border-neutral-200 rounded-xl shadow-sm">
<h4 className="font-semibold text-neutral-900 mb-2">Suggested Next Step</h4>
<p className="text-sm text-neutral-600">{report.suggestedNextStep}</p>
</div>
</div>
</div>
);
}
function About() {
return (
<div className="p-4 max-w-xl mx-auto mt-8 text-neutral-800">
<h2 className="text-3xl font-bold mb-4">About WildProof</h2>
<p className="text-lg leading-relaxed mb-4">
WildProof turns a 20-minute walk into structured, evidence-backed field research using local AI.
</p>
<p className="leading-relaxed">
Instead of telling you to go outside and guessing what you took a photo of, we structure your observation, identify missing data, and make explicit distinctions between what is directly observed versus what is inferred.
</p>
</div>
);
}
export default function App() {
return (
<Router>
<div className="min-h-screen flex flex-col font-sans bg-neutral-50 pb-20 md:pb-0">
<header className="bg-white border-b border-neutral-200 sticky top-0 z-10 shadow-sm">
<shellId: 13 completed with exit code 0>
{
"command": "git diff HEAD~1..HEAD -- src\\App.tsx | Select-String -Pattern '^\\+|^-' | Select-Object -First 180",
"description": "Review frontend fixes"
}
--- a/src/App.tsx
+++ b/src/App.tsx
+ const [quality, setQuality] = useState<{score: number, source: string} | null>(null);
+
+ const fetchReport = async (exp: Expedition) => {
+ setErrorMsg(null);
+ try {
+ const baseUrl = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000';
+ const res = await fetch(`${baseUrl}/analyze`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ missionType: exp.missionType, evidence: exp.evidence })
+ });
+ if (!res.ok) throw new Error('Backend unavailable');
+ const data = await res.json();
+
+ // Fetch quality score
+ let qData = { score: 85, model: 'baseline' };
+ try {
+ const qRes = await fetch(`${baseUrl}/predict-quality`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ num_photos: exp.evidence.filter(e => e.kind === 'photo').length,
+ num_notes: exp.evidence.filter(e => e.kind === 'note').length,
+ duration_seconds: 1200
+ })
+ });
+ if (qRes.ok) qData = await qRes.json();
+ } catch (e) { console.warn("Quality endpoint failed", e); }
+
+ const updated = { ...exp, status: 'complete' as const, report: data };
+ await saveExpedition(updated);
+ setExpedition(updated);
+ setQuality({ score: qData.score, source: qData.model });
+ } catch {
+ setErrorMsg("Local AI Offline. Viewing Mock Fallback Data.");
+ const mockReport: FieldReport = {
+ summary: "[MOCK REPORT] A brief walk yielding partial insights into the local habitat.",
+ observations: [
+ { claim: "Found broadleaf patterns near the pathway.", confidence: "observed", supportingEvidenceIds: [] },
+ { claim: "Likely a moderately humid micro-climate.", confidence: "inferred", supportingEvidenceIds: [] },
+ ],
+ missingEvidence: ["Audio capturing ambient bird sounds.", "Temperature readings."],
+ suggestedNextStep: "Return at dusk to capture nocturnal insect patterns."
+ };
+ setExpedition({ ...exp, report: mockReport });
+ }
+ };
- if (!exp.report) {
- try {
- const res = await fetch('http://localhost:8000/analyze', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ missionType: exp.missionType, evidence: exp.evidence })
- });
- if (!res.ok) throw new Error('Backend unavailable');
- const data = await res.json();
- const updated = { ...exp, status: 'complete' as const, report: data };
- await saveExpedition(updated);
- setExpedition(updated);
- } catch {
- setErrorMsg("AI analysis unavailable. Showing a locally generated evidence summary.");
- const mockReport: FieldReport = {
- summary: "A brief walk yielding partial insights into the local habitat (Mocked).",
- observations: [
- { claim: "Found broadleaf patterns near the pathway.", confidence: "observed",
supportingEvidenceIds: [] },
- { claim: "Likely a moderately humid micro-climate.", confidence: "inferred", supportingEvidenceIds:
[] },
- ],
- missingEvidence: ["Audio capturing ambient bird sounds.", "Temperature readings."],
- suggestedNextStep: "Return at dusk to capture nocturnal insect patterns."
- };
- const updated = { ...exp, status: 'complete' as const, report: mockReport };
- await saveExpedition(updated);
- setExpedition(updated);
- }
+ if (!exp.report || exp.status !== 'complete') {
+ await fetchReport(exp);
- <div className="bg-amber-100 border border-amber-300 text-amber-800 p-4 rounded-xl mb-6 flex items-start
gap-2">
- <Info className="w-5 h-5 flex-shrink-0 mt-0.5" />
- <p>{errorMsg}</p>
+ <div className="bg-amber-100 border border-amber-300 text-amber-900 p-4 rounded-xl mb-6 flex items-start
justify-between gap-4 shadow-sm">
+ <div className="flex items-start gap-2">
+ <Info className="w-5 h-5 flex-shrink-0 mt-0.5 text-amber-600" />
+ <div>
+ <p className="font-bold uppercase text-xs tracking-wider mb-1">Mock Fallback Mode</p>
+ <p className="text-sm">{errorMsg}</p>
+ </div>
+ </div>
+ <button
+ onClick={() => fetchReport(expedition)}
+ className="bg-amber-200 hover:bg-amber-300 text-amber-900 px-4 py-2 rounded-lg text-sm font-bold
shadow-sm transition"
+ >
+ Retry AI Analysis
+ </button>
+ </div>
+ )}
+
+ {quality && !errorMsg && (
+ <div className="bg-blue-50 border border-blue-100 p-4 rounded-xl mb-6 flex items-center justify-between
shadow-sm">
+ <div>
+ <p className="text-xs font-bold text-blue-500 uppercase tracking-wide">Data Quality Score</p>
+ <p className="text-sm text-blue-800">Scored via {quality.source === 'tabpfn' ? 'TabPFN Model' : 'Baseline
Heuristic'}</p>
+ </div>
+ <div className="text-3xl font-extrabold text-blue-600">{quality.score.toFixed(0)}/100</div>
<shellId: 12 completed with exit code 0>
{"command":"& 'E:\\HACKTOBERFEST WEEK 1 CHALLENGE\\venv\\Scripts\\python.exe' -c \"import sys; sys.path.insert(0, 'api'); import main; print('imported', main.TABPFN_AVAILABLE)\"","description":"Smoke-...
imported False
<shellId: 14 completed with exit code 0>
{
"resourcePath": "E:\\HACKTOBERFEST WEEK 1 CHALLENGE"
}
Following is the information about the Python environment:
1. Environment Type: venv
2. Version: 3.11.9
3. Command Prefix to run Python in a terminal is: `"e:/HACKTOBERFEST WEEK 1 CHALLENGE/venv/Scripts/python.exe"`
Instead of running `Python sample.py` in the terminal, you will now run: `"e:/HACKTOBERFEST WEEK 1 CHALLENGE/venv/Scripts/python.exe" sample.py`
Similarly instead of running `Python -c "import sys;...."` in the terminal, you will now run: `"e:/HACKTOBERFEST WEEK 1 CHALLENGE/venv/Scripts/python.exe" -c "import sys;...."`
4. Below is a list of the Python packages, each in the format <name> or <name> (<version>). The version may be omitted if unknown:
- annotated-doc (0.0.5)
- annotated-types (0.8.0)
- anyio (4.15.1)
- certifi (2026.7.22)
- charset-normalizer (3.5.2)
- click (8.5.0)
- cloudpickle (3.1.2)
- colorama (0.4.6)
- dnspython (2.9.0)
- email-validator (2.3.0)
- fastapi (0.111.0)
- fastapi-cli (0.0.32)
- filelock (4.1.0)
- fsspec (2026.9.0)
- h11 (0.16.0)
- httpcore (1.0.9)
- httptools (0.9.0)
- httpx (0.27.0)
- idna (3.20)
- jinja2 (3.1.6)
- joblib (1.6.0)
- markdown-it-py (4.2.0)
- markupsafe (3.0.4)
- mdurl (0.1.2)
- mpmath (1.3.0)
- networkx (3.6.1)
- nexus-rpc (1.4.0)
- numpy (1.26.4)
- orjson (3.13.0)
- pandas (2.2.2)
- pip (24.0)
- protobuf (7.36.2)
- pydantic (2.8.2)
- pydantic-core (2.20.1)
- pygments (2.21.0)
- python-dateutil (2.9.0.post0)
- python-dotenv (1.2.4)
- python-multipart (0.0.32)
- pytz (2026.5)
- pyyaml (6.0.3)
- requests (2.34.2)
- rich (15.0.0)
- rich-toolkit (0.20.6)
- scikit-learn (1.5.0)
- scipy (1.17.1)
- sentry-sdk (2.71.0)
- setuptools (84.0.0)
- shellingham (1.5.4)
- six (1.17.0)
- sniffio (1.3.1)
- starlette (0.37.2)
- sympy (1.14.0)
- tabpfn (0.1.10)
- temporalio (1.34.0)
- threadpoolctl (3.7.0)
- torch (2.14.1)
- typer (0.27.3)
- types-protobuf (7.35.1.20260906)
- typing-extensions (4.16.0)
- tzdata (2026.5)
- ujson (6.0.0)
- urllib3 (2.8.0)
- uvicorn (0.30.1)
- watchfiles (1.3.0)
- websockets (17.2)
{"command":"& 'E:\\HACKTOBERFEST WEEK 1 CHALLENGE\\venv\\Scripts\\python.exe' -c \"from tabpfn import TabPFNClassifier; print('class ok'); import pandas as pd, numpy as np; X=pd.DataFrame({'num_photos...
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "E:\HACKTOBERFEST WEEK 1 CHALLENGE\venv\Lib\site-packages\tabpfn\__init__.py", line 1, in <module>
from tabpfn.scripts.transformer_prediction_interface import TabPFNClassifier
File "E:\HACKTOBERFEST WEEK 1 CHALLENGE\venv\Lib\site-packages\tabpfn\scripts\transformer_prediction_interface.py", line 20, in <module>
from tabpfn.scripts.model_builder import load_model, load_model_only_inference
File "E:\HACKTOBERFEST WEEK 1 CHALLENGE\venv\Lib\site-packages\tabpfn\scripts\model_builder.py", line 4, in <module>
from tabpfn.transformer import TransformerModel
File "E:\HACKTOBERFEST WEEK 1 CHALLENGE\venv\Lib\site-packages\tabpfn\transformer.py", line 9, in <module>
from tabpfn.layer import TransformerEncoderLayer, _get_activation_fn
File "E:\HACKTOBERFEST WEEK 1 CHALLENGE\venv\Lib\site-packages\tabpfn\layer.py", line 5, in <module>
from torch.nn.modules.transformer import _get_activation_fn, Module, Tensor, Optional, MultiheadAttention, Linear, Dropout, LayerNorm
ImportError: cannot import name 'Optional' from 'torch.nn.modules.transformer' (E:\HACKTOBERFEST WEEK 1 CHALLENGE\venv\Lib\site-packages\torch\nn\modules\transformer.py)
<shellId: 15 completed with exit code 1>
Much better, but not submission-ready yet
I’d raise it from 6/10 to 7/10 for MVP readiness.
The latest commit, a4a1f43, addressed several important issues:
- Quality scoring is now visible in the UI.
- API URL is configurable through
VITE_API_BASE_URL. - CORS is no longer fully open.
- Mock fallback is clearly labeled.
- Retry action was added.
- README is more honest about text-only/photo-metadata analysis.
- Frontend build and lint pass.
- Python syntax checks pass.
- CSS cleanup improved the polish.
The frontend validation is healthy:
npm run build ✅
npm run lint ✅
Python compile ✅
However, I found one critical runtime failure and two major credibility gaps.
🔴 Critical: TabPFN is still unavailable at runtime
The backend imports successfully, but reports:
TABPFN_AVAILABLE = False
Direct testing shows the real cause:
ImportError:
cannot import name 'Optional' from 'torch.nn.modules.transformer'
The current try/except ImportError hides this incompatibility and silently falls back to the heuristic. Therefore, despite the README saying TabPFN is fully wired, the live API currently returns:
{
"model": "baseline_heuristic"
}
This means the current TabPFN prize claim is not yet real.
What to do
Do one of these before submission:
Option A — Recommended for the deadline
Make the fallback honest:
- Return
tabpfn_unavailablewith the dependency error category. - Display
Baseline Heuristicin the UI. - Keep the TabPFN experiment as a reproducible, separately documented experiment.
- Do not claim live TabPFN integration.
Option B — Try to repair the environment
Pin compatible versions of tabpfn and torch, then verify:
import tabpfn
TabPFNClassifier(...)
fit(...)
predict_proba(...)
Do not spend more than 60–90 minutes on this. The current old tabpfn==0.1.10 dependency is incompatible with the installed Torch version.
Also, the backend currently catches only ImportError around the entire startup model setup. If model initialization fails for another reason, the server could crash instead of falling back cleanly.
🟠 Temporal is improved, but still not fully product-integrated
The Temporal workflow now calls real Ollama/Gemma instead of returning the previous hardcoded report. That is a meaningful improvement.
But these parts remain simulated:
async def validate_report(report):
return report
async def run_tabpfn_quality_prediction(report):
return 95
async def persist_report(report):
return "Report fully processed and saved!"
Also, the frontend still calls:
/analyze
not:
/analyze-durable
So the primary product flow does not actually use Temporal. Temporal is currently a separate backend endpoint/demo path.
Current honest category position
You can say:
“The project includes a Temporal-backed durable analysis path with real Gemma execution and retry behavior.”
You should not yet say:
“The main app analysis pipeline is fully powered by Temporal.”
Minimum improvement
Wire the frontend to /analyze-durable behind a setting or button:
Generate Report
→ Temporal workflow
→ Gemma analysis
→ validation
→ quality score
→ result
Even if persistence remains a documented prototype, judges must be able to trigger the durable path.
🟡 Quality-score fallback is still misleading
In App.tsx, the frontend starts with:
let qData = { score: 85, model: 'baseline' };
If /predict-quality fails, the UI still displays a score of 85, which looks like a real result.
That violates the otherwise improved transparency of the fallback UI.
Fix
Use:
let qData: { score: number; model: string } | null = null;
Only render the quality card when the endpoint actually succeeds.
If it fails, show:
Quality scoring unavailable
Do not manufacture an 85 score.
🟡 Fallback report is visible but not persisted
The fallback path does:
setExpedition({ ...exp, report: mockReport });
but does not call saveExpedition.
So after refreshing the report page, the app may attempt the AI request again rather than preserving the fallback state.
That is not disastrous, but it undermines the offline/reliable workflow.
Either:
- Persist the fallback report with
status: 'failed', or - Keep it in memory but clearly treat it as a temporary preview.
I recommend:
status: "failed"
report: mockReport
and a visible “Retry AI Analysis” action.
Current phase status
| Phase | Status now | Assessment |
|---|---|---|
| 1. Foundation | ✅ | Complete |
| 2. Evidence capture | ⚠️ | Photos and notes only |
| 3. Mock report flow | ✅ | Clearly labeled now |
| 4. Gemma | ✅/⚠️ | Real text analysis; no image pixels |
| 5. TabPFN | 🔴 | Fallback active due dependency incompatibility |
| 6. Temporal | ⚠️ | Real Gemma path, but not main UI flow and quality/persistence mocked |
| 7. Sentry | ⚠️ | Instrumentation exists; trace evidence still needed |
| 8. UI polish/deployment | ⚠️ | UI improved; deployment not verified |
Prize-category recommendation right now
Safe to enter
- Overall winner
- Best Use of Gemma
- Best Use of Sentry Agent Tracing, once you capture real trace screenshots
- Best Use of Render, once the deployed URL is verified
- Best Use of Entire, if you link the actual build session
Do not claim strongly yet
- Best Use of TabPFN — currently not live
- Best Use of Temporal — currently demonstrable but not the main app flow
The current strongest honest pitch is:
WildProof is an offline-first field evidence app using local Gemma to transform notes and photographic metadata into uncertainty-aware reports, with an experimental Temporal workflow for durable analysis and a measurable quality-scoring path.
That is credible. The stronger version:
WildProof uses Gemma + live TabPFN + Temporal in the main user flow.
is not yet true.
Three fixes I would do next
1. Fix or disclose TabPFN immediately
This is the top priority because the current backend silently falls back.
2. Remove the fake frontend score
Never display 85 when the quality request failed.
3. Add a “Durable Analysis” switch or use Temporal by default
A visible badge would make the category much easier to defend:
Analysis mode: Temporal durable workflow
Final verdict
The project is now a credible polished prototype, and the previous review issues were partially addressed well.
The biggest remaining problem is not visual quality. It is runtime truthfulness:
- TabPFN is installed but unusable in the current environment.
- Temporal exists but is not the default frontend path.
- Some Temporal stages are still hardcoded.
- The quality UI can display a fabricated fallback score.
Fix those three areas, and I’d rate it around 8/10 for a strong submission.
Community Wisdom: EcoID: An Offline Plant Identifier That Gets You Outside
The current direction is stronger than another plant-identification app because WildProof emphasizes evidence and uncertainty. That advantage is now visible in the UI and README, but the backend must preserve the same honesty for TabPFN and Temporal claims.
Community Wisdom: I Taught Local AI to Help Me Notice the World
The neighboring project validates the emotional premise but makes technical differentiation important. WildProof’s differentiators should be the evidence-linked report, explicit fallback state, measurable quality score, and demonstrable durable workflow—not just “local AI plus nature.”
How about now?
I’ll run a focused regression check on the latest commit: TabPFN availability, quality-score fallback behavior, Temporal wiring, and build/lint status.
{
"command": "git status --short; git log --oneline -6",
"description": "Inspect latest commit state"
}
8ac6eaa fix: address final MVP judging gaps (TabPFN honesty, App fallback persistence, Temporal switch)
a4a1f43 fix: address MVP judging gaps (TabPFN api, Temporal real integration, Fallback UI, CORS, CSS cleanup)
f51f1a9 chore: rename FieldProof to WildProof across entire stack
98ae635 docs: update README with correct working directory for backend
ae889c1 feat: Phase 8 UI Polish and README
fab4ab1 chore: ensure venv is ignored
<shellId: 16 completed with exit code 0>
{"command":"$i=0; Get-Content api\\main.py | ForEach-Object { $i++; if($_ -match 'TABPFN|predict_quality|tabpfn|fallback|CORS|allow_origins|analyze-durable|validate_report|persist') { '{0}: {1}' -f $i...
2: from fastapi.middleware.cors import CORSMiddleware
31: CORSMiddleware,
32: allow_origins=["http://localhost:5173", "https://wildproof.onrender.com"],
175: @app.post("/analyze-durable")
206: def predict_quality(features: QualityPredictionRequest):
207: # Live TabPFN integration requires older torch versions and heavy memory,
209: # (See api/tabpfn_experiment.py for the reproducible TabPFN experiment!)
218: return {"score": score, "model": "baseline_heuristic (tabpfn_unavailable)"}
<shellId: 17 completed with exit code 0>
{"command":"$i=0; Get-Content src\\App.tsx | ForEach-Object { $i++; if($_ -match 'qData|quality|analyze-durable|VITE_API|fallback|Mock|Temporal|saveExpedition') { '{0}: {1}' -f $i,$_ } }","description...
4: import { getExpedition, saveExpedition } from './lib/db';
45: await saveExpedition(newExp);
112: await saveExpedition(updated);
128: await saveExpedition(updated);
135: await saveExpedition(updated);
141: await saveExpedition(updated);
213: const [quality, setQuality] = useState<{score: number, source: string} | null>(null);
214: const [useTemporal, setUseTemporal] = useState<boolean>(true);
219: const baseUrl = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000';
220: const endpoint = useTemporal ? '/analyze-durable' : '/analyze';
230: if (useTemporal && data.result) {
231: data = data.result; // Unwrap Temporal result
234: // Fetch quality score
235: let qData = null;
237: const qRes = await fetch(`${baseUrl}/predict-quality`, {
247: if (qRes.ok) qData = await qRes.json();
248: } catch (e) { console.warn("Quality endpoint failed", e); }
251: await saveExpedition(updated);
253: if (qData) {
254: setQuality({ score: qData.score, source: qData.model });
257: setErrorMsg(`Local AI or Temporal Offline. Viewing Mock Fallback Data.`);
258: const mockReport: FieldReport = {
259: summary: "[MOCK REPORT] A brief walk yielding partial insights into the local habitat.",
267: // Persist fallback with failed status so it doesn't infinitely loop
268: const updated = { ...exp, status: 'failed' as const, report: mockReport };
269: await saveExpedition(updated);
278: // If no report, or if it failed previously, fetch it unless we already have the fallback loaded
299: <span className="text-sm font-bold text-neutral-500">Temporal Workflow</span>
301: onClick={() => setUseTemporal(!useTemporal)}
302: className={`w-12 h-6 rounded-full transition-colors relative flex items-center ${useTemporal ? 'bg-emerald-500' : 'bg-neutral-300'}`}
304: <div className={`w-4 h-4 bg-white rounded-full absolute shadow transition-transform ${useTemporal ? 'translate-x-7' : 'translate-x-1'}`} />
314: <p className="font-bold uppercase text-xs tracking-wider mb-1">Mock Fallback Mode</p>
327: {quality && !errorMsg && (
330: <p className="text-xs font-bold text-blue-500 uppercase tracking-wide">Data Quality Score</p>
331: <p className="text-sm text-blue-800">Scored via {quality.source === 'tabpfn' ? 'TabPFN Model' : 'Baseline Heuristic'}</p>
333: <div className="text-3xl font-extrabold text-blue-600">{quality.score.toFixed(0)}/100</div>
<shellId: 18 completed with exit code 0>
{
"command": "npm run build; npm run lint",
"description": "Run frontend validation",
"initial_wait": 120,
"mode": "sync"
}
> [email protected] build
> tsc -b && vite build
vite v8.3.4 building client environment for production...
transforming...
✓ 2189 modules transformed.
rendering chunks...
computing gzip size...
dist/index.html 0.49 kB │ gzip: 0.32 kB
dist/assets/index-DZffQ7u8.css 25.02 kB │ gzip: 5.34 kB
dist/assets/index-BlPAb4MV.js 566.86 kB │ gzip: 183.32 kB
[plugin builtin:vite-reporter]
(!) Some chunks are larger than 500 kB after minification. Consider:
- Using dynamic import() to code-split the application
- Use build.rolldownOptions.output.codeSplitting to improve chunking: https://rolldown.rs/reference/OutputOptions.codeSplitting
- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.
✓ built in 6.71s
[PLUGIN_TIMINGS] JavaScript callbacks ran for 6.4s of this 6.7s build (96%).
The slowest callbacks, timed inside each callback (the wait before a callback starts is excluded, the time it awaits is included):
- plugin @tailwindcss/vite:generate:build transform (81%, 5.4s, 1 call)
Additional callback time came from callbacks under 1s.
See https://rolldown.rs/reference/InputOptions.checks#bundlertimings for more details.
> [email protected] lint
> oxlint
src/App.tsx:280:19: warning react-hooks(exhaustive-deps): React Hook useEffect has a missing dependency: 'fetchReport' help: Either include it or remove the dependency array.
<shellId: 19 completed with exit code 0>
{
"filePaths": [
"E:\\HACKTOBERFEST WEEK 1 CHALLENGE\\src",
"E:\\HACKTOBERFEST WEEK 1 CHALLENGE\\api"
]
}
No errors found.
{
"command": "$lines=Get-Content api\\main.py; $lines[195..225]",
"description": "Inspect honest quality fallback"
}
logger.error(f"Temporal execution failed: {e}")
raise HTTPException(status_code=500, detail="Temporal workflow failed or server is not running.")
class QualityPredictionRequest(BaseModel):
num_photos: int
num_notes: int
total_note_length: int
duration_seconds: int
@app.post("/predict-quality")
def predict_quality(features: QualityPredictionRequest):
# Live TabPFN integration requires older torch versions and heavy memory,
# so the API gracefully falls back to a baseline heuristic.
# (See api/tabpfn_experiment.py for the reproducible TabPFN experiment!)
evidence_count = features.num_photos + features.num_notes
if evidence_count >= 3 and features.total_note_length >= 40:
score = 85
elif evidence_count > 0:
score = 50
else:
score = 10
return {"score": score, "model": "baseline_heuristic (tabpfn_unavailable)"}
<shellId: 20 completed with exit code 0>
{
"command": "$lines=Get-Content src\\App.tsx; $lines[208..285]",
"description": "Inspect Temporal and fallback flow"
}
const { id } = useParams<{ id: string }>();
const [expedition, setExpedition] = useState<Expedition | null>(null);
const [errorMsg, setErrorMsg] = useState<string | null>(null);
const [quality, setQuality] = useState<{score: number, source: string} | null>(null);
const [useTemporal, setUseTemporal] = useState<boolean>(true);
const fetchReport = async (exp: Expedition) => {
setErrorMsg(null);
try {
const baseUrl = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000';
const endpoint = useTemporal ? '/analyze-durable' : '/analyze';
const res = await fetch(`${baseUrl}${endpoint}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ missionType: exp.missionType, evidence: exp.evidence })
});
if (!res.ok) throw new Error('Backend unavailable');
let data = await res.json();
if (useTemporal && data.result) {
data = data.result; // Unwrap Temporal result
}
// Fetch quality score
let qData = null;
try {
const qRes = await fetch(`${baseUrl}/predict-quality`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
num_photos: exp.evidence.filter(e => e.kind === 'photo').length,
num_notes: exp.evidence.filter(e => e.kind === 'note').length,
duration_seconds: 1200,
total_note_length: exp.evidence.filter(e => e.kind === 'note').reduce((acc, e) => acc + (e.text?.length || 0), 0)
})
});
if (qRes.ok) qData = await qRes.json();
} catch (e) { console.warn("Quality endpoint failed", e); }
const updated = { ...exp, status: 'complete' as const, report: data };
await saveExpedition(updated);
setExpedition(updated);
if (qData) {
setQuality({ score: qData.score, source: qData.model });
}
} catch {
setErrorMsg(`Local AI or Temporal Offline. Viewing Mock Fallback Data.`);
const mockReport: FieldReport = {
summary: "[MOCK REPORT] A brief walk yielding partial insights into the local habitat.",
observations: [
{ claim: "Found broadleaf patterns near the pathway.", confidence: "observed", supportingEvidenceIds: [] },
{ claim: "Likely a moderately humid micro-climate.", confidence: "inferred", supportingEvidenceIds: [] },
],
missingEvidence: ["Audio capturing ambient bird sounds.", "Temperature readings."],
suggestedNextStep: "Return at dusk to capture nocturnal insect patterns."
};
// Persist fallback with failed status so it doesn't infinitely loop
const updated = { ...exp, status: 'failed' as const, report: mockReport };
await saveExpedition(updated);
setExpedition(updated);
}
};
useEffect(() => {
if (id) {
getExpedition(id).then(async exp => {
if (exp) {
// If no report, or if it failed previously, fetch it unless we already have the fallback loaded
if (!exp.report || (exp.status !== 'complete' && exp.status !== 'failed')) {
await fetchReport(exp);
} else {
setExpedition(exp);
}
}
});
}
<shellId: 21 completed with exit code 0>
{
"command": "$lines=Get-Content api\\temporal_workflow.py; $lines[30..135]",
"description": "Inspect workflow realism"
}
import json
import httpx
@activity.defn
async def run_gemma_analysis(evidence: EvidenceData) -> ReportData:
activity.logger.info(f"Running REAL Gemma Analysis for {evidence.mission_type}...")
prompt = f"Mission: {evidence.mission_type}. Analyze this field evidence:\n"
for e in evidence.evidence_items:
if e.get("kind") == "note":
prompt += f"- Note: {e.get('text')}\n"
elif e.get("kind") == "photo":
prompt += "- Photo: [Image metadata omitted for text-only model]\n"
prompt += "\nOutput JSON with summary, observations (with claim, confidence), missing_evidence, and suggested_next_step."
# This will fail (and therefore trigger Temporal's durable retries) if Ollama is not running!
async with httpx.AsyncClient(timeout=60.0) as client:
response = await client.post(
"http://localhost:11434/api/generate",
json={"model": "gemma:2b", "prompt": prompt, "stream": False, "format": "json"}
)
response.raise_for_status()
result = response.json()
try:
data = json.loads(result["response"])
return ReportData(
summary=data.get("summary", "Fallback summary"),
observations=data.get("observations", []),
missing_evidence=data.get("missing_evidence", []),
suggested_next_step=data.get("suggested_next_step", "")
)
except Exception:
raise RuntimeError("Failed to parse Gemma output into valid JSON")
@activity.defn
async def validate_report(report: ReportData) -> ReportData:
activity.logger.info("Validating AI JSON Schema...")
# Simulates Pydantic validation
return report
@activity.defn
async def run_tabpfn_quality_prediction(report: ReportData) -> int:
activity.logger.info("Running TabPFN tabular prediction on expedition quality...")
async with httpx.AsyncClient(timeout=10.0) as client:
try:
# We call our own API to use the exact same TabPFN/fallback logic
response = await client.post(
"http://localhost:8000/predict-quality",
json={"num_photos": 1, "num_notes": len(report.observations), "total_note_length": len(report.summary), "duration_seconds": 600}
)
return response.json().get("score", 85)
except Exception:
return 85
@activity.defn
async def persist_report(report: ReportData) -> ReportData:
activity.logger.info("Persisting durable report to Database...")
return report
# ---------------------------------------------------------
# WORKFLOW
# ---------------------------------------------------------
@workflow.defn
class ExpeditionAnalysisWorkflow:
@workflow.run
async def analyze_expedition(self, evidence: EvidenceData) -> Dict[str, Any]:
# Step 1: Normalize
normalized = await workflow.execute_activity(
normalize_evidence,
evidence,
start_to_close_timeout=timedelta(seconds=10),
)
# Step 2: Run Gemma Analysis
raw_report = await workflow.execute_activity(
run_gemma_analysis,
normalized,
start_to_close_timeout=timedelta(seconds=60),
)
# Step 3: Validate
validated_report = await workflow.execute_activity(
validate_report,
raw_report,
start_to_close_timeout=timedelta(seconds=10),
)
# Step 4: Quality Prediction
quality_score = await workflow.execute_activity(
run_tabpfn_quality_prediction,
validated_report,
start_to_close_timeout=timedelta(seconds=30),
)
# Step 5: Persist
final_report = await workflow.execute_activity(
persist_report,
validated_report,
start_to_close_timeout=timedelta(seconds=10),
)
# Return as a dictionary that matches the frontend FieldReport schema
<shellId: 23 completed with exit code 0>
{"command":"& 'E:\\HACKTOBERFEST WEEK 1 CHALLENGE\\venv\\Scripts\\python.exe' -c \"import sys; sys.path.insert(0, 'api'); import main; print('TABPFN_AVAILABLE=', main.TABPFN_AVAILABLE); print(main.pre...
Traceback (most recent call last):
File "<string>", line 1, in <module>
AttributeError: module 'main' has no attribute 'TABPFN_AVAILABLE'
Sentry is attempting to send 2 pending events
Waiting up to 2 seconds
Press Ctrl-Break to quit
<shellId: 22 completed with exit code 1>
{
"command": "$lines=Get-Content api\\temporal_workflow.py; $lines[120..170]",
"description": "Inspect final workflow return"
}
# Step 4: Quality Prediction
quality_score = await workflow.execute_activity(
run_tabpfn_quality_prediction,
validated_report,
start_to_close_timeout=timedelta(seconds=30),
)
# Step 5: Persist
final_report = await workflow.execute_activity(
persist_report,
validated_report,
start_to_close_timeout=timedelta(seconds=10),
)
# Return as a dictionary that matches the frontend FieldReport schema
return {
"summary": final_report.summary,
"observations": final_report.observations,
"missing_evidence": final_report.missing_evidence,
"suggested_next_step": final_report.suggested_next_step,
"quality_score": quality_score
}
<shellId: 24 completed with exit code 0>
Better, but there is now one new blocking integration bug
I’d rate it 7.5/10 as a prototype, but not ready for a live judge demo yet.
The latest commit, 8ac6eaa, successfully addressed the three previous issues:
- TabPFN fallback is now honest instead of pretending to be live.
- Mock fallback reports are persisted with
failedstatus. - Temporal is now enabled by default from the frontend.
- Quality scores are no longer fabricated when the endpoint fails.
- Frontend build passes.
- Lint passes with one warning.
- Workspace diagnostics report no errors.
What is now good
✅ Fallback behavior is much more trustworthy
The API now explicitly returns:
{
"model": "baseline_heuristic (tabpfn_unavailable)"
}
That is the right decision given the current dependency incompatibility.
The frontend also avoids displaying a fake score when the request fails, and fallback reports are persisted instead of disappearing after refresh.
✅ Temporal is now the default path
The frontend defaults to:
const [useTemporal, setUseTemporal] = useState<boolean>(true);
and sends reports through:
/analyze-durable
That makes the Temporal category claim much more defensible than before.
✅ The implementation is now honest about TabPFN
This is a positive change even though the model is unavailable. You can say:
“WildProof includes a reproducible TabPFN experiment and a production-safe baseline fallback when the local TabPFN runtime is incompatible.”
You should not say that the deployed app uses live TabPFN inference.
🔴 Blocking issue: backend field names do not match the frontend
The backend returns snake_case fields:
{
"missing_evidence": [],
"suggested_next_step": "..."
}
But the frontend renders camelCase fields:
report.missingEvidence
report.suggestedNextStep
The same issue exists in the Temporal result.
The backend’s Pydantic aliases are configured in the input model, but the frontend still receives or unwraps snake_case data. This means a real report can reach the UI with:
report.missingEvidence === undefined
report.suggestedNextStep === undefined
and the report screen can crash when it executes:
report.missingEvidence.map(...)
Fix this before demoing
Choose one canonical wire format.
The fastest fix is to normalize the response in the frontend:
const normalizedReport: FieldReport = {
summary: data.summary,
observations: data.observations,
missingEvidence: data.missingEvidence ?? data.missing_evidence ?? [],
suggestedNextStep:
data.suggestedNextStep ?? data.suggested_next_step ?? '',
qualityScore: data.qualityScore ?? data.quality_score,
};
Apply it after both /analyze and /analyze-durable responses.
Better long-term: configure the backend to serialize aliases as camelCase, but the frontend normalization is safer for the deadline.
🟠 Temporal quality scoring is still not fully honest
The workflow now calls the quality endpoint, but it sends synthetic values:
{
"num_photos": 1,
"num_notes": len(report.observations),
"total_note_length": len(report.summary),
"duration_seconds": 600
}
It should use the actual EvidenceData, not the generated report.
Otherwise:
- Photo count is always
1. - Duration is always
600. - Note count is based on observations, not user notes.
- The score does not represent the actual expedition.
Correct direction
Change the activity to receive both the original evidence and report:
@dataclass
class QualityInput:
evidence: EvidenceData
report: ReportData
Then calculate:
- Actual photo count.
- Actual note count.
- Actual note length.
- Actual duration.
- Audio count if added later.
This is important for the TabPFN/baseline story even if live TabPFN remains unavailable.
🟡 Temporal fallback still returns a fabricated score on failure
In temporal_workflow.py, the quality activity still does:
return response.json().get("score", 85)
and on exception:
return 85
That reintroduces the exact fabricated-score problem you just removed from the frontend.
Replace it with an explicit result:
return {
"score": None,
"model": "unavailable",
"reason": "quality endpoint unavailable",
}
Or let the activity fail so Temporal retries it and ultimately reports a real failure.
🟡 The backend smoke test exposed a stale assumption
My smoke test expected main.TABPFN_AVAILABLE, but that symbol has now been removed. That is not itself a product bug; it confirms the fallback refactor changed the backend contract.
Add a simple health response such as:
{
"status": "ok",
"quality_model": "baseline_heuristic",
"tabpfn": "unavailable"
}
That makes deployment verification and article screenshots much easier.
Current phase status
| Phase | Status | Assessment |
|---|---|---|
| 1. Foundation | ✅ | Complete |
| 2. Evidence capture | ⚠️ | Photos and notes only |
| 3. Mock report flow | ✅ | Persisted and labeled |
| 4. Gemma | ✅/⚠️ | Real text analysis, no image pixels |
| 5. TabPFN | ✅ Honest fallback | Live model unavailable |
| 6. Temporal | ✅/⚠️ | Main path wired, quality inputs still synthetic |
| 7. Sentry | ⚠️ | Instrumentation present; evidence screenshots still needed |
| 8. UI polish/deployment | ⚠️ | Build passes; production behavior not verified |
Prize-category recommendation now
Strong and honest
- Overall winner
- Best Use of Gemma
- Best Use of Sentry Agent Tracing
- Best Use of Render, after verifying deployment
- Best Use of Entire, if the actual agent session is linked
Defensible with careful wording
-
Best Use of Temporal
- The workflow is real and now used by the main flow.
- Fix the quality activity fallback and use real evidence metrics first.
Do not enter as live integration
-
Best Use of TabPFN
- Enter only if the rules allow a documented experiment/prototype.
- Otherwise list it as an experimental category, not a core live feature.
Immediate final checklist
Before the next demo:
- Normalize snake_case/camelCase report fields.
- Run a real end-to-end report with Ollama and Temporal.
- Confirm the report page does not crash.
- Use actual evidence metrics in the Temporal quality step.
- Remove the
85fallback from the Temporal activity. - Fix the React exhaustive-deps warning.
- Capture:
- Temporal workflow success.
- Temporal retry.
- Gemma report.
- Baseline quality score.
- Sentry trace.
- Verify the deployed URL rather than relying only on local build output.
Bottom line
The project is now substantially more credible than the previous version. The major strategic decision—being honest about TabPFN unavailability—was correct.
The next step is not adding another feature. It is fixing the report response-shape mismatch, because that can break the real Temporal/Gemma demo even though the build is green. After that, WildProof is close to a strong, defensible submission.
Community Wisdom: I Taught Local AI to Help Me Notice the World
The current implementation now differentiates itself better from generic outdoor AI projects through explicit fallback states and uncertainty-aware reports. The remaining work should focus on making the live technical path as reliable as the product story.
Community Wisdom: EcoID: An Offline Plant Identifier That Gets You Outside
Since offline nature AI is already a visible category, the strongest advantage remains trustworthy evidence handling. The response-shape bug and synthetic quality metrics are precisely the kind of details to fix so that the implementation matches that promise end to end.
How about now?
I’ll verify the latest state specifically for the last blockers: response-shape normalization, Temporal quality fallback, actual evidence metrics, and end-to-end build/lint.
{
"command": "git status --short; git log --oneline -6",
"description": "Inspect latest changes"
}
0590c66 fix: normalize schema, fix Temporal quality input, update health check
8ac6eaa fix: address final MVP judging gaps (TabPFN honesty, App fallback persistence, Temporal switch)
a4a1f43 fix: address MVP judging gaps (TabPFN api, Temporal real integration, Fallback UI, CORS, CSS cleanup)
f51f1a9 chore: rename FieldProof to WildProof across entire stack
98ae635 docs: update README with correct working directory for backend
ae889c1 feat: Phase 8 UI Polish and README
<shellId: 25 completed with exit code 0>
{"command":"$i=0; Get-Content src\\App.tsx | ForEach-Object { $i++; if($_ -match 'normalize|missing_evidence|missingEvidence|suggested_next|suggestedNext|analyze-durable|quality|useEffect|fetchReport'...
1: import { useState, useEffect, useCallback } from 'react';
90: useEffect(() => {
213: const [quality, setQuality] = useState<{score: number, source: string} | null>(null);
216: const fetchReport = useCallback(async (exp: Expedition) => {
220: const endpoint = useTemporal ? '/analyze-durable' : '/analyze';
234: const normalizedReport: FieldReport = {
237: missingEvidence: data.missingEvidence ?? data.missing_evidence ?? [],
238: suggestedNextStep: data.suggestedNextStep ?? data.suggested_next_step ?? ''
241: // Fetch quality score
244: const qRes = await fetch(`${baseUrl}/predict-quality`, {
255: } catch (e) { console.warn("Quality endpoint failed", e); }
257: const updated = { ...exp, status: 'complete' as const, report: normalizedReport };
261: setQuality({ score: qData.score, source: qData.model });
271: missingEvidence: ["Audio capturing ambient bird sounds.", "Temperature readings."],
272: suggestedNextStep: "Return at dusk to capture nocturnal insect patterns."
281: useEffect(() => {
287: await fetchReport(exp);
294: }, [id, fetchReport]);
326: onClick={() => fetchReport(expedition)}
334: {quality && !errorMsg && (
337: <p className="text-xs font-bold text-blue-500 uppercase tracking-wide">Data Quality Score</p>
338: <p className="text-sm text-blue-800">Scored via {quality.source === 'tabpfn' ? 'TabPFN Model' : 'Baseline Heuristic'}</p>
340: <div className="text-3xl font-extrabold text-blue-600">{quality.score.toFixed(0)}/100</div>
368: {report.missingEvidence.map((e: string, i: number) => <li key={i}>{e}</li>)}
373: <p className="text-sm text-neutral-600">{report.suggestedNextStep}</p>
<shellId: 26 completed with exit code 0>
{"command":"$i=0; Get-Content api\\temporal_workflow.py | ForEach-Object { $i++; if($_ -match 'Quality|quality|num_photos|num_notes|duration|return 85|EvidenceData|ReportData') { '{0}: {1}' -f $i,$_ }...
10: class EvidenceData:
15: class ReportData:
22: class QualityInput:
23: evidence: EvidenceData
24: report: ReportData
31: async def normalize_evidence(evidence: EvidenceData) -> EvidenceData:
41: async def run_gemma_analysis(evidence: EvidenceData) -> ReportData:
64: return ReportData(
74: async def validate_report(report: ReportData) -> ReportData:
80: async def run_tabpfn_quality_prediction(inputs: QualityInput) -> Dict[str, Any]:
81: activity.logger.info("Running TabPFN tabular prediction on expedition quality...")
83: num_photos = sum(1 for e in inputs.evidence.evidence_items if e.get("kind") == "photo")
84: num_notes = sum(1 for e in inputs.evidence.evidence_items if e.get("kind") == "note")
91: "http://localhost:8000/predict-quality",
93: "num_photos": num_photos,
94: "num_notes": num_notes,
96: "duration_seconds": 600
103: activity.logger.error(f"Quality endpoint failed: {e}")
105: return {"score": None, "model": "unavailable", "reason": "quality endpoint unavailable"}
108: async def persist_report(report: ReportData) -> ReportData:
119: async def analyze_expedition(self, evidence: EvidenceData) -> Dict[str, Any]:
141: # Step 4: Quality Prediction
142: quality_score_data = await workflow.execute_activity(
143: run_tabpfn_quality_prediction,
144: QualityInput(evidence=normalized, report=validated_report),
161: "quality_score": quality_score_data.get("score") if quality_score_data else None
<shellId: 27 completed with exit code 0>
{"command":"$i=0; Get-Content api\\main.py | ForEach-Object { $i++; if($_ -match 'health|quality_model|tabpfn|missing_evidence|suggested_next|model') { '{0}: {1}' -f $i,$_ } }","description":"Check ba...
3: from pydantic import BaseModel, ValidationError, Field
39: MODEL_NAME = "gemma:2b" # Or "gemma", "llama3.2-vision", depending on what is installed locally
41: class EvidencePayload(BaseModel):
48: class AnalyzeRequest(BaseModel):
52: class Observation(BaseModel):
57: class FieldReport(BaseModel):
60: missingEvidence: List[str] = Field(alias="missing_evidence")
61: suggestedNextStep: str = Field(alias="suggested_next_step")
66: @app.get("/health")
67: def health_check():
70: "quality_model": "baseline_heuristic",
71: "tabpfn": "unavailable"
85: "model": MODEL_NAME,
95: raise HTTPException(status_code=503, detail="Local AI model is unavailable.")
97: @app.post("/analyze", response_model=FieldReport)
129: "missing_evidence": ["What evidence is missing to make a stronger conclusion?"],
130: "suggested_next_step": "A single actionable next step."
162: "missing_evidence": ["..."],
163: "suggested_next_step": "..."
188: evidence_items=[ev.model_dump() for ev in request.evidence]
203: class QualityPredictionRequest(BaseModel):
211: # Live TabPFN integration requires older torch versions and heavy memory,
213: # (See api/tabpfn_experiment.py for the reproducible TabPFN experiment!)
222: return {"score": score, "model": "baseline_heuristic (tabpfn_unavailable)"}
<shellId: 28 completed with exit code 0>
{
"command": "npm run build; npm run lint",
"description": "Run current frontend validation",
"initial_wait": 120,
"mode": "sync"
}
> [email protected] build
> tsc -b && vite build
vite v8.3.4 building client environment for production...
transforming...
✓ 2189 modules transformed.
rendering chunks...
computing gzip size...
dist/index.html 0.49 kB │ gzip: 0.32 kB
dist/assets/index-DZffQ7u8.css 25.02 kB │ gzip: 5.34 kB
dist/assets/index-BPhIAsN9.js 567.06 kB │ gzip: 183.37 kB
[plugin builtin:vite-reporter]
(!) Some chunks are larger than 500 kB after minification. Consider:
- Using dynamic import() to code-split the application
- Use build.rolldownOptions.output.codeSplitting to improve chunking: https://rolldown.rs/reference/OutputOptions.codeSplitting
- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.
✓ built in 753ms
> [email protected] lint
> oxlint
<shellId: 29 completed with exit code 0>
{
"filePaths": [
"E:\\HACKTOBERFEST WEEK 1 CHALLENGE\\src",
"E:\\HACKTOBERFEST WEEK 1 CHALLENGE\\api"
]
}
No errors found.
{
"command": "$lines=Get-Content api\\temporal_workflow.py; $lines[75..112]",
"description": "Verify Temporal metric calculation"
}
# Simulates Pydantic validation
return report
@activity.defn
async def run_tabpfn_quality_prediction(inputs: QualityInput) -> Dict[str, Any]:
activity.logger.info("Running TabPFN tabular prediction on expedition quality...")
num_photos = sum(1 for e in inputs.evidence.evidence_items if e.get("kind") == "photo")
num_notes = sum(1 for e in inputs.evidence.evidence_items if e.get("kind") == "note")
total_note_length = sum(len(e.get("text", "")) for e in inputs.evidence.evidence_items if e.get("kind") == "note")
async with httpx.AsyncClient(timeout=10.0) as client:
try:
# We call our own API to use the exact same TabPFN/fallback logic
response = await client.post(
"http://localhost:8000/predict-quality",
json={
"num_photos": num_photos,
"num_notes": num_notes,
"total_note_length": total_note_length,
"duration_seconds": 600
}
)
response.raise_for_status()
data = response.json()
return {"score": data.get("score"), "model": data.get("model")}
except Exception as e:
activity.logger.error(f"Quality endpoint failed: {e}")
# Raise an error to allow Temporal to retry, or fallback honestly.
return {"score": None, "model": "unavailable", "reason": "quality endpoint unavailable"}
@activity.defn
async def persist_report(report: ReportData) -> ReportData:
activity.logger.info("Persisting durable report to Database...")
return report
# ---------------------------------------------------------
# WORKFLOW
<shellId: 30 completed with exit code 0>
{
"command": "$lines=Get-Content api\\main.py; $lines[60..78]",
"description": "Verify backend health output"
}
suggestedNextStep: str = Field(alias="suggested_next_step")
class Config:
populate_by_name = True
@app.get("/health")
def health_check():
return {
"status": "ok",
"quality_model": "baseline_heuristic",
"tabpfn": "unavailable"
}
@app.get("/sentry-debug")
def trigger_error():
logger.error("Simulated performance issue / timeout for Sentry Tracing.")
raise Exception("Simulated timeout for Sentry!")
async def generate_from_ollama(prompt: str) -> str:
<shellId: 32 completed with exit code 0>
{"command":"& 'E:\\HACKTOBERFEST WEEK 1 CHALLENGE\\venv\\Scripts\\python.exe' -m py_compile api\\main.py api\\temporal_workflow.py api\\temporal_worker.py api\\tabpfn_experiment.py","description":"Val...
<shellId: 31 completed with exit code 0>
This is now in good shape
I’d rate the current implementation 8/10 for MVP readiness and 7/10 for submission credibility.
The latest commit, 0590c66, fixed the main blocker from the previous review:
- Frontend now normalizes both
camelCaseandsnake_casereport fields. - Temporal quality scoring receives actual evidence counts and note length.
- The fabricated Temporal
85fallback is gone. -
/healthexplicitly reports the quality-model status. - The React hook dependency warning is fixed.
- Frontend build passes.
- Frontend lint passes.
- Python syntax validation passes.
- TabPFN limitations are now honestly exposed.
Current validation
npm run build ✅
npm run lint ✅
Python compile ✅
Workspace errors ✅ none
The only build warning is the Vite bundle-size warning, which is not a submission blocker.
What is now genuinely defensible
Gemma
The standard and Temporal analysis paths both call Ollama/Gemma.
The project is honest that photos currently contribute metadata rather than image understanding. That is acceptable if the article clearly states it.
Temporal
The main frontend path now defaults to the durable endpoint:
/analyze-durable
The workflow includes:
- Evidence normalization.
- Gemma analysis.
- Report validation stage.
- Quality scoring.
- Report return.
Temporal now receives actual expedition evidence for:
- Photo count.
- Note count.
- Note length.
That is a meaningful improvement.
Fallback behavior
The project now correctly distinguishes:
- Real AI report.
- Mock fallback report.
- Baseline quality score.
- Unavailable quality scoring.
This is exactly the kind of transparency judges appreciate.
TabPFN
The implementation is now honest:
tabpfn: unavailable
quality_model: baseline_heuristic
That is better than pretending a broken runtime is live.
Remaining issues
1. Temporal still hardcodes duration
In temporal_workflow.py, the quality activity still sends:
"duration_seconds": 600
This is the only remaining issue in the evidence-feature calculation.
You can either:
- Compute duration from
startedAtandcompletedAt, or - Rename the feature to
estimated_duration_secondsand state that clearly.
For the deadline, I would compute it from the evidence timestamps or pass started_at and completed_at into EvidenceData.
This is minor compared with the previous blockers, but it matters if you show the quality score as based on real expedition data.
2. Report validation is still only a pass-through
The Temporal stage is named validate_report, but currently returns the input unchanged.
That is fine for an MVP only if you describe it accurately. Better would be to reuse the same Pydantic schema used by FastAPI and reject invalid confidence values or missing fields.
3. Persistence is still in-memory workflow output
persist_report returns the report but does not persist it to a backend database.
The browser does persist the final report to IndexedDB, so the user experience is still functional. Just avoid saying Temporal persists to a server-side database.
4. No real runtime end-to-end test yet
The static checks are clean, but the strongest remaining validation is:
Ollama running
Temporal server running
Temporal worker running
FastAPI running
Frontend running
Generate report through default Temporal path
That is the test judges will effectively perform.
Prize-category status
Strong
- Overall winner
- Best Use of Gemma
- Best Use of Temporal
- Best Use of Sentry Agent Tracing, assuming you capture traces
- Best Use of Render, after verifying the deployed URL
Conditional
- Best Use of TabPFN
The current implementation supports:
“A reproducible TabPFN experiment plus a baseline production fallback.”
It does not support:
“Live TabPFN inference in the deployed application.”
Enter this category only if the challenge accepts experimental/integrated use, and explain the runtime incompatibility directly in the article.
Not currently supported
- ElevenLabs
- MongoDB Atlas
- Tiger Data
- Arduino
- DigitalOcean
Do not add these now unless a category is genuinely worth sacrificing stability for.
What I would do next
Must do
- Run the full local Temporal/Gemma path.
- Verify the report page renders:
- Summary.
- Observations.
- Missing evidence.
- Suggested next step.
- Quality score.
- Capture a Temporal retry screenshot.
- Capture a Sentry trace screenshot.
- Verify the deployed URL.
- Write the article around the actual limitations.
Nice to do
- Replace hardcoded
600seconds. - Add real Pydantic validation inside the Temporal activity.
- Add a “quality model unavailable” explanation in the report UI.
- Add a small API smoke-test script.
Do not do now
- Add accounts.
- Add social features.
- Add maps.
- Add a full vision model.
- Add MongoDB just for a category.
- Spend excessive time repairing TabPFN unless the environment fix is straightforward.
Final verdict
WildProof has moved from “polished scaffold with overstated integrations” to a credible, honest, technically differentiated MVP.
The only remaining concern is runtime verification. If the Temporal/Gemma demo works locally and you document the TabPFN limitation clearly, I would submit with:
- Overall winner
- Best Use of Gemma
- Best Use of Temporal
- Best Use of Sentry Agent Tracing
- Best Use of Render
- Best Use of Entire, if the session link is available
The current implementation is now strong enough to stop expanding scope and focus on demo evidence and the DEV article.
Community Wisdom: I Taught Local AI to Help Me Notice the World
WildProof now has enough technical differentiation to avoid being another generic outdoor companion: its strongest story is explicit uncertainty plus durable processing. The remaining work should emphasize proof and measured behavior rather than more features.
Community Wisdom: EcoID: An Offline Plant Identifier That Gets You Outside
The project is wisely avoiding a direct race to build another plant identifier. The current evidence-first design is more defensible, especially now that the runtime limitations and fallback behavior are clearly exposed.
The session is a focused engineering review. It captures the decisions to separate photo metadata from image understanding, make fallback behavior visible, document TabPFN’s runtime limitation, and align the prize-category claims with verifiable implementation evidence.
The session is available on DEV at Building WildProof for Hacktoberfest Touch Grass.
Prize Categories
Overall Winner
WildProof directly addresses Touch Grass by turning time outdoors into purposeful evidence gathering rather than another screen-based reminder.
Best Use of Gemma
Gemma is the core open-weight model that converts field notes and mission context into a structured report with explicit observed, inferred, and unknown confidence labels.
Best Use of Temporal
Temporal coordinates the multi-stage analysis workflow and allows model activities to retry without discarding the evidence collected during the expedition.
Best Use of Sentry Agent Tracing
Sentry hooks cover frontend and FastAPI errors, request timing, and model/workflow failures when SENTRY_DSN is configured.
Best Use of Render
Render deployment configuration is included for hosting the frontend and backend. A verified public deployment URL will be added after end-to-end deployment testing.
Best Use of GitHub Copilot
GitHub Copilot supported product exploration, implementation, review, debugging, validation, and the final accuracy pass on the project’s technical claims.
Best Use of TabPFN
WildProof includes a reproducible baseline-versus-TabPFN experiment for small tabular evidence-quality data. The production endpoint uses an explicitly labeled baseline when the local TabPFN runtime is unavailable.
What’s Next
- Add an opt-in local vision-model path for image-aware analysis.
- Add audio capture and local sound transcription.
- Add opt-in geolocation with clear privacy controls.
- Compare repeated observations over time.
- Replace synthetic quality data with a human-labeled field dataset.
- Add background synchronization when connectivity returns.
For now, WildProof is intentionally small: choose a mission, go outside, collect evidence, and return with a report that is honest about what was actually observed.
Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.