# Project export: High-Note

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: CruzHacks 2025
- Tagline: Compare notes with classmates and receive quality assessment
- Devpost: https://devpost.com/software/high-note
- GitHub: https://github.com/alitaquie/cruzhacks2025
- Video: https://player.vimeo.com/video/1111867844?byline=0&portrait=0&title=0#t=
- Result: winner (Education Hacks)
- Team: 3 GitHub contributor(s) — Mohammad Taquie (73 commits), Khaled (10 commits), JaydenChung (8 commits)

## Devpost submission (written by the team)

### Overview

First Place for Best Education Hack 🏆 We set out to create a tool that helps students build and improve notes collaboratively, not just take better notes individually. Our goal is to streamline the note-taking process, make studying more effective, and turn a class’s collective knowledge into personalized feedback. The magic of High Note grows as more students contribute. Every note added strengthens the class dataset, making the comparative analysis more thorough and the AI feedback more insightful. Students can then see: What they nailed, What they nailed, What they missed, What they missed, And how their notes stack up against the crowd. And how their notes stack up against the crowd. It’s not just about taking notes, it’s about learning together.

### What it does

High Note is an AI-powered study assistant that helps students take their note-taking game to the next level. Whether you’re preparing for a test, reviewing a lecture, or just comparing notes with friends, High Note makes studying smarter and more collaborative. Students can create or join sessions (aka lobbies) for specific classes and upload or paste in their notes. Once submitted, High Note uses advanced Natural Language Processing (NLP) to extract key concepts and compare them with notes from other students in the same session. It then builds a personalized AI-powered feedback dashboard that: 📊 Evaluates the quality of your notes 📊 Evaluates the quality of your notes 📚 Highlights key concepts you covered well 📚 Highlights key concepts you covered well ⚠️ Suggests important concepts you missed and additions ⚠️ Suggests important concepts you missed and additions 💡 Identifies areas for improvement 💡 Identifies areas for improvement 🌟 Identifies your strengths as a note-taker 🌟 Identifies your strengths as a note-taker And the best part? It generates a personalized "High Note" — a copy of your original notes, intelligently annotated and enhanced with suggestions and concepts pulled from your peers’ notes, so you can study better, faster, and more effectively. How We Built It High Note is powered by a full-stack blend of modern tools and AI services to create a seamless, intelligent note-enhancement experience. Backend We built our backend using FastAPI, which gave us a lightweight, high-performance framework to handle API routes, file uploads, and session management. We built our backend using FastAPI, which gave us a lightweight, high-performance framework to handle API routes, file uploads, and session management. User-submitted notes are processed with RAKE (Rapid Automatic Keyword Extraction) to extract key concepts. User-submitted notes are processed with RAKE (Rapid Automatic Keyword Extraction) to extract key concepts. We then extract key concepts from the entire class dataset stored in MongoDB, and run a comparative analysis between the individual notes and class-wide concepts. We then extract key concepts from the entire class dataset stored in MongoDB, and run a comparative analysis between the individual notes and class-wide concepts. Two dynamic semantic similarity thresholds are calculated and applied: One filters out duplicate ideas within student notes. The other identifies whether concepts between users' notes and the class dataset match closely enough to count as the same "topic." Two dynamic semantic similarity thresholds are calculated and applied: One filters out duplicate ideas within student notes. The other identifies whether concepts between users' notes and the class dataset match closely enough to count as the same "topic." 🧠 AI Integration We integrated Google’s Gemini API (Flash 1.5) to analyze extracted concepts, providing: Concept hierarchy and relationships Learning gaps and missing ideas Personalized study recommendations We integrated Google’s Gemini API (Flash 1.5) to analyze extracted concepts, providing: Concept hierarchy and relationships Learning gaps and missing ideas Personalized study recommendations The Gemini model helps us enhance the user's notes and generate what we call a "High Note" - a refined, annotated version of their original content. The Gemini model helps us enhance the user's notes and generate what we call a "High Note" - a refined, annotated version of their original content. Frontend Our frontend is built with React and Tailwind CSS, allowing us to create responsive UI components and smooth navigation across views. Our frontend is built with React and Tailwind CSS, allowing us to create responsive UI components and smooth navigation across views. Users can create/join lobbies, upload notes, view dashboards, and receive live feedback, all within a sleek interface. Users can create/join lobbies, upload notes, view dashboards, and receive live feedback, all within a sleek interface. Authentication & UX We implemented protected routes to ensure only authenticated users can access sessions, submit notes, and view AI-generated feedback. We implemented protected routes to ensure only authenticated users can access sessions, submit notes, and view AI-generated feedback. Each note submission contributes to the collective learning dataset, continuously improving feedback quality for everyone. Each note submission contributes to the collective learning dataset, continuously improving feedback quality for everyone. Output & Feedback After analysis, users can download their High Notes, enriched with improved structure, missed concepts, and insights. After analysis, users can download their High Notes, enriched with improved structure, missed concepts, and insights. Every new submission feeds back into our MongoDB database, helping improve class-wide suggestions over time. Every new submission feeds back into our MongoDB database, helping improve class-wide suggestions over time.

### Challenges we ran into

Deployment. We ran out of time to be able to deploy and were only able to have our amazing idea running locally. One of the biggest challenges was integrating the High Note annotation system with our NLP pipeline. We had to design it in a way that allowed Google Gemini to generate feedback based on class-wide concepts, without accidentally repeating or summarizing the user’s own notes — this required careful prompt engineering and smart data separation. We also had to: Tune semantic similarity thresholds to avoid false positives or overly generic matches. Tune semantic similarity thresholds to avoid false positives or overly generic matches. Align our React frontend with our FastAPI backend to ensure real-time concept analysis and feedback rendering. Align our React frontend with our FastAPI backend to ensure real-time concept analysis and feedback rendering. Work with RAKE, MongoDB, and Gemini all in the same flow while keeping performance responsive. Work with RAKE, MongoDB, and Gemini all in the same flow while keeping performance responsive. And, of course, navigating a team project in a high-pressure environment meant dealing with last-minute bugs, Git merges, and sleepless decision-making. And, of course, navigating a team project in a high-pressure environment meant dealing with last-minute bugs, Git merges, and sleepless decision-making. Accomplishments That We're Proud Of We built a fully functional NLP pipeline that extracts, compares, and enhances notes — all in one weekend. We built a fully functional NLP pipeline that extracts, compares, and enhances notes — all in one weekend. Created a seamless React + FastAPI full-stack integration from scratch. Created a seamless React + FastAPI full-stack integration from scratch. Managed real-time note processing and feedback generation, without relying on pre-made tools or templates. Managed real-time note processing and feedback generation, without relying on pre-made tools or templates. Pulled it off with great team communication, even when our repo was a little... chaotic. Pulled it off with great team communication, even when our repo was a little... chaotic. And hey, we didn’t sleep, but we shipped something awesome. And hey, we didn’t sleep, but we shipped something awesome. What We Learned Frontend-first planning matters. Starting backend logic too early without syncing with frontend components cost us time. Frontend-first planning matters. Starting backend logic too early without syncing with frontend components cost us time. Setting up protected routes and user auth early helps prevent late-stage issues with session access. Setting up protected routes and user auth early helps prevent late-stage issues with session access. Using Gemini AI requires clear prompts and strict input-output formatting — great prompt design is half the battle. Using Gemini AI requires clear prompts and strict input-output formatting — great prompt design is half the battle. Realized the power of semantic thresholds in NLP — too strict, and nuance gets lost; too loose, and everything matches everything. Realized the power of semantic thresholds in NLP — too strict, and nuance gets lost; too loose, and everything matches everything. What's Next for High Note Deployment: Our top priority is getting this online and shareable for students everywhere. Deployment: Our top priority is getting this online and shareable for students everywhere. Smarter AI Prompts: Enhancing Gemini feedback with more structured explanations and interactive suggestions. Smarter AI Prompts: Enhancing Gemini feedback with more structured explanations and interactive suggestions. Compare with school readings and documents Compare with school readings and documents Analyze Job Descriptions and Resume's Analyze Job Descriptions and Resume's UI Polish: Improving usability, adding onboarding steps, and expanding session management tools. UI Polish: Improving usability, adding onboarding steps, and expanding session management tools.

## README (from the GitHub repository)

# High Note

Our project allows users to create or join a session for their desired class. Got an upcoming test? Wanna compare notes with a friend? Join a session, submit your notes. High-Note takes the key concepts in your notes and compares them to the key concepts found in your classmate's notes.
High-Note will generate a "High Note" for you which is essentially your original note annotated with improved suggestions based on other key concepts found in class. 
 This application is built with FastAPI and utilizes MongoDB using a React Frontend for data storage and Google Generative AI for specific features.

**Current Python Requirement:** Python 3.10.10

---

## Table of Contents

* Prerequisites
* Setup Instructions
* Running the Application
* Usage

---

## Prerequisites

Before you begin, ensure you have the following installed and configured:

* **Python:** Version 3.10.10. You can download it from [python.org](https://www.python.org/). Verify your installation with `python --version` or `python3 --version`.
* **MongoDB:** A running MongoDB instance (local, Docker, or a cloud service like MongoDB Atlas). You will need the connection string.
* **Google Generative AI API Key:** Access to the Google Generative AI service and your API key. You can obtain this from the [Google AI documentation](https://ai.google.dev/).
* **Git:** Required for cloning the repository.

---

## Setup Instructions

Follow these steps to get your development environment set up:

### 1. Clone the Repository

Clone this project from its source repository. Replace `<repository-url>` with the actual URL.


   ```bash
   git clone <repository-url>
   cd <repository-directory>
```
### 2. Create and Activate Virtual Environment

It's highly recommended to use a virtual environment to manage project dependencies.

## Create the environment:

```bash
python -m venv venv
```
*On Windows, you might need to use `python` instead of `python3`.*

**Activate the environment:**

**macOS/Linux:**
```bash
source venv/bin/activate
```
**Windows (Command Prompt/PowerShell):**
```bash
.\venv\Scripts\activate
```
Your terminal prompt should now indicate that you are in the (venv) environment.

### 3. Install Dependencies
```bash
pip install -r requirements.txt
```
### 4. Initialize System Environment Variables

Create a .env file at the root of your project. This file should include your configuration values. For example:
```bash
# MongoDB connection string
MONGODB_URI=mongodb://username:password@host:port/dbname

# Google Generative AI API Key
GOOGLE_API_KEY=your_google_api_key

# JWT settings (if using authentication)
SECRET_KEY=your_secret_key
ALGORITHM=HS256
ACCESS_TOKEN_EXPIRE_MINUTES=30
```
### 5. **Download NLTK Data**

Since the project uses NLTK (e.g., for stopwords), ensure that the required datasets are downloaded. You can download the stopwords data with:
```bash
python -c "import nltk; nltk.download('stopwords')"
```

### 6. Running the Application

To start the application server, use Uvicorn. Assuming your main FastAPI application instance is named `app` and is located in `app/main.py`, run the following command. The `--reload` flag enables auto-reloading when code changes are detected, which is useful for development.

Assuming your main FastAPI application is defined in a module (for instance, app/main.py), start the server using Uvicorn:
```bash
uvicorn app.main:app --reload
```
### 7. Running the Frontend
```bash
cd frontend
```
Then install dependences
```bash
npm install
```
After successfully downloading the neccessary dependencies
```bash
npm start
```


## Detected evidence (automated analysis)

Indexed codebase: 33 recognized source files, 162 KB.
- CSS (language) — detected in the code
- FastAPI (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- Google Gemini (technology) — claimed on Devpost, not found in the code
- MongoDB (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (41 of 41)

```
.DS_Store
.gitignore
app/db.py
app/extract.py
app/routes/auth.py
app/routes/lobby.py
app/routes/routes.py
frontend/.gitignore
frontend/package-lock.json.bak
frontend/package.json
frontend/postcss.config.js
frontend/public/index.html
frontend/public/manifest.json
frontend/public/robots.txt
frontend/README.md
frontend/src/App.css
frontend/src/App.js
frontend/src/App.test.js
frontend/src/components/Auth/Login.js
frontend/src/components/HighNote.js
frontend/src/components/HomeButton.js
frontend/src/components/Layout/MainLayout.js
frontend/src/components/LogoutButton.js
frontend/src/components/notes/NoteSubmitter.js
frontend/src/components/Pages/AnalysisPage.js
frontend/src/components/Pages/Hub.js
frontend/src/components/Pages/LandingPage.js
frontend/src/components/System/CreateLobby.css
frontend/src/components/System/CreateLobby.js
frontend/src/components/System/Lobby.js
frontend/src/components/System/LobbyLayout.js
frontend/src/context/AuthContext.js
frontend/src/index.css
frontend/src/index.js
frontend/src/reportWebVitals.js
frontend/src/setupTests.js
frontend/tailwind.config.js
main.py
README.md
requirements.txt
test_gemini.py
```

### Dependencies

- frontend/package.json: @headlessui/react@^2.2.1, @heroicons/react@^2.2.0, @tailwindcss/postcss@^4.1.3, @testing-library/dom@^10.4.0, @testing-library/jest-dom@^6.6.3, @testing-library/react@^16.3.0, @testing-library/user-event@^13.5.0, autoprefixer@^10.4.21, axios@^1.8.4, framer-motion@^12.6.5, postcss@^8.5.3, react@^19.1.0, react-dom@^19.1.0, react-dropzone@^14.3.8, react-router-dom@^7.5.0, react-scripts@5.0.1, tailwindcss@^3.0.0, web-vitals@^2.1.4
- requirements.txt: fastapi@>=0.95.0, google-generativeai@>=0.1.0, motor@>=3.2.0, nltk@>=3.8.0, passlib[bcrypt]@>=1.7.4, PyPDF2@>=3.0.1, python-dotenv@>=1.0.0, python-jose@>=3.3.0, rake-nltk@>=2.0.4, sentence-transformers@>=2.2.2, uvicorn[standard]@>=0.18.0

### Recent commits (newest first)

- removing empty tatus file
- remove env file from repo
- readme.md and requirements.txt
- final frontend submission
- coverage analysis logic improved
- Merge pull request #37 from alitaquie/dev2
- performance
- Merge pull request #36 from alitaquie/dev2
- fixed high note and nlp techqunices
- was
- hub done2
- hub done
- Merge pull request #34 from alitaquie/fix-empty
- frontend
- pdf & first submission
- frontend
- log-out button
- Merge pull request #33 from alitaquie/ali-new-frontend
- hub rework
- Merge pull request #32 from alitaquie/pdf-uploads

## Key source files (fetched from GitHub, selected and truncated for size)

### requirements.txt

```
fastapi>=0.95.0
uvicorn[standard]>=0.18.0
motor>=3.2.0
PyPDF2>=3.0.1
google-generativeai>=0.1.0
python-dotenv>=1.0.0
rake-nltk>=2.0.4
nltk>=3.8.0
sentence-transformers>=2.2.2
python-jose>=3.3.0
passlib[bcrypt]>=1.7.4
```

### frontend/package.json

```
{
  "name": "frontend",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@headlessui/react": "^2.2.1",
    "@heroicons/react": "^2.2.0",
    "@testing-library/dom": "^10.4.0",
    "@testing-library/jest-dom": "^6.6.3",
    "@testing-library/react": "^16.3.0",
    "@testing-library/user-event": "^13.5.0",
    "axios": "^1.8.4",
    "framer-motion": "^12.6.5",
    "react": "^19.1.0",
    "react-dom": "^19.1.0",
    "react-dropzone": "^14.3.8",
    "react-router-dom": "^7.5.0",
    "react-scripts": "5.0.1",
    "web-vitals": "^2.1.4"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "devDependencies": {
    "@tailwindcss/postcss": "^4.1.3",
    "autoprefixer": "^10.4.21",
    "postcss": "^8.5.3",
    "tailwindcss": "^3.0.0"
  }
}

```

### main.py

```python
from fastapi import FastAPI, Depends
from fastapi.middleware.cors import CORSMiddleware
from app.routes.routes import router as note_router
from app.routes.lobby import router as lobby_router
from app.routes.auth import router as auth_router, get_current_user
from dotenv import load_dotenv
import os

# Load environment variables from .env file
load_dotenv()

app = FastAPI()

# Configure CORS to allow requests from both frontend development origins
app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:3001", "http://localhost:3000"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Include routers
app.include_router(auth_router, prefix="/auth", tags=["auth"])
app.include_router(note_router, prefix="/notes", tags=["notes"], dependencies=[Depends(get_current_user)])
app.include_router(lobby_router, prefix="/lobby", tags=["lobby"], dependencies=[Depends(get_current_user)])

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

```

### frontend/src/index.js

```javascript
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import './index.css'; // or './tailwind.css' — whatever you named it


const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

```

### frontend/src/App.js

```javascript
// src/App.js
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import { AuthProvider } from './context/AuthContext';
import LandingPage from './components/Pages/LandingPage';
import Login from './components/Auth/Login';
import Hub from './components/Pages/Hub';
import Lobby from './components/System/Lobby';
import AnalysisPage from './components/Pages/AnalysisPage';
import MainLayout from './components/Layout/MainLayout';

function App() {
  return (
    <AuthProvider>
      <Router>
        <Routes>
          {/* Routes without the home button */}
          <Route path="/" element={<LandingPage />} />
          <Route path="/login" element={<Login />} />

          {/* Routes with the home button */}
          <Route element={<MainLayout />}>
            <Route path="/app/*" element={<Hub />} />
            <Route path="/lobby/:lobbyId" element={<Lobby />} />
            <Route path="/analysis" element={<AnalysisPage />} />
          </Route>
        </Routes>
      </Router>
    </AuthProvider>
  );
}

export default App;

```

### test_gemini.py

```python
import os
from dotenv import load_dotenv
import google.generativeai as genai

# Load environment variables from .env
load_dotenv()

# Get API key from environment
api_key = os.environ.get('GEMINI_API_KEY')
print(f"API key loaded: {bool(api_key)}")
print(f"API key value: {api_key}")

# Try to use the API key
try:
    genai.configure(api_key=api_key)
    model = genai.GenerativeModel('gemini-1.5-flash')
    response = model.generate_content('Say hello!')
    print(f"API response: {response.text}")
    print("Gemini API is working correctly!")
except Exception as e:
    print(f"Error: {str(e)}")
    print("Gemini API is not working correctly!") 
```

### frontend/postcss.config.js

```javascript
// postcss.config.js
module.exports = {
  plugins: [
    require('@tailwindcss/postcss')({ // Correct for v3
      config: './tailwind.config.js'
    }),
    require('autoprefixer')
  ],
}
```

### app/db.py

```python
import os
from motor.motor_asyncio import AsyncIOMotorClient
from fastapi import HTTPException
from contextlib import asynccontextmanager

MONGODB_URL = os.getenv("MONGODB_URL", "mongodb://localhost:27017")

# Create a single client instance
client = AsyncIOMotorClient(MONGODB_URL)

@asynccontextmanager
async def get_database_client():
    try:
        # Initialize auth_db indexes if they don't exist
        await client.auth_db.users.create_index("username", unique=True)
        await client.auth_db.users.create_index("email", unique=True)
        
        # Initialize notes_db indexes if they don't exist
        await client.notes_db.notes.create_index("title")
        await client.notes_db.notes.create_index("created_at")
        await client.notes_db.notes.create_index("user_id")
        
        yield client
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Database error: {str(e)}")

async def fetch_all_notes(db_client: AsyncIOMotorClient):
    try:
        db = db_client.notes_db
        cursor = db.notes.find({})
        return await cursor.to_list(length=None)
    except Exception as e:
        raise HTTPException(status_code=500, detail="Failed to fetch notes from the database.")

```

### frontend/tailwind.config.js

```javascript
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./src/**/*.{js,jsx,ts,tsx}",
    "./public/index.html", // optional but good practice
  ],
  theme: {
    extend: {
      colors: {
        primary: '#7c3aed',   // purple-600
        secondary: '#6366f1', // indigo-500
        neonPink: '#ec4899',
        softWhite: '#f3f4f6',
      },
      animation: {
        fadeIn: 'fadeIn 0.5s ease-out forwards',
        float: 'float 3s ease-in-out infinite',
        pulseFast: 'pulse 1.5s cubic-bezier(0.4, 0, 0.6, 1) infinite',
      },
      keyframes: {
        fadeIn: {
          '0%': { opacity: '0', transform: 'translateY(20px)' },
          '100%': { opacity: '1', transform: 'translateY(0)' },
        },
        float: {
          '0%, 100%': { transform: 'translateY(0)' },
          '50%': { transform: 'translateY(-5px)' },
        },
      },
      fontFamily: {
        display: ['Poppins', 'sans-serif'],
        body: ['Inter', 'sans-serif'],
      },
      boxShadow: {
        glow: '0 0 10px rgba(255, 255, 255, 0.2)',
      },
      theme: {
        extend: {
          animation: {
            bounce: 'bounce 1.5s infinite',
          },
        },
      }
      
    },
  },
  plugins: [],
}

```

### app/extract.py

```python
import re
from typing import List
from difflib import SequenceMatcher
from rake_nltk import Rake
from nltk.corpus import stopwords
from sentence_transformers import SentenceTransformer, util
import nltk

# Download required resources if not already present
nltk.download('stopwords')
nltk.download('punkt_tab')  # Downloads additional tokenizer data

# Initialize the sentence transformer model once (reuse across calls)
model = SentenceTransformer('paraphrase-MiniLM-L6-v2')


def calculate_dynamic_threshold(text_length: int, class_size: int = 1) -> float:
    """
    Calculate a dynamic threshold based on text length and class size.
    Returns a value between 0.65 and 0.85.
    """
    base_threshold = 0.75
    # Adjust factors based on text length and class size
    length_factor = min(1.2, max(0.8, text_length / 1000))
    class_factor = min(1.1, max(0.9, class_size / 10))
    dynamic_value = base_threshold * length_factor * class_factor
    # Constrain the output threshold to be between 0.65 and 0.85
    return min(0.85, max(0.65, dynamic_value))


def normalize_phrase(text: str) -> str:
    """
    Normalize a phrase: lowercases the text and removes non-alphanumeric characters.
    """
    return re.sub(r'[\W_]+', ' ', text.lower()).strip()


def is_similar(phrase_a: str, phrase_b: str, threshold: float = 0.75, method: str = 'string') -> bool:
    """
    Check if two phrases are similar based on a threshold.
    If method == 'string', it uses SequenceMatcher.
    If method == 'semantic', it uses SentenceTransformer embeddings.
    """
    if method == 'string':
        return SequenceMatcher(None, normalize_phrase(phrase_a), normalize_phrase(phrase_b)).ratio() >= threshold
    elif method == 'semantic':
        emb_a = model.encode(phrase_a, convert_to_tensor=True)
        emb_b = model.encode(phrase_b, convert_to_tensor=True)
        # Use cosine similarity from sentence-transformers utility
        return util.pytorch_cos_sim(emb_a, emb_b).item() >= threshold
    else:
        raise ValueError("Unsupported similarity method")


def filter_similar_phrases(phrases: List[str], threshold: float = 0.75, method: str = 'string') -> List[str]:
    """
    Filter out phrases that are similar to each other.
    Only one phrase from a similar group is kept.
    """
    filtered = []
    for phrase in phrases:
        if not any(is_similar(phrase, existing, threshold, method) for existing in filtered):
            filtered.append(phrase)
    return filtered


def extract_key_concepts(
    text: str,
    num_concepts: int = 10,
    threshold: float = 0.75,
    similarity_method: str = 'string',
    class_size: int = 1
) -> List[str]:
    """
    Extract key concepts from the given text using the RAKE algorithm.
    
    Args:
        text: The text to extract concepts from.
        num_concepts: Maximum number of concepts to extract (default 10).
        threshold: Similarity threshold for filtering similar concepts (default 0.75).
        similarity_method: 'string' or 'semantic' for phrase comparison.
        class_size: Class size used to adjust the dynamic threshold.
        
    Returns:
        A list of extracted key concepts.
    """
    print(f"Extracting key concepts from text of length {len(text)}")
    print(f"Parameters: num_concepts={num_concepts}, threshold={threshold}, method={similarity_method}, class_size={class_size}")
    
    # If the text is too short for meaningful extraction, return empty list.
    if not text or len(text) < 50:
        print("Text too short for meaningful extraction")
        return []
    
    # Calculate a dynamic threshold based on text length and class size.
    dynamic_threshold = calculate_dynamic_threshold(len(text), class_size)
    effective_threshold = min(threshold, dynamic_threshold)
    print(f"Calculated dynamic threshold: {dynamic_threshold}, effective_threshold: {effective_threshold}")
    
    try:
        # Initialize RAKE with English stopwords from NLTK.
        rake = Rake(stopwords=stopwords.words('english'))
        rake.extract_keywords_from_text(text)
        ranked = rake.get_ranked_phrases()
        print(f"RAKE found {len(ranked)} initial phrases")
        
        # Optionally filter out phrases by length (e.g., too short or too long phrases)
        filtered_by_length = [phrase for phrase in ranked if 3 <= len(phrase) <= 100]
        print(f"After length filtering: {len(filtered_by_length)} phrases")
        
        # Filter out similar phrases using the effective threshold and chosen method.
        unique = filter_similar_phrases(filtered_by_length, effective_threshold, similarity_method)
        print(f"After similarity filtering: {len(unique)} unique concepts")
        
        # Return the top concepts based on the requested number.
        result = unique[:num_concepts]
        print(f"Final concepts extracted: {result}")
        return result
    except Exception as e:
        print(f"Error extracting key concepts: {e}")
        return []

```

[23 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]