Skip to content

Instantly share code, notes, and snippets.

@digispect-intel
Created April 8, 2025 11:34
Show Gist options
  • Select an option

  • Save digispect-intel/a4d00cf146780ddc7c7140fc6177f000 to your computer and use it in GitHub Desktop.

Select an option

Save digispect-intel/a4d00cf146780ddc7c7140fc6177f000 to your computer and use it in GitHub Desktop.
My Dialog
Display the source blob
Display the rendered blob
Raw
{"cells": [{"cell_type": "markdown", "metadata": {}, "source": ["I'm taking part in a hackathon today. Here is the relavent information.\n", "\n", "# {Tech: Berlin} AI Hackathon #3\n", "\n", "## Important Links:\n", "\n", "## Agenda\n", "\n", "### Saturday\n", "\n", "09:00 - Door\u2019s Open & Networking\n", "\n", "10:00 - Opening \n", "\n", "11:00 - Hackathon Start\n", "\n", "12:30 - Lunch\n", "\n", "19:00 - Dinner\n", "\n", "### Sunday\n", "\n", "12:30 - Lunch\n", "\n", "14:00 - Competition Opt-In Deadline\n", "\n", "15:00 - Announcement of Finalists\n", "\n", "16:00 - Finalist Pitches\n", "\n", "17:30 - Award Ceremony\n", "\n", "## Hackathon Tracks\n", "\n", "### \ud83e\udde9 **Track 1: Open Innovation**\n", "\n", "**Challenge:** Got an idea that doesn\u2019t fit into a specific track? The **Open Innovation** track is your chance to build **anything you can dream up** using cutting-edge AI technologies. Whether you want to tackle a niche problem, explore a passion project, or push the boundaries of what\u2019s possible, this track is for you.\n", "\n", "**Your Goal:** Create an AI-powered solution that showcases **innovation, creativity, and technical prowess**. Use the provided APIs\u2014including the Mistral API, LMNT, and any other tools at your disposal\u2014to build a project that **solves a real-world problem** or opens up **new possibilities**.\n", "\n", "This track is your playground. Think beyond the obvious and aim to deliver something **novel, impactful, and scalable**.\n", "\n", "### **\u267b\ufe0f\u00a0Track 2: Choco - AI for Sustainability: Tackling Food Waste in the Global Supply Chain**\n", "\n", "**Challenge:** \n", "\n", "An estimated\u00a040% of global food production is wasted due to inefficiencies - from farms to restaurants and consumers - leading to severe economic and environmental impacts. Agriculture and food distribution remain among the least digitized industries, presenting a significant opportunity for AI-driven solutions to reduce waste and boost sustainability.\n", "\n", "**Your Goal:** \n", "\n", "Create practical, impactful AI-powered tools that tackle key sources of food waste across the supply chain, empowering stakeholders without requiring extensive domain expertise.\n", "\n", "**Potential Use Cases:**\n", "\n", "- **Smart Ordering for Restaurants & Stores**\n", " \n", " Restaurants and stores typically order via voicemails, handwritten notes, or brief phone calls, often without confirmation or tracking. Build an AI-powered ordering assistant that accepts diverse input types, confirms orders instantly, and provides real-time tracking.\n", " \n", "- **Intelligent Inventory Management for Restaurants**\n", " \n", " Restaurants struggle with inventory management due to complex stock flows and variable product shelf-life, resulting in waste or shortages. Develop an AI tool that predicts inventory needs, monitors freshness, and proactively flags items requiring attention.\n", " \n", "- **Empowering Consumers through Transparent Choices**\n", " \n", " Consumers lack accessible information on the environmental impact of their purchases, limiting sustainable choices. Create an AI-driven app that reveals products' sustainability footprints, enabling consumers to make informed, eco-friendly decisions.\n", " \n", "\n", "**\ud83c\udfc6\u00a0Track Prize: 150 EUR Gift Card (per person) for [Cookies Cream](https://cookiescream.com/de/) (Veggie Michelin Star Restaurant in Berlin)**\n", "\n", "### **\ud83d\udcda\u00a0Track 3: Knowunity - AI-Driven Personalized Learning for Education**\n", "\n", "**Challenge:**\n", "\n", "In today\u2019s educational landscape, students have diverse learning needs and paces. Traditional teaching methods often struggle to accommodate these differences, resulting in a one-size-fits-all approach that doesn\u2019t maximize learning outcomes. \n", "\n", "With the power of AI, we can revolutionize personalized learning. By leveraging AI-driven tools, we can create dynamic and adaptive learning experiences that cater to individual needs, foster engagement, and optimize educational pathways. \n", "\n", "Your challenge is to develop an AI-powered solution that personalizes learning journeys, supports educators with real-time insights, and improves student success.\n", "\n", "**Your Goal:** \n", "\n", "Build an AI-powered solution that creates personalized learning experiences, supports adaptive content delivery, provides actionable insights to educators, and improves student engagement and performance.\n", "\n", "**Potential Use Cases:**\n", "\n", "- Adaptive Learning Pathways: AI analyzes students\u2019 progress, strengths, and weaknesses to create personalized learning journeys. The system adjusts the pace, difficulty, and type of content dynamically to ensure each student is learning in the most effective way possible.\n", "- Smart Tutoring Assistant: An AI-powered assistant that provides real-time help to students. It can answer questions, provide hints, explain concepts in different ways, and suggest additional resources based on the student\u2019s learning style and progress.\n", "- Educator Insights Dashboard: AI aggregates data on student performance, providing educators with real-time insights into individual and class-wide progress. This tool helps teachers identify students at risk, track learning trends, and adjust their teaching methods accordingly.\n", "- Behavioral Analytics for Student Engagement: AI analyzes students\u2019 interactions with learning materials (videos, quizzes, discussions) to predict and monitor engagement. The system provides recommendations to both students and educators on how to increase engagement and retention.\n", "- AI-Powered Feedback Generator: AI analyzes student submissions (essays, assignments) and provides personalized, actionable feedback. It can identify common mistakes, suggest improvements, and help students better understand their areas for growth.\n", "\n", "This challenge is about leveraging AI\u2019s potential to create a more personalized, effective, and engaging learning experience for all students while providing educators with tools to enhance their teaching strategies.\n", "\n", "**\ud83c\udfc6\u00a0Track Prize:** Cursor Subscription for 1 year per team member.\n", "\n", "### **\u2615\ufe0f Track 4: Next Level Coffee - Brewing Better Coffee with AI**\n", "\n", "Coffee is personal, complex, and increasingly powered by data. At Next Level\n", "Coffee(NLC), we\u2019ve built nunc.\u2014a connected espresso machine and grinder that\n", "captures detailed IoT data from every single shot brewed and bean ground.\n", "\n", "In this track, you\u2019ll work with real data from nunc. to either optimize the brewing\n", "process or personalize the coffee experience through AI. Your solution should\n", "show how technology can elevate the art and science of coffee.\n", "\n", " **\ud83d\udca1\u00a0Choose one of two use cases**\n", "\n", "1. **Brew Optimization**\n", "\n", "Can we predict the perfect grind size before we even grind?\n", "Your task is to build a model or AI agent that uses historical brewing and grinding\n", "data along with insights into the physics of espresso machines to recommend the\n", "ideal grind size before a shot is pulled. The goal? Ensure the machine reaches the\n", "desired peak pressure of 6\u20139 bars during extraction.\n", "Inputs you can use include:\n", "\n", "- Coffee roast used, dosed weight, grinder temp, previous shot details etc.\n", "(provided by NLC)\n", "- Publicly available data that you might think is relevant to this use case\n", "\n", "Your solution should be designed for seamless integration into the nunc.\n", "ecosystem and should minimize the number of espresso shots that fall outside\n", "the optimal 6\u20139 bar extraction pressure range, improving consistency and brew\n", "quality over time.\n", "\n", "1. **Personalized Coffee Recommendation Agent**\n", "\n", "What if your coffee machine knew your taste better than you do?\n", "\n", "Build an AI agent that takes a user\u2019s preferences\u2014and deduces what kind of\n", "coffee they\u2019d love. No need for fancy cupping notes or flavor wheel terms. Just\n", "real talk: \u201cI like fruity coffee but not too sour,\u201d or \u201cI want something smooth to\n", "drink with milk.\u201d\n", "The agent should:\n", "\n", "- Ask thoughtful, simple questions\n", "- Translate human preferences into coffee parameters\n", "- Recommend coffees from the nunc. lineup that match the user\u2019s profile\n", "\n", "**Regardless of which use case you choose**, your team must also analyze the\n", "provided nunc. dataset and surface **at least 3 actionable insights** that could\n", "improve:\n", "\n", "- The **engineering** of the nunc. machine itself (e.g., hardware, sensors,\n", "algorithms), or\n", "- The **brewing experience** for users (e.g., consistency, usability,\n", "personalization)\n", "\n", "We\u2019re looking for curious minds who dig deep and bring fresh ideas to the\n", "surface.\n", "\n", "**\ud83c\udfc6\u00a0Track Prize: Pick your Perk**\n", "\n", "- 1 Year of Free Coffee\n", "- 1-Year AI Subscription\n", "- 6 Months of Coffee + 6 Months of AI Tools\n", "\n", "## **Competition Rules & Submission Guidelines**\n", "\n", "To compete in the hackathon and have your project considered by the jury, all participants must adhere to the following rules and submission requirements. Failure to meet these guidelines may result in disqualification.\n", "\n", "---\n", "\n", "## \u2705 **Submission Requirements**\n", "\n", "To qualify for the final judging, you must submit the following by **Sunday at 14:00 (sharp)** as a team of 1-5 people:\n", "\n", "### 1. Opt-In Form\n", "\n", "- Complete the official **Hackathon Opt-In Form** before the deadline\n", "- Late submissions will not be accepted\n", "\n", "### 2. Project Presentation\n", "\n", "- Record a **2-minute video demo** of your project (using Loom or equivalent platform)\n", "- Your presentation must include:\n", " - Clear identification of the problem you're solving\n", " - Detailed explanation of your AI solution\n", " - Demonstration of key features with a live walkthrough\n", "\n", "### 3. Open Source Repository\n", "\n", "- Provide a **public GitHub repository** containing your project's source code\n", "- Your repository must include:\n", " - Comprehensive **README** with setup and installation instructions\n", " - Clear documentation of all APIs, frameworks, and tools utilized\n", " - Sufficient technical documentation to enable thorough jury evaluation\n", "\n", "---\n", "\n", "## **Competition Mode**\n", "\n", "Our hackathon features a **dynamic two-stage competition format**, culminating in an exciting **live final presentation** event.\n", "\n", "### Stage 1: Track Stage\n", "\n", "### Open Innovation Track\n", "\n", "- Build anything aligned with your creative vision - complete freedom of topic choice\n", "- **3 finalist teams** will advance to the Finalist Stage\n", "- Judging criteria: creativity, technical complexity, with bonus points for effective use of partner technologies\n", "\n", "### Specialized Challenge Tracks\n", "\n", "- Tackle specific real-world challenges presented by our partner companies\n", "- **1 finalist team** (Track Winner) selected from each specialized track\n", "- Winners receive dedicated Track Prizes independent of the finals\n", "\n", "### Stage 2: Finalist Stage\n", "\n", "- All finalists will showcase their projects **live before the jury and audience**\n", "- Each team delivers a **5-minute presentation** followed by an interactive **Q&A session** with the jury\n", "- After all presentations, the jury will select the **top 3 winners** based on:\n", " - Innovation potential\n", " - Real-world impact\n", " - Technical execution quality\n", "- These top 3 teams will be awarded the prestigious Finalist Stage Prizes\n", "\n", "## Prizes\n", "\n", "### Specific Track Prizes\n", "\n", "- See in the specific track description\n", "\n", "### Finalist Stage Prizes\n", "\n", "### 1st Place\n", "\n", "- 600\u20ac Mistral AI Credits\n", "- 600\u20ac ElevenLabs Credits\n", "- 600\u20ac Weaviate Credits\n", "- 1 year n8n cloud subscription per person\n", "\n", "### 2nd Place\n", "\n", "- 300\u20ac Mistral AI Credits\n", "- 300\u20ac ElevenLabs Credits\n", "- 300\u20ac Weaviate Credits\n", "- 1 year n8n cloud subscription per person\n", "\n", "### 3rd Place\n", "\n", "- 100\u20ac Mistral AI Credits\n", "- 100\u20ac ElevenLabs Credits\n", "- 100\u20ac Weaviate Credits\n", "- 1 year n8n cloud subscription per person\n", "\n", "## Resources (Partner Tech & more)\n", "\n", "### Infrastructure:\n", "\n", "- ElevenLabs: [1-month off Creator Plan](https://elevenlabs.io/?coupon=M2GQ09)\n", "- Mistral AI: [Free Credits](https://console.mistral.ai/)\n", "- Weaviate: [Free Sandbox Environment](https://weaviate.io/)\n", "- Beyond Presence\n", "- Distil Labs"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Here is our selected challenge:\n", "\n", " Use Case 2: Instant Dial-In - Scan Your Coffee, Personalize Your Flavor\n", " Can we dial in the perfect espresso just by looking at the coffee? This use case is especially\n", " designed for coffee nerds. Your task:\n", " Build a model or AI agent that uses visual input (like coffee bag photos, roast level, origin, and\n", " tasting notes) and combines it with knowledge of coffee extraction science to recommend the\n", " ideal espresso shot parameters for a new coffee.\n", " The goal? Enable users to simply scan or upload a photo* of the coffee and instantly receive\n", " personalized dial-in recommendations\u2014no guesswork, no waste.\n", " In addition to image and coffee data, the model should also ask the user for their desired\n", " flavor experience\u2014for example: balanced, fruity, bold, sweet, or chocolatey. Based on both\n", " the realistic potential of the coffee (roast level, processing, origin) and the user\u2019s input, the\n", " model should adjust the recipe accordingly.\n", " Your model should determine the shot based on the following parameters:\n", " \u2022 Flow or pressure control of the shot \u2014 recommend a target flow rate or pressure profile\n", " \u2022 Brewing temperature\n", " \u2022 Grind setting, using 120 \u03bcm of the \u201cBAR ITALIA\u201d as the reference point\n", " \u2022 Brew ratio \u2014 the ratio of ground coffee to total water used\n", " Your solution should integrate effortlessly into the nunc. ecosystem, combining visual\n", " recognition, taste profiling, and extraction physics to deliver fast, high-fidelity recipes tailored\n", " to both the coffee and the drinker\u2014all with a single scan and a tap."]}, {"cell_type": "markdown", "metadata": {}, "source": ["Here are the collective notes from our team:\n", "\n", " Notes\n", " Chosen problem: use case 2\n", " Input is image, model detects coffee brand, according to detection results get brew parameters based on available coffee brand database, \n", " mapping from user preferences to grinding and brewing parameters\n", " mapping from grind and brew parameters to coffee tastes\n", " 4 key attributes from coffee package\n", " country/origin\n", " roast level (med, dark, light)\n", " variety (arabica, robusta)\n", " process (natural, washed)\n", " Others (harder to obtain or less important)\n", " Elevation \n", " Roast Date\n", " Uer input:\n", " What kind of drink? (espresso, americano)\n", " Intensity (strong, med, mild)\n", "\n", " Questions\n", " what kinds of actionable insights shall we get from the data by analysing it? What are to expected, as an example\n", " how to link eventID between grinding and brewing\n", " the mapping from brand to grind and brew process/params \n", " Is there any kind of constraint on the type or form of user input for part 2?\n", " what taste profiles can be affected by brewing conditions?\n", " What is affected mainly by the bean \n", "\n", " Internal Questions\n", " What affects the taste profile and how?\n", " How does the bean/roast/blend/color affect the taste profile?\n", " How do the grind/brew/consumer-environment and machine settings affect the taste profile?\n", " How does the latter affect the former?\n", "\n", " Solution Components\n", " UI Input: Formly\n", " Make a simple UI and record data to google sheets\n", " input 4 user arguments\n", " strong-light\n", " fruity-bold\n", " sour-bitter\n", " Read input data from google sheet,\n", " OCR (Image processing)\n", " Extract structured data (json: simple coffee profile) from coffee package image (png), four values\n", " country/origin\n", " use 7 largest regions\n", " three regions/groups:\n", " center, south america\n", " aisa, pacific\n", " africa\n", " roast level (dark, med-dark, med-light, light)\n", " variety (arabica, robusta)\n", " process (natural, washed, honey)\n", " Map: coffee profile to machine parameters\n", " Analyze machine parameter data\n", " Map: Taste Profiles to Machine parameters\n", " 10 taste profiles for machines\n", " LLM summary for final \n", " Prompt needed"]}, {"cell_type": "markdown", "metadata": {}, "source": ["I want to make FastHTML app that can become the foundation for this solution.\n", "\n", "1. I am quite experienced with HTML, CSS and javascript.\n", "2. I want a simple interface that is fast to implemnt, but still looks nice enought for a demo in a hackathon\n", "3. No specific design in mind.\n", "1. I want to use anything from franken-ui, monster-ui, or tailwind.\n", "2. Let's start with a basic html template. The functionality will be completed by my teammates."]}, {"cell_type": "markdown", "metadata": {}, "source": ["Fast HTML Monster UI Starter Template"]}, {"cell_type": "markdown", "metadata": {}, "source": ["#### Base Repo"]}, {"cell_type": "code", "metadata": {}, "source": ["file_contents = fetch_multiple_files(api, 'digispect-intel', 'nunc_perfect_cup_of_coffee', get_files)"], "outputs": [], "execution_count": null}, {"cell_type": "markdown", "metadata": {}, "source": ["#### AI Agent Framework repo"]}, {"cell_type": "markdown", "metadata": {}, "source": ["A reminder on our actual challenge: IMPORTANT!\n", "\n", " Use Case 2: Instant Dial-In - Scan Your Coffee, Personalize Your Flavor\n", " Can we dial in the perfect espresso just by looking at the coffee? This use case is especially\n", " designed for coffee nerds. Your task:\n", " Build a model or AI agent that uses visual input (like coffee bag photos, roast level, origin, and\n", " tasting notes) and combines it with knowledge of coffee extraction science to recommend the\n", " ideal espresso shot parameters for a new coffee.\n", " The goal? Enable users to simply scan or upload a photo* of the coffee and instantly receive\n", " personalized dial-in recommendations\u2014no guesswork, no waste.\n", " In addition to image and coffee data, the model should also ask the user for their desired\n", " flavor experience\u2014for example: balanced, fruity, bold, sweet, or chocolatey. Based on both\n", " the realistic potential of the coffee (roast level, processing, origin) and the user\u2019s input, the\n", " model should adjust the recipe accordingly.\n", " Your model should determine the shot based on the following parameters:\n", " \u2022 Flow or pressure control of the shot \u2014 recommend a target flow rate or pressure profile\n", " \u2022 Brewing temperature\n", " \u2022 Grind setting, using 120 \u03bcm of the \u201cBAR ITALIA\u201d as the reference point\n", " \u2022 Brew ratio \u2014 the ratio of ground coffee to total water used\n", " Your solution should integrate effortlessly into the nunc. ecosystem, combining visual\n", " recognition, taste profiling, and extraction physics to deliver fast, high-fidelity recipes tailored\n", " to both the coffee and the drinker\u2014all with a single scan and a tap."]}, {"cell_type": "markdown", "metadata": {}, "source": ["**AI Prompt**\n", "\n", "Please create me a 2-minute pitch that I can use as the basis for a video.\n", "\n", " Pitch Presentation Outline\n", "\n", " Record a 2-minute video demo of your project (using Loom or equivalent platform)\n", "\n", " Your presentation must include:\n", " - Clear identification of the problem you're solving\n", " - Detailed explanation of your AI solution\n", " - Demonstration of key features with a live walkthrough\n", "\n", "You can use this as an intro:\n", "\n", "\n", " [Opening] \"Have you ever bought a beautiful bag of specialty coffee, only to waste half of it trying to dial in the perfect espresso? Hi, I'm Dave, and together with Bangshang and Sourabh, we are building the NUNC Perfect Cup of Coffee for Next Level Coffee. In doing so, we're solving one of the most frustrating problems for coffee lovers everywhere.\""]}, {"cell_type": "markdown", "metadata": {}, "source": ["**AI Response**\n", "\n", "Here's a 2-minute pitch script that builds on your intro:\n", "\n", "```\n", "[Opening] \"Have you ever bought a beautiful bag of specialty coffee, only to waste half of it trying to dial in the perfect espresso? Hi, I'm Dave, and together with Bangshang and Sourabh, we are building the NUNC Perfect Cup of Coffee for Next Level Coffee. In doing so, we're solving one of the most frustrating problems for coffee lovers everywhere.\"\n", "\n", "[Problem] \"The challenge is real: every coffee bean is unique, with different origins, roast levels, and processing methods that all impact how it should be brewed. Even experienced baristas spend precious time and coffee trying to find the perfect parameters. For home enthusiasts, it's even more frustrating - they might never discover what their coffee truly has to offer.\"\n", "\n", "[Solution] \"Our solution is simple yet powerful: just scan your coffee bag with your phone, tell us your flavor preferences, and our AI instantly provides personalized brewing parameters tailored specifically to that coffee and your taste.\"\n", "\n", "[Technology] \"Behind the scenes, we've built a sophisticated AI system that:\n", "- Uses computer vision to extract key coffee attributes like origin, roast level, and processing method\n", "- Combines this with coffee extraction science and our database of successful brewing parameters\n", "- Adapts recommendations based on your personal flavor preferences\n", "- Delivers precise brewing parameters in seconds\"\n", "\n", "[Demo] \"Let me show you how it works. I'll scan this bag of Ethiopian coffee... The AI identifies it as a light roast, natural process coffee from Yirgacheffe. Now I'll select that I prefer a fruity, bright flavor profile... And there we go! The app recommends a brewing temperature of 93\u00b0C, a grind setting of 110 microns, a 1:2 brew ratio, and a specific pressure profile that starts low and gradually increases.\"\n", "\n", "[Benefits] \"No more wasted coffee. No more frustration. Just perfect espresso from your first shot. And as you use the system, it learns your preferences and becomes even more personalized over time.\"\n", "\n", "[Integration] \"Best of all, this seamlessly integrates with the NUNC ecosystem, allowing for a frictionless experience for coffee lovers at any skill level.\"\n", "\n", "[Closing] \"We're passionate about helping people experience the true potential of specialty coffee. With NUNC Perfect Cup, we're making that possible from the very first brew. Thank you.\"\n", "```\n", "\n", "This script covers all the required elements while staying within the 2-minute timeframe. For the video, you could demonstrate the app interface while explaining the technology, showing a mock scan of a coffee bag and the resulting parameter recommendations."]}, {"cell_type": "markdown", "metadata": {}, "source": ["**AI Prompt**\n", "\n", "Please make it less marketing and sales oriented and more for a tech crowd."]}, {"cell_type": "markdown", "metadata": {}, "source": ["**AI Response**\n", "\n", "Here's a more technically focused pitch for your hackathon presentation:\n", "\n", "```\n", "[Opening] \"Have you ever bought a beautiful bag of specialty coffee, only to waste half of it trying to dial in the perfect espresso? Hi, I'm Dave, and together with Bangshang and Sourabh, we are building the NUNC Perfect Cup of Coffee for Next Level Coffee. In doing so, we're solving one of the most frustrating problems for coffee lovers everywhere.\"\n", "\n", "[Problem] \"The technical challenge is fascinating: espresso extraction is governed by multiple interdependent variables - grind size, temperature, pressure profiles, and brew ratios - all of which must be optimized for each unique coffee bean. Currently, this optimization is largely manual, inconsistent, and wasteful.\"\n", "\n", "[Solution] \"We've developed an AI-driven system that automates this optimization process through computer vision and machine learning. Our solution extracts key coffee attributes from packaging images and combines this with extraction science principles to generate precise brewing parameters.\"\n", "\n", "[Technical Architecture] \"Our system consists of three core components:\n", "- A computer vision module using OCR and image classification to extract coffee metadata\n", "- A recommendation engine built on a dataset of successful extraction parameters\n", "- A parameter optimization algorithm that maps coffee attributes to brewing variables while accounting for user preferences\"\n", "\n", "[Implementation Details] \"We're using Mistral's vision capabilities for image analysis. Our backend is Python-based with FastHTML for the interface. The recommendation system uses a combination of rules-based extraction science and machine learning to generate parameters.\"\n", "\n", "[Demo] \"In this demo, our system processes the image input, extracts that this is an Ethiopian Yirgacheffe with light roast level and natural processing. The algorithm then calculates optimal parameters based on these attributes and the user's preference for fruity notes. The output includes precise numerical values: 93\u00b0C brewing temperature, 110\u03bcm grind setting referenced to BAR ITALIA standards, a 1:2 brew ratio, and a pressure profile with specific ramping characteristics.\"\n", "\n", "[Data Processing] \"What's particularly interesting is how we handle the multivariate optimization problem. Different origins and processing methods create distinct flavor compounds that extract at different rates under varying conditions. Our system models these relationships to maximize desired flavor compounds while minimizing undesirable ones.\"\n", "\n", "[Future Development] \"We've implemented a feedback loop that will allow the system to continuously improve as users report their actual results, creating a self-optimizing recommendation engine.\"\n", "\n", "[Closing] \"This project demonstrates how computer vision and machine learning can solve complex optimization problems in unexpected domains. Thank you.\"\n", "```\n", "\n", "This version emphasizes the technical aspects of your solution, focusing on architecture, implementation details, and the underlying optimization problem rather than marketing benefits."]}, {"cell_type": "markdown", "metadata": {}, "source": ["**AI Prompt**\n", "\n", "Please convert that into text to use in a ~10 slide deck"]}, {"cell_type": "markdown", "metadata": {}, "source": ["**AI Response**\n", "\n", "I'll help you create text for a ~10 slide deck based on your coffee recommendation AI challenge. Here's content you can use for each slide:\n", "\n", "**Slide 1: Title**\n", "# Perfect Cup AI\n", "*Personalized coffee recommendations through visual AI*\n", "Hackathon Track 4: Next Level Coffee\n", "\n", "**Slide 2: The Challenge**\n", "* Create an AI system that recommends perfect espresso parameters from coffee bag images\n", "* Enable users to scan a coffee bag and receive instant brewing recommendations\n", "* Combine visual recognition with personalized taste preferences\n", "* Deliver precise brewing parameters with no guesswork\n", "\n", "**Slide 3: Our Solution**\n", "* A vision-powered AI agent that:\n", " - Analyzes coffee package images to extract key attributes\n", " - Asks for user taste preferences\n", " - Recommends precise brewing parameters\n", " - Delivers personalized coffee experiences\n", "\n", "**Slide 4: Technical Architecture**\n", "* Vision module: Extracts coffee attributes from images\n", "* OCR module: Reads text from coffee packaging\n", "* Recommendation engine: Maps coffee profiles to brewing parameters\n", "* User interface: Simple, intuitive experience\n", "\n", "**Slide 5: Key Features**\n", "* Coffee attribute extraction:\n", " - Origin/country\n", " - Roast level\n", " - Variety (Arabica/Robusta)\n", " - Processing method\n", "* Personalized taste mapping\n", "* Precise parameter recommendations\n", "\n", "**Slide 6: User Experience**\n", "1. Upload coffee bag image\n", "2. Select taste preferences:\n", " - Intensity (strong/mild)\n", " - Profile (fruity/bold)\n", " - Acidity (sour/bitter)\n", "3. Receive personalized brewing recipe\n", "\n", "**Slide 7: Brewing Parameters**\n", "Our AI recommends precise:\n", "* Flow/pressure profiles\n", "* Brewing temperature\n", "* Grind setting (using 120 \u03bcm BAR ITALIA reference)\n", "* Brew ratio (coffee to water)\n", "\n", "**Slide 8: Technology Stack**\n", "* FastHTML & MonsterUI for frontend\n", "* Mistral Vision AI for image analysis\n", "* ControlFlow for agent orchestration\n", "* Custom recommendation engine\n", "\n", "**Slide 9: Demo**\n", "[Placeholder for live demo or screenshots]\n", "* Scan coffee bag\n", "* Select preferences\n", "* View personalized recommendations\n", "\n", "**Slide 10: Next Steps**\n", "* Integration with nunc. ecosystem\n", "* Expanded coffee database\n", "* User feedback loop for improved recommendations\n", "* Mobile app development"]}, {"cell_type": "markdown", "metadata": {}, "source": ["**AI Prompt**\n", "\n", "Please provide me with a plan to improve the recommender module, based on the current $`file_contents`."]}, {"cell_type": "markdown", "metadata": {}, "source": ["**AI Response**\n", "\n", "Looking at the current recommender module in the Perfect Cup of Coffee project, I can see some opportunities for improvement. Here's a plan to enhance the recommender functionality:\n", "\n", "## Current State of the Recommender Module\n", "\n", "The existing recommender takes user preferences and optional coffee information (from OCR) and generates brewing recommendations using a Mistral AI agent. It's well-structured but has room for enhancement.\n", "\n", "## Improvement Plan\n", "\n", "### 1. Enhanced Data Models\n", "\n", "First, let's improve the data models to capture more detailed coffee characteristics:\n", "\n", "```python\n", "class CoffeePreferences(BaseModel):\n", " intensity: str = Field(description=\"Preferred coffee intensity (strong, medium, light)\")\n", " flavor_profile: str = Field(description=\"Preferred flavor profile (fruity, bold, balanced)\")\n", " acidity: str = Field(description=\"Preferred acidity level (sour, bitter, balanced)\")\n", " drink_type: str = Field(description=\"Type of coffee drink (espresso, americano, etc.)\")\n", " notes: Optional[str] = Field(None, description=\"Any additional preference notes\")\n", "```\n", "\n", "### 2. Add Knowledge Base Integration\n", "\n", "Would you like me to add a knowledge base component to improve recommendations based on coffee science principles?"]}, {"cell_type": "markdown", "metadata": {}, "source": ["**AI Prompt**\n", "\n", "Yes please give me a demo of what the knowledge base would look like."]}, {"cell_type": "markdown", "metadata": {}, "source": ["**AI Response**\n", "\n", "Here's a demonstration of what a knowledge base component could look like for the coffee recommender system:\n", "\n", "```python\n", "class CoffeeKnowledgeBase:\n", " \"\"\"Knowledge base for coffee brewing parameters based on coffee characteristics\"\"\"\n", " \n", " # Origin-based temperature recommendations\n", " origin_temp_map = {\n", " \"ethiopia\": {\"min_temp\": 91, \"max_temp\": 94, \"notes\": \"Ethiopian coffees often benefit from higher temperatures to highlight floral and fruity notes\"},\n", " \"colombia\": {\"min_temp\": 90, \"max_temp\": 93, \"notes\": \"Colombian coffees extract well at standard temperatures\"},\n", " \"brazil\": {\"min_temp\": 88, \"max_temp\": 92, \"notes\": \"Brazilian coffees often have chocolate notes that develop at slightly lower temperatures\"},\n", " # More origins can be added\n", " }\n", " \n", " # Roast level brewing parameters\n", " roast_params = {\n", " \"light\": {\n", " \"temp_range\": (92, 96),\n", " \"grind_offset\": -5, # Finer than reference point\n", " \"brew_ratio\": \"1:2.5\",\n", " \"notes\": \"Light roasts need higher temperatures and finer grinds to properly extract\"\n", " },\n", " \"medium\": {\n", " \"temp_range\": (90, 94),\n", " \"grind_offset\": 0, # At reference point\n", " \"brew_ratio\": \"1:2\",\n", " \"notes\": \"Medium roasts work well with standard parameters\"\n", " },\n", " \"dark\": {\n", " \"temp_range\": (88, 92),\n", " \"grind_offset\": 5, # Coarser than reference point\n", " \"brew_ratio\": \"1:1.5\",\n", " \"notes\": \"Dark roasts extract more easily and benefit from lower temperatures\"\n", " }\n", " }\n", " \n", " # Flavor profile adjustments\n", " flavor_adjustments = {\n", " \"fruity\": {\"temp_offset\": 1, \"grind_offset\": -2, \"flow_rate\": \"slower start, gradual increase\"},\n", " \"bold\": {\"temp_offset\": -1, \"grind_offset\": 2, \"flow_rate\": \"high pressure throughout\"},\n", " \"balanced\": {\"temp_offset\": 0, \"grind_offset\": 0, \"flow_rate\": \"standard pressure profile\"},\n", " # More profiles can be added\n", " }\n", "```\n", "\n", "Would you like me to continue with how this knowledge base could be integrated with the recommender module?"]}, {"cell_type": "markdown", "metadata": {}, "source": ["**AI Prompt**\n", "\n", "How would that look in the code?"]}, {"cell_type": "markdown", "metadata": {}, "source": ["**AI Response**\n", "\n", "Here's how the knowledge base could be integrated into the recommender module code:\n", "\n", "```python\n", "from modules.agent_config import setup_agents\n", "from pydantic import Field, BaseModel\n", "from typing import Optional\n", "import json\n", "\n", "# Import the knowledge base\n", "from modules.coffee_knowledge import CoffeeKnowledgeBase\n", "\n", "def get_recommendation(preferences, coffee_info=None):\n", " \"\"\"Get coffee recommendations based on user preferences and coffee information\n", " \n", " Args:\n", " preferences (dict): User taste preferences (intensity, flavor, acidity, drink_type)\n", " coffee_info (dict, optional): Coffee information extracted from package image\n", " \n", " Returns:\n", " dict: Detailed brewing recommendations\n", " \"\"\"\n", " try:\n", " # Get recommendation agent\n", " agents = setup_agents()\n", " recommendation_agent = agents[\"recommendation_agent\"]\n", " \n", " # Initialize knowledge base\n", " kb = CoffeeKnowledgeBase()\n", " \n", " # Apply knowledge base insights to enrich the context\n", " knowledge_context = \"\"\n", " \n", " # Add origin-based recommendations if available\n", " if coffee_info and 'origin' in coffee_info:\n", " origin = coffee_info['origin'].lower()\n", " for key in kb.origin_temp_map:\n", " if key in origin:\n", " knowledge_context += f\"\\nOrigin insight: {kb.origin_temp_map[key]['notes']}\"\n", " break\n", " \n", " # Add roast-based recommendations if available\n", " if coffee_info and 'roast_level' in coffee_info:\n", " roast = coffee_info['roast_level'].lower()\n", " for key in kb.roast_params:\n", " if key in roast:\n", " knowledge_context += f\"\\nRoast insight: {kb.roast_params[key]['notes']}\"\n", " break\n", " \n", " # Format preferences for the agent with added knowledge\n", " preferences_str = \"\\n\".join([f\"- {key}: {value}\" for key, value in preferences.items()])\n", " \n", " # Add coffee information if available\n", " coffee_info_str = \"\"\n", " if coffee_info:\n", " coffee_info_str = \"\\nCoffee Information:\\n\" + \"\\n\".join([f\"- {key}: {value}\" for key, value in coffee_info.items()])\n", " \n", " # Rest of the function continues as before...\n", "```\n", "\n", "Would you like me to explain how the knowledge base would influence the actual brewing parameters calculation?"]}, {"cell_type": "markdown", "metadata": {}, "source": ["**AI Prompt**\n", "\n", "How can I create some test runs to simulate how it would work in the demo? I mean what kind of clickflows can I run through in the demo?"]}, {"cell_type": "markdown", "metadata": {}, "source": ["**AI Response**\n", "\n", "For your demo, you can create several test runs to showcase the application's functionality. Here are some clickflow scenarios you could demonstrate:\n", "\n", "1. **Basic Preference Selection Flow**\n", " - Start at the homepage\n", " - Select taste preferences (intensity, flavor profile, acidity, drink type)\n", " - Click \"Find Your Perfect Coffee\"\n", " - Show the recommendation results\n", "\n", "2. **Coffee Package Scan Flow**\n", " - Start at the homepage\n", " - Upload a coffee package image\n", " - Show the OCR processing (with a loading indicator)\n", " - Display the extracted information and brewing recommendations\n", "\n", "3. **Combined Approach Flow**\n", " - Start at the homepage\n", " - Select some preferences\n", " - Upload a coffee package image\n", " - Show how the system combines both inputs for a personalized recommendation\n", "\n", "4. **Error Handling Flow**\n", " - Demonstrate what happens with poor quality images\n", " - Show graceful error messages and fallback recommendations\n", "\n", "Would you like me to elaborate on any of these flows or suggest how to prepare mock data for the demo?"]}, {"cell_type": "markdown", "metadata": {}, "source": ["**AI Prompt**\n", "\n", "Please show me the entire new module including all changes"]}, {"cell_type": "markdown", "metadata": {}, "source": ["**AI Response**\n", "\n", "Here's the complete improved recommender module with all changes incorporated:\n", "\n", "```python\n", "from modules.agent_config import setup_agents\n", "from pydantic import Field, BaseModel\n", "from typing import Optional, Dict, Any, List, Tuple\n", "import json\n", "\n", "class CoffeePreferences(BaseModel):\n", " intensity: str = Field(description=\"Preferred coffee intensity (strong, medium, light)\")\n", " flavor_profile: str = Field(description=\"Preferred flavor profile (fruity, bold, balanced)\")\n", " acidity: str = Field(description=\"Preferred acidity level (sour, bitter, balanced)\")\n", " drink_type: str = Field(description=\"Type of coffee drink (espresso, americano, etc.)\")\n", " notes: Optional[str] = Field(None, description=\"Any additional preference notes\")\n", "\n", "class CoffeeKnowledgeBase:\n", " \"\"\"Knowledge base for coffee brewing parameters based on coffee characteristics\"\"\"\n", " \n", " # Origin-based temperature recommendations\n", " origin_temp_map = {\n", " \"ethiopia\": {\"min_temp\": 91, \"max_temp\": 94, \"notes\": \"Ethiopian coffees often benefit from higher temperatures to highlight floral and fruity notes\"},\n", " \"colombia\": {\"min_temp\": 90, \"max_temp\": 93, \"notes\": \"Colombian coffees extract well at standard temperatures\"},\n", " \"brazil\": {\"min_temp\": 88, \"max_temp\": 92, \"notes\": \"Brazilian coffees often have chocolate notes that develop at slightly lower temperatures\"},\n", " \"kenya\": {\"min_temp\": 92, \"max_temp\": 95, \"notes\": \"Kenyan coffees are often bright and acidic, benefiting from higher temperatures\"},\n", " \"guatemala\": {\"min_temp\": 89, \"max_temp\": 93, \"notes\": \"Guatemalan coffees have balanced acidity and body that develops well at medium temperatures\"},\n", " \"costa rica\": {\"min_temp\": 90, \"max_temp\": 93, \"notes\": \"Costa Rican coffees are often clean with bright acidity, extracting well at medium-high temperatures\"}\n", " }\n", " \n", " # Roast level brewing parameters\n", " roast_params = {\n", " \"light\": {\n", " \"temp_range\": (92, 96),\n", " \"grind_offset\": -5, # Finer than reference point\n", " \"brew_ratio\": \"1:2.5\",\n", " \"flow_rate\": \"slow start, gradual increase\",\n", " \"notes\": \"Light roasts need higher temperatures and finer grinds to properly extract\"\n", " },\n", " \"medium\": {\n", " \"temp_range\": (90, 94),\n", " \"grind_offset\": 0, # At reference point\n", " \"brew_ratio\": \"1:2\",\n", " \"flow_rate\": \"standard profile\",\n", " \"notes\": \"Medium roasts work well with standard parameters\"\n", " },\n", " \"dark\": {\n", " \"temp_range\": (88, 92),\n", " \"grind_offset\": 5, # Coarser than reference point\n", " \"brew_ratio\": \"1:1.5\",\n", " \"flow_rate\": \"fast start, declining pressure\",\n", " \"notes\": \"Dark roasts extract more easily and benefit from lower temperatures\"\n", " }\n", " }\n", " \n", " # Flavor profile adjustments\n", " flavor_adjustments = {\n", " \"fruity\": {\"temp_offset\": 1, \"grind_offset\": -2, \"flow_rate\": \"slower start, gradual increase\"},\n", " \"bold\": {\"temp_offset\": -1, \"grind_offset\": 2, \"flow_rate\": \"high pressure throughout\"},\n", " \"balanced\": {\"temp_offset\": 0, \"grind_offset\": 0, \"flow_rate\": \"standard pressure profile\"},\n", " \"sweet\": {\"temp_offset\": -1, \"grind_offset\": -1, \"flow_rate\": \"medium pressure, extended pre-infusion\"},\n", " \"chocolatey\": {\"temp_offset\": -2, \"grind_offset\": 1, \"flow_rate\": \"high pressure start, gradual decline\"}\n", " }\n", " \n", " # Processing method influences\n", " process_influences = {\n", " \"washed\": {\"clarity\": \"high\", \"body\": \"medium\", \"acidity_boost\": 1},\n", " \"natural\": {\"clarity\": \"medium\", \"body\": \"full\", \"acidity_boost\": -1},\n", " \"honey\": {\"clarity\": \"medium-high\", \"body\": \"medium-full\", \"acidity_boost\": 0}\n", " }\n", " \n", " # Reference points\n", " reference = {\n", " \"grind_setting\": 120, # \u03bcm, BAR ITALIA reference point\n", " \"base_temp\": 93, # Celsius\n", " \"base_ratio\": \"1:2\", # Coffee to water ratio\n", " \"base_time\": 28 # Seconds for standard shot\n", " }\n", " \n", " def calculate_parameters(self, coffee_info: Dict[str, Any], preferences: Dict[str, str]) -> Dict[str, Any]:\n", " \"\"\"Calculate brewing parameters based on coffee info and user preferences\"\"\"\n", " \n", " # Start with default values\n", " brewing_temp = self.reference[\"base_temp\"]\n", " grind_setting = self.reference[\"grind_setting\"]\n", " brew_ratio = self.reference[\"base_ratio\"]\n", " flow_rate = \"standard profile\"\n", " brewing_time = self.reference[\"base_time\"]\n", " \n", " # Adjust based on roast level\n", " if coffee_info and 'roast_level' in coffee_info:\n", " roast = coffee_info['roast_level'].lower()\n", " for key in self.roast_params:\n", " if key in roast:\n", " roast_data = self.roast_params[key]\n", " brewing_temp = (roast_data[\"temp_range\"][0] + roast_data[\"temp_range\"][1]) / 2\n", " grind_setting += roast_data[\"grind_offset\"]\n", " brew_ratio = roast_data[\"brew_ratio\"]\n", " flow_rate = roast_data[\"flow_rate\"]\n", " break\n", " \n", " # Adjust based on origin\n", " if coffee_info and 'origin' in coffee_info:\n", " origin = coffee_info['origin'].lower()\n", " for key in self.origin_temp_map:\n", " if key in origin:\n", " origin_data = self.origin_temp_map[key]\n", " origin_temp = (origin_data[\"min_temp\"] + origin_data[\"max_temp\"]) / 2\n", " # Blend with existing temp recommendation\n", " brewing_temp = (brewing_temp + origin_temp) / 2\n", " break\n", " \n", " # Adjust based on processing method\n", " if coffee_info and 'process' in coffee_info:\n", " process = coffee_info['process'].lower()\n", " for key in self.process_influences:\n", " if key in process:\n", " process_data = self.process_influences[key]\n", " # Adjust acidity based on process\n", " if process_data[\"acidity_boost\"] > 0:\n", " brewing_temp += 1 # Higher temp for more acidity\n", " elif process_data[\"acidity_boost\"] < 0:\n", " brewing_temp -= 1 # Lower temp for less acidity\n", " break\n", " \n", " # Fine-tune based on user preferences\n", " if 'flavor_profile' in preferences:\n", " flavor = preferences['flavor_profile'].lower()\n", " for key in self.flavor_adjustments:\n", " if key in flavor:\n", " flavor_data = self.flavor_adjustments[key]\n", " brewing_temp += flavor_data[\"temp_offset\"]\n", " grind_setting += flavor_data[\"grind_offset\"]\n", " flow_rate = flavor_data[\"flow_rate\"]\n", " break\n", " \n", " # Adjust for intensity preference\n", " if 'intensity' in preferences:\n", " intensity = preferences['intensity'].lower()\n", " if 'strong' in intensity:\n", " grind_setting -= 3 # Finer grind for stronger coffee\n", " brew_ratio = \"1:1.5\" # Less water for stronger coffee\n", " elif 'light' in intensity:\n", " grind_setting += 3 # Coarser grind for lighter coffee\n", " brew_ratio = \"1:2.5\" # More water for lighter coffee\n", " \n", " # Adjust for acidity preference\n", " if 'acidity' in preferences:\n", " acidity = preferences['acidity'].lower()\n", " if 'sour' in acidity:\n", " brewing_temp += 2 # Higher temp to enhance acidity\n", " elif 'bitter' in acidity:\n", " brewing_temp -= 2 # Lower temp to reduce bitterness\n", " \n", " # Ensure parameters are within reasonable bounds\n", " brewing_temp = max(88, min(96, brewing_temp))\n", " grind_setting = max(100, min(140, grind_setting))\n", " \n", " # Return calculated parameters\n", " return {\n", " \"brewing_temp\": int(brewing_temp),\n", " \"grind_setting\": int(grind_setting),\n", " \"brew_ratio\": brew_ratio,\n", " \"flow_rate\": flow_rate,\n", " \"brewing_time\": brewing_time\n", " }\n", "\n", "class BrewingRecommendation(BaseModel):\n", " coffee_name: str = Field(description=\"Name of recommended coffee\")\n", " description: str = Field(description=\"Brief description of flavor profile\")\n", " origin: str = Field(description=\"Country/region of origin\")\n", " roast_level: str = Field(description=\"Roast level\")\n", " flow_rate: str = Field(description=\"Recommended flow rate or pressure profile\")\n", " brewing_temp: int = Field(description=\"Recommended brewing temperature in Celsius\")\n", " grind_setting: int = Field(description=\"Recommended grind setting in \u03bcm\")\n", " brew_ratio: str = Field(description=\"Ratio of ground coffee to total water used\")\n", " brewing_time: int = Field(description=\"Recommended brewing time in seconds\")\n", " notes: Optional[str] = Field(None, description=\"Additional brewing notes or tips\")\n", "\n", "def get_recommendation(preferences, coffee_info=None):\n", " \"\"\"Get coffee recommendations based on user preferences and coffee information\n", " \n", " Args:\n", " preferences (dict): User taste preferences (intensity, flavor, acidity, drink_type)\n", " coffee_info (dict, optional): Coffee information extracted from package image\n", " \n", " Returns:\n", " dict: Detailed brewing recommendations\n", " \"\"\"\n", " try:\n", " # Get recommendation agent\n", " agents = setup_agents()\n", " recommendation_agent = agents[\"recommendation_agent\"]\n", " \n", " # Initialize knowledge base\n", " kb = CoffeeKnowledgeBase()\n", " \n", " # Calculate initial parameters from knowledge base\n", " calculated_params = kb.calculate_parameters(coffee_info, preferences)\n", " \n", " # Format preferences for the agent\n", " preferences_str = \"\\n\".join([f\"- {key}: {value}\" for key, value in preferences.items()])\n", " \n", " # Add coffee information if available\n", " coffee_info_str = \"\"\n", " if coffee_info:\n", " coffee_info_str = \"\\nCoffee Information:\\n\" + \"\\n\".join([f\"- {key}: {value}\" for key, value in coffee_info.items()])\n", " \n", " # Add calculated parameters to context\n", " params_str = \"\\nCalculated Parameters:\\n\" + \"\\n\".join([f\"- {key}: {value}\" for key, value in calculated_params.items()])\n", " \n", " # Use ControlFlow to generate recommendations with a user message\n", " prompt = f\"\"\"\n", " Based on these coffee preferences:\n", " {preferences_str}\n", " {coffee_info_str}\n", " {params_str}\n", " \n", " Provide a coffee recommendation with brewing parameters tailored to these preferences.\n", " Take into account the coffee origin, roast level, and user's taste preferences to provide accurate recommendations.\n", " Use the calculated parameters as a starting point but feel free to adjust them based on your expertise.\n", " \"\"\"\n", " \n", " recommendation = recommendation_agent.run(prompt, result_type=BrewingRecommendation)\n", " \n", " # Convert to dictionary for JSON serialization\n", " result = recommendation.model_dump()\n", " return json.dumps(result)\n", " \n", " except Exception as e:\n", " print(f\"Error generating recommendation: {e}\")\n", " # Return a default recommendation if there's an error\n", " default_recommendation = {\n", " \"coffee_name\": \"House Blend\",\n", " \"description\": \"A balanced, medium roast coffee with notes of chocolate and nuts\",\n", " \"origin\": \"Brazil\",\n", " \"roast_level\": \"Medium\",\n", " \"flow_rate\": \"Steady 2ml/s\",\n", " \"brewing_temp\": 93,\n", " \"grind_setting\": 120,\n", " \"brew_ratio\": \"1:2\",\n", " \"brewing_time\": 28,\n", " \"notes\": \"For a stronger flavor, try a slightly finer grind.\"\n", " }\n", " return json.dumps(default_recommendation)\n", "```\n", "\n", "This improved module includes:\n", "\n", "1. A comprehensive coffee knowledge base with data on origins, roast levels, and flavor profiles\n", "2. A calculation function that applies coffee science principles to determine brewing parameters\n", "3. Enhanced data models using Pydantic for better type safety and validation\n", "4. Integration with the existing ControlFlow agent system\n", "5. Fallback mechanisms for error handling\n", "\n", "Would you like me to explain any specific part of this implementation in more detail?"]}, {"cell_type": "markdown", "metadata": {}, "source": ["**AI Prompt**\n", "\n", "The Recommended Coffee and Brewing Parameters are always the same, regardless of the inputs, at least when no image is attached."]}, {"cell_type": "markdown", "metadata": {}, "source": ["**AI Response**\n", "\n", "You're right about that issue. The current implementation always returns the same recommendation when no image is provided. Let's fix that by modifying the process_preferences function in app.py to use our recommender system properly.\n", "\n", "The problem is that when handling user preferences without an image, the app is just returning hardcoded values instead of using the recommender module. Here's what we need to change:\n", "\n", "1. In the process_preferences function, we should actually call the get_recommendation function from our recommender module\n", "2. We should parse the response and use it to populate the page dynamically\n", "\n", "Would you like me to show you how to update the process_preferences function in app.py to fix this issue?"]}, {"cell_type": "markdown", "metadata": {}, "source": ["**AI Prompt**\n", "\n", "yes"]}, {"cell_type": "markdown", "metadata": {}, "source": ["**AI Response**\n", "\n", "Here's how we can update the `process_preferences` function in app.py to properly use our recommender module:\n", "\n", "```python\n", "async def process_preferences(request):\n", " form = await request.form()\n", " intensity = form.get(\"intensity\")\n", " flavor = form.get(\"flavor\")\n", " acidity = form.get(\"acidity\")\n", " drink_type = form.get(\"drink_type\")\n", " \n", " # Collect user preferences\n", " preferences = {\n", " \"intensity\": intensity,\n", " \"flavor_profile\": flavor, # Note: Changed from \"flavor\" to match our model\n", " \"acidity\": acidity,\n", " \"drink_type\": drink_type\n", " }\n", " \n", " # Get recommendation using our recommender module\n", " recommendation_json = get_recommendation(preferences)\n", " recommendation = parse_agent_response(recommendation_json)\n", " \n", " return Titled(\"Next Level Coffee | nunc.\",\n", " Div(cls=\"max-w-5xl mx-auto px-4 py-8\")(\n", " Div(cls=\"flex justify-between items-center mb-8\")(\n", " H1(\"Perfect Cup of Coffee\", cls=\"title-main text-center mb-2\")\n", " ),\n", " H1(\"Your Coffee Match\", cls=\"text-4xl text-center mb-6\"),\n", " Div(cls=\"card rounded-lg p-6 shadow-md\")(\n", " H2(\"Based on Your Preferences\", cls=\"text-2xl mb-4\"),\n", " P(f\"Intensity: {intensity}\", cls=\"text-muted\"),\n", " P(f\"Flavor Profile: {flavor}\", cls=\"text-muted\"),\n", " P(f\"Acidity: {acidity}\", cls=\"text-muted\"),\n", " P(f\"Drink Type: {drink_type}\", cls=\"text-muted\"),\n", " \n", " Div(cls=\"mt-6 p-4 bg-white rounded-lg border border-gray-200\")(\n", " H3(\"Recommended Coffee\", cls=\"font-bold mb-2\"),\n", " P(recommendation.get(\"coffee_name\", \"Ethiopian Yirgacheffe\"), cls=\"text-lg\"),\n", " P(recommendation.get(\"description\", \"Light roast, fruity with citrus notes\"), cls=\"text-sm text-muted\"),\n", " \n", " H3(\"Brewing Parameters\", cls=\"font-bold mt-4 mb-2\"),\n", " P(f\"Grind Setting: {recommendation.get('grind_setting', 'Medium-fine')} \u03bcm\", cls=\"text-muted\"),\n", " P(f\"Water Temperature: {recommendation.get('brewing_temp', 92)}\u00b0C\", cls=\"text-muted\"),\n", " P(f\"Brewing Time: {recommendation.get('brewing_time', 150) // 60}:{recommendation.get('brewing_time', 150) % 60:02d} minutes\", cls=\"text-muted\"),\n", " P(f\"Flow Rate: {recommendation.get('flow_rate', 'Steady 2ml/s')}\", cls=\"text-muted\"),\n", " P(f\"Brew Ratio: {recommendation.get('brew_ratio', '1:2')}\", cls=\"text-muted\"),\n", " \n", " H3(\"Notes\", cls=\"font-bold mt-4 mb-2\") if recommendation.get(\"notes\") else \"\",\n", " P(recommendation.get(\"notes\", \"\"), cls=\"text-muted italic\")\n", " ),\n", " \n", " Div(cls=\"mt-6\")(\n", " A(\"\u2190 Back to Home\", href=\"/\", cls=\"text-accent hover:underline font-bold\")\n", " )\n", " )\n", " )\n", " )\n", "```\n", "\n", "This updated function:\n", "\n", "1. Collects user preferences from the form\n", "2. Passes them to our `get_recommendation` function\n", "3. Parses the JSON response\n", "4. Uses the recommendation data to populate the page dynamically\n", "\n", "Notice I also made a small change from \"flavor\" to \"flavor_profile\" to match our model's field name, and added display for flow rate and brew ratio parameters.\n", "\n", "Would you like me to suggest any other improvements to ensure the recommendations are properly personalized?"]}, {"cell_type": "markdown", "metadata": {}, "source": ["**AI Prompt**\n", "\n", "yes please"]}, {"cell_type": "markdown", "metadata": {}, "source": ["**AI Response**\n", "\n", "Here are additional improvements to ensure the recommendations are properly personalized:\n", "\n", "1. **Add Coffee Variety Options to the UI**\n", "\n", "We should expand the user input options to collect more detailed preferences. This could be added to the main form in app.py:\n", "\n", "```python\n", "Div(cls=\"mb-4\")(\n", " Label(\"Coffee Origin Preference:\", fr=\"origin_pref\", cls=\"block mb-2 font-bold\"),\n", " Select(id=\"origin_pref\", name=\"origin_pref\", cls=\"w-full p-2 border rounded bg-white\")(\n", " Option(\"No Preference\", value=\"any\", selected=\"selected\"),\n", " Option(\"African (Bright & Fruity)\", value=\"african\"),\n", " Option(\"Latin American (Balanced)\", value=\"latin_american\"),\n", " Option(\"Asian/Pacific (Earthy)\", value=\"asian_pacific\")\n", " )\n", ")\n", "```\n", "\n", "2. **Add Logging for Debugging**\n", "\n", "Let's add better logging to track what's happening with our recommendations:\n", "\n", "```python\n", "def get_recommendation(preferences, coffee_info=None):\n", " # Add at beginning of function\n", " print(f\"Generating recommendation with preferences: {preferences}\")\n", " if coffee_info:\n", " print(f\"Coffee info provided: {coffee_info}\")\n", " \n", " # Add before returning result\n", " print(f\"Generated recommendation: {result}\")\n", "```\n", "\n", "3. **Add Feedback Mechanism**\n", "\n", "We could add a feedback mechanism to improve recommendations over time:\n", "\n", "```python\n", "# Add to the results page\n", "Div(cls=\"mt-6 p-4 bg-white rounded-lg border border-gray-200\")(\n", " H3(\"How was this recommendation?\", cls=\"font-bold mb-2\"),\n", " Form(action=\"/feedback\", method=\"post\")(\n", " Input(type=\"hidden\", name=\"recommendation_id\", value=recommendation.get(\"coffee_name\", \"\")),\n", " Div(cls=\"flex space-x-4\")(\n", " Button(\"\ud83d\udc4d Perfect!\", type=\"submit\", name=\"rating\", value=\"good\", \n", " cls=\"px-4 py-2 bg-green-100 hover:bg-green-200 rounded\"),\n", " Button(\"\ud83d\udc4e Needs Improvement\", type=\"submit\", name=\"rating\", value=\"bad\", \n", " cls=\"px-4 py-2 bg-red-100 hover:bg-red-200 rounded\")\n", " )\n", " )\n", ")\n", "```\n", "\n", "4. **Personalize Based on Previous Selections**\n", "\n", "We could implement a simple session-based history to refine recommendations:\n", "\n", "```python\n", "# Add to app.py (near the top with other imports)\n", "from starlette.middleware.sessions import SessionMiddleware\n", "app.add_middleware(SessionMiddleware, secret_key=\"your-secret-key\")\n", "\n", "# Then in the process_preferences function\n", "async def process_preferences(request):\n", " # Get session data\n", " session = request.session\n", " if \"history\" not in session:\n", " session[\"history\"] = []\n", " \n", " # After getting the recommendation\n", " # Store this preference in history\n", " session[\"history\"].append({\n", " \"preferences\": preferences,\n", " \"recommendation\": recommendation\n", " })\n", " \n", " # Limit history size\n", " if len(session[\"history\"]) > 5:\n", " session[\"history\"] = session[\"history\"][-5:]\n", "```\n", "\n", "Would you like me to elaborate on any of these improvements or suggest additional enhancements?"]}, {"cell_type": "markdown", "metadata": {}, "source": ["**AI Prompt**\n", "\n", "Where do 3 & 4 actually go?"]}, {"cell_type": "markdown", "metadata": {}, "source": ["**AI Response**\n", "\n", "Good question! Let me clarify where enhancements 3 and 4 should be placed in the codebase:\n", "\n", "### 3. Feedback Mechanism\n", "\n", "The feedback mechanism UI would be added to the results page. Specifically:\n", "\n", "1. In the `process_preferences` function in app.py, add the feedback form to the returned HTML, just before the \"Back to Home\" link:\n", "\n", "```python\n", "async def process_preferences(request):\n", " # ... existing code ...\n", " \n", " return Titled(\"Next Level Coffee | nunc.\",\n", " Div(cls=\"max-w-5xl mx-auto px-4 py-8\")(\n", " # ... existing code ...\n", " Div(cls=\"card rounded-lg p-6 shadow-md\")(\n", " # ... existing code ...\n", " \n", " # Add the feedback mechanism here, before the \"Back to Home\" link\n", " Div(cls=\"mt-6 p-4 bg-white rounded-lg border border-gray-200\")(\n", " H3(\"How was this recommendation?\", cls=\"font-bold mb-2\"),\n", " Form(action=\"/feedback\", method=\"post\")(\n", " Input(type=\"hidden\", name=\"recommendation_id\", value=recommendation.get(\"coffee_name\", \"\")),\n", " Div(cls=\"flex space-x-4\")(\n", " Button(\"\ud83d\udc4d Perfect!\", type=\"submit\", name=\"rating\", value=\"good\", \n", " cls=\"px-4 py-2 bg-green-100 hover:bg-green-200 rounded\"),\n", " Button(\"\ud83d\udc4e Needs Improvement\", type=\"submit\", name=\"rating\", value=\"bad\", \n", " cls=\"px-4 py-2 bg-red-100 hover:bg-red-200 rounded\")\n", " )\n", " )\n", " ),\n", " \n", " Div(cls=\"mt-6\")(\n", " A(\"\u2190 Back to Home\", href=\"/\", cls=\"text-accent hover:underline font-bold\")\n", " )\n", " )\n", " )\n", " )\n", "```\n", "\n", "2. You would also need to add a new route to handle the feedback submission:\n", "\n", "```python\n", "@rt(\"/feedback\", methods=[\"POST\"])\n", "async def post_feedback(request):\n", " form = await request.form()\n", " recommendation_id = form.get(\"recommendation_id\")\n", " rating = form.get(\"rating\")\n", " \n", " # Here you would store the feedback in a database or log file\n", " print(f\"Feedback received: {rating} for recommendation {recommendation_id}\")\n", " \n", " # Redirect back to home with a thank you message\n", " return Titled(\"Feedback Received | nunc.\",\n", " Div(cls=\"max-w-4xl mx-auto px-4 py-8 text-center\")(\n", " H1(\"Thank You for Your Feedback!\", cls=\"text-4xl mb-6\"),\n", " P(\"Your input helps us improve our recommendations.\", cls=\"mb-6\"),\n", " A(\"\u2190 Back to Home\", href=\"/\", cls=\"text-accent hover:underline font-bold\")\n", " )\n", " )\n", "```\n", "\n", "### 4. Session-Based History\n", "\n", "For the session-based history:\n", "\n", "1. Add the SessionMiddleware to app.py near the top of the file, after creating the app:\n", "\n", "```python\n", "from starlette.middleware.sessions import SessionMiddleware\n", "\n", "app, rt = fast_app(hdrs=(\n", " # ... existing headers ...\n", "))\n", "\n", "# Add session middleware\n", "app.add_middleware(SessionMiddleware, secret_key=\"your-secret-key\")\n", "```\n", "\n", "2. Modify the `get_recommendation` function in modules/recommender.py to accept and use session history:\n", "\n", "```python\n", "def get_recommendation(preferences, coffee_info=None, history=None):\n", " \"\"\"Get coffee recommendations based on user preferences and coffee information\n", " \n", " Args:\n", " preferences (dict): User taste preferences (intensity, flavor, acidity, drink_type)\n", " coffee_info (dict, optional): Coffee information extracted from package image\n", " history (list, optional): Previous user preferences and recommendations\n", " \n", " Returns:\n", " dict: Detailed brewing recommendations\n", " \"\"\"\n", " try:\n", " # ... existing code ...\n", " \n", " # Add history context if available\n", " history_context = \"\"\n", " if history and len(history) > 0:\n", " history_context = \"\\nUser History:\\n\"\n", " for entry in history[-3:]: # Use last 3 entries\n", " history_context += f\"- Previously liked: {entry.get('recommendation', {}).get('coffee_name', 'Unknown')}\\n\"\n", " \n", " # Use ControlFlow to generate recommendations with a user message\n", " prompt = f\"\"\"\n", " Based on these coffee preferences:\n", " {preferences_str}\n", " {coffee_info_str}\n", " {params_str}\n", " {history_context}\n", " \n", " Provide a coffee recommendation with brewing parameters tailored to these preferences.\n", " Take into account the coffee origin, roast level, and user's taste preferences to provide accurate recommendations.\n", " Use the calculated parameters as a starting point but feel free to adjust them based on your expertise.\n", " \"\"\"\n", " \n", " # ... rest of existing code ...\n", " \n", " except Exception as e:\n", " # ... existing error handling ...\n", "```\n", "\n", "3. Then update the `process_preferences` function in app.py to pass the session history:\n", "\n", "```python\n", "async def process_preferences(request):\n", " # ... existing code ...\n", " \n", " # Get session data\n", " session = request.session\n", " if \"history\" not in session:\n", " session[\"history\"] = []\n", " \n", " # Get recommendation using our recommender module with history\n", " recommendation_json = get_recommendation(preferences, history=session.get(\"history\", []))\n", " recommendation = parse_agent_response(recommendation_json)\n", " \n", " # Store this preference in history\n", " session[\"history\"].append({\n", " \"preferences\": preferences,\n", " \"recommendation\": recommendation\n", " })\n", " \n", " # Limit history size\n", " if len(session[\"history\"]) > 5:\n", " session[\"history\"] = session[\"history\"][-5:]\n", " \n", " # ... rest of existing code ...\n", "```\n", "\n", "Does this clarify where these enhancements should be placed in the codebase?"]}, {"cell_type": "markdown", "metadata": {}, "source": ["**AI Prompt**\n", "\n", "Show process_preferences in complete"]}, {"cell_type": "markdown", "metadata": {}, "source": ["**AI Response**\n", "\n", "Here's the complete `process_preferences` function with all the enhancements incorporated:\n", "\n", "```python\n", "async def process_preferences(request):\n", " form = await request.form()\n", " intensity = form.get(\"intensity\")\n", " flavor = form.get(\"flavor\")\n", " acidity = form.get(\"acidity\")\n", " drink_type = form.get(\"drink_type\")\n", " origin_pref = form.get(\"origin_pref\", \"any\")\n", " \n", " # Collect user preferences\n", " preferences = {\n", " \"intensity\": intensity,\n", " \"flavor_profile\": flavor, # Note: Changed from \"flavor\" to match our model\n", " \"acidity\": acidity,\n", " \"drink_type\": drink_type,\n", " \"origin_preference\": origin_pref\n", " }\n", " \n", " # Get session data\n", " session = request.session\n", " if \"history\" not in session:\n", " session[\"history\"] = []\n", " \n", " # Get recommendation using our recommender module with history\n", " recommendation_json = get_recommendation(preferences, history=session.get(\"history\", []))\n", " recommendation = parse_agent_response(recommendation_json)\n", " \n", " # Store this preference in history\n", " session[\"history\"].append({\n", " \"preferences\": preferences,\n", " \"recommendation\": recommendation\n", " })\n", " \n", " # Limit history size\n", " if len(session[\"history\"]) > 5:\n", " session[\"history\"] = session[\"history\"][-5:]\n", " \n", " return Titled(\"Next Level Coffee | nunc.\",\n", " Div(cls=\"max-w-5xl mx-auto px-4 py-8\")(\n", " Div(cls=\"flex justify-between items-center mb-8\")(\n", " H1(\"Perfect Cup of Coffee\", cls=\"title-main text-center mb-2\")\n", " ),\n", " H1(\"Your Coffee Match\", cls=\"text-4xl text-center mb-6\"),\n", " Div(cls=\"card rounded-lg p-6 shadow-md\")(\n", " H2(\"Based on Your Preferences\", cls=\"text-2xl mb-4\"),\n", " P(f\"Intensity: {intensity}\", cls=\"text-muted\"),\n", " P(f\"Flavor Profile: {flavor}\", cls=\"text-muted\"),\n", " P(f\"Acidity: {acidity}\", cls=\"text-muted\"),\n", " P(f\"Drink Type: {drink_type}\", cls=\"text-muted\"),\n", " P(f\"Origin Preference: {origin_pref}\", cls=\"text-muted\") if origin_pref != \"any\" else \"\",\n", " \n", " Div(cls=\"mt-6 p-4 bg-white rounded-lg border border-gray-200\")(\n", " H3(\"Recommended Coffee\", cls=\"font-bold mb-2\"),\n", " P(recommendation.get(\"coffee_name\", \"Ethiopian Yirgacheffe\"), cls=\"text-lg\"),\n", " P(recommendation.get(\"description\", \"Light roast, fruity with citrus notes\"), cls=\"text-sm text-muted\"),\n", " \n", " H3(\"Origin\", cls=\"font-bold mt-4 mb-2\"),\n", " P(recommendation.get(\"origin\", \"Ethiopia\"), cls=\"text-muted\"),\n", " \n", " H3(\"Brewing Parameters\", cls=\"font-bold mt-4 mb-2\"),\n", " P(f\"Grind Setting: {recommendation.get('grind_setting', 'Medium-fine')} \u03bcm\", cls=\"text-muted\"),\n", " P(f\"Water Temperature: {recommendation.get('brewing_temp', 92)}\u00b0C\", cls=\"text-muted\"),\n", " P(f\"Brewing Time: {recommendation.get('brewing_time', 150) // 60}:{recommendation.get('brewing_time', 150) % 60:02d} minutes\", cls=\"text-muted\"),\n", " P(f\"Flow Rate: {recommendation.get('flow_rate', 'Steady 2ml/s')}\", cls=\"text-muted\"),\n", " P(f\"Brew Ratio: {recommendation.get('brew_ratio', '1:2')}\", cls=\"text-muted\"),\n", " \n", " H3(\"Notes\", cls=\"font-bold mt-4 mb-2\") if recommendation.get(\"notes\") else \"\",\n", " P(recommendation.get(\"notes\", \"\"), cls=\"text-muted italic\")\n", " ),\n", " \n", " # Feedback mechanism\n", " Div(cls=\"mt-6 p-4 bg-white rounded-lg border border-gray-200\")(\n", " H3(\"How was this recommendation?\", cls=\"font-bold mb-2\"),\n", " Form(action=\"/feedback\", method=\"post\")(\n", " Input(type=\"hidden\", name=\"recommendation_id\", value=recommendation.get(\"coffee_name\", \"\")),\n", " Div(cls=\"flex space-x-4\")(\n", " Button(\"\ud83d\udc4d Perfect!\", type=\"submit\", name=\"rating\", value=\"good\", \n", " cls=\"px-4 py-2 bg-green-100 hover:bg-green-200 rounded\"),\n", " Button(\"\ud83d\udc4e Needs Improvement\", type=\"submit\", name=\"rating\", value=\"bad\", \n", " cls=\"px-4 py-2 bg-red-100 hover:bg-red-200 rounded\")\n", " )\n", " )\n", " ),\n", " \n", " # Previous recommendations (if any)\n", " Div(cls=\"mt-6 p-4 bg-white rounded-lg border border-gray-200 hidden\" if len(session.get(\"history\", [])) <= 1 else \"mt-6 p-4 bg-white rounded-lg border border-gray-200\")(\n", " H3(\"Your Previous Recommendations\", cls=\"font-bold mb-2\"),\n", " Ul(cls=\"list-disc pl-5\")(\n", " *[Li(f\"{h['recommendation'].get('coffee_name', 'Unknown')} ({h['preferences'].get('intensity', '')} - {h['preferences'].get('flavor_profile', '')})\") \n", " for h in session.get(\"history\", [])[:-1][-3:]] # Last 3 excluding current\n", " )\n", " ),\n", " \n", " Div(cls=\"mt-6\")(\n", " A(\"\u2190 Back to Home\", href=\"/\", cls=\"text-accent hover:underline font-bold\")\n", " )\n", " )\n", " )\n", " )\n", "```\n", "\n", "This complete function:\n", "\n", "1. Collects all user preferences including the new origin preference\n", "2. Manages session history for personalization\n", "3. Calls the recommender with the history context\n", "4. Displays the recommendation with all brewing parameters\n", "5. Shows a feedback mechanism\n", "6. Displays previous recommendations (if any)\n", "7. Formats everything in a clean, user-friendly layout\n", "\n", "The function now provides a much more personalized experience by considering user history and allowing for feedback."]}, {"cell_type": "markdown", "metadata": {}, "source": ["**AI Prompt**\n", "\n", "Show post_feedback in complete"]}, {"cell_type": "markdown", "metadata": {}, "source": ["**AI Response**\n", "\n", "Here's the complete `post_feedback` function to handle user feedback submissions:\n", "\n", "```python\n", "@rt(\"/feedback\", methods=[\"POST\"])\n", "async def post_feedback(request):\n", " form = await request.form()\n", " recommendation_id = form.get(\"recommendation_id\")\n", " rating = form.get(\"rating\")\n", " \n", " # Get current timestamp\n", " from datetime import datetime\n", " timestamp = datetime.now().strftime(\"%Y-%m-%d %H:%M:%S\")\n", " \n", " # Here you would store the feedback in a database or log file\n", " # For now, we'll just log it to the console and a simple feedback log file\n", " feedback_data = {\n", " \"timestamp\": timestamp,\n", " \"recommendation_id\": recommendation_id,\n", " \"rating\": rating\n", " }\n", " \n", " print(f\"Feedback received: {rating} for recommendation {recommendation_id}\")\n", " \n", " # Save feedback to a log file\n", " try:\n", " import os\n", " import json\n", " \n", " # Create directory if it doesn't exist\n", " os.makedirs(\"data\", exist_ok=True)\n", " \n", " # Append to feedback log file\n", " with open(\"data/feedback_log.jsonl\", \"a\") as f:\n", " f.write(json.dumps(feedback_data) + \"\\n\")\n", " except Exception as e:\n", " print(f\"Error saving feedback: {e}\")\n", " \n", " # Update session data to record this feedback\n", " session = request.session\n", " if \"history\" in session and len(session[\"history\"]) > 0:\n", " # Add rating to the most recent recommendation\n", " session[\"history\"][-1][\"feedback\"] = rating\n", " \n", " # Prepare thank you message based on the rating\n", " thank_you_message = \"Thank you for your positive feedback!\" if rating == \"good\" else \"Thank you for helping us improve!\"\n", " follow_up_message = \"We're glad you enjoyed this recommendation!\" if rating == \"good\" else \"We'll use your feedback to improve our recommendations.\"\n", " \n", " # Redirect back to home with a thank you message\n", " return Titled(\"Feedback Received | nunc.\",\n", " Div(cls=\"max-w-4xl mx-auto px-4 py-8 text-center\")(\n", " H1(thank_you_message, cls=\"text-4xl mb-6\"),\n", " P(follow_up_message, cls=\"mb-6\"),\n", " P(f\"Recommendation: {recommendation_id}\", cls=\"text-sm text-muted mb-8\"),\n", " \n", " # Offer next steps\n", " Div(cls=\"flex justify-center space-x-4\")(\n", " A(\"\u2190 Back to Home\", href=\"/\", \n", " cls=\"px-6 py-3 bg-accent text-dark font-bold rounded-lg hover:bg-accent-hover\"),\n", " A(\"Try Another Recommendation\", href=\"/#coffee-form\", \n", " cls=\"px-6 py-3 border border-accent text-accent font-bold rounded-lg hover:bg-accent hover:text-dark\")\n", " )\n", " )\n", " )\n", "```\n", "\n", "This function:\n", "\n", "1. Captures the feedback (recommendation ID and rating) from the form submission\n", "2. Records a timestamp for the feedback\n", "3. Logs the feedback to the console\n", "4. Saves the feedback to a JSON Lines file in a data directory\n", "5. Updates the user's session history with their feedback\n", "6. Displays a personalized thank you message based on whether the feedback was positive or negative\n", "7. Provides clear next steps for the user (return home or try another recommendation)\n", "\n", "The function creates a complete feedback loop that both stores the user's opinion and provides a good user experience with appropriate follow-up options."]}, {"cell_type": "markdown", "metadata": {}, "source": ["**AI Prompt**\n", "\n", "Show get_recommendation in complete"]}, {"cell_type": "markdown", "metadata": {}, "source": ["**AI Response**\n", "\n", "Here's the complete `get_recommendation` function with all enhancements:\n", "\n", "```python\n", "def get_recommendation(preferences, coffee_info=None, history=None):\n", " \"\"\"Get coffee recommendations based on user preferences and coffee information\n", " \n", " Args:\n", " preferences (dict): User taste preferences (intensity, flavor_profile, acidity, drink_type)\n", " coffee_info (dict, optional): Coffee information extracted from package image\n", " history (list, optional): Previous user preferences and recommendations\n", " \n", " Returns:\n", " dict: Detailed brewing recommendations\n", " \"\"\"\n", " try:\n", " # Print debug information\n", " print(f\"Generating recommendation with preferences: {preferences}\")\n", " if coffee_info:\n", " print(f\"Coffee info provided: {coffee_info}\")\n", " if history:\n", " print(f\"User has {len(history)} previous recommendations\")\n", " \n", " # Get recommendation agent\n", " agents = setup_agents()\n", " recommendation_agent = agents[\"recommendation_agent\"]\n", " \n", " # Initialize knowledge base\n", " kb = CoffeeKnowledgeBase()\n", " \n", " # Calculate initial parameters from knowledge base\n", " calculated_params = kb.calculate_parameters(coffee_info, preferences)\n", " \n", " # Format preferences for the agent\n", " preferences_str = \"\\n\".join([f\"- {key}: {value}\" for key, value in preferences.items()])\n", " \n", " # Add coffee information if available\n", " coffee_info_str = \"\"\n", " if coffee_info:\n", " coffee_info_str = \"\\nCoffee Information:\\n\" + \"\\n\".join([f\"- {key}: {value}\" for key, value in coffee_info.items()])\n", " \n", " # Add calculated parameters to context\n", " params_str = \"\\nCalculated Parameters:\\n\" + \"\\n\".join([f\"- {key}: {value}\" for key, value in calculated_params.items()])\n", " \n", " # Add history context if available\n", " history_context = \"\"\n", " if history and len(history) > 0:\n", " history_context = \"\\nUser History:\\n\"\n", " for i, entry in enumerate(history[-3:]): # Use last 3 entries\n", " rec = entry.get('recommendation', {})\n", " pref = entry.get('preferences', {})\n", " feedback = entry.get('feedback', 'unknown')\n", " \n", " history_context += f\"- Previous recommendation {i+1}: {rec.get('coffee_name', 'Unknown')} \"\n", " history_context += f\"(Origin: {rec.get('origin', 'Unknown')}, \"\n", " history_context += f\"Roast: {rec.get('roast_level', 'Unknown')})\\n\"\n", " history_context += f\" User preferences: {pref.get('intensity', '')}, {pref.get('flavor_profile', '')}, {pref.get('acidity', '')}\\n\"\n", " history_context += f\" User feedback: {feedback}\\n\"\n", " \n", " # Adjust for origin preference if specified\n", " origin_context = \"\"\n", " if preferences.get('origin_preference') and preferences.get('origin_preference') != 'any':\n", " origin_pref = preferences.get('origin_preference')\n", " origin_mapping = {\n", " 'african': \"African coffees like Ethiopian, Kenyan, or Rwandan that are known for bright acidity and fruity notes\",\n", " 'latin_american': \"Latin American coffees like Colombian, Costa Rican, or Guatemalan that are known for balance and chocolate notes\",\n", " 'asian_pacific': \"Asian/Pacific coffees like Sumatran, Indonesian, or Vietnamese that are known for earthy, spicy notes and full body\"\n", " }\n", " if origin_pref in origin_mapping:\n", " origin_context = f\"\\nUser has expressed preference for {origin_mapping[origin_pref]}.\"\n", " \n", " # Use ControlFlow to generate recommendations with a user message\n", " prompt = f\"\"\"\n", " Based on these coffee preferences:\n", " {preferences_str}\n", " {coffee_info_str}\n", " {params_str}\n", " {history_context}\n", " {origin_context}\n", " \n", " Provide a coffee recommendation with brewing parameters tailored to these preferences.\n", " Take into account the coffee origin, roast level, and user's taste preferences to provide accurate recommendations.\n", " Use the calculated parameters as a starting point but feel free to adjust them based on your expertise.\n", " \n", " If the user has provided feedback on previous recommendations, use this to refine your recommendation.\n", " Positive feedback suggests similar coffees, while negative feedback suggests trying different characteristics.\n", " \"\"\"\n", " \n", " # Create a structured output model\n", " recommendation = recommendation_agent.run(prompt, result_type=BrewingRecommendation)\n", " \n", " # Convert to dictionary for JSON serialization\n", " result = recommendation.model_dump()\n", " \n", " # Print the final recommendation for debugging\n", " print(f\"Generated recommendation: {result}\")\n", " \n", " return json.dumps(result)\n", " \n", " except Exception as e:\n", " print(f\"Error generating recommendation: {e}\")\n", " # Return a default recommendation if there's an error\n", " default_recommendation = {\n", " \"coffee_name\": \"House Blend\",\n", " \"description\": \"A balanced, medium roast coffee with notes of chocolate and nuts\",\n", " \"origin\": \"Brazil\",\n", " \"roast_level\": \"Medium\",\n", " \"flow_rate\": \"Steady 2ml/s\",\n", " \"brewing_temp\": 93,\n", " \"grind_setting\": 120,\n", " \"brew_ratio\": \"1:2\",\n", " \"brewing_time\": 28,\n", " \"notes\": \"For a stronger flavor, try a slightly finer grind.\"\n", " }\n", " return json.dumps(default_recommendation)\n", "```\n", "\n", "This enhanced function:\n", "\n", "1. Adds detailed logging for debugging purposes\n", "2. Handles user history to personalize recommendations\n", "3. Processes user feedback from previous recommendations\n", "4. Adds special handling for origin preferences\n", "5. Provides a more detailed prompt to the AI agent with contextual information\n", "6. Maintains robust error handling with fallback to default recommendations\n", "7. Uses the knowledge base to calculate initial parameters\n", "8. Returns structured, consistent recommendations via Pydantic models\n", "\n", "The function now provides truly personalized recommendations by considering the user's past interactions, their specific preferences, and applying coffee science principles through the knowledge base."]}, {"cell_type": "markdown", "metadata": {}, "source": ["**AI Prompt**\n", "\n", "It is missing the extra user input for the feedback. Where exactly does that go? Show complete functions please."]}, {"cell_type": "markdown", "metadata": {}, "source": ["**AI Response**\n", "\n", "You're right. We need to add a way for users to provide more detailed feedback beyond just the thumbs up/down. Here's the complete implementation with the enhanced feedback functionality:\n", "\n", "First, let's update the `process_preferences` function with the feedback form that includes a text area for detailed comments:\n", "\n", "```python\n", "async def process_preferences(request):\n", " form = await request.form()\n", " intensity = form.get(\"intensity\")\n", " flavor = form.get(\"flavor\")\n", " acidity = form.get(\"acidity\")\n", " drink_type = form.get(\"drink_type\")\n", " origin_pref = form.get(\"origin_pref\", \"any\")\n", " \n", " # Collect user preferences\n", " preferences = {\n", " \"intensity\": intensity,\n", " \"flavor_profile\": flavor, # Note: Changed from \"flavor\" to match our model\n", " \"acidity\": acidity,\n", " \"drink_type\": drink_type,\n", " \"origin_preference\": origin_pref\n", " }\n", " \n", " # Get session data\n", " session = request.session\n", " if \"history\" not in session:\n", " session[\"history\"] = []\n", " \n", " # Get recommendation using our recommender module with history\n", " recommendation_json = get_recommendation(preferences, history=session.get(\"history\", []))\n", " recommendation = parse_agent_response(recommendation_json)\n", " \n", " # Store this preference in history\n", " session[\"history\"].append({\n", " \"preferences\": preferences,\n", " \"recommendation\": recommendation\n", " })\n", " \n", " # Limit history size\n", " if len(session[\"history\"]) > 5:\n", " session[\"history\"] = session[\"history\"][-5:]\n", " \n", " return Titled(\"Next Level Coffee | nunc.\",\n", " Div(cls=\"max-w-5xl mx-auto px-4 py-8\")(\n", " Div(cls=\"flex justify-between items-center mb-8\")(\n", " H1(\"Perfect Cup of Coffee\", cls=\"title-main text-center mb-2\")\n", " ),\n", " H1(\"Your Coffee Match\", cls=\"text-4xl text-center mb-6\"),\n", " Div(cls=\"card rounded-lg p-6 shadow-md\")(\n", " H2(\"Based on Your Preferences\", cls=\"text-2xl mb-4\"),\n", " P(f\"Intensity: {intensity}\", cls=\"text-muted\"),\n", " P(f\"Flavor Profile: {flavor}\", cls=\"text-muted\"),\n", " P(f\"Acidity: {acidity}\", cls=\"text-muted\"),\n", " P(f\"Drink Type: {drink_type}\", cls=\"text-muted\"),\n", " P(f\"Origin Preference: {origin_pref}\", cls=\"text-muted\") if origin_pref != \"any\" else \"\",\n", " \n", " Div(cls=\"mt-6 p-4 bg-white rounded-lg border border-gray-200\")(\n", " H3(\"Recommended Coffee\", cls=\"font-bold mb-2\"),\n", " P(recommendation.get(\"coffee_name\", \"Ethiopian Yirgacheffe\"), cls=\"text-lg\"),\n", " P(recommendation.get(\"description\", \"Light roast, fruity with citrus notes\"), cls=\"text-sm text-muted\"),\n", " \n", " H3(\"Origin\", cls=\"font-bold mt-4 mb-2\"),\n", " P(recommendation.get(\"origin\", \"Ethiopia\"), cls=\"text-muted\"),\n", " \n", " H3(\"Brewing Parameters\", cls=\"font-bold mt-4 mb-2\"),\n", " P(f\"Grind Setting: {recommendation.get('grind_setting', 'Medium-fine')} \u03bcm\", cls=\"text-muted\"),\n", " P(f\"Water Temperature: {recommendation.get('brewing_temp', 92)}\u00b0C\", cls=\"text-muted\"),\n", " P(f\"Brewing Time: {recommendation.get('brewing_time', 150) // 60}:{recommendation.get('brewing_time', 150) % 60:02d} minutes\", cls=\"text-muted\"),\n", " P(f\"Flow Rate: {recommendation.get('flow_rate', 'Steady 2ml/s')}\", cls=\"text-muted\"),\n", " P(f\"Brew Ratio: {recommendation.get('brew_ratio', '1:2')}\", cls=\"text-muted\"),\n", " \n", " H3(\"Notes\", cls=\"font-bold mt-4 mb-2\") if recommendation.get(\"notes\") else \"\",\n", " P(recommendation.get(\"notes\", \"\"), cls=\"text-muted italic\")\n", " ),\n", " \n", " # Enhanced feedback mechanism with comments\n", " Div(cls=\"mt-6 p-4 bg-white rounded-lg border border-gray-200\")(\n", " H3(\"How was this recommendation?\", cls=\"font-bold mb-2\"),\n", " Form(action=\"/feedback\", method=\"post\")(\n", " Input(type=\"hidden\", name=\"recommendation_id\", value=recommendation.get(\"coffee_name\", \"\")),\n", " Div(cls=\"flex space-x-4 mb-4\")(\n", " Button(\"\ud83d\udc4d Perfect!\", type=\"submit\", name=\"rating\", value=\"good\", \n", " cls=\"px-4 py-2 bg-green-100 hover:bg-green-200 rounded\"),\n", " Button(\"\ud83d\udc4e Needs Improvement\", type=\"submit\", name=\"rating\", value=\"bad\", \n", " cls=\"px-4 py-2 bg-red-100 hover:bg-red-200 rounded\")\n", " ),\n", " Div(cls=\"mt-4\")(\n", " Label(\"Your Comments (Optional):\", fr=\"feedback_comments\", cls=\"block mb-2 text-sm font-bold\"),\n", " TextArea(id=\"feedback_comments\", name=\"feedback_comments\", placeholder=\"What did you like or dislike about this recommendation?\", \n", " cls=\"w-full p-2 border rounded bg-white h-24\"),\n", " P(\"Your feedback helps us improve our recommendations.\", cls=\"text-xs text-muted mt-1\")\n", " ),\n", " Div(cls=\"mt-4\")(\n", " Button(\"Submit Feedback\", type=\"submit\", cls=\"px-4 py-2 bg-accent text-dark hover:bg-accent-hover rounded font-bold\")\n", " )\n", " )\n", " ),\n", " \n", " # Previous recommendations (if any)\n", " Div(cls=\"mt-6 p-4 bg-white rounded-lg border border-gray-200 hidden\" if len(session.get(\"history\", [])) <= 1 else \"mt-6 p-4 bg-white rounded-lg border border-gray-200\")(\n", " H3(\"Your Previous Recommendations\", cls=\"font-bold mb-2\"),\n", " Ul(cls=\"list-disc pl-5\")(\n", " *[Li(f\"{h['recommendation'].get('coffee_name', 'Unknown')} ({h['preferences'].get('intensity', '')} - {h['preferences'].get('flavor_profile', '')})\") \n", " for h in session.get(\"history\", [])[:-1][-3:]] # Last 3 excluding current\n", " )\n", " ),\n", " \n", " Div(cls=\"mt-6\")(\n", " A(\"\u2190 Back to Home\", href=\"/\", cls=\"text-accent hover:underline font-bold\")\n", " )\n", " )\n", " )\n", " )\n", "```\n", "\n", "Now, let's update the `post_feedback` function to handle the detailed comments:\n", "\n", "```python\n", "@rt(\"/feedback\", methods=[\"POST\"])\n", "async def post_feedback(request):\n", " form = await request.form()\n", " recommendation_id = form.get(\"recommendation_id\")\n", " rating = form.get(\"rating\")\n", " comments = form.get(\"feedback_comments\", \"\")\n", " \n", " # Get current timestamp\n", " from datetime import datetime\n", " timestamp = datetime.now().strftime(\"%Y-%m-%d %H:%M:%S\")\n", " \n", " # Here you would store the feedback in a database or log file\n", " # For now, we'll just log it to the console and a simple feedback log file\n", " feedback_data = {\n", " \"timestamp\": timestamp,\n", " \"recommendation_id\": recommendation_id,\n", " \"rating\": rating,\n", " \"comments\": comments\n", " }\n", " \n", " print(f\"Feedback received: {rating} for recommendation {recommendation_id}\")\n", " if comments:\n", " print(f\"User comments: {comments}\")\n", " \n", " # Save feedback to a log file\n", " try:\n", " import os\n", " import json\n", " \n", " # Create directory if it doesn't exist\n", " os.makedirs(\"data\", exist_ok=True)\n", " \n", " # Append to feedback log file\n", " with open(\"data/feedback_log.jsonl\", \"a\") as f:\n", " f.write(json.dumps(feedback_data) + \"\\n\")\n", " except Exception as e:\n", " print(f\"Error saving feedback: {e}\")\n", " \n", " # Update session data to record this feedback\n", " session = request.session\n", " if \"history\" in session and len(session[\"history\"]) > 0:\n", " # Add rating and comments to the most recent recommendation\n", " session[\"history\"][-1][\"feedback\"] = rating\n", " if comments:\n", " session[\"history\"][-1][\"feedback_comments\"] = comments\n", " \n", " # Prepare thank you message based on the rating\n", " thank_you_message = \"Thank you for your positive feedback!\" if rating == \"good\" else \"Thank you for helping us improve!\"\n", " follow_up_message = \"We're glad you enjoyed this recommendation!\" if rating == \"good\" else \"We'll use your feedback to improve our recommendations.\"\n", " \n", " # Add comment acknowledgment if provided\n", " comment_acknowledgment = \"\"\n", " if comments:\n", " comment_acknowledgment = P(\"We've recorded your detailed comments. This helps us understand your preferences better.\", cls=\"mb-4\")\n", " \n", " # Redirect back to home with a thank you message\n", " return Titled(\"Feedback Received | nunc.\",\n", " Div(cls=\"max-w-4xl mx-auto px-4 py-8 text-center\")(\n", " H1(thank_you_message, cls=\"text-4xl mb-6\"),\n", " P(follow_up_message, cls=\"mb-4\"),\n", " comment_acknowledgment,\n", " P(f\"Recommendation: {recommendation_id}\", cls=\"text-sm text-muted mb-8\"),\n", " \n", " # Offer next steps\n", " Div(cls=\"flex justify-center space-x-4\")(\n", " A(\"\u2190 Back to Home\", href=\"/\", \n", " cls=\"px-6 py-3 bg-accent text-dark font-bold rounded-lg hover:bg-accent-hover\"),\n", " A(\"Try Another Recommendation\", href=\"/#coffee-form\", \n", " cls=\"px-6 py-3 border border-accent text-accent font-bold rounded-lg hover:bg-accent hover:text-dark\")\n", " )\n", " )\n", " )\n", "```\n", "\n", "Finally, let's update the `get_recommendation` function to incorporate the detailed feedback comments:\n", "\n", "```python\n", "def get_recommendation(preferences, coffee_info=None, history=None):\n", " \"\"\"Get coffee recommendations based on user preferences and coffee information\n", " \n", " Args:\n", " preferences (dict): User taste preferences (intensity, flavor_profile, acidity, drink_type)\n", " coffee_info (dict, optional): Coffee information extracted from package image\n", " history (list, optional): Previous user preferences and recommendations\n", " \n", " Returns:\n", " dict: Detailed brewing recommendations\n", " \"\"\"\n", " try:\n", " # Print debug information\n", " print(f\"Generating recommendation with preferences: {preferences}\")\n", " if coffee_info:\n", " print(f\"Coffee info provided: {coffee_info}\")\n", " if history:\n", " print(f\"User has {len(history)} previous recommendations\")\n", " \n", " # Get recommendation agent\n", " agents = setup_agents()\n", " recommendation_agent = agents[\"recommendation_agent\"]\n", " \n", " # Initialize knowledge base\n", " kb = CoffeeKnowledgeBase()\n", " \n", " # Calculate initial parameters from knowledge base\n", " calculated_params = kb.calculate_parameters(coffee_info, preferences)\n", " \n", " # Format preferences for the agent\n", " preferences_str = \"\\n\".join([f\"- {key}: {value}\" for key, value in preferences.items()])\n", " \n", " # Add coffee information if available\n", " coffee_info_str = \"\"\n", " if coffee_info:\n", " coffee_info_str = \"\\nCoffee Information:\\n\" + \"\\n\".join([f\"- {key}: {value}\" for key, value in coffee_info.items()])\n", " \n", " # Add calculated parameters to context\n", " params_str = \"\\nCalculated Parameters:\\n\" + \"\\n\".join([f\"- {key}: {value}\" for key, value in calculated_params.items()])\n", " \n", " # Add history context if available\n", " history_context = \"\"\n", " if history and len(history) > 0:\n", " history_context = \"\\nUser History:\\n\"\n", " for i, entry in enumerate(history[-3:]): # Use last 3 entries\n", " rec = entry.get('recommendation', {})\n", " pref = entry.get('preferences', {})\n", " feedback = entry.get('feedback', 'unknown')\n", " comments = entry.get('feedback_comments', '')\n", " \n", " history_context += f\"- Previous recommendation {i+1}: {rec.get('coffee_name', 'Unknown')} \"\n", " history_context += f\"(Origin: {rec.get('origin', 'Unknown')}, \"\n", " history_context += f\"Roast: {rec.get('roast_level', 'Unknown')})\\n\"\n", " history_context += f\" User preferences: {pref.get('intensity', '')}, {pref.get('flavor_profile', '')}, {pref.get('acidity', '')}\\n\"\n", " history_context += f\" User feedback: {feedback}\\n\"\n", " if comments:\n", " history_context += f\" User comments: \\\"{comments}\\\"\\n\"\n", " \n", " # Adjust for origin preference if specified\n", " origin_context = \"\"\n", " if preferences.get('origin_preference') and preferences.get('origin_preference') != 'any':\n", " origin_pref = preferences.get('origin_preference')\n", " origin_mapping = {\n", " 'african': \"African coffees like Ethiopian, Kenyan, or Rwandan that are known for bright acidity and fruity notes\",\n", " 'latin_american': \"Latin American coffees like Colombian, Costa Rican, or Guatemalan that are known for balance and chocolate notes\",\n", " 'asian_pacific': \"Asian/Pacific coffees like Sumatran, Indonesian, or Vietnamese that are known for earthy, spicy notes and full body\"\n", " }\n", " if origin_pref in origin_mapping:\n", " origin_context = f\"\\nUser has expressed preference for {origin_mapping[origin_pref]}.\"\n", " \n", " # Use ControlFlow to generate recommendations with a user message\n", " prompt = f\"\"\"\n", " Based on these coffee preferences:\n", " {preferences_str}\n", " {coffee_info_str}\n", " {params_str}\n", " {history_context}\n", " {origin_context}\n", " \n", " Provide a coffee recommendation with brewing parameters tailored to these preferences.\n", " Take into account the coffee origin, roast level, and user's taste preferences to provide accurate recommendations.\n", " Use the calculated parameters as a starting point but feel free to adjust them based on your expertise.\n", " \n", " If the user has provided feedback on previous recommendations, use this to refine your recommendation.\n", " Pay special attention to any detailed comments the user has provided about previous recommendations.\n", " Positive feedback suggests similar coffees, while negative feedback suggests trying different characteristics.\n", " \"\"\"\n", " \n", " # Create a structured output model\n", " recommendation = recommendation_agent.run(prompt, result_type=BrewingRecommendation)\n", " \n", " # Convert to dictionary for JSON serialization\n", " result = recommendation.model_dump()\n", " \n", " # Print the final recommendation for debugging\n", " print(f\"Generated recommendation: {result}\")\n", " \n", " return json.dumps(result)\n", " \n", " except Exception as e:\n", " print(f\"Error generating recommendation: {e}\")\n", " # Return a default recommendation if there's an error\n", " default_recommendation = {\n", " \"coffee_name\": \"House Blend\",\n", " \"description\": \"A balanced, medium roast coffee with notes of chocolate and nuts\",\n", " \"origin\": \"Brazil\",\n", " \"roast_level\": \"Medium\",\n", " \"flow_rate\": \"Steady 2ml/s\",\n", " \"brewing_temp\": 93,\n", " \"grind_setting\": 120,\n", " \"brew_ratio\": \"1:2\",\n", " \"brewing_time\": 28,\n", " \"notes\": \"For a stronger flavor, try a slightly finer grind.\"\n", " }\n", " return json.dumps(default_recommendation)\n", "```\n", "\n", "These complete functions now include:\n", "\n", "1. A text area in the feedback form for users to provide detailed comments\n", "2. Storage of those comments in both the feedback log and session history\n", "3. Incorporation of detailed feedback comments in the recommendation prompt\n", "4. Acknowledgment of the user's comments in the thank you page\n", "\n", "This creates a much more comprehensive feedback system that can capture qualitative information about what"]}, {"cell_type": "markdown", "metadata": {}, "source": ["**AI Prompt**\n", "\n", "Traceback (most recent call last):\n", " File \"/home/chkn/.pyenv/versions/coffee_hack/lib/python3.11/site-packages/uvicorn/protocols/http/httptools_impl.py\", line 409, in run_asgi\n", " result = await app( # type: ignore[func-returns-value]\n", " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", " File \"/home/chkn/.pyenv/versions/coffee_hack/lib/python3.11/site-packages/uvicorn/middleware/proxy_headers.py\", line 60, in __call__\n", " return await self.app(scope, receive, send)\n", " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", " File \"/home/chkn/.pyenv/versions/coffee_hack/lib/python3.11/site-packages/starlette/applications.py\", line 112, in __call__\n", " await self.middleware_stack(scope, receive, send)\n", " File \"/home/chkn/.pyenv/versions/coffee_hack/lib/python3.11/site-packages/starlette/middleware/errors.py\", line 187, in __call__\n", " raise exc\n", " File \"/home/chkn/.pyenv/versions/coffee_hack/lib/python3.11/site-packages/starlette/middleware/errors.py\", line 165, in __call__\n", " await self.app(scope, receive, _send)\n", " File \"/home/chkn/.pyenv/versions/coffee_hack/lib/python3.11/site-packages/starlette/middleware/sessions.py\", line 85, in __call__\n", " await self.app(scope, receive, send_wrapper)\n", " File \"/home/chkn/.pyenv/versions/coffee_hack/lib/python3.11/site-packages/starlette/middleware/exceptions.py\", line 62, in __call__\n", " await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)\n", " File \"/home/chkn/.pyenv/versions/coffee_hack/lib/python3.11/site-packages/starlette/_exception_handler.py\", line 53, in wrapped_app\n", " raise exc\n", " File \"/home/chkn/.pyenv/versions/coffee_hack/lib/python3.11/site-packages/starlette/_exception_handler.py\", line 42, in wrapped_app\n", " await app(scope, receive, sender)\n", " File \"/home/chkn/.pyenv/versions/coffee_hack/lib/python3.11/site-packages/starlette/routing.py\", line 714, in __call__\n", " await self.middleware_stack(scope, receive, send)\n", " File \"/home/chkn/.pyenv/versions/coffee_hack/lib/python3.11/site-packages/starlette/routing.py\", line 734, in app\n", " await route.handle(scope, receive, send)\n", " File \"/home/chkn/.pyenv/versions/coffee_hack/lib/python3.11/site-packages/starlette/routing.py\", line 288, in handle\n", " await self.app(scope, receive, send)\n", " File \"/home/chkn/.pyenv/versions/coffee_hack/lib/python3.11/site-packages/starlette/routing.py\", line 76, in app\n", " await wrap_app_handling_exceptions(app, request)(scope, receive, send)\n", " File \"/home/chkn/.pyenv/versions/coffee_hack/lib/python3.11/site-packages/starlette/_exception_handler.py\", line 53, in wrapped_app\n", " raise exc\n", " File \"/home/chkn/.pyenv/versions/coffee_hack/lib/python3.11/site-packages/starlette/_exception_handler.py\", line 42, in wrapped_app\n", " await app(scope, receive, sender)\n", " File \"/home/chkn/.pyenv/versions/coffee_hack/lib/python3.11/site-packages/starlette/routing.py\", line 73, in app\n", " response = await f(request)\n", " ^^^^^^^^^^^^^^^^\n", " File \"/home/chkn/.pyenv/versions/coffee_hack/lib/python3.11/site-packages/fasthtml/core.py\", line 575, in _f\n", " if not resp: resp = await _wrap_call(f, req, sig.parameters)\n", " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", " File \"/home/chkn/.pyenv/versions/coffee_hack/lib/python3.11/site-packages/fasthtml/core.py\", line 435, in _wrap_call\n", " return await _handle(f, wreq)\n", " ^^^^^^^^^^^^^^^^^^^^^^\n", " File \"/home/chkn/.pyenv/versions/coffee_hack/lib/python3.11/site-packages/fasthtml/core.py\", line 226, in _handle\n", " return (await f(*args, **kwargs)) if is_async_callable(f) else await run_in_threadpool(f, *args, **kwargs)\n", " ^^^^^^^^^^^^^^^^^^^^^^^^\n", " File \"/home/chkn/code/05_services/hackathons/nunc_perfect_cup_of_coffee/app.py\", line 434, in post\n", " return await process_preferences(request)\n", " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", " File \"/home/chkn/code/05_services/hackathons/nunc_perfect_cup_of_coffee/app.py\", line 222, in process_preferences\n", " TextArea(id=\"feedback_comments\", name=\"feedback_comments\", placeholder=\"What did you like or dislike about this recommendation?\",\n", " ^^^^^^^^\n", "NameError: name 'TextArea' is not defined"]}, {"cell_type": "markdown", "metadata": {}, "source": ["**AI Response**\n", "\n", "It looks like we have an error with the `TextArea` component. In FastHTML, the correct component name is `TextArea` but it might not be directly imported or available in your current setup.\n", "\n", "Let's fix this error by replacing `TextArea` with the correct FastHTML component. There are two options:\n", "\n", "1. Import TextArea specifically if it's available in a module\n", "2. Use a standard HTML textarea element with the Input component\n", "\n", "Let's modify the `process_preferences` function to fix this error:\n", "\n", "```python\n", "async def process_preferences(request):\n", " # ... existing code ...\n", " \n", " # In the feedback form section, replace:\n", " # TextArea(id=\"feedback_comments\", name=\"feedback_comments\", placeholder=\"What did you like or dislike about this recommendation?\", \n", " # cls=\"w-full p-2 border rounded bg-white h-24\")\n", " \n", " # With:\n", " Input(type=\"textarea\", id=\"feedback_comments\", name=\"feedback_comments\", \n", " placeholder=\"What did you like or dislike about this recommendation?\", \n", " cls=\"w-full p-2 border rounded bg-white h-24\")\n", " \n", " # ... rest of the function ...\n", "```\n", "\n", "If that doesn't work, you can try using the raw HTML approach:\n", "\n", "```python\n", "async def process_preferences(request):\n", " # ... existing code ...\n", " \n", " # In the feedback form section, replace:\n", " # TextArea(id=\"feedback_comments\", name=\"feedback_comments\", placeholder=\"What did you like or dislike about this recommendation?\", \n", " # cls=\"w-full p-2 border rounded bg-white h-24\")\n", " \n", " # With:\n", " Div(Safe('<textarea id=\"feedback_comments\" name=\"feedback_comments\" placeholder=\"What did you like or dislike about this recommendation?\" class=\"w-full p-2 border rounded bg-white h-24\"></textarea>'))\n", " \n", " # ... rest of the function ...\n", "```\n", "\n", "Would you like me to provide the complete updated function with this fix?"]}, {"cell_type": "markdown", "metadata": {}, "source": ["**AI Prompt**\n", "\n", "Just make a simple text field"]}, {"cell_type": "markdown", "metadata": {}, "source": ["**AI Response**\n", "\n", "Let's simplify by using a regular text input field instead of a textarea:\n", "\n", "```python\n", "# Replace the textarea with a simple text input\n", "Input(type=\"text\", id=\"feedback_comments\", name=\"feedback_comments\", \n", " placeholder=\"What did you like or dislike about this recommendation?\", \n", " cls=\"w-full p-2 border rounded bg-white\")\n", "```\n", "\n", "This should work without any issues since Input is a standard component in FastHTML. The text field will be simpler but still allow users to provide their feedback."]}, {"cell_type": "markdown", "metadata": {}, "source": ["**AI Prompt**\n", "\n", "1 more thing. It is working now, but we want the user input to go on both the image analysis output, as well the recommendation without the image analysis. What needs to change. Show complete functions."]}, {"cell_type": "markdown", "metadata": {}, "source": ["**AI Response**\n", "\n", "We need to update both the `process_preferences` and `analyze_image` functions to include the feedback mechanism. Here are the complete functions with the necessary changes:\n", "\n", "First, let's update `process_preferences`:\n", "\n", "```python\n", "async def process_preferences(request):\n", " form = await request.form()\n", " intensity = form.get(\"intensity\")\n", " flavor = form.get(\"flavor\")\n", " acidity = form.get(\"acidity\")\n", " drink_type = form.get(\"drink_type\")\n", " origin_pref = form.get(\"origin_pref\", \"any\")\n", " \n", " # Collect user preferences\n", " preferences = {\n", " \"intensity\": intensity,\n", " \"flavor_profile\": flavor, # Note: Changed from \"flavor\" to match our model\n", " \"acidity\": acidity,\n", " \"drink_type\": drink_type,\n", " \"origin_preference\": origin_pref\n", " }\n", " \n", " # Get session data\n", " session = request.session\n", " if \"history\" not in session:\n", " session[\"history\"] = []\n", " \n", " # Get recommendation using our recommender module with history\n", " recommendation_json = get_recommendation(preferences, history=session.get(\"history\", []))\n", " recommendation = parse_agent_response(recommendation_json)\n", " \n", " # Store this preference in history\n", " session[\"history\"].append({\n", " \"preferences\": preferences,\n", " \"recommendation\": recommendation\n", " })\n", " \n", " # Limit history size\n", " if len(session[\"history\"]) > 5:\n", " session[\"history\"] = session[\"history\"][-5:]\n", " \n", " return Titled(\"Next Level Coffee | nunc.\",\n", " Div(cls=\"max-w-5xl mx-auto px-4 py-8\")(\n", " Div(cls=\"flex justify-between items-center mb-8\")(\n", " H1(\"Perfect Cup of Coffee\", cls=\"title-main text-center mb-2\")\n", " ),\n", " H1(\"Your Coffee Match\", cls=\"text-4xl text-center mb-6\"),\n", " Div(cls=\"card rounded-lg p-6 shadow-md\")(\n", " H2(\"Based on Your Preferences\", cls=\"text-2xl mb-4\"),\n", " P(f\"Intensity: {intensity}\", cls=\"text-muted\"),\n", " P(f\"Flavor Profile: {flavor}\", cls=\"text-muted\"),\n", " P(f\"Acidity: {acidity}\", cls=\"text-muted\"),\n", " P(f\"Drink Type: {drink_type}\", cls=\"text-muted\"),\n", " P(f\"Origin Preference: {origin_pref}\", cls=\"text-muted\") if origin_pref != \"any\" else \"\",\n", " \n", " Div(cls=\"mt-6 p-4 bg-white rounded-lg border border-gray-200\")(\n", " H3(\"Recommended Coffee\", cls=\"font-bold mb-2\"),\n", " P(recommendation.get(\"coffee_name\", \"Ethiopian Yirgacheffe\"), cls=\"text-lg\"),\n", " P(recommendation.get(\"description\", \"Light roast, fruity with citrus notes\"), cls=\"text-sm text-muted\"),\n", " \n", " H3(\"Origin\", cls=\"font-bold mt-4 mb-2\"),\n", " P(recommendation.get(\"origin\", \"Ethiopia\"), cls=\"text-muted\"),\n", " \n", " H3(\"Brewing Parameters\", cls=\"font-bold mt-4 mb-2\"),\n", " P(f\"Grind Setting: {recommendation.get('grind_setting', 'Medium-fine')} \u03bcm\", cls=\"text-muted\"),\n", " P(f\"Water Temperature: {recommendation.get('brewing_temp', 92)}\u00b0C\", cls=\"text-muted\"),\n", " P(f\"Brewing Time: {recommendation.get('brewing_time', 150) // 60}:{recommendation.get('brewing_time', 150) % 60:02d} minutes\", cls=\"text-muted\"),\n", " P(f\"Flow Rate: {recommendation.get('flow_rate', 'Steady 2ml/s')}\", cls=\"text-muted\"),\n", " P(f\"Brew Ratio: {recommendation.get('brew_ratio', '1:2')}\", cls=\"text-muted\"),\n", " \n", " H3(\"Notes\", cls=\"font-bold mt-4 mb-2\") if recommendation.get(\"notes\") else \"\",\n", " P(recommendation.get(\"notes\", \"\"), cls=\"text-muted italic\")\n", " ),\n", " \n", " # Enhanced feedback mechanism with simple text input\n", " Div(cls=\"mt-6 p-4 bg-white rounded-lg border border-gray-200\")(\n", " H3(\"How was this recommendation?\", cls=\"font-bold mb-2\"),\n", " Form(action=\"/feedback\", method=\"post\")(\n", " Input(type=\"hidden\", name=\"recommendation_id\", value=recommendation.get(\"coffee_name\", \"\")),\n", " Div(cls=\"flex space-x-4 mb-4\")(\n", " Button(\"\ud83d\udc4d Perfect!\", type=\"submit\", name=\"rating\", value=\"good\", \n", " cls=\"px-4 py-2 bg-green-100 hover:bg-green-200 rounded\"),\n", " Button(\"\ud83d\udc4e Needs Improvement\", type=\"submit\", name=\"rating\", value=\"bad\", \n", " cls=\"px-4 py-2 bg-red-100 hover:bg-red-200 rounded\")\n", " ),\n", " Div(cls=\"mt-4\")(\n", " Label(\"Your Comments (Optional):\", fr=\"feedback_comments\", cls=\"block mb-2 text-sm font-bold\"),\n", " Input(type=\"text\", id=\"feedback_comments\", name=\"feedback_comments\", \n", " placeholder=\"What did you like or dislike about this recommendation?\", \n", " cls=\"w-full p-2 border rounded bg-white\"),\n", " P(\"Your feedback helps us improve our recommendations.\", cls=\"text-xs text-muted mt-1\")\n", " ),\n", " Div(cls=\"mt-4\")(\n", " Button(\"Submit Feedback\", type=\"submit\", cls=\"px-4 py-2 bg-accent text-dark hover:bg-accent-hover rounded font-bold\")\n", " )\n", " )\n", " ),\n", " \n", " # Previous recommendations (if any)\n", " Div(cls=\"mt-6 p-4 bg-white rounded-lg border border-gray-200 hidden\" if len(session.get(\"history\", [])) <= 1 else \"mt-6 p-4 bg-white rounded-lg border border-gray-200\")(\n", " H3(\"Your Previous Recommendations\", cls=\"font-bold mb-2\"),\n", " Ul(cls=\"list-disc pl-5\")(\n", " *[Li(f\"{h['recommendation'].get('coffee_name', 'Unknown')} ({h['preferences'].get('intensity', '')} - {h['preferences'].get('flavor_profile', '')})\") \n", " for h in session.get(\"history\", [])[:-1][-3:]] # Last 3 excluding current\n", " )\n", " ),\n", " \n", " Div(cls=\"mt-6\")(\n", " A(\"\u2190 Back to Home\", href=\"/\", cls=\"text-accent hover:underline font-bold\")\n", " )\n", " )\n", " )\n", " )\n", "```\n", "\n", "Now, let's update the `create_results_page` function which is used by `analyze_image`:\n", "\n", "```python\n", "def create_results_page(coffee_info, recommendation, image_url=None):\n", " \"\"\"Create a results page from coffee info and recommendations\"\"\"\n", " \n", " # Get session data (we need to handle the case where there's no request context)\n", " session = {}\n", " try:\n", " from starlette.requests import Request\n", " request = Request(scope=coffee_info.get(\"_scope\", {}))\n", " session = request.session\n", " except Exception:\n", " session = {}\n", " \n", " # Prepare history display\n", " history_display = \"\"\n", " if \"history\" in session and len(session.get(\"history\", [])) > 1:\n", " history_items = [Li(f\"{h['recommendation'].get('coffee_name', 'Unknown')} ({h['preferences'].get('intensity', '')} - {h['preferences'].get('flavor_profile', '')})\") \n", " for h in session.get(\"history\", [])[:-1][-3:]]\n", " history_display = Div(cls=\"mt-6 p-4 bg-white rounded-lg border border-gray-200\")(\n", " H3(\"Your Previous Recommendations\", cls=\"font-bold mb-2\"),\n", " Ul(cls=\"list-disc pl-5\")(*history_items)\n", " )\n", " \n", " return Titled(\"Next Level Coffee | nunc.\",\n", " Div(cls=\"max-w-4xl mx-auto px-4 py-8\")(\n", " Div(cls=\"flex justify-between items-center mb-8\")(\n", " H1(\"Perfect Cup of Coffee\", cls=\"title-main text-center\")\n", " ),\n", " H1(\"Coffee Package Analysis\", cls=\"text-4xl text-center mb-6\"),\n", " Div(cls=\"card rounded-lg p-6 shadow-md\")(\n", " H2(\"Detected Coffee Information\", cls=\"text-2xl mb-4\"),\n", " # Add image display if available\n", " Div(cls=\"mb-6 text-center\" if image_url else \"hidden\")(\n", " Img(src=image_url, cls=\"max-h-64 mx-auto rounded-lg shadow-md\")\n", " ) if image_url else \"\",\n", " Div(cls=\"mt-6 p-4 bg-white rounded-lg border border-gray-200\")(\n", " H3(\"Package Details\", cls=\"font-bold mb-2\"),\n", " P(f\"Origin: {coffee_info.get('origin', 'Unknown')}\", cls=\"text-muted\"),\n", " P(f\"Roast Level: {coffee_info.get('roast_level', 'Unknown')}\", cls=\"text-muted\"),\n", " P(f\"Variety: {coffee_info.get('variety', 'Unknown')}\", cls=\"text-muted\"),\n", " P(f\"Process: {coffee_info.get('process', 'Unknown')}\", cls=\"text-muted\"),\n", " \n", " H3(\"Recommended Brewing Parameters\", cls=\"font-bold mt-4 mb-2\"),\n", " P(f\"Flow Rate: {recommendation.get('flow_rate', 'Unknown')}\", cls=\"text-muted\"),\n", " P(f\"Brewing Temperature: {recommendation.get('brewing_temp', 'Unknown')}\u00b0C\", cls=\"text-muted\"),\n", " P(f\"Grind Setting: {recommendation.get('grind_setting', 'Unknown')}\", cls=\"text-muted\"),\n", " P(f\"Brew Ratio: {recommendation.get('brew_ratio', 'Unknown')}\", cls=\"text-muted\"),\n", " P(f\"Brewing Time: {recommendation.get('brewing_time', 'Unknown')} seconds\", cls=\"text-muted\"),\n", " \n", " H3(\"Tasting Notes\", cls=\"font-bold mt-4 mb-2\"),\n", " P(recommendation.get('description', 'No description available'), cls=\"text-muted\"),\n", " P(recommendation.get('notes', ''), cls=\"text-muted mt-2 italic\")\n", " ),\n", " \n", " # Enhanced feedback mechanism with simple text input\n", " Div(cls=\"mt-6 p-4 bg-white rounded-lg border border-gray-200\")(\n", " H3(\"How was this recommendation?\", cls=\"font-bold mb-2\"),\n", " Form(action=\"/feedback\", method=\"post\")(\n", " Input(type=\"hidden\", name=\"recommendation_id\", value=recommendation.get(\"coffee_name\", \"\")),\n", " Div(cls=\"flex space-x-4 mb-4\")(\n", " Button(\"\ud83d\udc4d Perfect!\", type=\"submit\", name=\"rating\", value=\"good\", \n", " cls=\"px-4 py-2 bg-green-100 hover:bg-green-200 rounded\"),\n", " Button(\"\ud83d\udc4e Needs Improvement\", type=\"submit\", name=\"rating\", value=\"bad\", \n", " cls=\"px-4 py-2 bg-red-100 hover:bg-red-200 rounded\")\n", " ),\n", " Div(cls=\"mt-4\")(\n", " Label(\"Your Comments (Optional):\", fr=\"feedback_comments\", cls=\"block mb-2 text-sm font-bold\"),\n", " Input(type=\"text\", id=\"feedback_comments\", name=\"feedback_comments\", \n", " placeholder=\"What did you like or dislike about this recommendation?\", \n", " cls=\"w-full p-2 border rounded bg-white\"),\n", " P(\"Your feedback helps us improve our recommendations.\", cls=\"text-xs text-muted mt-1\")\n", " ),\n", " Div(cls=\"mt-4\")(\n", " Button(\"Submit Feedback\", type=\"submit\", cls=\"px-4 py-2 bg-accent text-dark hover:bg-accent-hover rounded font-bold\")\n", " )\n", " )\n", " ),\n", " \n", " # Display history if available\n", " history_display,\n", " \n", " Div(cls=\"mt-6\")(\n", " A(\"\u2190 Back to Home\", href=\"/\", cls=\"text-accent hover:underline font-bold\")\n", " )\n", " )\n", " )\n", " )\n", "```\n", "\n", "Additionally, we need to update the `analyze_image` function to store the recommendation in session history:\n", "\n", "```python\n", "async def analyze_image(request):\n", " form = await request.form()\n", " \n", " # Get user preferences from the form\n", " intensity = form.get(\"intensity\", \"medium\")\n", " flavor = form.get(\"flavor\", \"balanced\")\n", " acidity = form.get(\"acidity\", \"balanced\")\n", " drink_type = form.get(\"drink_type\", \"espresso\")\n", " origin_pref = form.get(\"origin_pref\", \"any\")\n", " \n", " # Collect user preferences\n", " preferences = {\n", " \"intensity\": intensity,\n", " \"flavor_profile\": flavor,\n", " \"acidity\": acidity,\n", " \"drink_type\": drink_type,\n", " \"origin_preference\": origin_pref\n", " }\n", " \n", " # Get session data\n", " session = request.session\n", " if \"history\" not in session:\n", " session[\"history\"] = []\n", " \n", " image_file = form.get(\"coffee-image\")\n", " image_url = None\n", " \n", " # Save the image temporarily\n", " if image_file and hasattr(image_file, \"filename\") and image_file.filename:\n", " import os\n", " import uuid\n", " from modules.vision import analyze_coffee_image\n", " \n", " # Create a unique filename to avoid conflicts\n", " unique_id = str(uuid.uuid4())[:8]\n", " original_filename = image_file.filename\n", " image_filename = f\"{unique_id}_{original_filename}\"\n", " \n", " # Ensure directory exists\n", " os.makedirs(\"static/uploads\", exist_ok=True)\n", " \n", " # Full path to save the image\n", " save_path = f\"static/uploads/{image_filename}\"\n", " \n", " # Read and save the image\n", " with open(save_path, \"wb\") as f:\n", " content = await image_file.read()\n", " f.write(content)\n", " \n", " # Process image using vision model\n", " coffee_info = analyze_coffee_image(save_path)\n", " parsed_coffee_info = parse_agent_response(coffee_info)\n", "\n", " # Get recommendation using history\n", " recommendation = get_recommendation(preferences, parsed_coffee_info, session.get(\"history\", []))\n", " parsed_recommendation = parse_agent_response(recommendation)\n", " \n", " # Store this preference and recommendation in history\n", " session[\"history\"].append({\n", " \"preferences\": preferences,\n", " \"recommendation\": parsed_recommendation\n", " })\n", " \n", " # Limit history size\n", " if len(session[\"history\"]) > 5:\n", " session[\"history\"] = session[\"history\"][-5:]\n", " \n", " # Set the image URL for display\n", " image_url = f\"/static/uploads/{image_filename}\"\n", " \n", " # Add request scope to coffee_info for session access in create_results_page\n", " parsed_coffee_info[\"_scope\"] = request.scope\n", " \n", " return create_results_page(parsed_coffee_info, parsed_recommendation, image_url)\n", " else:\n", " # Handle case when no image is provided\n", " return Titled(\"Error\",\n", " Div(cls=\"max-w-4xl mx-auto px-4 py-8\")(\n", " H1(\"Error\", cls=\"text-4xl text-center mb-6\"),\n", " P(\"No image was provided. Please upload a coffee package image.\", cls=\"text-center\")\n", " )\n", " )\n", "```\n", "\n", "With these changes, both the regular preference-based recommendations and the image-based recommendations will:\n", "\n", "1. Store recommendations in session history\n", "2. Display the feedback form with the text input field\n", "3. Show previous recommendations when available\n", "4"]}], "metadata": {"kernelspec": {"display_name": "python3", "language": "python", "name": "python3"}}, "nbformat": 4, "nbformat_minor": 4}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment