diff --git a/week8/community_contributions/muthama/price_is_right_final.py b/week8/community_contributions/muthama/price_is_right_final.py
new file mode 100644
index 0000000..eb32e6b
--- /dev/null
+++ b/week8/community_contributions/muthama/price_is_right_final.py
@@ -0,0 +1,164 @@
+import logging
+import queue
+import threading
+import time
+import gradio as gr
+import plotly.graph_objects as go
+
+from deal_agent_framework import DealAgentFramework
+# from agents.deals import Opportunity, Deal
+from log_utils import reformat
+
+
+
+class QueueHandler(logging.Handler):
+ def __init__(self, log_queue):
+ super().__init__()
+ self.log_queue = log_queue
+
+ def emit(self, record):
+ self.log_queue.put(self.format(record))
+
+def html_for(log_data):
+ output = '
'.join(log_data[-18:])
+ return f"""
+
+ {output}
+
+ """
+
+def setup_logging(log_queue):
+ handler = QueueHandler(log_queue)
+ formatter = logging.Formatter(
+ "[%(asctime)s] %(message)s",
+ datefmt="%Y-%m-%d %H:%M:%S %z",
+ )
+ handler.setFormatter(formatter)
+ logger = logging.getLogger()
+ logger.addHandler(handler)
+ logger.setLevel(logging.INFO)
+
+
+class App:
+
+ def __init__(self):
+ self.agent_framework = None
+
+ def get_agent_framework(self):
+ if not self.agent_framework:
+ self.agent_framework = DealAgentFramework()
+ self.agent_framework.init_agents_as_needed()
+ return self.agent_framework
+
+ def run(self):
+ with gr.Blocks(title="The Price is Right", fill_width=True) as ui:
+
+ log_data = gr.State([])
+
+ def table_for(opps):
+ return [[opp.deal.product_description, f"${opp.deal.price:.2f}", f"${opp.estimate:.2f}", f"${opp.discount:.2f}", opp.deal.url] for opp in opps]
+
+ def update_output(log_data, log_queue, result_queue):
+ def live_table():
+ return table_for(self.get_agent_framework().memory)
+
+ final_result = None
+ while True:
+ try:
+ message = log_queue.get_nowait()
+ log_data.append(reformat(message))
+ yield log_data, html_for(log_data), live_table()
+ continue
+ except queue.Empty:
+ pass
+
+ try:
+ final_result = result_queue.get_nowait()
+ yield log_data, html_for(log_data), (final_result or live_table())
+ break
+ except queue.Empty:
+ time.sleep(0.05)
+
+ def get_plot():
+ documents, vectors, colors = DealAgentFramework.get_plot_data(max_datapoints=1000)
+ # Create the 3D scatter plot
+ fig = go.Figure(data=[go.Scatter3d(
+ x=vectors[:, 0],
+ y=vectors[:, 1],
+ z=vectors[:, 2],
+ mode='markers',
+ marker=dict(size=2, color=colors, opacity=0.7),
+ )])
+
+ fig.update_layout(
+ scene=dict(xaxis_title='x',
+ yaxis_title='y',
+ zaxis_title='z',
+ aspectmode='manual',
+ aspectratio=dict(x=2.2, y=2.2, z=1), # Make x-axis twice as long
+ camera=dict(
+ eye=dict(x=1.6, y=1.6, z=0.8) # Adjust camera position
+ )),
+ height=400,
+ margin=dict(r=5, b=1, l=5, t=2)
+ )
+
+ return fig
+
+ def do_run():
+ new_opportunities = self.get_agent_framework().run()
+ table = table_for(new_opportunities)
+ return table
+
+ def run_with_logging(initial_log_data):
+ log_queue = queue.Queue()
+ result_queue = queue.Queue()
+ setup_logging(log_queue)
+
+ def worker():
+ result = do_run() # this updates memory during execution
+ result_queue.put(result)
+
+ thread = threading.Thread(target=worker, daemon=True)
+ thread.start()
+
+ for log_data, output_html, table in update_output(initial_log_data, log_queue, result_queue):
+ yield log_data, output_html, table
+
+ def do_select(selected_index: gr.SelectData):
+ opportunities = self.get_agent_framework().memory
+ row = selected_index.index[0]
+ opportunity = opportunities[row]
+ self.get_agent_framework().planner.messenger.alert(opportunity)
+
+ with gr.Row():
+ gr.Markdown('The Price is Right - Autonomous Agent Framework that hunts for deals
')
+ with gr.Row():
+ gr.Markdown('A proprietary fine-tuned LLM deployed on Modal and a RAG pipeline with a frontier model collaborate to send push notifications with great online deals.
')
+ with gr.Row():
+ opportunities_dataframe = gr.Dataframe(
+ headers=["Deals found so far", "Price", "Estimate", "Discount", "URL"],
+ wrap=True,
+ column_widths=[6, 1, 1, 1, 3],
+ row_count=10,
+ col_count=5,
+ max_height=400,
+ )
+ with gr.Row():
+ with gr.Column(scale=1):
+ logs = gr.HTML()
+ with gr.Column(scale=1):
+ plot = gr.Plot(value=get_plot(), show_label=False)
+
+ ui.load(run_with_logging, inputs=[log_data], outputs=[log_data, logs, opportunities_dataframe])
+
+ timer = gr.Timer(value=300, active=True)
+ timer.tick(run_with_logging, inputs=[log_data], outputs=[log_data, logs, opportunities_dataframe])
+
+ opportunities_dataframe.select(do_select)
+
+ ui.launch(share=False, inbrowser=True)
+
+if __name__=="__main__":
+ App().run()
+
\ No newline at end of file
diff --git a/week8/community_contributions/muthama/week8_exercise_solution-Stephen.ipynb b/week8/community_contributions/muthama/week8_exercise_solution-Stephen.ipynb
new file mode 100644
index 0000000..75da0cf
--- /dev/null
+++ b/week8/community_contributions/muthama/week8_exercise_solution-Stephen.ipynb
@@ -0,0 +1,88 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "a71ed017-e1b0-4299-88b3-f0eb05adc4df",
+ "metadata": {},
+ "source": [
+ "# The Price is Right\n",
+ "\n",
+ "The final step is to build a User Interface\n",
+ "\n",
+ "We will use more advanced aspects of Gradio - building piece by piece."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "b77940b8",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "133.0\n"
+ ]
+ }
+ ],
+ "source": [
+ "import modal"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "6449363f",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "!modal deploy -m pricer_service2"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "3c67160e",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "Pricer = modal.Cls.from_name(\"pricer-service\", \"Pricer\")\n",
+ "pricer = Pricer()\n",
+ "reply = pricer.price.remote(\"Quadcast HyperX condenser mic, connects via usb-c to your computer for crystal clear audio\")\n",
+ "print(reply)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "48506465-1c7a-433f-a665-b277a8b4665c",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "!python price_is_right_final.py"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": ".venv",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.12.12"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}