Real IMAP connection via imapflow library: - Connects to IMAP server (IMAPFlow, TLS on port 993) - Fetches recent messages (configurable limit, default 20) - Extracts: from, fromEmail, subject, date, snippet (200 chars) - Thread-to-contact matching (PRD §6.1): 1. Match sender email against persons table 2. If no person match, extract domain and match against company.website 3. Returns matchedCompany + matchedPerson for each email - Audit-logged (action: crm.mailsync:imap) - Zod validated (host, port, user, password, folder, limit) - 502 on IMAP connection failure with detail - 400 on validation failure Tested: invalid creds → 502 'IMAP connection failed', missing host → 400
79 lines
2.6 KiB
Python
Executable File
79 lines
2.6 KiB
Python
Executable File
from __future__ import annotations
|
|
|
|
from .models import Question, AnswerResult
|
|
|
|
|
|
class Evaluator:
|
|
"""Evaluates user answers against correct answers.
|
|
|
|
- single_choice / true_false: exact match
|
|
- fill_blank: case-insensitive, strip whitespace
|
|
- short_answer: no auto-scoring, returns needs_review=True
|
|
"""
|
|
|
|
def evaluate_answers(
|
|
self,
|
|
questions: list[Question],
|
|
user_answers: dict[str, str],
|
|
) -> list[AnswerResult]:
|
|
results: list[AnswerResult] = []
|
|
|
|
for q in questions:
|
|
user_answer = user_answers.get(q.id)
|
|
|
|
if q.type == "short_answer":
|
|
# Short answer: cannot auto-evaluate, needs human/LLM review
|
|
results.append(
|
|
AnswerResult(
|
|
question_id=q.id,
|
|
user_answer=user_answer,
|
|
is_correct=None,
|
|
score=None,
|
|
feedback="需要人工/LLM评判",
|
|
error_type="pending_review",
|
|
needs_review=True,
|
|
)
|
|
)
|
|
continue
|
|
|
|
if user_answer is None:
|
|
results.append(
|
|
AnswerResult(
|
|
question_id=q.id,
|
|
user_answer=None,
|
|
is_correct=False,
|
|
score=0.0,
|
|
feedback=f"未作答。正确答案:{q.answer}。{q.explanation}",
|
|
error_type="no_answer",
|
|
needs_review=False,
|
|
)
|
|
)
|
|
continue
|
|
|
|
if q.type == "fill_blank":
|
|
# Fill-in-the-blank: case-insensitive, strip whitespace
|
|
is_correct = (
|
|
user_answer.strip().lower() == str(q.answer).strip().lower()
|
|
)
|
|
else:
|
|
# single_choice / true_false: exact match
|
|
is_correct = user_answer.strip() == str(q.answer).strip()
|
|
|
|
results.append(
|
|
AnswerResult(
|
|
question_id=q.id,
|
|
user_answer=user_answer,
|
|
is_correct=is_correct,
|
|
score=1.0 if is_correct else 0.0,
|
|
feedback=(
|
|
q.explanation
|
|
if is_correct
|
|
else f"回答错误。正确答案:{q.answer}。{q.explanation}"
|
|
),
|
|
error_type="none" if is_correct else "concept_confusion",
|
|
needs_review=False,
|
|
)
|
|
)
|
|
|
|
return results
|