Spaces:
Sleeping
feat(ui): Overhaul Gradio app for interactivity and evaluation
Browse filesTransforms the basic Gradio interface into a polished, feature-rich web application designed for demonstration and evaluation. The new UI provides a significantly improved user experience with interactive examples and adds robust features for capturing and analyzing the RAG pipeline's output.
This work completes the transition from a back-end proof-of-concept to a user-facing demo.
Key Changes:
- **Gradio App Overhaul (`app.py`):**
- Rewrote the UI using `gr.Blocks` for a custom two-column layout.
- Added an "Example Scenarios" section with radio buttons to quickly populate the form with pre-defined student narratives.
- Implemented an "Evaluation Data" accordion that displays the full RAG output (inputs, retrieval results, LLM output) in a JSON viewer.
- Added a "Download JSON" button to allow users to save the complete evaluation data for offline analysis.
- Refactored the API call to be a generator (`yield`), enabling progressive updates like "Processing..." messages and interactive button states.
- **Refactored Utilities (`utils.py`):**
- Created a new `format_evidence_for_display` function to centralize the logic for creating rich, cited evidence snippets, now used by both the app and the notebook.
- Added `load_citations` and `create_evaluation_bundle` helpers to support the new UI's data requirements.
- **Notebook Improvements (`fot_recommender_poc.ipynb`):**
- Updated the notebook to use the new `format_evidence_for_display` utility, resulting in a cleaner and more professional presentation of the evidence base.
- app.py +179 -63
- notebooks/fot_recommender_poc.ipynb +102 -201
- src/fot_recommender/utils.py +98 -21
|
@@ -3,68 +3,86 @@ import faiss
|
|
| 3 |
import os
|
| 4 |
import numpy as np
|
| 5 |
import sys
|
|
|
|
|
|
|
|
|
|
| 6 |
from pathlib import Path
|
| 7 |
from dotenv import load_dotenv
|
| 8 |
|
|
|
|
|
|
|
| 9 |
load_dotenv()
|
| 10 |
|
| 11 |
-
|
| 12 |
-
|
|
|
|
|
|
|
| 13 |
load_knowledge_base,
|
| 14 |
initialize_embedding_model,
|
| 15 |
generate_recommendation_summary,
|
| 16 |
)
|
| 17 |
-
|
| 18 |
-
# --- Define the project root based on this script's location ---
|
| 19 |
-
APP_ROOT = Path(__file__).parent
|
| 20 |
|
| 21 |
# --- Define ABSOLUTE paths to the data artifacts ---
|
| 22 |
FAISS_INDEX_PATH = APP_ROOT / "data" / "processed" / "faiss_index.bin"
|
| 23 |
KB_PATH = APP_ROOT / "data" / "processed" / "knowledge_base_final_chunks.json"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
|
|
|
|
|
|
|
| 25 |
ACCESS_PASSWORD = os.environ.get("DEMO_PASSWORD", "")
|
| 26 |
-
if ACCESS_PASSWORD and len(ACCESS_PASSWORD) > 10: # Check if it looks like a real key
|
| 27 |
-
print("✅ DEMO_PASSWORD secret loaded successfully from the environment.")
|
| 28 |
-
else:
|
| 29 |
-
print("❌ FATAL: DEMO_PASSWORD secret was NOT FOUND in the environment.")
|
| 30 |
-
|
| 31 |
FOT_GOOGLE_API_KEY = os.environ.get("FOT_GOOGLE_API_KEY", "")
|
| 32 |
-
if FOT_GOOGLE_API_KEY and len(FOT_GOOGLE_API_KEY) > 10: # Check if it looks like a real key
|
| 33 |
-
print("✅ FOT_GOOGLE_API_KEY secret loaded successfully from the environment.")
|
| 34 |
-
else:
|
| 35 |
-
print("❌ FATAL: FOT_GOOGLE_API_KEY secret was NOT FOUND in the environment.")
|
| 36 |
-
|
| 37 |
-
print("--- Initializing API: Loading models and data... ---")
|
| 38 |
|
| 39 |
-
# ---
|
|
|
|
| 40 |
index = faiss.read_index(str(FAISS_INDEX_PATH))
|
| 41 |
knowledge_base_chunks = load_knowledge_base(str(KB_PATH))
|
|
|
|
| 42 |
embedding_model = initialize_embedding_model()
|
| 43 |
-
|
| 44 |
print("✅ API initialized successfully.")
|
| 45 |
|
| 46 |
-
# --- Define the core RAG function that the API exposes ---
|
| 47 |
-
|
| 48 |
-
|
| 49 |
def get_recommendations_api(student_narrative, persona, password):
|
| 50 |
-
"""The main function that runs the RAG pipeline
|
| 51 |
if password != ACCESS_PASSWORD:
|
| 52 |
-
|
|
|
|
|
|
|
| 53 |
if not student_narrative:
|
| 54 |
-
|
|
|
|
|
|
|
|
|
|
| 55 |
|
| 56 |
# 1. RETRIEVE
|
| 57 |
-
query_embedding = np.asarray(embedding_model.encode([student_narrative])).astype(
|
| 58 |
-
"float32"
|
| 59 |
-
)
|
| 60 |
scores, indices = index.search(query_embedding, k=3)
|
| 61 |
-
retrieved_chunks_with_scores = [
|
| 62 |
-
|
| 63 |
-
for i, score in zip(indices[0], scores[0])
|
| 64 |
-
if score >= 0.4
|
| 65 |
-
]
|
| 66 |
if not retrieved_chunks_with_scores:
|
| 67 |
-
|
|
|
|
| 68 |
|
| 69 |
# 2. GENERATE
|
| 70 |
synthesized_recommendation = generate_recommendation_summary(
|
|
@@ -74,33 +92,131 @@ def get_recommendations_api(student_narrative, persona, password):
|
|
| 74 |
persona=persona,
|
| 75 |
)
|
| 76 |
|
| 77 |
-
# 3. Augment with evidence
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
import os
|
| 4 |
import numpy as np
|
| 5 |
import sys
|
| 6 |
+
import json
|
| 7 |
+
import tempfile
|
| 8 |
+
import datetime
|
| 9 |
from pathlib import Path
|
| 10 |
from dotenv import load_dotenv
|
| 11 |
|
| 12 |
+
from fot_recommender.utils import load_citations
|
| 13 |
+
|
| 14 |
load_dotenv()
|
| 15 |
|
| 16 |
+
# --- Define the project root and paths ---
|
| 17 |
+
APP_ROOT = Path(__file__).parent
|
| 18 |
+
sys.path.insert(0, str(APP_ROOT / "src"))
|
| 19 |
+
from fot_recommender.rag_pipeline import (
|
| 20 |
load_knowledge_base,
|
| 21 |
initialize_embedding_model,
|
| 22 |
generate_recommendation_summary,
|
| 23 |
)
|
| 24 |
+
from fot_recommender.utils import format_evidence_for_display
|
|
|
|
|
|
|
| 25 |
|
| 26 |
# --- Define ABSOLUTE paths to the data artifacts ---
|
| 27 |
FAISS_INDEX_PATH = APP_ROOT / "data" / "processed" / "faiss_index.bin"
|
| 28 |
KB_PATH = APP_ROOT / "data" / "processed" / "knowledge_base_final_chunks.json"
|
| 29 |
+
CITATIONS_PATH = APP_ROOT / "data" / "processed" / "citations.json"
|
| 30 |
+
|
| 31 |
+
# --- Define Example Narratives for the UI (with new 'short_title') ---
|
| 32 |
+
EXAMPLE_NARRATIVES = [
|
| 33 |
+
{
|
| 34 |
+
"short_title": "Overwhelmed",
|
| 35 |
+
"title": "Overwhelmed Freshman (Academic & Attendance)",
|
| 36 |
+
"narrative": "A comprehensive support plan is urgently needed for this freshman. Academic performance is a critical concern, with failures in both Math and English leading to a credit deficiency of only 2 out of 4 expected credits. This academic struggle is compounded by a drop in attendance to 85% and a recent behavioral flag for an outburst in class, suggesting the student is significantly overwhelmed by the transition to high school."
|
| 37 |
+
},
|
| 38 |
+
{
|
| 39 |
+
"short_title": "Withdrawn",
|
| 40 |
+
"title": "Withdrawn Freshman (Social-Emotional)",
|
| 41 |
+
"narrative": "Academically, this freshman appears to be thriving, with a high GPA and perfect attendance. A closer look at classroom performance, however, reveals a student who is completely withdrawn. They do not participate in discussions or engage in any extracurricular activities, and teacher notes repeatedly describe them as 'isolated.' The lack of behavioral flags is a result of non-engagement, not positive conduct, pointing to a clear need for interventions focused on social-emotional learning and school connectedness."
|
| 42 |
+
},
|
| 43 |
+
{
|
| 44 |
+
"short_title": "Disruptive",
|
| 45 |
+
"title": "Disruptive Freshman (Behavioral)",
|
| 46 |
+
"narrative": "While this student's academics and credits earned are currently on track and attendance is acceptable at 92%, a significant pattern of disruptive behavior is jeopardizing their long-term success. An accumulation of five behavioral flags across multiple classes indicates a primary need for interventions in behavior management and positive conduct. Support should be focused on mentoring and strategies to foster appropriate classroom engagement before these behaviors begin to negatively impact their academic standing."
|
| 47 |
+
}
|
| 48 |
+
]
|
| 49 |
+
# Use the short title for the UI, but map it to the full narrative
|
| 50 |
+
EXAMPLE_MAP = {ex["short_title"]: ex["narrative"] for ex in EXAMPLE_NARRATIVES}
|
| 51 |
+
EXAMPLE_TITLES = list(EXAMPLE_MAP.keys())
|
| 52 |
|
| 53 |
+
|
| 54 |
+
# --- Load Environment Variables and Secrets ---
|
| 55 |
ACCESS_PASSWORD = os.environ.get("DEMO_PASSWORD", "")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
FOT_GOOGLE_API_KEY = os.environ.get("FOT_GOOGLE_API_KEY", "")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
|
| 58 |
+
# --- Initialize models and data ---
|
| 59 |
+
print("--- Initializing API: Loading models and data... ---")
|
| 60 |
index = faiss.read_index(str(FAISS_INDEX_PATH))
|
| 61 |
knowledge_base_chunks = load_knowledge_base(str(KB_PATH))
|
| 62 |
+
citations_map = load_citations(str(CITATIONS_PATH))
|
| 63 |
embedding_model = initialize_embedding_model()
|
|
|
|
| 64 |
print("✅ API initialized successfully.")
|
| 65 |
|
|
|
|
|
|
|
|
|
|
| 66 |
def get_recommendations_api(student_narrative, persona, password):
|
| 67 |
+
"""The main function that runs the RAG pipeline and prepares data for export."""
|
| 68 |
if password != ACCESS_PASSWORD:
|
| 69 |
+
yield "Authentication failed.", gr.update(interactive=True), gr.update(visible=False), None, gr.update(visible=False)
|
| 70 |
+
return
|
| 71 |
+
|
| 72 |
if not student_narrative:
|
| 73 |
+
yield "Please enter a student narrative.", gr.update(interactive=True), gr.update(visible=False), None, gr.update(visible=False)
|
| 74 |
+
return
|
| 75 |
+
|
| 76 |
+
yield "Processing...", gr.update(interactive=False), gr.update(visible=False), None, gr.update(visible=False)
|
| 77 |
|
| 78 |
# 1. RETRIEVE
|
| 79 |
+
query_embedding = np.asarray(embedding_model.encode([student_narrative])).astype("float32")
|
|
|
|
|
|
|
| 80 |
scores, indices = index.search(query_embedding, k=3)
|
| 81 |
+
retrieved_chunks_with_scores = [(knowledge_base_chunks[i], score) for i, score in zip(indices[0], scores[0]) if score >= 0.4]
|
| 82 |
+
|
|
|
|
|
|
|
|
|
|
| 83 |
if not retrieved_chunks_with_scores:
|
| 84 |
+
yield "Could not find relevant interventions.", gr.update(interactive=True), gr.update(visible=False), None, gr.update(visible=False)
|
| 85 |
+
return
|
| 86 |
|
| 87 |
# 2. GENERATE
|
| 88 |
synthesized_recommendation = generate_recommendation_summary(
|
|
|
|
| 92 |
persona=persona,
|
| 93 |
)
|
| 94 |
|
| 95 |
+
# 3. Augment with evidence for UI
|
| 96 |
+
formatted_evidence = format_evidence_for_display(retrieved_chunks_with_scores, citations_map)
|
| 97 |
+
evidence_header = "\n\n---\n\n### Evidence Base\n"
|
| 98 |
+
evidence_list_str = ""
|
| 99 |
+
for evidence in formatted_evidence:
|
| 100 |
+
evidence_list_str += f"\n- **{evidence['title']}**\n"
|
| 101 |
+
evidence_list_str += f" - **Source:** {evidence['source']}\n"
|
| 102 |
+
evidence_list_str += f" - **Page(s):** {evidence['pages']}\n"
|
| 103 |
+
evidence_list_str += f" - **Relevance Score:** {evidence['score']}\n"
|
| 104 |
+
evidence_list_str += f" - **Content Snippet:**\n > {evidence['content_snippet']}\n"
|
| 105 |
+
|
| 106 |
+
final_output = synthesized_recommendation + evidence_header + evidence_list_str
|
| 107 |
+
|
| 108 |
+
# 4. Assemble Evaluation Data
|
| 109 |
+
evaluation_data = {
|
| 110 |
+
"timestamp": datetime.datetime.now().isoformat(),
|
| 111 |
+
"inputs": {"student_narrative": student_narrative, "persona": persona},
|
| 112 |
+
"retrieval_results": [
|
| 113 |
+
{
|
| 114 |
+
"chunk_title": chunk['title'], "relevance_score": float(score),
|
| 115 |
+
"source_document": chunk['source_document'], "page_info": chunk.get('fot_pages', 'N/A'),
|
| 116 |
+
"original_content": chunk.get('original_content', ''), "citation_info": citations_map.get(chunk['source_document'], {})
|
| 117 |
+
} for chunk, score in retrieved_chunks_with_scores
|
| 118 |
+
],
|
| 119 |
+
"llm_output": {"synthesized_recommendation": synthesized_recommendation},
|
| 120 |
+
"final_ui_output": final_output
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
# 5. Create a temporary file for download
|
| 124 |
+
with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix=".json", encoding='utf-8') as f:
|
| 125 |
+
json.dump(evaluation_data, f, indent=4)
|
| 126 |
+
temp_file_path = f.name
|
| 127 |
+
|
| 128 |
+
yield final_output, gr.update(interactive=True), gr.update(visible=True), evaluation_data, gr.update(value=temp_file_path, visible=True)
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
# --- UI Helper Functions ---
|
| 132 |
+
def clear_all():
|
| 133 |
+
"""Clears inputs, outputs, and hides the export section."""
|
| 134 |
+
return "", None, "", gr.update(visible=False), None, gr.update(visible=False, value=None)
|
| 135 |
+
|
| 136 |
+
def update_narrative_from_example(selection):
|
| 137 |
+
"""Populates the narrative textbox when an example radio button is selected."""
|
| 138 |
+
return EXAMPLE_MAP.get(selection, "")
|
| 139 |
+
|
| 140 |
+
# --- Custom CSS for horizontal radio buttons ---
|
| 141 |
+
CUSTOM_CSS = """
|
| 142 |
+
/* Target the container of the radio buttons and make them horizontal */
|
| 143 |
+
.radio-horizontal .gr-form {
|
| 144 |
+
flex-direction: row;
|
| 145 |
+
flex-wrap: wrap;
|
| 146 |
+
gap: 0.5rem; /* Adjust spacing between buttons */
|
| 147 |
+
}
|
| 148 |
+
"""
|
| 149 |
+
|
| 150 |
+
# --- Gradio Interface ---
|
| 151 |
+
with gr.Blocks(theme=gr.themes.Soft(), css=CUSTOM_CSS) as interface:
|
| 152 |
+
gr.Markdown(
|
| 153 |
+
"""
|
| 154 |
+
# Freshman On-Track Intervention Recommender
|
| 155 |
+
*A live API demonstrating the FOT Recommender.*
|
| 156 |
+
"""
|
| 157 |
+
)
|
| 158 |
+
with gr.Row(equal_height=False):
|
| 159 |
+
with gr.Column(scale=1):
|
| 160 |
+
with gr.Group():
|
| 161 |
+
narrative_input = gr.Textbox(
|
| 162 |
+
lines=8,
|
| 163 |
+
label="Student Narrative",
|
| 164 |
+
placeholder="Describe the student's situation here, or select an example below.",
|
| 165 |
+
)
|
| 166 |
+
|
| 167 |
+
# Use the new short titles and apply the custom CSS class
|
| 168 |
+
example_radio = gr.Radio(
|
| 169 |
+
EXAMPLE_TITLES,
|
| 170 |
+
label="Load an Example Scenario",
|
| 171 |
+
info="Select one to populate the narrative above. Typing a custom narrative will clear this selection.",
|
| 172 |
+
elem_classes=["radio-horizontal"]
|
| 173 |
+
)
|
| 174 |
+
|
| 175 |
+
persona_input = gr.Radio(
|
| 176 |
+
["teacher", "parent", "principal"],
|
| 177 |
+
label="Who is this recommendation for?",
|
| 178 |
+
value="teacher",
|
| 179 |
+
elem_classes=["radio-horizontal"] # Apply same style here for consistency
|
| 180 |
+
)
|
| 181 |
+
password_input = gr.Textbox(
|
| 182 |
+
label="Access Key",
|
| 183 |
+
type="password",
|
| 184 |
+
info="Enter the access key for the demo."
|
| 185 |
+
)
|
| 186 |
+
|
| 187 |
+
with gr.Row():
|
| 188 |
+
clear_btn = gr.Button("Clear")
|
| 189 |
+
submit_btn = gr.Button("Submit", variant="primary")
|
| 190 |
+
|
| 191 |
+
with gr.Column(scale=2):
|
| 192 |
+
recommendation_output = gr.Markdown(label="Synthesized Recommendation", show_copy_button=True)
|
| 193 |
+
with gr.Accordion("Evaluation Data", open=False, visible=False) as eval_accordion:
|
| 194 |
+
json_viewer = gr.JSON(label="Evaluation JSON")
|
| 195 |
+
download_btn = gr.DownloadButton("Download JSON", visible=False)
|
| 196 |
+
|
| 197 |
+
|
| 198 |
+
# --- Event Handlers ---
|
| 199 |
+
example_radio.change(
|
| 200 |
+
fn=update_narrative_from_example,
|
| 201 |
+
inputs=example_radio,
|
| 202 |
+
outputs=narrative_input
|
| 203 |
+
)
|
| 204 |
+
narrative_input.input(
|
| 205 |
+
fn=lambda: None,
|
| 206 |
+
inputs=None,
|
| 207 |
+
outputs=example_radio
|
| 208 |
+
)
|
| 209 |
+
submit_btn.click(
|
| 210 |
+
fn=get_recommendations_api,
|
| 211 |
+
inputs=[narrative_input, persona_input, password_input],
|
| 212 |
+
outputs=[recommendation_output, submit_btn, eval_accordion, json_viewer, download_btn]
|
| 213 |
+
)
|
| 214 |
+
clear_btn.click(
|
| 215 |
+
fn=clear_all,
|
| 216 |
+
inputs=[],
|
| 217 |
+
outputs=[narrative_input, example_radio, recommendation_output, eval_accordion, json_viewer, download_btn]
|
| 218 |
+
)
|
| 219 |
+
|
| 220 |
+
|
| 221 |
+
if __name__ == "__main__":
|
| 222 |
+
interface.launch()
|
|
@@ -6,11 +6,11 @@
|
|
| 6 |
"metadata": {},
|
| 7 |
"source": [
|
| 8 |
"# Freshman On-Track (FOT) Intervention Recommender\n",
|
| 9 |
-
"### A
|
| 10 |
"\n",
|
| 11 |
-
"**Goal:** To show, in a few
|
| 12 |
"\n",
|
| 13 |
-
"This notebook demonstrates the core Retrieval
|
| 14 |
]
|
| 15 |
},
|
| 16 |
{
|
|
@@ -27,7 +27,7 @@
|
|
| 27 |
},
|
| 28 |
{
|
| 29 |
"cell_type": "code",
|
| 30 |
-
"execution_count":
|
| 31 |
"id": "97f37783",
|
| 32 |
"metadata": {},
|
| 33 |
"outputs": [
|
|
@@ -42,15 +42,14 @@
|
|
| 42 |
}
|
| 43 |
],
|
| 44 |
"source": [
|
| 45 |
-
"import sys, os, warnings\n",
|
| 46 |
"from pathlib import Path\n",
|
| 47 |
-
"from tqdm import TqdmWarning\n",
|
| 48 |
"\n",
|
| 49 |
"# This prevents common, harmless warnings from cluttering the output.\n",
|
| 50 |
"os.environ[\"TOKENIZERS_PARALLELISM\"] = \"false\"\n",
|
| 51 |
-
"warnings.filterwarnings(\"ignore\", category=
|
| 52 |
"\n",
|
| 53 |
-
"# Clones the project from GitHub
|
| 54 |
"PROJECT_DIR = \"fot-intervention-recommender\"\n",
|
| 55 |
"if not Path(PROJECT_DIR).is_dir():\n",
|
| 56 |
" print(\"🚀 Downloading project files...\")\n",
|
|
@@ -124,11 +123,13 @@
|
|
| 124 |
"\n",
|
| 125 |
"Now, we take the student's story and find the most relevant strategies from our **Knowledge Base**—a curated library of best practices and proven interventions.\n",
|
| 126 |
"\n",
|
| 127 |
-
"
|
| 128 |
-
"1.
|
| 129 |
-
"2.
|
|
|
|
|
|
|
| 130 |
"\n",
|
| 131 |
-
"
|
| 132 |
]
|
| 133 |
},
|
| 134 |
{
|
|
@@ -158,9 +159,7 @@
|
|
| 158 |
"name": "stderr",
|
| 159 |
"output_type": "stream",
|
| 160 |
"text": [
|
| 161 |
-
"Batches:
|
| 162 |
-
" return forward_call(*args, **kwargs)\n",
|
| 163 |
-
"Batches: 100%|████████████████████████████████████| 1/1 [00:02<00:00, 2.08s/it]\n"
|
| 164 |
]
|
| 165 |
},
|
| 166 |
{
|
|
@@ -172,13 +171,14 @@
|
|
| 172 |
"FAISS index created with 27 vectors.\n",
|
| 173 |
"\n",
|
| 174 |
"Searching for top 3 interventions for query: 'This student is struggling to keep up with coursework, having failed one core cl...'\n",
|
| 175 |
-
"Found 3 relevant interventions.\n"
|
|
|
|
| 176 |
]
|
| 177 |
},
|
| 178 |
{
|
| 179 |
"data": {
|
| 180 |
"text/markdown": [
|
| 181 |
-
"
|
| 182 |
],
|
| 183 |
"text/plain": [
|
| 184 |
"<IPython.core.display.Markdown object>"
|
|
@@ -190,7 +190,15 @@
|
|
| 190 |
{
|
| 191 |
"data": {
|
| 192 |
"text/markdown": [
|
| 193 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 194 |
],
|
| 195 |
"text/plain": [
|
| 196 |
"<IPython.core.display.Markdown object>"
|
|
@@ -202,7 +210,26 @@
|
|
| 202 |
{
|
| 203 |
"data": {
|
| 204 |
"text/markdown": [
|
| 205 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 206 |
],
|
| 207 |
"text/plain": [
|
| 208 |
"<IPython.core.display.Markdown object>"
|
|
@@ -214,7 +241,47 @@
|
|
| 214 |
{
|
| 215 |
"data": {
|
| 216 |
"text/markdown": [
|
| 217 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 218 |
],
|
| 219 |
"text/plain": [
|
| 220 |
"<IPython.core.display.Markdown object>"
|
|
@@ -225,31 +292,28 @@
|
|
| 225 |
}
|
| 226 |
],
|
| 227 |
"source": [
|
| 228 |
-
"# Import the necessary functions from our project's code\n",
|
| 229 |
"from fot_recommender.rag_pipeline import (\n",
|
| 230 |
" load_knowledge_base,\n",
|
| 231 |
" initialize_embedding_model,\n",
|
| 232 |
" create_embeddings,\n",
|
| 233 |
" create_vector_db,\n",
|
| 234 |
-
" search_interventions
|
| 235 |
-
" generate_recommendation_summary\n",
|
| 236 |
")\n",
|
| 237 |
"from fot_recommender.utils import display_recommendations\n",
|
| 238 |
"\n",
|
| 239 |
-
"#
|
| 240 |
-
"\n",
|
| 241 |
-
"# 1. Load the chunked knowledge base\n",
|
| 242 |
"kb_path = project_path / \"data\" / \"processed\" / \"knowledge_base_final_chunks.json\"\n",
|
|
|
|
| 243 |
"knowledge_base_chunks = load_knowledge_base(str(kb_path))\n",
|
|
|
|
|
|
|
| 244 |
"\n",
|
| 245 |
-
"# 2. Initialize
|
| 246 |
"embedding_model = initialize_embedding_model()\n",
|
| 247 |
-
"\n",
|
| 248 |
-
"# 3. Create embeddings and the vector database\n",
|
| 249 |
"embeddings = create_embeddings(knowledge_base_chunks, embedding_model)\n",
|
| 250 |
"vector_db = create_vector_db(embeddings)\n",
|
| 251 |
"\n",
|
| 252 |
-
"#
|
| 253 |
"retrieved_interventions = search_interventions(\n",
|
| 254 |
" query=student_query,\n",
|
| 255 |
" model=embedding_model,\n",
|
|
@@ -259,10 +323,9 @@
|
|
| 259 |
" min_similarity_score=0.4\n",
|
| 260 |
")\n",
|
| 261 |
"\n",
|
| 262 |
-
"# Display
|
| 263 |
-
"
|
| 264 |
-
"
|
| 265 |
-
" display(Markdown(f\"- **{chunk['title']}** (Source: *{chunk['source_document']}*, Relevance: {score:.2f})\"))"
|
| 266 |
]
|
| 267 |
},
|
| 268 |
{
|
|
@@ -270,178 +333,16 @@
|
|
| 270 |
"id": "2202209d",
|
| 271 |
"metadata": {},
|
| 272 |
"source": [
|
| 273 |
-
"## Step 4:
|
| 274 |
-
"\n",
|
| 275 |
-
"Finding the right documents is only half the battle. Raw research isn't very helpful to a busy teacher. \n",
|
| 276 |
-
"\n",
|
| 277 |
-
"In this final step, we use a powerful Large Language Model (Google's Gemini API) to act as an expert instructional coach. We give it the student's story and the relevant strategies we just retrieved. The AI's job is to **synthesize** this information into a concise, practical recommendation tailored specifically for a teacher.\n",
|
| 278 |
-
"\n",
|
| 279 |
-
"This is the final output of our system."
|
| 280 |
-
]
|
| 281 |
-
},
|
| 282 |
-
{
|
| 283 |
-
"cell_type": "code",
|
| 284 |
-
"execution_count": 4,
|
| 285 |
-
"id": "62ee35bc",
|
| 286 |
-
"metadata": {},
|
| 287 |
-
"outputs": [
|
| 288 |
-
{
|
| 289 |
-
"name": "stdout",
|
| 290 |
-
"output_type": "stream",
|
| 291 |
-
"text": [
|
| 292 |
-
"\n",
|
| 293 |
-
"Synthesizing recommendation for persona: 'teacher' using Gemini...\n",
|
| 294 |
-
"Synthesis complete.\n"
|
| 295 |
-
]
|
| 296 |
-
},
|
| 297 |
-
{
|
| 298 |
-
"data": {
|
| 299 |
-
"text/markdown": [
|
| 300 |
-
"### Final Synthesized Recommendation for the Teacher"
|
| 301 |
-
],
|
| 302 |
-
"text/plain": [
|
| 303 |
-
"<IPython.core.display.Markdown object>"
|
| 304 |
-
]
|
| 305 |
-
},
|
| 306 |
-
"metadata": {},
|
| 307 |
-
"output_type": "display_data"
|
| 308 |
-
},
|
| 309 |
-
{
|
| 310 |
-
"data": {
|
| 311 |
-
"text/markdown": [
|
| 312 |
-
"This student is experiencing academic difficulty, reflected in a 2.5 GPA and a failing grade in one core class, coupled with attendance concerns (88% attendance versus a 90% target) and one behavioral incident. To address these challenges and support the student's path to graduation, the following interventions are recommended:\n",
|
| 313 |
-
"\n",
|
| 314 |
-
"\n",
|
| 315 |
-
"**1. Implement a Tiered Intervention Strategy:** Determine the extent to which attendance is contributing to the student's academic struggles. (\"Strategy: Differentiating Intervention Tiers\"). If attendance is a significant factor, refer the student to the appropriate support services (Success Team or Attendance Dean, as indicated by the BAG Report format) to address these issues directly. This allows more focused support from the teaching staff for academic interventions.\n",
|
| 316 |
-
"\n",
|
| 317 |
-
"**2. Utilize a Robust Intervention Tracking System:** Implement a system to monitor the student's progress, focusing on attendance, GPA, and behavior. (\"Tool: Intervention Tracking\"). This system should clearly document interventions (e.g., tutoring sessions, mentorship meetings), and track the student’s progress in each core course (GPA and attendance rates) at two checkpoints within a ten-week period. The \"BAG Report\" format provides a useful template to track behavior, attendance and grades. This data will inform adjustments to the support plan.\n",
|
| 318 |
-
"\n",
|
| 319 |
-
"**3. Regularly Review the Student's \"BAG Report\" (or Equivalent):** Use a reporting mechanism (such as the BAG report example) to regularly review the student's performance across all three key areas: Behavior, Attendance, and Grades. This visual representation highlights areas of strength and areas requiring immediate intervention, allowing for proactive adjustments to support strategies. This aligns with the recommendation to monitor multiple key performance indicators to improve student outcomes effectively.\n"
|
| 320 |
-
],
|
| 321 |
-
"text/plain": [
|
| 322 |
-
"<IPython.core.display.Markdown object>"
|
| 323 |
-
]
|
| 324 |
-
},
|
| 325 |
-
"metadata": {},
|
| 326 |
-
"output_type": "display_data"
|
| 327 |
-
}
|
| 328 |
-
],
|
| 329 |
-
"source": [
|
| 330 |
-
"from dotenv import load_dotenv\n",
|
| 331 |
-
"\n",
|
| 332 |
-
"# Load the API key from a .env file (if it exists)\n",
|
| 333 |
-
"load_dotenv(project_path / '.env') \n",
|
| 334 |
-
"api_key = os.getenv(\"FOT_GOOGLE_API_KEY\")\n",
|
| 335 |
-
"\n",
|
| 336 |
-
"if not api_key:\n",
|
| 337 |
-
" print(\"✋ FOT_GOOGLE_API_KEY not found. Please provide your Google API key to generate the summary.\")\n",
|
| 338 |
-
" final_recommendation = \"(API Key not provided - could not generate summary)\"\n",
|
| 339 |
-
"else:\n",
|
| 340 |
-
" final_recommendation = generate_recommendation_summary(\n",
|
| 341 |
-
" retrieved_chunks=retrieved_interventions,\n",
|
| 342 |
-
" student_narrative=student_query,\n",
|
| 343 |
-
" api_key=api_key,\n",
|
| 344 |
-
" persona=\"teacher\"\n",
|
| 345 |
-
" )\n",
|
| 346 |
-
"\n",
|
| 347 |
-
"display(Markdown(\"### Final Synthesized Recommendation for the Teacher\"))\n",
|
| 348 |
-
"display(Markdown(final_recommendation))"
|
| 349 |
-
]
|
| 350 |
-
},
|
| 351 |
-
{
|
| 352 |
-
"cell_type": "markdown",
|
| 353 |
-
"id": "d3718297",
|
| 354 |
-
"metadata": {},
|
| 355 |
-
"source": [
|
| 356 |
-
"## Bonus: See the Evidence\n",
|
| 357 |
"\n",
|
| 358 |
-
"
|
| 359 |
-
]
|
| 360 |
-
},
|
| 361 |
-
{
|
| 362 |
-
"cell_type": "code",
|
| 363 |
-
"execution_count": 5,
|
| 364 |
-
"id": "1b0cb720",
|
| 365 |
-
"metadata": {},
|
| 366 |
-
"outputs": [
|
| 367 |
-
{
|
| 368 |
-
"name": "stdout",
|
| 369 |
-
"output_type": "stream",
|
| 370 |
-
"text": [
|
| 371 |
-
"\n",
|
| 372 |
-
"--- Top Recommended Interventions ---\n",
|
| 373 |
-
"\n",
|
| 374 |
-
"--- Recommendation 1 (Similarity Score: 0.5735) ---\n",
|
| 375 |
-
" Title: Strategy: Differentiating Intervention Tiers\n",
|
| 376 |
-
" Source: NCS_OTToolkit_2ndEd_October_2017_updated.pdf (Pages: 46)\n",
|
| 377 |
-
" \n",
|
| 378 |
-
" Content Snippet:\n",
|
| 379 |
-
" \"To what degree is attendance playing a role in student performance? To whom do you refer Tier 3 students who have serious attendance issues (inside and outside of the school) so that the Success Team can really concentrate on supporting Tier 2 students?...\"\n",
|
| 380 |
-
"--------------------------------------------------\n",
|
| 381 |
-
"\n",
|
| 382 |
-
"--- Recommendation 2 (Similarity Score: 0.5416) ---\n",
|
| 383 |
-
" Title: Tool: Intervention Tracking\n",
|
| 384 |
-
" Source: NCS_OTToolkit_2ndEd_October_2017_updated.pdf (Pages: 49)\n",
|
| 385 |
-
" \n",
|
| 386 |
-
" Content Snippet:\n",
|
| 387 |
-
" \"Features of Good Intervention Tracking Tools:\n",
|
| 388 |
-
" • Name of the intervention and what key performance indicator it addresses (attendance, point-in-time On-Track rates, GPA, behavior metric, etc.)\n",
|
| 389 |
-
" • Names of the targeted students\n",
|
| 390 |
-
" ° If tracking grades, include each core course's average expressed as a percentage\n",
|
| 391 |
-
" • Intervention contacts/implementation evidence\n",
|
| 392 |
-
" ° Tutoring attendance\n",
|
| 393 |
-
" ° Mentorship contact dates\n",
|
| 394 |
-
" ° \"Office hours\" visits\n",
|
| 395 |
-
" • Point-in-time progress on the key performance...\"\n",
|
| 396 |
-
"--------------------------------------------------\n",
|
| 397 |
-
"\n",
|
| 398 |
-
"--- Recommendation 3 (Similarity Score: 0.5328) ---\n",
|
| 399 |
-
" Title: Tool: BAG Report (Example)\n",
|
| 400 |
-
" Source: NCS_OTToolkit_2ndEd_October_2017_updated.pdf (Pages: 61)\n",
|
| 401 |
-
" \n",
|
| 402 |
-
" Content Snippet:\n",
|
| 403 |
-
" \"Student: Keith\n",
|
| 404 |
-
" Grade Level: 9\n",
|
| 405 |
-
" 8th Period Teacher: Donson\n",
|
| 406 |
-
" The numbers below reflect totals through Semester 1\n",
|
| 407 |
-
" \n",
|
| 408 |
-
" BEHAVIOR - In what ways do I contribute to a Safe and Respectful school climate?\n",
|
| 409 |
-
" • # of Infractions (# of Major Infractions): 5 (1)\n",
|
| 410 |
-
" • # of Days of In-School-Suspension (ISS): 10\n",
|
| 411 |
-
" • # of Days of Out-of-School-Suspension (OSS): 0\n",
|
| 412 |
-
" If I have any questions regarding my misconducts, I should schedule an appointment with the Dean of Discipline.\n",
|
| 413 |
-
" \n",
|
| 414 |
-
" ATTENDANCE - Do my actions r...\"\n",
|
| 415 |
-
"--------------------------------------------------\n"
|
| 416 |
-
]
|
| 417 |
-
}
|
| 418 |
-
],
|
| 419 |
-
"source": [
|
| 420 |
-
"display_recommendations(retrieved_interventions)"
|
| 421 |
-
]
|
| 422 |
-
},
|
| 423 |
-
{
|
| 424 |
-
"cell_type": "markdown",
|
| 425 |
-
"id": "254d4cdf",
|
| 426 |
-
"metadata": {},
|
| 427 |
-
"source": [
|
| 428 |
-
"## Explore the Live Demo!\n",
|
| 429 |
"\n",
|
| 430 |
-
"
|
| 431 |
"\n",
|
| 432 |
-
"
|
| 433 |
"\n",
|
| 434 |
-
"
|
| 435 |
-
"#### [👉 Click Here to Launch the Live FOT Recommender API](https://huggingface.co/spaces/chuckfinca/fot-recommender-api)\n"
|
| 436 |
]
|
| 437 |
-
},
|
| 438 |
-
{
|
| 439 |
-
"cell_type": "code",
|
| 440 |
-
"execution_count": null,
|
| 441 |
-
"id": "64867bc5-2762-4c69-aa72-e4e7cf911019",
|
| 442 |
-
"metadata": {},
|
| 443 |
-
"outputs": [],
|
| 444 |
-
"source": []
|
| 445 |
}
|
| 446 |
],
|
| 447 |
"metadata": {
|
|
|
|
| 6 |
"metadata": {},
|
| 7 |
"source": [
|
| 8 |
"# Freshman On-Track (FOT) Intervention Recommender\n",
|
| 9 |
+
"### A Proof-of-Concept\n",
|
| 10 |
"\n",
|
| 11 |
+
"**Goal:** To show, in just a few steps, how we can turn a description of a struggling student into a set of clear, actionable, and evidence-based strategies.\n",
|
| 12 |
"\n",
|
| 13 |
+
"This notebook demonstrates the core **Retrieval** engine that powers our recommender. It shows how the system intelligently finds the most relevant documents from a knowledge base to match a student's needs."
|
| 14 |
]
|
| 15 |
},
|
| 16 |
{
|
|
|
|
| 27 |
},
|
| 28 |
{
|
| 29 |
"cell_type": "code",
|
| 30 |
+
"execution_count": 1,
|
| 31 |
"id": "97f37783",
|
| 32 |
"metadata": {},
|
| 33 |
"outputs": [
|
|
|
|
| 42 |
}
|
| 43 |
],
|
| 44 |
"source": [
|
| 45 |
+
"import sys, os, warnings, json\n",
|
| 46 |
"from pathlib import Path\n",
|
|
|
|
| 47 |
"\n",
|
| 48 |
"# This prevents common, harmless warnings from cluttering the output.\n",
|
| 49 |
"os.environ[\"TOKENIZERS_PARALLELISM\"] = \"false\"\n",
|
| 50 |
+
"warnings.filterwarnings(\"ignore\", category=FutureWarning) # Suppress specific torch warning\n",
|
| 51 |
"\n",
|
| 52 |
+
"# Clones the project from GitHub if not already present.\n",
|
| 53 |
"PROJECT_DIR = \"fot-intervention-recommender\"\n",
|
| 54 |
"if not Path(PROJECT_DIR).is_dir():\n",
|
| 55 |
" print(\"🚀 Downloading project files...\")\n",
|
|
|
|
| 123 |
"\n",
|
| 124 |
"Now, we take the student's story and find the most relevant strategies from our **Knowledge Base**—a curated library of best practices and proven interventions.\n",
|
| 125 |
"\n",
|
| 126 |
+
"This next cell will perform the core retrieval logic:\n",
|
| 127 |
+
"1. Load the pre-processed knowledge base and citation data.\n",
|
| 128 |
+
"2. Initialize the text embedding model.\n",
|
| 129 |
+
"3. Create a searchable Facebook AI Similarity Search (FAISS) vector index.\n",
|
| 130 |
+
"4. Use the student query to find the top 3 most similar interventions.\n",
|
| 131 |
"\n",
|
| 132 |
+
"The output will show the evidence-based strategies our system identified."
|
| 133 |
]
|
| 134 |
},
|
| 135 |
{
|
|
|
|
| 159 |
"name": "stderr",
|
| 160 |
"output_type": "stream",
|
| 161 |
"text": [
|
| 162 |
+
"Batches: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 1/1 [00:02<00:00, 2.24s/it]\n"
|
|
|
|
|
|
|
| 163 |
]
|
| 164 |
},
|
| 165 |
{
|
|
|
|
| 171 |
"FAISS index created with 27 vectors.\n",
|
| 172 |
"\n",
|
| 173 |
"Searching for top 3 interventions for query: 'This student is struggling to keep up with coursework, having failed one core cl...'\n",
|
| 174 |
+
"Found 3 relevant interventions.\n",
|
| 175 |
+
"✅ Successfully loaded models and retrieved the top 3 most relevant interventions from the knowledge base.\n"
|
| 176 |
]
|
| 177 |
},
|
| 178 |
{
|
| 179 |
"data": {
|
| 180 |
"text/markdown": [
|
| 181 |
+
"### Evidence Base"
|
| 182 |
],
|
| 183 |
"text/plain": [
|
| 184 |
"<IPython.core.display.Markdown object>"
|
|
|
|
| 190 |
{
|
| 191 |
"data": {
|
| 192 |
"text/markdown": [
|
| 193 |
+
"\n",
|
| 194 |
+
"**Strategy: Differentiating Intervention Tiers**\n",
|
| 195 |
+
"- **Source:** *Freshman On‑Track Toolkit (2nd Edition)* (Network for College Success, 2017).\n",
|
| 196 |
+
"- **Page(s):** Pages: 46\n",
|
| 197 |
+
"- **Relevance Score:** 0.57\n",
|
| 198 |
+
"- **Content Snippet:**\n",
|
| 199 |
+
"> To what degree is attendance playing a role in student performance? To whom do you refer Tier 3 students who have serious attendance issues (inside and outside of the school) so that the Success Team can really concentrate on supporting Tier 2 students?\n",
|
| 200 |
+
"\n",
|
| 201 |
+
"---\n"
|
| 202 |
],
|
| 203 |
"text/plain": [
|
| 204 |
"<IPython.core.display.Markdown object>"
|
|
|
|
| 210 |
{
|
| 211 |
"data": {
|
| 212 |
"text/markdown": [
|
| 213 |
+
"\n",
|
| 214 |
+
"**Tool: Intervention Tracking**\n",
|
| 215 |
+
"- **Source:** *Freshman On‑Track Toolkit (2nd Edition)* (Network for College Success, 2017).\n",
|
| 216 |
+
"- **Page(s):** Pages: 49\n",
|
| 217 |
+
"- **Relevance Score:** 0.54\n",
|
| 218 |
+
"- **Content Snippet:**\n",
|
| 219 |
+
"> Features of Good Intervention Tracking Tools:\n",
|
| 220 |
+
"> • Name of the intervention and what key performance indicator it addresses (attendance, point-in-time On-Track rates, GPA, behavior metric, etc.)\n",
|
| 221 |
+
"> • Names of the targeted students\n",
|
| 222 |
+
"> ° If tracking grades, include each core course's average expressed as a percentage\n",
|
| 223 |
+
"> • Intervention contacts/implementation evidence\n",
|
| 224 |
+
"> ° Tutoring attendance\n",
|
| 225 |
+
"> ° Mentorship contact dates\n",
|
| 226 |
+
"> ° \"Office hours\" visits\n",
|
| 227 |
+
"> • Point-in-time progress on the key performance indicator impacted by the intervention\n",
|
| 228 |
+
"> ° Should include at least 2 checkpoints within a 10-week period\n",
|
| 229 |
+
"> ° If tracking grades, provide an average expressed as a percentage for each core course\n",
|
| 230 |
+
"> ° If tracking attendance, provide number of cumulative absences and/or tardies\n",
|
| 231 |
+
"\n",
|
| 232 |
+
"---\n"
|
| 233 |
],
|
| 234 |
"text/plain": [
|
| 235 |
"<IPython.core.display.Markdown object>"
|
|
|
|
| 241 |
{
|
| 242 |
"data": {
|
| 243 |
"text/markdown": [
|
| 244 |
+
"\n",
|
| 245 |
+
"**Tool: BAG Report (Example)**\n",
|
| 246 |
+
"- **Source:** *Freshman On‑Track Toolkit (2nd Edition)* (Network for College Success, 2017).\n",
|
| 247 |
+
"- **Page(s):** Pages: 61\n",
|
| 248 |
+
"- **Relevance Score:** 0.53\n",
|
| 249 |
+
"- **Content Snippet:**\n",
|
| 250 |
+
"> Student: Keith\n",
|
| 251 |
+
"> Grade Level: 9\n",
|
| 252 |
+
"> 8th Period Teacher: Donson\n",
|
| 253 |
+
"> The numbers below reflect totals through Semester 1\n",
|
| 254 |
+
"> \n",
|
| 255 |
+
"> BEHAVIOR - In what ways do I contribute to a Safe and Respectful school climate?\n",
|
| 256 |
+
"> • # of Infractions (# of Major Infractions): 5 (1)\n",
|
| 257 |
+
"> • # of Days of In-School-Suspension (ISS): 10\n",
|
| 258 |
+
"> • # of Days of Out-of-School-Suspension (OSS): 0\n",
|
| 259 |
+
"> If I have any questions regarding my misconducts, I should schedule an appointment with the Dean of Discipline.\n",
|
| 260 |
+
"> \n",
|
| 261 |
+
"> ATTENDANCE - Do my actions reflect the real me?\n",
|
| 262 |
+
"> • Days Enrolled: 80\n",
|
| 263 |
+
"> • Days Present: 73\n",
|
| 264 |
+
"> • Days Absent: 7\n",
|
| 265 |
+
"> • My Year-to-Date Attendance Rate is 91%\n",
|
| 266 |
+
"> If I have any questions regarding my attendance, I should schedule an appointment with the Attendance Dean.\n",
|
| 267 |
+
"> \n",
|
| 268 |
+
"> GRADES - How am I doing academically in my classes? Do my grades represent my true ability?\n",
|
| 269 |
+
"> Period | Courses | Teacher | Grade\n",
|
| 270 |
+
"> P1 | Algebra 1 | Flint | D\n",
|
| 271 |
+
"> P2 | English 1 | Lemon | B\n",
|
| 272 |
+
"> P3 | World Studies | Moeller | C\n",
|
| 273 |
+
"> P4 | PE I-Health | Spann | A\n",
|
| 274 |
+
"> P5 | Lunch | | \n",
|
| 275 |
+
"> P6 | Science | Tyson | D\n",
|
| 276 |
+
"> P7 | Photography | McCain | B\n",
|
| 277 |
+
"> P8 | Intro to Comp | Penny | A\n",
|
| 278 |
+
"> \n",
|
| 279 |
+
"> My Estimated GPA is 2.57\n",
|
| 280 |
+
"> (this estimate does NOT include any previous semesters)\n",
|
| 281 |
+
"> \n",
|
| 282 |
+
"> If I have any questions regarding my grade in a course, I should schedule an appointment with my Teacher.\n",
|
| 283 |
+
"\n",
|
| 284 |
+
"---\n"
|
| 285 |
],
|
| 286 |
"text/plain": [
|
| 287 |
"<IPython.core.display.Markdown object>"
|
|
|
|
| 292 |
}
|
| 293 |
],
|
| 294 |
"source": [
|
|
|
|
| 295 |
"from fot_recommender.rag_pipeline import (\n",
|
| 296 |
" load_knowledge_base,\n",
|
| 297 |
" initialize_embedding_model,\n",
|
| 298 |
" create_embeddings,\n",
|
| 299 |
" create_vector_db,\n",
|
| 300 |
+
" search_interventions\n",
|
|
|
|
| 301 |
")\n",
|
| 302 |
"from fot_recommender.utils import display_recommendations\n",
|
| 303 |
"\n",
|
| 304 |
+
"# 1. Load data\n",
|
|
|
|
|
|
|
| 305 |
"kb_path = project_path / \"data\" / \"processed\" / \"knowledge_base_final_chunks.json\"\n",
|
| 306 |
+
"citations_path = project_path / \"data\" / \"processed\" / \"citations.json\"\n",
|
| 307 |
"knowledge_base_chunks = load_knowledge_base(str(kb_path))\n",
|
| 308 |
+
"with open(citations_path, \"r\") as f:\n",
|
| 309 |
+
" citations_map = {item[\"source_document\"]: item for item in json.load(f)}\n",
|
| 310 |
"\n",
|
| 311 |
+
"# 2. Initialize models and DB (quietly)\n",
|
| 312 |
"embedding_model = initialize_embedding_model()\n",
|
|
|
|
|
|
|
| 313 |
"embeddings = create_embeddings(knowledge_base_chunks, embedding_model)\n",
|
| 314 |
"vector_db = create_vector_db(embeddings)\n",
|
| 315 |
"\n",
|
| 316 |
+
"# 3. Perform search (quietly)\n",
|
| 317 |
"retrieved_interventions = search_interventions(\n",
|
| 318 |
" query=student_query,\n",
|
| 319 |
" model=embedding_model,\n",
|
|
|
|
| 323 |
" min_similarity_score=0.4\n",
|
| 324 |
")\n",
|
| 325 |
"\n",
|
| 326 |
+
"# 4. Display a clean summary and the rich results\n",
|
| 327 |
+
"print(f\"✅ Successfully loaded models and retrieved the top {len(retrieved_interventions)} most relevant interventions from the knowledge base.\")\n",
|
| 328 |
+
"display_recommendations(retrieved_interventions, citations_map)"
|
|
|
|
| 329 |
]
|
| 330 |
},
|
| 331 |
{
|
|
|
|
| 333 |
"id": "2202209d",
|
| 334 |
"metadata": {},
|
| 335 |
"source": [
|
| 336 |
+
"## Step 4: See the Full System in the Live Demo!\n",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 337 |
"\n",
|
| 338 |
+
"You've just seen the core **Retrieval** engine at work. The system successfully took a student's story and identified the most relevant, evidence-based strategies from our knowledge base.\n",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 339 |
"\n",
|
| 340 |
+
"The final step in our RAG pipeline is **Generation**, where we use a Large Language Model to synthesize this evidence into a clear, actionable recommendation for an educator. This step requires a secure API key, so we've hosted it in an interactive web application.\n",
|
| 341 |
"\n",
|
| 342 |
+
"Click the link below to see the full system in action. You can use the student narrative from this notebook or try your own!\n",
|
| 343 |
"\n",
|
| 344 |
+
"### [👉 Click Here to Launch the Live FOT Recommender API](https://huggingface.co/spaces/chuckfinca/fot-recommender-api)"
|
|
|
|
| 345 |
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 346 |
}
|
| 347 |
],
|
| 348 |
"metadata": {
|
|
@@ -1,27 +1,104 @@
|
|
| 1 |
-
|
|
|
|
|
|
|
| 2 |
|
| 3 |
-
|
| 4 |
-
def display_recommendations(results: List[Tuple[Dict[str, Any], float]]):
|
| 5 |
"""
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
Args:
|
| 10 |
-
results: A list of tuples, where each tuple contains a result chunk (dict)
|
| 11 |
-
and its similarity score (float).
|
| 12 |
"""
|
| 13 |
if not results:
|
| 14 |
-
|
| 15 |
return
|
| 16 |
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import datetime
|
| 2 |
+
import json
|
| 3 |
+
from IPython.display import display, Markdown
|
| 4 |
|
| 5 |
+
def display_recommendations(results: list, citations_map: dict):
|
|
|
|
| 6 |
"""
|
| 7 |
+
Displays the retrieved recommendations in a rich, Markdown-formatted output
|
| 8 |
+
directly within a Jupyter/Colab notebook by using the shared formatter.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
"""
|
| 10 |
if not results:
|
| 11 |
+
display(Markdown("### No relevant interventions were found for this query."))
|
| 12 |
return
|
| 13 |
|
| 14 |
+
# 1. Get the formatted data from the shared function
|
| 15 |
+
formatted_evidence = format_evidence_for_display(results, citations_map)
|
| 16 |
+
|
| 17 |
+
display(Markdown("### Evidence Base"))
|
| 18 |
+
|
| 19 |
+
# 2. Loop through the clean data and render it for the notebook
|
| 20 |
+
for evidence in formatted_evidence:
|
| 21 |
+
recommendation_md = f"""
|
| 22 |
+
**{evidence['title']}**
|
| 23 |
+
- **Source:** {evidence['source']}
|
| 24 |
+
- **Page(s):** {evidence['pages']}
|
| 25 |
+
- **Relevance Score:** {evidence['score']}
|
| 26 |
+
- **Content Snippet:**
|
| 27 |
+
> {evidence['content_snippet']}
|
| 28 |
+
|
| 29 |
+
---
|
| 30 |
+
"""
|
| 31 |
+
display(Markdown(recommendation_md))
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def create_evaluation_bundle(
|
| 36 |
+
student_narrative: str,
|
| 37 |
+
persona: str,
|
| 38 |
+
retrieved_chunks_with_scores: list,
|
| 39 |
+
synthesized_recommendation: str,
|
| 40 |
+
citations_map: dict
|
| 41 |
+
) -> dict:
|
| 42 |
+
"""
|
| 43 |
+
Assembles a comprehensive dictionary for evaluation and logging purposes.
|
| 44 |
+
"""
|
| 45 |
+
evaluation_data = {
|
| 46 |
+
"timestamp": datetime.datetime.now().isoformat(),
|
| 47 |
+
"inputs": {
|
| 48 |
+
"student_narrative": student_narrative,
|
| 49 |
+
"persona": persona,
|
| 50 |
+
},
|
| 51 |
+
"retrieval_results": [
|
| 52 |
+
{
|
| 53 |
+
"chunk_title": chunk['title'],
|
| 54 |
+
"relevance_score": float(score),
|
| 55 |
+
"source_document": chunk['source_document'],
|
| 56 |
+
"page_info": chunk.get('fot_pages', 'N/A'),
|
| 57 |
+
"original_content": chunk.get('original_content', ''),
|
| 58 |
+
"citation_info": citations_map.get(chunk['source_document'], {})
|
| 59 |
+
} for chunk, score in retrieved_chunks_with_scores
|
| 60 |
+
],
|
| 61 |
+
"llm_output": {
|
| 62 |
+
"synthesized_recommendation": synthesized_recommendation
|
| 63 |
+
}
|
| 64 |
+
}
|
| 65 |
+
return evaluation_data
|
| 66 |
+
|
| 67 |
+
def format_evidence_for_display(results: list, citations_map: dict) -> list:
|
| 68 |
+
"""
|
| 69 |
+
Takes raw search results and formats them into a structured list of dictionaries
|
| 70 |
+
ready for display in any environment.
|
| 71 |
+
"""
|
| 72 |
+
evidence_list = []
|
| 73 |
+
for chunk, score in results:
|
| 74 |
+
source_doc = chunk.get('source_document', 'N/A')
|
| 75 |
+
citation_info = citations_map.get(source_doc, {})
|
| 76 |
+
|
| 77 |
+
# Consolidate all the formatting logic here
|
| 78 |
+
title = citation_info.get('title', 'N/A')
|
| 79 |
+
author = citation_info.get('author', 'N/A')
|
| 80 |
+
year = citation_info.get('year', 'N/A')
|
| 81 |
+
source_string = f"*{title}* ({author}, {year})."
|
| 82 |
+
|
| 83 |
+
page_info = chunk.get('fot_pages', 'N/A')
|
| 84 |
+
|
| 85 |
+
original_content = chunk.get("original_content", "Content not available.").strip()
|
| 86 |
+
blockquote_content = original_content.replace('\n', '\n> ')
|
| 87 |
+
|
| 88 |
+
evidence_list.append({
|
| 89 |
+
"title": chunk['title'],
|
| 90 |
+
"source": source_string,
|
| 91 |
+
"pages": page_info,
|
| 92 |
+
"score": f"{score:.2f}",
|
| 93 |
+
"content_snippet": blockquote_content
|
| 94 |
+
})
|
| 95 |
+
|
| 96 |
+
return evidence_list
|
| 97 |
+
|
| 98 |
+
def load_citations(path):
|
| 99 |
+
try:
|
| 100 |
+
with open(path, "r", encoding="utf-8") as f:
|
| 101 |
+
citations_list = json.load(f)
|
| 102 |
+
return {item["source_document"]: item for item in citations_list}
|
| 103 |
+
except (FileNotFoundError, json.JSONDecodeError):
|
| 104 |
+
return {}
|