""" ClickUp Data Source — محاكاة ClickUp API. يقرأ data/clickup_tasks.csv ويُرجعه بنفس الشكل الذي سيُرجعه ClickUp API الحقيقي، وبنفس الـ interface بالضبط. يوم الإنتاج: نكتب ClickUpAPISource بنفس دالة fetch_tasks() التي تستدعي GET /api/v2/list/{list_id}/task — والـ Sync Engine في logic.py لا يتغير حرفًا واحدًا. خريطة الحقول (المحاكاة ← ClickUp API): task_id ← task["id"] task_name ← task["name"] client ← task["list"]["name"] (الـ List = العميل) assignee_id ← task["assignees"][0]["id"] status ← task["status"]["status"] priority ← task["priority"]["priority"] task_value ← custom_field "Task Value" (الحماية من تقسيم المهام) due_date ← task["due_date"] (ms epoch → date) completed_at ← task["date_done"] (ms epoch → date) quality_rating ← custom_field "Quality Rating" client_feedback ← custom_field "Client Feedback" """ from __future__ import annotations import os from abc import ABC, abstractmethod import pandas as pd BASE_DIR = os.path.dirname(os.path.abspath(__file__)) TASKS_FILE = os.path.join(BASE_DIR, "data", "clickup_tasks.csv") TASK_COLUMNS = [ "task_id", "task_name", "client", "assignee_id", "status", "priority", "task_value", "due_date", "completed_at", "quality_rating", "client_feedback", ] class TaskSource(ABC): """الواجهة التي يعتمد عليها الـ Sync Engine.""" @abstractmethod def fetch_tasks(self) -> pd.DataFrame: """يُرجع كل المهام بأعمدة TASK_COLUMNS.""" class CSVClickUpSource(TaskSource): """محاكاة: المصدر ملف CSV بدل استدعاء الـ API.""" def __init__(self, path: str = TASKS_FILE): self.path = path def fetch_tasks(self) -> pd.DataFrame: df = pd.read_csv(self.path) # نفس التطبيع الذي سيلزم مع الـ API الحقيقي (ms epoch → datetime) df["due_date"] = pd.to_datetime(df["due_date"], errors="coerce") df["completed_at"] = pd.to_datetime(df["completed_at"], errors="coerce") df["task_value"] = pd.to_numeric(df["task_value"], errors="coerce").fillna(0).astype(int) df["quality_rating"] = pd.to_numeric(df["quality_rating"], errors="coerce") return df def fetch_tasks_for(self, employee_id: str) -> pd.DataFrame: df = self.fetch_tasks() return df[df["assignee_id"] == employee_id] # --------------------------------------------------------------------------- # نسخة الإنتاج — تُفعَّل يوم الربط الحقيقي، بنفس الـ interface تمامًا. # # class ClickUpAPISource(TaskSource): # def __init__(self, api_token: str, list_ids: list[str]): # self.api_token = api_token # self.list_ids = list_ids # # def fetch_tasks(self) -> pd.DataFrame: # rows = [] # for list_id in self.list_ids: # r = requests.get( # f"https://api.clickup.com/api/v2/list/{list_id}/task", # headers={"Authorization": self.api_token}, # params={"include_closed": "true"}, # ) # for t in r.json()["tasks"]: # rows.append(_map_api_task(t)) # return pd.DataFrame(rows, columns=TASK_COLUMNS) # --------------------------------------------------------------------------- def get_task_source() -> TaskSource: """نقطة التبديل الوحيدة: return ClickUpAPISource(token, lists) في الإنتاج.""" return CSVClickUpSource()