YashashMathur's picture
SQL Data Analyst OpenEnv - Initial commit
d103a0f verified
raw
history blame
1.94 kB
import sqlite3
from .base import BaseTask
class ChurnAnalysisTask(BaseTask):
"""Task 3 — Hard: Find users who placed exactly 3 orders and then churned."""
task_id = "churn_analysis"
difficulty = "hard"
max_steps = 20
question = "Find the email addresses of users who placed exactly 3 orders and then never ordered again (churned after their 3rd purchase). Return as a comma-separated list."
relevant_tables = ["users", "orders"]
sql_hint = "CTE with COUNT and HAVING"
def compute_ground_truth(self, conn: sqlite3.Connection) -> None:
result = conn.execute("""
WITH order_counts AS (
SELECT user_id, COUNT(*) AS total_orders,
MAX(created_at) AS last_order_date
FROM orders
WHERE status = 'completed'
GROUP BY user_id
HAVING COUNT(*) = 3
),
churned AS (
SELECT oc.user_id
FROM order_counts oc
WHERE oc.last_order_date < DATE('now', '-90 days')
)
SELECT u.email
FROM users u
JOIN churned c ON u.id = c.user_id
""").fetchall()
self.ground_truth = {row[0].lower() for row in result}
def grade(self, submitted_answer: str) -> float:
if not submitted_answer.strip():
return 0.0
submitted = {e.strip().lower() for e in submitted_answer.split(",") if "@" in e}
if not submitted:
return 0.0
correct = {e.lower() for e in self.ground_truth}
tp = len(submitted & correct)
if tp == 0:
return 0.0
precision = tp / len(submitted) if submitted else 0
recall = tp / len(correct) if correct else 0
if precision + recall == 0:
return 0.0
f1 = 2 * precision * recall / (precision + recall)
return round(f1, 3)