| import requests |
| import json |
| from typing import Optional, Dict, Any |
| import logging |
|
|
| logging.basicConfig(level=logging.INFO) |
| logger = logging.getLogger(__name__) |
|
|
| API_URL = "https://0xarchit-citytrack.hf.space" |
|
|
| class CityTrackAPIClient: |
| def __init__(self, base_url: str = API_URL): |
| self.base_url = base_url.rstrip("/") |
| self.session = requests.Session() |
| self.access_token: Optional[str] = None |
|
|
| def admin_login(self, email: str, password: str, expected_role: Optional[str] = "admin") -> Dict[str, Any]: |
| try: |
| payload: Dict[str, Any] = { |
| "email": email, |
| "password": password, |
| "expected_role": expected_role, |
| } |
| response = self.session.post( |
| f"{self.base_url}/admin/login", |
| json=payload, |
| headers={"Content-Type": "application/json"}, |
| ) |
| response.raise_for_status() |
| data = response.json() |
| token = data.get("access_token") |
| if not token: |
| return {"error": "Missing access_token", "response": data} |
| self.access_token = token |
| self.session.headers.update({"Authorization": f"Bearer {token}"}) |
| return data |
| except Exception as e: |
| logger.error(f"Admin login failed: {e}") |
| return {"error": str(e)} |
| |
| def health_check(self) -> Dict[str, Any]: |
| """Check API health status""" |
| try: |
| response = self.session.get(f"{self.base_url}/health/health") |
| response.raise_for_status() |
| return response.json() |
| except Exception as e: |
| logger.error(f"Health check failed: {e}") |
| return {"status": "error", "message": str(e)} |
| |
| def get_issues(self, limit: int = 10, skip: int = 0) -> Dict[str, Any]: |
| """Fetch all issues""" |
| try: |
| response = self.session.get( |
| f"{self.base_url}/issues", |
| params={"limit": limit, "skip": skip} |
| ) |
| response.raise_for_status() |
| return response.json() |
| except Exception as e: |
| logger.error(f"Failed to fetch issues: {e}") |
| return {"error": str(e)} |
| |
| def get_issue_by_id(self, issue_id: str) -> Dict[str, Any]: |
| """Fetch a specific issue""" |
| try: |
| response = self.session.get(f"{self.base_url}/issues/{issue_id}") |
| response.raise_for_status() |
| return response.json() |
| except Exception as e: |
| logger.error(f"Failed to fetch issue {issue_id}: {e}") |
| return {"error": str(e)} |
| |
| def create_issue(self, payload: Dict[str, Any]) -> Dict[str, Any]: |
| """Create a new issue""" |
| try: |
| response = self.session.post( |
| f"{self.base_url}/issues", |
| json=payload, |
| headers={"Content-Type": "application/json"} |
| ) |
| response.raise_for_status() |
| return response.json() |
| except Exception as e: |
| logger.error(f"Failed to create issue: {e}") |
| return {"error": str(e)} |
| |
| def update_issue(self, issue_id: str, payload: Dict[str, Any]) -> Dict[str, Any]: |
| """Update an existing issue""" |
| try: |
| response = self.session.put( |
| f"{self.base_url}/issues/{issue_id}", |
| json=payload, |
| headers={"Content-Type": "application/json"} |
| ) |
| response.raise_for_status() |
| return response.json() |
| except Exception as e: |
| logger.error(f"Failed to update issue {issue_id}: {e}") |
| return {"error": str(e)} |
| |
| def get_departments(self) -> Dict[str, Any]: |
| """Fetch all departments""" |
| try: |
| response = self.session.get(f"{self.base_url}/admin/departments") |
| response.raise_for_status() |
| return response.json() |
| except Exception as e: |
| logger.error(f"Failed to fetch departments: {e}") |
| return {"error": str(e)} |
|
|
| def create_department(self, name: str, code: str, description: Optional[str] = None, |
| categories: Optional[str] = None, default_sla_hours: int = 48, |
| escalation_email: Optional[str] = None) -> Dict[str, Any]: |
| try: |
| payload: Dict[str, Any] = { |
| "name": name, |
| "code": code, |
| "description": description, |
| "categories": categories, |
| "default_sla_hours": default_sla_hours, |
| "escalation_email": escalation_email, |
| } |
| response = self.session.post( |
| f"{self.base_url}/admin/departments", |
| json=payload, |
| headers={"Content-Type": "application/json"}, |
| ) |
| response.raise_for_status() |
| return response.json() |
| except Exception as e: |
| logger.error(f"Failed to create department {code}: {e}") |
| return {"error": str(e)} |
| |
| def get_workers(self, department_id: Optional[str] = None) -> Dict[str, Any]: |
| """Fetch all workers or workers from a specific department""" |
| try: |
| params = {} |
| if department_id: |
| params["department_id"] = department_id |
| |
| response = self.session.get( |
| f"{self.base_url}/admin/members", |
| params=params |
| ) |
| response.raise_for_status() |
| data = response.json() |
| if isinstance(data, list): |
| return [m for m in data if isinstance(m, dict) and m.get("role") == "worker"] |
| return data |
| except Exception as e: |
| logger.error(f"Failed to fetch workers: {e}") |
| return {"error": str(e)} |
| |
| def get_worker_tasks(self) -> Dict[str, Any]: |
| """Fetch tasks assigned to the current worker (from JWT)""" |
| try: |
| response = self.session.get(f"{self.base_url}/worker/tasks") |
| response.raise_for_status() |
| return response.json() |
| except Exception as e: |
| logger.error(f"Failed to fetch worker tasks: {e}") |
| return {"error": str(e)} |
| |
| def assign_issue_to_worker(self, issue_id: str, worker_id: str) -> Dict[str, Any]: |
| """Assign an issue to a worker""" |
| try: |
| response = self.session.post( |
| f"{self.base_url}/issues/{issue_id}/assign", |
| json={"worker_id": worker_id}, |
| headers={"Content-Type": "application/json"} |
| ) |
| response.raise_for_status() |
| return response.json() |
| except Exception as e: |
| logger.error(f"Failed to assign issue: {e}") |
| return {"error": str(e)} |
| |
| def resolve_issue(self, issue_id: str, payload: Dict[str, Any]) -> Dict[str, Any]: |
| """Mark an issue as resolved""" |
| try: |
| response = self.session.put( |
| f"{self.base_url}/issues/{issue_id}/resolve", |
| json=payload, |
| headers={"Content-Type": "application/json"} |
| ) |
| response.raise_for_status() |
| return response.json() |
| except Exception as e: |
| logger.error(f"Failed to resolve issue {issue_id}: {e}") |
| return {"error": str(e)} |
| |
| def get_issue_stats(self) -> Dict[str, Any]: |
| """Get issue statistics""" |
| try: |
| response = self.session.get(f"{self.base_url}/admin/stats") |
| response.raise_for_status() |
| return response.json() |
| except Exception as e: |
| logger.error(f"Failed to fetch stats: {e}") |
| return {"error": str(e)} |
| |
| def get_heatmap_data(self, city: Optional[str] = None) -> Dict[str, Any]: |
| """Get heatmap data for geospatial visualization""" |
| try: |
| params = {} |
| if city: |
| params["city"] = city |
| |
| response = self.session.get( |
| f"{self.base_url}/admin/stats/heatmap", |
| params=params |
| ) |
| response.raise_for_status() |
| return response.json() |
| except Exception as e: |
| logger.error(f"Failed to fetch heatmap data: {e}") |
| return {"error": str(e)} |
| |
| def create_worker(self, department_id: str, name: str, email: str, |
| locality: str, city: str = "Himachal Pradesh", |
| max_workload: int = 10, password: str = "12345678", |
| phone: Optional[str] = None, role: str = "worker") -> Dict[str, Any]: |
| try: |
| payload = { |
| "department_id": department_id, |
| "name": name, |
| "email": email, |
| "phone": phone, |
| "role": role, |
| "city": city, |
| "locality": locality, |
| "max_workload": max_workload, |
| "password": password, |
| } |
| response = self.session.post( |
| f"{self.base_url}/admin/members", |
| json=payload, |
| headers={"Content-Type": "application/json"} |
| ) |
| response.raise_for_status() |
| return response.json() |
| except Exception as e: |
| logger.error(f"Failed to create worker {name}: {e}") |
| return {"error": str(e)} |
| |
| def bulk_create_workers(self, department_code: str, department_id: str, |
| worker_names: list, locations: list) -> Dict[str, Any]: |
| """Bulk create workers with Gmail alias format: zrxarchit+<name>.<department>@gmail.com""" |
| created_workers = [] |
| failed_workers = [] |
| |
| if len(worker_names) != len(locations): |
| return {"error": "Number of names and locations must match"} |
| |
| phone_prefix_by_department = { |
| "PWD": "101", |
| "SANITATION": "202", |
| "TRAFFIC": "303", |
| } |
|
|
| for idx, name in enumerate(worker_names): |
| location = locations[idx % len(locations)] |
| name_parts = name.lower().replace(" ", ".") |
| email = f"zrxarchit+{name_parts}.{department_code.lower()}@gmail.com" |
| prefix = phone_prefix_by_department.get(department_code.upper(), "999") |
| phone = f"9{prefix}{idx + 1:06d}" |
| |
| response = self.create_worker( |
| department_id=department_id, |
| name=name, |
| email=email, |
| locality=location, |
| phone=phone, |
| password="12345678", |
| role="worker", |
| ) |
| |
| if "error" not in response: |
| created_workers.append({ |
| "name": name, |
| "email": email, |
| "locality": location, |
| "status": "created" |
| }) |
| logger.info(f"✓ Created: {name} ({email}) - {location}") |
| else: |
| failed_workers.append({ |
| "name": name, |
| "email": email, |
| "error": response.get("error") |
| }) |
| logger.warning(f"✗ Failed: {name} ({email}) - {response.get('error')}") |
| |
| return { |
| "department": department_code, |
| "total": len(worker_names), |
| "created": len(created_workers), |
| "failed": len(failed_workers), |
| "workers": created_workers, |
| "failures": failed_workers |
| } |
|
|
|
|
| def main(): |
| """Example usage of the API client""" |
| print("=" * 80) |
| print("CityTrack API Client - Worker Bulk Creation") |
| print("=" * 80) |
| print(f"Base URL: {API_URL}") |
| print("=" * 80) |
| |
| client = CityTrackAPIClient() |
| |
| NEARBY_LOCATIONS = [ |
| "Una", "Haroli", "Amb", "Kasauli", "Baddi", |
| "Nalagarh", "Solan", "Parwanoo", "Kalka", "Kurali" |
| ] |
| |
| PWD_WORKERS = [ |
| "Ramesh Kumar", "Sukesh Singh", "Harish Patel", "Vikram Sharma", "Ajay Kumar", |
| "Rajesh Tiwari", "Manoj Singh", "Arjun Verma", "Deepak Yadav", "Sandeep Gupta" |
| ] |
| |
| SANITATION_WORKERS = [ |
| "Suresh Singh", "Mohan Lal", "Ravi Kumar", "Anita Devi", "Asha Sharma", |
| "Priya Singh", "Meera Patel", "Kavya Reddy", "Neha Verma", "Pooja Kumari" |
| ] |
| |
| TRAFFIC_WORKERS = [ |
| "Priya Sharma", "Anil Kumar", "Bhavna Singh", "Nitin Patel", "Sanjay Verma", |
| "Rohit Sharma", "Dinesh Kumar", "Sachin Singh", "Amit Patel", "Vishal Reddy" |
| ] |
| |
| print("\n[1] Health Check:") |
| health = client.health_check() |
| print(json.dumps(health, indent=2)) |
|
|
| print("\n[2] Admin Login:") |
| login = client.admin_login(email="zrxarchit@gmail.com", password="12345678", expected_role="admin") |
| if "error" in login: |
| print(json.dumps(login, indent=2)) |
| return |
| print(json.dumps({"token_type": login.get("token_type"), "user": login.get("user")}, indent=2)) |
| |
| print("\n[3] Fetching Departments:") |
| departments_response = client.get_departments() |
| |
| departments_map = {} |
| if isinstance(departments_response, list): |
| for dept in departments_response: |
| departments_map[dept["code"]] = dept["id"] |
| print(f" - {dept['name']} (Code: {dept['code']}, ID: {dept['id']})") |
| elif isinstance(departments_response, dict) and "departments" in departments_response: |
| for dept in departments_response["departments"]: |
| departments_map[dept["code"]] = dept["id"] |
| print(f" - {dept['name']} (Code: {dept['code']}, ID: {dept['id']})") |
| else: |
| print(" Error fetching departments:", departments_response) |
| return |
| |
| if not departments_map: |
| print(" No departments found. Creating baseline departments...") |
| baseline = [ |
| ("Public Works Department", "PWD"), |
| ("Sanitation Department", "SANITATION"), |
| ("Traffic Department", "TRAFFIC"), |
| ] |
| for name, code in baseline: |
| created = client.create_department(name=name, code=code) |
| if "error" in created: |
| print(json.dumps({"department": code, "result": created}, indent=2)) |
|
|
| departments_response = client.get_departments() |
| departments_map = {} |
| if isinstance(departments_response, list): |
| for dept in departments_response: |
| departments_map[dept["code"]] = dept["id"] |
| print(f" - {dept['name']} (Code: {dept['code']}, ID: {dept['id']})") |
| elif isinstance(departments_response, dict) and "departments" in departments_response: |
| for dept in departments_response["departments"]: |
| departments_map[dept["code"]] = dept["id"] |
| print(f" - {dept['name']} (Code: {dept['code']}, ID: {dept['id']})") |
| else: |
| print(" Error fetching departments:", departments_response) |
| return |
|
|
| if not departments_map: |
| print(" ERROR: Failed to create/fetch departments.") |
| return |
| |
| print("\n[4] Creating PWD Workers (10 workers):") |
| print("-" * 80) |
| if "PWD" in departments_map: |
| pwd_result = client.bulk_create_workers( |
| department_code="PWD", |
| department_id=departments_map["PWD"], |
| worker_names=PWD_WORKERS, |
| locations=NEARBY_LOCATIONS |
| ) |
| print(json.dumps(pwd_result, indent=2)) |
| else: |
| print(" ERROR: PWD department not found") |
| |
| print("\n[5] Creating Sanitation Workers (10 workers):") |
| print("-" * 80) |
| if "SANITATION" in departments_map: |
| sanitation_result = client.bulk_create_workers( |
| department_code="SANITATION", |
| department_id=departments_map["SANITATION"], |
| worker_names=SANITATION_WORKERS, |
| locations=NEARBY_LOCATIONS |
| ) |
| print(json.dumps(sanitation_result, indent=2)) |
| else: |
| print(" ERROR: SANITATION department not found") |
| |
| print("\n[6] Creating Traffic Workers (10 workers):") |
| print("-" * 80) |
| if "TRAFFIC" in departments_map: |
| traffic_result = client.bulk_create_workers( |
| department_code="TRAFFIC", |
| department_id=departments_map["TRAFFIC"], |
| worker_names=TRAFFIC_WORKERS, |
| locations=NEARBY_LOCATIONS |
| ) |
| print(json.dumps(traffic_result, indent=2)) |
| else: |
| print(" ERROR: TRAFFIC department not found") |
| |
| print("\n[7] Fetching All Workers:") |
| workers = client.get_workers() |
| if isinstance(workers, list): |
| print(f" Total workers: {len(workers)}") |
| for worker in workers[:5]: |
| print(f" - {worker.get('name')} ({worker.get('email')})") |
| if len(workers) > 5: |
| print(f" ... and {len(workers) - 5} more") |
| else: |
| print(json.dumps(workers, indent=2)[:500] + "...") |
| |
| print("\n" + "=" * 80) |
| print("Bulk Worker Creation Complete!") |
| print("=" * 80) |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|