Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ __pycache__
.env
.agent/
.DS_Store
examples/
uploads/
35 changes: 35 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
.PHONY: help start stop rebuild purge logs

help:
@echo "Usage: make [target]"
@echo ""
@echo "Targets:"
@echo " start Start the application"
@echo " stop Stop the application"
@echo " rebuild Rebuild containers (applies requirements.txt and migrations)"
@echo " purge Remove database, uploads, docker images, and volumes (CAUTION!)"
@echo " logs View logs"

start:
docker compose up -d

stop:
docker compose down

rebuild:
docker compose down
docker compose build --no-cache
docker compose up -d

purge:
@read -p "Are you sure you want to purge everything (database, uploads, volumes)? [y/N] " confirm; \
if [ "$$confirm" = "y" ] || [ "$$confirm" = "Y" ]; then \
docker compose down -v --rmi all --remove-orphans; \
rm -rf uploads/*; \
echo "Purge complete."; \
else \
echo "Purge cancelled."; \
fi

logs:
docker compose logs -f
16 changes: 2 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,9 @@

<img src="assets/logo.png" align="right" width="100" alt="Interview-Analyzer Logo">

> 🚧 **WORK IN PROGRESS** 🚧

Interview-Analyzer is a tiny application designed to help you analyze and improve your job interview performance. It leverages AI to transcribe and analyze your interview recordings, providing actionable feedback.

---

![example new analysis](assets/example_new_analysis.png)
_Interface for uploading and analyzing a new interview._

---

![example analyzed interview](assets/example_analyzed_interview.png)
_Detailed analysis and feedback view._

---
![example](assets/example.png)

## Features

Expand Down Expand Up @@ -82,5 +70,5 @@ The project includes **Adminer** for easy database management.

- Access Adminer at `http://localhost:8080`.
- System: PostgreSQL.
- Server: `db`.
- Server: `database`.
- Username/Password/Database: As defined in your `.env` file.
Binary file added assets/example.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed assets/example_analyzed_interview.png
Binary file not shown.
Binary file removed assets/example_new_analysis.png
Binary file not shown.
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ python-dotenv
psycopg2-binary
openai
redis
pypdf
32 changes: 15 additions & 17 deletions src/Home.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,18 @@

check_password()

st.title("Welcome to Interview-Analyzer 🤖")

st.markdown("""
This tool helps you analyze job interview transcriptions using AI.

### How to use:
1. Navigate to the **Interview-Analyzer** page from the sidebar.
2. Upload an audio recording of an interview.
3. The AI will transcribe the audio and generate a detailed analysis, including:
- Executive Summary
- Skills Assessment
- Behavioral Analysis (STAR method)
- Sentiment Analysis
- And more!

You can also browse and re-analyze past interviews using a different prompt.
""")
# Navigation Structure
pages = {
"General": [
st.Page("views/home.py", title="Home", icon="🏠"),
],
"Apps": [
st.Page("views/interview_analyzer.py", title="Interview Analyzer", icon="🤖"),
],
"Context": [
st.Page("views/cv.py", title="Curriculum Vitae", icon="📄"),
]
}

pg = st.navigation(pages)
pg.run()
104 changes: 102 additions & 2 deletions src/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def init_db(self):
if not self.conn:
return

query = """
query_interviews = """
CREATE TABLE IF NOT EXISTS interviews (
id SERIAL PRIMARY KEY,
audio_filename TEXT NOT NULL,
Expand All @@ -35,9 +35,33 @@ def init_db(self):
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
"""

query_cvs = """
CREATE TABLE IF NOT EXISTS cvs (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
filename TEXT NOT NULL,
text_content TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
"""

# Migration for existing tables: Add text_content column if it doesn't exist
query_migration = """
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='cvs' AND column_name='text_content') THEN
ALTER TABLE cvs ADD COLUMN text_content TEXT;
END IF;
END
$$;
"""

try:
with self.conn.cursor() as cur:
cur.execute(query)
cur.execute(query_interviews)
cur.execute(query_cvs)
cur.execute(query_migration)
self.conn.commit()
except Exception as e:
print(f"Error initializing DB: {e}")
Expand Down Expand Up @@ -127,3 +151,79 @@ def delete_interview(self, interview_id):
print(f"Error deleting interview: {e}")
self.conn.rollback()
return False

def save_cv(self, name, filename, text_content=None):
if not self.conn:
return None

query = """
INSERT INTO cvs (name, filename, text_content)
VALUES (%s, %s, %s)
RETURNING id;
"""
try:
with self.conn.cursor() as cur:
cur.execute(query, (name, filename, text_content))
cv_id = cur.fetchone()[0]
self.conn.commit()
return cv_id
except Exception as e:
print(f"Error saving CV: {e}")
self.conn.rollback()
return None

def get_all_cvs(self):
if not self.conn:
return []

query = "SELECT id, name, filename, created_at FROM cvs ORDER BY created_at DESC;"
try:
with self.conn.cursor() as cur:
cur.execute(query)
return cur.fetchall()
except Exception as e:
print(f"Error getting all CVs: {e}")
return []

def delete_cv(self, cv_id):
if not self.conn:
return False

query = "DELETE FROM cvs WHERE id = %s;"
try:
with self.conn.cursor() as cur:
cur.execute(query, (cv_id,))
self.conn.commit()
return True
except Exception as e:
print(f"Error deleting CV: {e}")
self.conn.rollback()
return False

def get_cv(self, cv_id):
if not self.conn:
return None

query = "SELECT id, name, filename, text_content, created_at FROM cvs WHERE id = %s;"
try:
with self.conn.cursor() as cur:
cur.execute(query, (cv_id,))
return cur.fetchone()
except Exception as e:
print(f"Error getting CV: {e}")
return None

def update_cv_text(self, cv_id, text_content):
if not self.conn:
return False

query = "UPDATE cvs SET text_content = %s WHERE id = %s;"
try:
with self.conn.cursor() as cur:
cur.execute(query, (text_content, cv_id))
self.conn.commit()
return True
except Exception as e:
print(f"Error updating CV text: {e}")
self.conn.rollback()
return False
14 changes: 10 additions & 4 deletions src/services/interview_orchestrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,13 @@ def get_all_interviews(self):
def get_interview(self, interview_id):
return self.db.get_interview(interview_id)

def process_new_interview(self, file_name, file_content, system_prompt):
def get_all_cvs(self):
return self.db.get_all_cvs()

def get_cv(self, cv_id):
return self.db.get_cv(cv_id)

def process_new_interview(self, file_name, file_content, system_prompt, cv_context=None):
"""
Orchestrates the upload, transcription, persistence, and analysis of a new interview.
Returns the new interview_id upon success.
Expand All @@ -34,7 +40,7 @@ def process_new_interview(self, file_name, file_content, system_prompt):
raise Exception("Failed to save transcription to database.")

# 3. Analyze
analysis_text = self.llm_service.analyze_interview(transcription_text, system_prompt)
analysis_text = self.llm_service.analyze_interview(transcription_text, system_prompt, cv_context)

# 4. Update DB with analysis
self.db.update_analysis(interview_id, analysis_text, system_prompt)
Expand All @@ -45,11 +51,11 @@ def process_new_interview(self, file_name, file_content, system_prompt):
if os.path.exists(file_path):
os.remove(file_path)

def reanalyze_interview(self, interview_id, transcription_text, new_prompt):
def reanalyze_interview(self, interview_id, transcription_text, new_prompt, cv_context=None):
"""
Re-runs the analysis on an existing transcription.
"""
new_analysis = self.llm_service.analyze_interview(transcription_text, new_prompt)
new_analysis = self.llm_service.analyze_interview(transcription_text, new_prompt, cv_context)
self.db.update_analysis(interview_id, new_analysis, new_prompt)
return new_analysis

Expand Down
9 changes: 7 additions & 2 deletions src/services/llm_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,19 @@ def transcribe_audio(self, file_path):
)
return transcription_response.text

def analyze_interview(self, transcription, system_prompt):
def analyze_interview(self, transcription, system_prompt, context_text=None):
"""
Analyzes the interview transcription using a ChatOpenAI model.
"""
chat_model = ChatOpenAI(model="gpt-4o", temperature=0.5, api_key=self.openai_api_key)

user_content = f"Transcript:\n{transcription}"
if context_text:
user_content = f"Context (e.g. CV):\n{context_text}\n\n" + user_content

messages = [
SystemMessage(content=system_prompt),
HumanMessage(content=f"Transcript:\n{transcription}")
HumanMessage(content=user_content)
]
response = chat_model.invoke(messages)
return response.content
Loading