CineDev's picture
fix: update interview grading logic in views.py
8a5cb28
Raw
History Blame Contribute Delete
32.4 kB
from rest_framework import generics, permissions, status, views
from rest_framework.response import Response
from .models import InterviewSession
from .serializers import InterviewSessionSerializer
from django.utils import timezone
class InterviewSessionListCreateView(generics.ListCreateAPIView):
serializer_class = InterviewSessionSerializer
permission_classes = [permissions.IsAuthenticated]
def get_queryset(self):
company = self.request.company
if not company:
return InterviewSession.objects.none()
queryset = InterviewSession.objects.filter(company=company)
# Filter by creator/recruiter if user is a recruiter and not a company admin
from companies.models import RecruiterProfile, CompanyAdminProfile
is_admin = CompanyAdminProfile.objects.filter(user=self.request.user).exists()
is_recruiter = RecruiterProfile.objects.filter(user=self.request.user).exists()
if is_recruiter and not is_admin:
queryset = queryset.filter(job__created_by=self.request.user)
return queryset.order_by('-scheduled_at')
class InterviewSessionDetailView(views.APIView):
# AllowAny for get and patch so candidate and recruiter can access details / reschedule
permission_classes = [permissions.AllowAny]
def get(self, request, room_id):
try:
session = InterviewSession.objects.get(room_id=room_id)
except InterviewSession.DoesNotExist:
return Response({"detail": "Interview session not found."}, status=status.HTTP_404_NOT_FOUND)
return Response(InterviewSessionSerializer(session).data, status=status.HTTP_200_OK)
def patch(self, request, room_id):
try:
session = InterviewSession.objects.get(room_id=room_id)
except InterviewSession.DoesNotExist:
return Response({"detail": "Interview session not found."}, status=status.HTTP_404_NOT_FOUND)
scheduled_at = request.data.get('scheduled_at')
if not scheduled_at:
return Response({"scheduled_at": ["This field is required."]}, status=status.HTTP_400_BAD_REQUEST)
try:
session.scheduled_at = scheduled_at
session.save()
return Response(InterviewSessionSerializer(session).data, status=status.HTTP_200_OK)
except Exception as e:
return Response({"detail": str(e)}, status=status.HTTP_400_BAD_REQUEST)
class UpdatePrivateNotesView(views.APIView):
permission_classes = [permissions.IsAuthenticated]
def post(self, request, room_id):
company = request.company
session = InterviewSession.objects.filter(company=company, room_id=room_id).first()
if not session:
return Response({"detail": "Interview session not found."}, status=status.HTTP_404_NOT_FOUND)
notes = request.data.get('notes', '')
session.private_notes = notes
session.save()
return Response(InterviewSessionSerializer(session).data, status=status.HTTP_200_OK)
class SubmitFeedbackView(views.APIView):
permission_classes = [permissions.IsAuthenticated]
def post(self, request, room_id):
company = request.company
session = InterviewSession.objects.filter(company=company, room_id=room_id).first()
if not session:
return Response({"detail": "Interview session not found."}, status=status.HTTP_404_NOT_FOUND)
feedback_data = request.data.get('feedback')
if not feedback_data:
return Response({"feedback": ["This field is required."]}, status=status.HTTP_400_BAD_REQUEST)
# Validate feedback structure
required_fields = ["technical_skills", "communication", "problem_solving", "culture_fit", "recommendation"]
for field in required_fields:
if field not in feedback_data:
return Response({"feedback": [f"Missing required scorecard field: '{field}'"]}, status=status.HTTP_400_BAD_REQUEST)
session.feedback = feedback_data
session.completed_at = timezone.now()
session.save()
# Update candidate application status
from candidates.models import Application
application = Application.objects.filter(job=session.job, candidate=session.candidate).first()
if application:
rec = feedback_data.get('recommendation')
if rec in ('Strong Yes', 'Yes'):
application.current_stage = "Offer"
application.status = "Offered"
elif rec in ('No', 'Strong No'):
application.current_stage = "Rejected"
application.status = "Rejected"
application.save()
return Response({
"message": "Structured feedback submitted successfully.",
"session": InterviewSessionSerializer(session).data
}, status=status.HTTP_200_OK)
class CandidateMyInterviewsView(views.APIView):
permission_classes = [permissions.AllowAny]
def get(self, request):
email = request.query_params.get('email')
if not email:
return Response({"detail": "email parameter is required."}, status=status.HTTP_400_BAD_REQUEST)
sessions = InterviewSession.objects.filter(candidate__email=email).order_by('-scheduled_at')
serializer = InterviewSessionSerializer(sessions, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
import base64
import requests
import json
import uuid
import os
import time
import logging
import re
import random
from html.parser import HTMLParser
from html import unescape
from django.conf import settings
from rest_framework.parsers import MultiPartParser, FormParser
from assessments.models import CodingQuestion
import jwt
import pdfplumber
logger = logging.getLogger(__name__)
class CodeforcesProblemParser(HTMLParser):
def __init__(self):
super().__init__(convert_charrefs=False)
self.in_statement = False
self.statement_depth = 0
self.current_section = None
self.current_section_depth = 0
self.title = ''
self.time_limit = ''
self.memory_limit = ''
self.statement_parts = []
self.input_parts = []
self.output_parts = []
self.sample_inputs = []
self.sample_outputs = []
self._capture_stack = []
def _has_class(self, attrs, class_name):
classes = ''
for key, value in attrs:
if key == 'class':
classes = value or ''
break
return class_name in classes.split()
def _start_capture(self, name):
self._capture_stack.append({
'name': name,
'depth': 1,
'parts': []
})
def _append_to_capture(self, text):
if self._capture_stack and text:
self._capture_stack[-1]['parts'].append(text)
def _finish_capture(self):
capture = self._capture_stack.pop()
text = self._normalize_text(''.join(capture['parts']))
name = capture['name']
if name == 'title':
self.title = text
elif name == 'time-limit':
self.time_limit = re.sub(r'^time limit per test\s*', '', text, flags=re.I)
elif name == 'memory-limit':
self.memory_limit = re.sub(r'^memory limit per test\s*', '', text, flags=re.I)
elif name == 'input':
text = re.sub(r'^input\s*\n?', '', text, flags=re.I).strip()
if text:
self.sample_inputs.append(text)
elif name == 'output':
text = re.sub(r'^output\s*\n?', '', text, flags=re.I).strip()
if text:
self.sample_outputs.append(text)
def _append_section_text(self, text):
if not self.in_statement or self._capture_stack:
return
if self.current_section == 'input-specification':
self.input_parts.append(text)
elif self.current_section == 'output-specification':
self.output_parts.append(text)
elif self.current_section not in ('sample-tests',):
self.statement_parts.append(text)
def handle_starttag(self, tag, attrs):
if self._capture_stack:
self._capture_stack[-1]['depth'] += 1
if tag in ('br', 'pre'):
self._append_to_capture('\n')
return
if self._has_class(attrs, 'problem-statement'):
self.in_statement = True
self.statement_depth = 1
return
if self.in_statement:
self.statement_depth += 1
section_names = [
'title',
'time-limit',
'memory-limit',
'input-specification',
'output-specification',
'sample-tests'
]
for name in section_names:
if self._has_class(attrs, name):
self.current_section = name
self.current_section_depth = self.statement_depth
if name in ('title', 'time-limit', 'memory-limit'):
self._start_capture(name)
return
if self.current_section == 'sample-tests':
if self._has_class(attrs, 'input'):
self._start_capture('input')
elif self._has_class(attrs, 'output'):
self._start_capture('output')
if tag in ('p', 'div') and self.in_statement:
self._append_section_text('\n')
elif tag == 'li' and self.in_statement:
self._append_section_text('\n- ')
elif tag == 'br' and self.in_statement:
self._append_section_text('\n')
def handle_endtag(self, tag):
if self._capture_stack:
if tag == 'pre':
self._append_to_capture('\n')
self._capture_stack[-1]['depth'] -= 1
if self._capture_stack[-1]['depth'] > 0:
return
self._finish_capture()
if self.current_section and self.statement_depth <= self.current_section_depth:
self.current_section = None
self.current_section_depth = 0
if self.in_statement:
self.statement_depth -= 1
if self.statement_depth <= 0:
self.in_statement = False
def handle_data(self, data):
text = unescape(data)
self._append_to_capture(text)
self._append_section_text(text)
def handle_entityref(self, name):
self.handle_data(f'&{name};')
def handle_charref(self, name):
self.handle_data(f'&#{name};')
def _normalize_text(self, text):
text = unescape(text).replace('\xa0', ' ')
text = re.sub(r'[ \t]+', ' ', text)
text = re.sub(r' *\n *', '\n', text)
text = re.sub(r'\n{3,}', '\n\n', text)
return text.strip()
def parsed(self):
statement = self._normalize_text(''.join(self.statement_parts))
input_spec = re.sub(
r'^input\s*\n?',
'',
self._normalize_text(''.join(self.input_parts)),
flags=re.I
).strip()
output_spec = re.sub(
r'^output\s*\n?',
'',
self._normalize_text(''.join(self.output_parts)),
flags=re.I
).strip()
samples = []
for sample_input, sample_output in zip(self.sample_inputs, self.sample_outputs):
if sample_input and sample_output:
samples.append({
"input": sample_input,
"expected_output": sample_output,
"is_hidden": False
})
return {
"title": self.title,
"statement": statement,
"input_spec": input_spec,
"output_spec": output_spec,
"time_limit": self.time_limit,
"memory_limit": self.memory_limit,
"test_cases": samples,
}
def fetch_codeforces_problem_details(problem_url, contest_id=None, problem_index=None):
urls = [problem_url]
if contest_id and problem_index:
contest_url = f"https://codeforces.com/contest/{contest_id}/problem/{problem_index}"
if contest_url not in urls:
urls.append(contest_url)
last_error = None
headers = {
"User-Agent": "Mozilla/5.0 (compatible; TalvexInterviewImporter/1.0)",
"Accept-Language": "en-US,en;q=0.9",
}
for url in urls:
try:
response = requests.get(url, headers=headers, timeout=18)
response.raise_for_status()
parser = CodeforcesProblemParser()
parser.feed(response.text)
parsed = parser.parsed()
if parsed.get('statement'):
return parsed
last_error = "problem statement markup was not found"
except Exception as exc:
last_error = exc
if last_error:
logger.warning(f"Failed to parse Codeforces problem page {problem_url}: {last_error}")
return {}
DEFAULT_CODING_QUESTIONS = [
{
"id": 1,
"title": "Two Sum",
"difficulty": "Easy",
"description": "Given an array of integers nums and an integer target, return indices of the two numbers such that they add up to target.\nYou may assume that each input would have exactly one solution, and you may not use the same element twice.\n\nExample 1:\nInput: nums = [2,7,11,15], target = 9\nOutput: [0,1]\nExplanation: nums[0] + nums[1] == 9, so we return [0, 1].",
"starter_code": {
"javascript": "function solution(nums, target) {\n // Write your code here\n return [];\n}",
"python": "def solution(nums, target):\n # Write your code here\n return []"
},
"test_cases": [
{"input": "[2,7,11,15], 9", "expected_output": "[0, 1]", "is_hidden": False}
]
},
{
"id": 2,
"title": "Reverse Integer",
"difficulty": "Medium",
"description": "Given a signed 32-bit integer x, return x with its digits reversed. If reversing x causes the value to go outside the signed 32-bit integer range [-2^31, 2^31 - 1], then return 0.\n\nExample 1:\nInput: x = 123\nOutput: 321",
"starter_code": {
"javascript": "function solution(x) {\n // Write your code here\n return 0;\n}",
"python": "def solution(x):\n # Write your code here\n return 0"
},
"test_cases": [
{"input": "123", "expected_output": "321", "is_hidden": False}
]
}
]
def upload_pdf_to_supabase(uploaded_file, bucket_name='interview-questions'):
ext = os.path.splitext(uploaded_file.name)[1]
file_name = f"{uuid.uuid4()}{ext}"
try:
jwt_secret = settings.SUPABASE_JWT_SECRET
decoded_secret = jwt_secret.encode('utf-8') if isinstance(jwt_secret, str) else jwt_secret
payload = {
"role": "service_role",
"iss": "supabase",
"iat": int(time.time()),
"exp": int(time.time() + 3600)
}
token = jwt.encode(payload, decoded_secret, algorithm="HS256")
if isinstance(token, bytes):
token = token.decode('utf-8')
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
bucket_check_url = f"{settings.SUPABASE_URL}/storage/v1/bucket/{bucket_name}"
check_res = requests.get(bucket_check_url, headers=headers)
if check_res.status_code != 200:
create_bucket_url = f"{settings.SUPABASE_URL}/storage/v1/bucket"
body = {"id": bucket_name, "name": bucket_name, "public": True}
requests.post(create_bucket_url, headers=headers, json=body)
upload_headers = {
"Authorization": f"Bearer {token}",
}
upload_url = f"{settings.SUPABASE_URL}/storage/v1/object/{bucket_name}/{file_name}"
file_data = uploaded_file.read()
content_type = "application/pdf"
upload_headers["Content-Type"] = content_type
res = requests.post(upload_url, headers=upload_headers, data=file_data)
if res.status_code == 200:
public_url = f"{settings.SUPABASE_URL}/storage/v1/object/public/{bucket_name}/{file_name}"
return public_url
except Exception as e:
logger.error(f"Failed to upload PDF to Supabase: {e}")
return None
def extract_pdf_via_gemini_multimodal(file_bytes, model_name="gemini-1.5-flash"):
api_key = os.environ.get('GEMINI_API_KEY') or os.environ.get('GOOGLE_API_KEY')
if not api_key:
logger.warning("No GEMINI_API_KEY or GOOGLE_API_KEY found.")
return None
url = f"https://generativelanguage.googleapis.com/v1beta/models/{model_name}:generateContent?key={api_key}"
base64_data = base64.b64encode(file_bytes).decode('utf-8')
prompt = (
"You are an expert technical interviewer. Parse the provided coding interview question PDF "
"(which may be typed or handwritten) and extract ALL coding questions into a single valid JSON array. "
"Each question object MUST strictly have the following fields:\n"
"- title: The name of the coding problem.\n"
"- difficulty: Either 'Easy', 'Medium', or 'Hard'.\n"
"- description: Clear and detailed description of the problem statement, parameters, and input/output rules.\n"
"- starter_code: A JSON object with language starter templates, e.g. {\"javascript\": \"function solution() {\\n\\n}\", \"python\": \"def solution():\\n pass\"}.\n"
"- test_cases: A JSON list of test case objects. E.g. [{\"input\": \"5\", \"expected_output\": \"10\", \"is_hidden\": false}].\n"
"\nReturn ONLY the JSON array inside a raw text. Do not wrap in markdown or markdown code blocks like ```json."
)
payload = {
"contents": [{
"parts": [
{"text": prompt},
{
"inlineData": {
"mimeType": "application/pdf",
"data": base64_data
}
}
]
}]
}
try:
response = requests.post(url, json=payload, timeout=20)
if response.status_code == 200:
res_json = response.json()
content = res_json['candidates'][0]['content']['parts'][0]['text']
content = content.replace("```json", "").replace("```", "").strip()
return json.loads(content)
else:
logger.error(f"Gemini {model_name} failed with status {response.status_code}: {response.text}")
except Exception as e:
logger.error(f"Error in Gemini multimodal parse: {e}")
return None
def parse_pdf_via_huggingface(raw_text):
api_key = os.environ.get('HUGGINGFACE_API_KEY') or os.environ.get('HF_API_KEY')
if not api_key:
return None
model_id = os.environ.get('HF_MODEL_ID', 'Qwen/Qwen2.5-7B-Instruct')
url = f"https://api-inference.huggingface.co/models/{model_id}"
headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
prompt = (
f"Parse the following text from an interview questions document and extract the coding questions into a valid JSON array of question objects. "
"Each question must contain: 'title', 'difficulty' ('Easy'/'Medium'/'Hard'), 'description', "
"'starter_code' (object with 'javascript' and 'python'), and 'test_cases' (list of objects with 'input', 'expected_output', 'is_hidden'). "
"Return ONLY valid JSON.\n\n"
f"Document Text:\n{raw_text}"
)
try:
response = requests.post(url, json={"inputs": prompt, "parameters": {"max_new_tokens": 1000}}, headers=headers, timeout=12)
if response.status_code == 200:
res_data = response.json()
content = ""
if isinstance(res_data, list) and len(res_data) > 0:
content = res_data[0].get('generated_text', '').strip()
elif isinstance(res_data, dict):
content = res_data.get('generated_text', '').strip()
json_match = re.search(r'\[.*\]', content, re.DOTALL)
if json_match:
return json.loads(json_match.group(0))
except Exception as e:
logger.error(f"Hugging Face PDF parsing fallback failed: {e}")
return None
def parse_pdf_heuristics(raw_text):
questions = []
if not raw_text:
raw_text = "Standard Coding Challenge"
parts = re.split(r'(?:Question|Problem|Task)\s*\d*\s*[:\-]\s*', raw_text, flags=re.IGNORECASE)
if len(parts) > 1:
for i, part in enumerate(parts[1:]):
lines = [l.strip() for l in part.split('\n') if l.strip()]
title = lines[0] if lines else f"Coding Challenge {i+1}"
desc = "\n".join(lines[1:]) if len(lines) > 1 else part
questions.append({
"title": title[:100],
"difficulty": "Medium",
"description": desc,
"starter_code": {
"javascript": "function solution() {\n // Write your code here\n}",
"python": "def solution():\n pass"
},
"test_cases": [{"input": "1", "expected_output": "1", "is_hidden": False}]
})
if not questions:
questions.append({
"title": "Algorithm Challenge",
"difficulty": "Medium",
"description": raw_text[:2000],
"starter_code": {
"javascript": "function solution() {\n // Write your code here\n}",
"python": "def solution():\n pass"
},
"test_cases": [{"input": "1", "expected_output": "1", "is_hidden": False}]
})
return questions
class InterviewQuestionsView(views.APIView):
permission_classes = [permissions.AllowAny]
def get(self, request, room_id):
try:
session = InterviewSession.objects.get(room_id=room_id)
except InterviewSession.DoesNotExist:
return Response({"detail": "Interview session not found."}, status=status.HTTP_404_NOT_FOUND)
if not session.questions:
questions_qs = CodingQuestion.objects.filter(company__isnull=True)
if questions_qs.exists():
session.questions = CodingQuestionSerializer(questions_qs, many=True).data
else:
session.questions = DEFAULT_CODING_QUESTIONS
session.save()
return Response(session.questions, status=status.HTTP_200_OK)
def post(self, request, room_id):
try:
session = InterviewSession.objects.get(room_id=room_id)
except InterviewSession.DoesNotExist:
return Response({"detail": "Interview session not found."}, status=status.HTTP_404_NOT_FOUND)
questions = request.data.get('questions')
if not isinstance(questions, list):
return Response({"detail": "questions field must be a list."}, status=status.HTTP_400_BAD_REQUEST)
session.questions = questions
session.save()
return Response(session.questions, status=status.HTTP_200_OK)
class InterviewImportPdfView(views.APIView):
permission_classes = [permissions.AllowAny]
parser_classes = [MultiPartParser, FormParser]
def post(self, request, room_id):
try:
session = InterviewSession.objects.get(room_id=room_id)
except InterviewSession.DoesNotExist:
return Response({"detail": "Interview session not found."}, status=status.HTTP_404_NOT_FOUND)
uploaded_file = request.FILES.get('file')
if not uploaded_file:
return Response({"error": "No PDF file uploaded."}, status=status.HTTP_400_BAD_REQUEST)
file_bytes = uploaded_file.read()
uploaded_file.seek(0)
pdf_url = upload_pdf_to_supabase(uploaded_file)
parsed_questions = None
# Level 1: Gemini 1.5 Flash
try:
logger.info("Attempting PDF multimodal parse via Gemini 1.5 Flash...")
parsed_questions = extract_pdf_via_gemini_multimodal(file_bytes, model_name="gemini-1.5-flash")
except Exception as e:
logger.error(f"Gemini 1.5 Flash failed: {e}")
# Level 2: Gemini 1.5 Pro
if not parsed_questions:
try:
logger.info("Attempting PDF multimodal parse via Gemini 1.5 Pro (Fallback)...")
parsed_questions = extract_pdf_via_gemini_multimodal(file_bytes, model_name="gemini-1.5-pro")
except Exception as e:
logger.error(f"Gemini 1.5 Pro failed: {e}")
# Level 3: Hugging Face Qwen-2.5
if not parsed_questions:
try:
logger.info("Attempting plain text PDF parse via Hugging Face...")
raw_text = ""
with pdfplumber.open(uploaded_file) as pdf:
for page in pdf.pages:
t = page.extract_text()
if t:
raw_text += t + "\n"
parsed_questions = parse_pdf_via_huggingface(raw_text)
except Exception as e:
logger.error(f"Hugging Face plain text PDF parse failed: {e}")
# Level 4: Heuristics
if not parsed_questions:
logger.warning("All LLM PDF parsers failed. Falling back to local heuristic segmenter.")
try:
uploaded_file.seek(0)
raw_text = ""
with pdfplumber.open(uploaded_file) as pdf:
for page in pdf.pages:
t = page.extract_text()
if t:
raw_text += t + "\n"
parsed_questions = parse_pdf_heuristics(raw_text)
except Exception as e:
logger.error(f"Local heuristic PDF segmenter failed: {e}")
parsed_questions = DEFAULT_CODING_QUESTIONS
sanitized_questions = []
for idx, q in enumerate(parsed_questions):
sanitized_questions.append({
"id": idx + 1,
"title": q.get('title', f"Parsed Question {idx+1}"),
"difficulty": q.get('difficulty', 'Medium'),
"description": q.get('description', 'Solve the problem.'),
"starter_code": q.get('starter_code', {
"javascript": "function solution() {\n // Write your code here\n}",
"python": "def solution():\n pass"
}),
"test_cases": q.get('test_cases', [
{"input": "1", "expected_output": "1", "is_hidden": False}
])
})
session.questions = sanitized_questions
session.save()
return Response({
"message": "PDF parsed successfully.",
"questions": session.questions,
"parsed_url": pdf_url
}, status=status.HTTP_200_OK)
class InterviewImportCodeforcesView(views.APIView):
permission_classes = [permissions.AllowAny]
def post(self, request, room_id):
try:
session = InterviewSession.objects.get(room_id=room_id)
except InterviewSession.DoesNotExist:
return Response({"detail": "Interview session not found."}, status=status.HTTP_404_NOT_FOUND)
count = request.data.get('count', 5)
try:
count = int(count)
except ValueError:
count = 5
cf_url = "https://codeforces.com/api/problemset.problems"
try:
res = requests.get(cf_url, timeout=10)
if res.status_code == 200:
data = res.json()
if data.get('status') == 'OK':
problems = data.get('result', {}).get('problems', [])
valid_problems = [
p for p in problems
if p.get('rating') and 800 <= p.get('rating') <= 1500
]
if not valid_problems:
valid_problems = problems
sample_size = min(count, len(valid_problems))
selected_cf = random.sample(valid_problems, sample_size)
imported_questions = []
for idx, p in enumerate(selected_cf):
contest_id = p.get('contestId')
index = p.get('index')
name = p.get('name')
rating = p.get('rating', 1000)
difficulty = 'Easy' if rating <= 1100 else 'Medium'
prob_link = f"https://codeforces.com/problemset/problem/{contest_id}/{index}"
tags = ", ".join(p.get('tags', []))
details = fetch_codeforces_problem_details(prob_link, contest_id, index)
statement = details.get('statement', '')
input_spec = details.get('input_spec', '')
output_spec = details.get('output_spec', '')
sample_tests = details.get('test_cases') or []
time_limit = details.get('time_limit', '')
memory_limit = details.get('memory_limit', '')
metadata_lines = [
f"Codeforces Problem {contest_id}{index} - {name}",
f"Link: {prob_link}",
f"Difficulty Rating: {rating}",
]
if tags:
metadata_lines.append(f"Tags: {tags}")
if time_limit:
metadata_lines.append(f"Time Limit: {time_limit}")
if memory_limit:
metadata_lines.append(f"Memory Limit: {memory_limit}")
if statement:
desc_sections = [
"\n".join(metadata_lines),
"Problem Statement:\n" + statement
]
if input_spec:
desc_sections.append("Input:\n" + input_spec)
if output_spec:
desc_sections.append("Output:\n" + output_spec)
desc = "\n\n".join(desc_sections)
else:
desc = (
f"{chr(10).join(metadata_lines)}\n\n"
f"Unable to fetch the full statement automatically. Please open the Codeforces link above for the complete problem.\n"
f"Write a solution program that processes standard input correctly."
)
imported_questions.append({
"id": idx + 1,
"title": f"Codeforces: {name}",
"difficulty": difficulty,
"description": desc,
"starter_code": {
"javascript": f"// Codeforces problem: {prob_link}\nfunction solution(input) {{\n // Process input lines here\n}}",
"python": f"# Codeforces problem: {prob_link}\ndef solution(input):\n # Process input lines here\n pass"
},
"test_cases": sample_tests or [
{"input": "1", "expected_output": "1", "is_hidden": False}
]
})
session.questions = imported_questions
session.save()
return Response(session.questions, status=status.HTTP_200_OK)
except Exception as e:
logger.error(f"Codeforces import failed: {e}")
return Response({"detail": "Failed to fetch from Codeforces API. Please try again later."}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)