Spaces:
Runtime error
Runtime error
Create iristrack_seed.py
Browse files- iristrack_seed.py +786 -0
iristrack_seed.py
ADDED
|
@@ -0,0 +1,786 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
iristrack_seed.py β IrisTrack Demo Environment Seeder
|
| 4 |
+
======================================================
|
| 5 |
+
Creates a complete IrisPlus Technologies test organization with:
|
| 6 |
+
- Kalpesh Morar (Org Admin)
|
| 7 |
+
- 8 employees across Tech, Sales, and Support departments
|
| 8 |
+
- KPI models per department
|
| 9 |
+
- 2 projects
|
| 10 |
+
- Tasks with subtasks (in various states)
|
| 11 |
+
- Weekly submissions (8 weeks of history) per employee
|
| 12 |
+
- Leave requests (mix of approved / pending / declined)
|
| 13 |
+
- Expenses (mix of approved / pending)
|
| 14 |
+
- Client meetings with intelligence
|
| 15 |
+
|
| 16 |
+
USAGE:
|
| 17 |
+
pip install firebase-admin python-dotenv
|
| 18 |
+
export FIREBASE='<your-service-account-json>'
|
| 19 |
+
export Firebase_DB='https://your-project.firebaseio.com'
|
| 20 |
+
python iristrack_seed.py
|
| 21 |
+
|
| 22 |
+
--reset flag: wipes the IrisPlus test org before re-seeding
|
| 23 |
+
python iristrack_seed.py --reset
|
| 24 |
+
|
| 25 |
+
All credentials printed to stdout at end of run.
|
| 26 |
+
"""
|
| 27 |
+
|
| 28 |
+
import os
|
| 29 |
+
import sys
|
| 30 |
+
import json
|
| 31 |
+
import uuid
|
| 32 |
+
import argparse
|
| 33 |
+
from datetime import datetime, timezone, date, timedelta
|
| 34 |
+
|
| 35 |
+
import firebase_admin
|
| 36 |
+
from firebase_admin import credentials, db, auth
|
| 37 |
+
|
| 38 |
+
try:
|
| 39 |
+
from dotenv import load_dotenv
|
| 40 |
+
load_dotenv()
|
| 41 |
+
except ImportError:
|
| 42 |
+
pass
|
| 43 |
+
|
| 44 |
+
# ββ Init ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 45 |
+
_fb_json = os.environ.get("FIREBASE")
|
| 46 |
+
_fb_url = os.environ.get("Firebase_DB")
|
| 47 |
+
if not _fb_json or not _fb_url:
|
| 48 |
+
print("ERROR: FIREBASE and Firebase_DB env vars required.")
|
| 49 |
+
sys.exit(1)
|
| 50 |
+
|
| 51 |
+
firebase_admin.initialize_app(
|
| 52 |
+
credentials.Certificate(json.loads(_fb_json)),
|
| 53 |
+
{"databaseURL": _fb_url},
|
| 54 |
+
)
|
| 55 |
+
db_ref = db.reference()
|
| 56 |
+
|
| 57 |
+
# ββ Helpers βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 58 |
+
def now_iso():
|
| 59 |
+
return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%f") + "Z"
|
| 60 |
+
|
| 61 |
+
def past_iso(days_ago=0, hours_ago=0):
|
| 62 |
+
dt = datetime.now(timezone.utc) - timedelta(days=days_ago, hours=hours_ago)
|
| 63 |
+
return dt.strftime("%Y-%m-%dT%H:%M:%S.%f") + "Z"
|
| 64 |
+
|
| 65 |
+
def week_id(weeks_ago=0):
|
| 66 |
+
d = date.today() - timedelta(weeks=weeks_ago)
|
| 67 |
+
return d.strftime("%G-W%V")
|
| 68 |
+
|
| 69 |
+
def next_weekday(d: date, weekday: int) -> date:
|
| 70 |
+
days_ahead = weekday - d.weekday()
|
| 71 |
+
if days_ahead <= 0:
|
| 72 |
+
days_ahead += 7
|
| 73 |
+
return d + timedelta(days=days_ahead)
|
| 74 |
+
|
| 75 |
+
def create_fb_user(email, password, display_name):
|
| 76 |
+
try:
|
| 77 |
+
user = auth.create_user(email=email, password=password, display_name=display_name)
|
| 78 |
+
print(f" β Created Auth user: {email}")
|
| 79 |
+
return user.uid
|
| 80 |
+
except Exception as e:
|
| 81 |
+
if "EMAIL_EXISTS" in str(e) or "email-already-exists" in str(e).lower():
|
| 82 |
+
user = auth.get_user_by_email(email)
|
| 83 |
+
print(f" ~ Existing Auth user: {email} β {user.uid}")
|
| 84 |
+
return user.uid
|
| 85 |
+
raise
|
| 86 |
+
|
| 87 |
+
ORG_ID = "irisplus-test-org-001"
|
| 88 |
+
PRINT_DIVIDER = "β" * 60
|
| 89 |
+
|
| 90 |
+
# ββ Departments βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 91 |
+
DEPARTMENTS = {
|
| 92 |
+
"dept-tech": {"deptId": "dept-tech", "name": "Technology", "description": "Engineering, infrastructure, product"},
|
| 93 |
+
"dept-sales": {"deptId": "dept-sales", "name": "Sales", "description": "Business development and client acquisition"},
|
| 94 |
+
"dept-support": {"deptId": "dept-support", "name": "Client Support","description": "Customer success and post-sale support"},
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
# ββ Team ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 98 |
+
TEAM = [
|
| 99 |
+
# (display_name, email, password, phone, position, dept_id, platform_role)
|
| 100 |
+
("Kalpesh Morar", "kalpesh@irisplus.co.zw", "IrisAdmin2026!", "+263771000001", "Chief Executive Officer", None, "org_admin"),
|
| 101 |
+
("Tendai Moyo", "tendai@irisplus.co.zw", "IrisTech2026!", "+263771000002", "Lead Software Engineer", "dept-tech", "dept_admin"),
|
| 102 |
+
("Rudo Chikwanda", "rudo@irisplus.co.zw", "IrisTech2026!", "+263771000003", "Frontend Developer", "dept-tech", "employee"),
|
| 103 |
+
("Farai Ncube", "farai@irisplus.co.zw", "IrisTech2026!", "+263771000004", "Backend Developer", "dept-tech", "employee"),
|
| 104 |
+
("Simba Dube", "simba@irisplus.co.zw", "IrisSales2026!", "+263771000005", "Sales Manager", "dept-sales", "dept_admin"),
|
| 105 |
+
("Nyasha Mutasa", "nyasha@irisplus.co.zw", "IrisSales2026!", "+263771000006", "Business Development Executive","dept-sales", "employee"),
|
| 106 |
+
("Chiedza Mhaka", "chiedza@irisplus.co.zw", "IrisSales2026!", "+263771000007", "Sales Executive", "dept-sales", "employee"),
|
| 107 |
+
("Tatenda Zvobgo", "tatenda@irisplus.co.zw", "IrisSupport2026!","+263771000008", "Support Lead", "dept-support", "dept_admin"),
|
| 108 |
+
("Mazvita Chirinda", "mazvita@irisplus.co.zw", "IrisSupport2026!","+263771000009", "Client Success Executive", "dept-support", "employee"),
|
| 109 |
+
]
|
| 110 |
+
|
| 111 |
+
# ββ KPI Models ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 112 |
+
KPI_MODELS = {
|
| 113 |
+
"kpi-tech": {
|
| 114 |
+
"modelId": "kpi-tech",
|
| 115 |
+
"role": "Software Engineer",
|
| 116 |
+
"kpi_categories": [
|
| 117 |
+
{"name": "Feature Development", "weight": 45, "description": "Building and shipping product features"},
|
| 118 |
+
{"name": "Bug Fixes & Maintenance", "weight": 25, "description": "Resolving defects and technical debt"},
|
| 119 |
+
{"name": "Code Review & Mentoring", "weight": 15, "description": "Supporting team quality and growth"},
|
| 120 |
+
{"name": "Documentation", "weight": 10, "description": "Technical writing and knowledge base"},
|
| 121 |
+
{"name": "Internal Admin", "weight": 5, "description": "Meetings, reporting, overhead"},
|
| 122 |
+
],
|
| 123 |
+
"synthesized_from": "seed",
|
| 124 |
+
},
|
| 125 |
+
"kpi-sales": {
|
| 126 |
+
"modelId": "kpi-sales",
|
| 127 |
+
"role": "Sales Executive",
|
| 128 |
+
"kpi_categories": [
|
| 129 |
+
{"name": "Revenue-Generating Activities", "weight": 40, "description": "Prospecting, pitching, closing"},
|
| 130 |
+
{"name": "Client Relationship Management","weight": 30, "description": "Nurturing existing accounts"},
|
| 131 |
+
{"name": "Product & Market Knowledge", "weight": 20, "description": "Staying sharp on offering and competitors"},
|
| 132 |
+
{"name": "Internal Administration", "weight": 10, "description": "CRM updates, reporting, internal meetings"},
|
| 133 |
+
],
|
| 134 |
+
"synthesized_from": "seed",
|
| 135 |
+
},
|
| 136 |
+
"kpi-support": {
|
| 137 |
+
"modelId": "kpi-support",
|
| 138 |
+
"role": "Client Support",
|
| 139 |
+
"kpi_categories": [
|
| 140 |
+
{"name": "Ticket Resolution", "weight": 50, "description": "Resolving client issues end-to-end"},
|
| 141 |
+
{"name": "Proactive Client Check-in","weight": 25, "description": "Reaching out before problems escalate"},
|
| 142 |
+
{"name": "Knowledge Base Updates", "weight": 15, "description": "Documenting solutions for future use"},
|
| 143 |
+
{"name": "Internal Escalations", "weight": 10, "description": "Coordinating with tech and sales on complex issues"},
|
| 144 |
+
],
|
| 145 |
+
"synthesized_from": "seed",
|
| 146 |
+
},
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
# ββ Projects ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 150 |
+
PROJECTS = {
|
| 151 |
+
"proj-iristrack": {
|
| 152 |
+
"projectId": "proj-iristrack",
|
| 153 |
+
"orgId": ORG_ID,
|
| 154 |
+
"name": "IrisTrack v1 Launch",
|
| 155 |
+
"description": "Internal rollout and external go-to-market for IrisTrack",
|
| 156 |
+
"color": "#00B4D8",
|
| 157 |
+
"active": True,
|
| 158 |
+
},
|
| 159 |
+
"proj-client-harare": {
|
| 160 |
+
"projectId": "proj-client-harare",
|
| 161 |
+
"orgId": ORG_ID,
|
| 162 |
+
"name": "Harare Enterprise Pilot",
|
| 163 |
+
"description": "Acquiring first 3 paying enterprise clients in Harare CBD",
|
| 164 |
+
"color": "#F59E0B",
|
| 165 |
+
"active": True,
|
| 166 |
+
},
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
# ββ Submissions data ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 170 |
+
SUBMISSION_TEMPLATES = {
|
| 171 |
+
"dept-tech": [
|
| 172 |
+
("Spent most of the week finalising the IrisTrack WhatsApp submission pipeline. The Twilio webhook is fully wired and we're now transcribing voice notes via AssemblyAI. Also reviewed two PRs from Rudo on the expense upload feature and fixed a bug where the week ID was using %W instead of the ISO %G-W%V format. Short on documentation time this week β pushed it to next.",
|
| 173 |
+
88, False),
|
| 174 |
+
("Heavy week on feature development. Shipped the subtask endpoint and the project grouping feature. Code review took a bit longer than expected because Farai's PR had some edge cases around the Firebase update logic. Spent about 15% of time on internal meetings and sprint planning β higher than usual.",
|
| 175 |
+
72, True),
|
| 176 |
+
("Mostly maintenance this week. Tracked down a memory leak in the HuggingFace deployment β turned out to be an unclosed AssemblyAI polling loop. Also updated the README and deployment docs. Feature development was lighter than target.",
|
| 177 |
+
61, False),
|
| 178 |
+
("Strong week. Shipped the KPI model preview endpoint and the mark-all-read notifications endpoint. Pair-programmed with Farai on the receipt image upload to Firebase Storage. Documentation is still behind β need to carve out time.",
|
| 179 |
+
84, False),
|
| 180 |
+
("Delivered the full admin dashboard analytics endpoint. Spent time reviewing Rudo's Lovable frontend work and giving feedback on the alignment gauge component. Some scope creep into support β helped Tatenda debug a WhatsApp delivery issue for about 3 hours which wasn't planned.",
|
| 181 |
+
69, True),
|
| 182 |
+
("Sprint focused on performance. Optimised the org analytics query β was doing N+1 reads on submissions. Reduced to two reads. Also mentored Rudo on TypeScript generics. Admin overhead was high this week due to board prep.",
|
| 183 |
+
78, False),
|
| 184 |
+
("Closed out 4 features this week: task preview workload assessment, project endpoints, subtask notifications, and the Resend invite email. All shipped clean. Code review cadence was good. Zero documentation work though.",
|
| 185 |
+
82, False),
|
| 186 |
+
("Week was split between a production incident (Gemini rate limit hit during bulk submission processing) and new feature work on the meeting recording pipeline. Incident resolution took half a day. Back on track by Wednesday.",
|
| 187 |
+
75, False),
|
| 188 |
+
],
|
| 189 |
+
"dept-sales": [
|
| 190 |
+
("Had three prospect meetings this week β two with HR managers at logistics firms in Avondale and one with a professional services firm in Borrowdale. One of the Avondale leads is warm, requesting a demo next week. Also spent time updating the CRM and preparing the IrisTrack pitch deck.",
|
| 191 |
+
91, False),
|
| 192 |
+
("Focused on account management this week. Three check-in calls with existing contacts. The Borrowdale firm came back with questions about data residency β escalated to Tendai for a technical response. Prospecting was lighter than target.",
|
| 193 |
+
68, True),
|
| 194 |
+
("Two client demos delivered. One converted to a trial agreement β Harare Enterprise Pilot first client confirmed. Proposal sent to second prospect. Market research on competitor pricing took more time than budgeted.",
|
| 195 |
+
87, False),
|
| 196 |
+
("CRM cleanup week. Updated 40+ contact records and rebuilt the pipeline view. Also attended a networking event at the Harare Chamber of Commerce β 6 new contacts. No direct revenue activities but important pipeline work.",
|
| 197 |
+
55, True),
|
| 198 |
+
("Strong prospecting week. Made 18 outbound calls, booked 4 discovery calls for next week. Spent time studying the new IrisTrack meeting intelligence feature to incorporate into the pitch. Client relationship work was minimal.",
|
| 199 |
+
73, False),
|
| 200 |
+
("Closed second Harare Enterprise Pilot client. Contract signed for 3-month pilot at $199/month. Celebration aside β need to focus on third client. Relationship management took significant time post-close.",
|
| 201 |
+
95, False),
|
| 202 |
+
("Pipeline review and forecast update with Kalpesh. Two follow-up proposals sent. One prospect went cold β needs re-engagement strategy. Market knowledge session with Tendai on product roadmap.",
|
| 203 |
+
71, False),
|
| 204 |
+
("Attended the Zimbabwe ICT conference. Three warm leads generated. Demo pipeline is healthy. Spent significant time on admin and conference logistics which pushed revenue activities below target.",
|
| 205 |
+
62, True),
|
| 206 |
+
],
|
| 207 |
+
"dept-support": [
|
| 208 |
+
("Resolved 12 client tickets this week. Two were escalated to tech β both were bugs in the WhatsApp submission flow (now fixed). Proactively called three clients nearing their monthly submission window. Updated the FAQ with three new entries.",
|
| 209 |
+
82, False),
|
| 210 |
+
("High ticket volume β 18 resolved. Most were onboarding questions from the new Harare Enterprise client. Spent less time on proactive check-ins than planned. Knowledge base updates done for 2 articles.",
|
| 211 |
+
71, False),
|
| 212 |
+
("Lighter ticket week β 8 resolved. Used the bandwidth for proactive outreach: called all 9 active clients to check satisfaction. Two flagged confusion about the leave request WhatsApp flow β documented and sent to Tendai for UX review.",
|
| 213 |
+
88, False),
|
| 214 |
+
("Processed all pending expense approvals for the week. Followed up on 3 flagged expenses. Ticket resolution was below target β a public holiday mid-week affected throughput. Knowledge base work was good.",
|
| 215 |
+
65, False),
|
| 216 |
+
("Resolved 15 tickets. One critical issue β client's WhatsApp number changed and submissions were being missed. Fixed with Farai's help. Proactive check-in rate was good. Escalations were minimal.",
|
| 217 |
+
84, False),
|
| 218 |
+
("Knowledge base sprint. Wrote 5 new articles covering the meeting recording flow, expense submission via WhatsApp, and the leave approval decision process. Ticket resolution was lighter as a result.",
|
| 219 |
+
76, False),
|
| 220 |
+
("Onboarded the third Harare Enterprise Pilot client. Ran a 2-hour onboarding session covering all features. Ticket volume low. Proactive outreach focused on the new client this week.",
|
| 221 |
+
79, False),
|
| 222 |
+
("End of pilot review for client 1. Prepared usage report and sat in on the renewal meeting with Simba. Ticket resolution normal. Internal escalation about API timeout errors β routed to tech.",
|
| 223 |
+
74, False),
|
| 224 |
+
],
|
| 225 |
+
}
|
| 226 |
+
|
| 227 |
+
# ββ Leave requests ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 228 |
+
LEAVE_REQUESTS = [
|
| 229 |
+
("nyasha@irisplus.co.zw", "annual", 5, "approved", "Family wedding in Bulawayo"),
|
| 230 |
+
("rudo@irisplus.co.zw", "sick", 2, "approved", "Flu β doctor's note attached"),
|
| 231 |
+
("chiedza@irisplus.co.zw", "annual", 3, "pending", "Short holiday next month"),
|
| 232 |
+
("farai@irisplus.co.zw", "study", 2, "approved", "AWS certification exam prep"),
|
| 233 |
+
("mazvita@irisplus.co.zw", "compassionate",3, "approved", "Bereavement β grandfather passed"),
|
| 234 |
+
("tatenda@irisplus.co.zw", "annual", 5, "declined", "Leave during peak support period β please reschedule"),
|
| 235 |
+
("simba@irisplus.co.zw", "annual", 5, "pending", "Annual leave β December"),
|
| 236 |
+
]
|
| 237 |
+
|
| 238 |
+
# ββ Expenses ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 239 |
+
EXPENSES = [
|
| 240 |
+
("nyasha@irisplus.co.zw", "Client lunch at Amanzi Restaurant with CFO of Harare Logistics Ltd", 35.00, "USD", "client_entertainment", "approved"),
|
| 241 |
+
("simba@irisplus.co.zw", "Fuel for client visits β Borrowdale and Avondale in one day", 18.00, "USD", "transport", "approved"),
|
| 242 |
+
("chiedza@irisplus.co.zw", "Zimbabwe ICT Conference registration fee", 50.00, "USD", "other", "approved"),
|
| 243 |
+
("rudo@irisplus.co.zw", "USB-C hub for home office setup β needed for dual monitor work", 42.00, "USD", "office_supplies", "pending"),
|
| 244 |
+
("farai@irisplus.co.zw", "AWS certification exam fee", 150.00, "USD", "other", "approved"),
|
| 245 |
+
("tatenda@irisplus.co.zw", "Client team dinner at Bronte Hotel β 5 people, post-onboarding", 95.00, "USD", "client_entertainment", "flagged"),
|
| 246 |
+
("mazvita@irisplus.co.zw", "Grab taxi to client site β Westgate", 8.00, "USD", "transport", "approved"),
|
| 247 |
+
("nyasha@irisplus.co.zw", "Team lunch during pipeline review session", 28.00, "USD", "meals", "pending"),
|
| 248 |
+
]
|
| 249 |
+
|
| 250 |
+
# ββ Tasks βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 251 |
+
TASKS = [
|
| 252 |
+
# (title, description, assignee_email, priority, kpi_cat, project_id, status, due_days_from_now, est_hours, subtasks)
|
| 253 |
+
(
|
| 254 |
+
"Finalise IrisTrack meeting recording endpoint",
|
| 255 |
+
"Complete the WhatsApp audio β AssemblyAI β Gemini meeting intelligence pipeline. Include dual summaries and action item extraction.",
|
| 256 |
+
"farai@irisplus.co.zw", "high", "Feature Development", "proj-iristrack", "in_progress", 3, 8.0,
|
| 257 |
+
["Write AssemblyAI polling logic", "Integrate Gemini meeting prompt", "Add WhatsApp distribution logic", "Write endpoint tests"],
|
| 258 |
+
),
|
| 259 |
+
(
|
| 260 |
+
"Build KPI alignment dashboard chart",
|
| 261 |
+
"React recharts component showing actual vs target KPI allocation for the employee dashboard. Use the alignment gauge design spec.",
|
| 262 |
+
"rudo@irisplus.co.zw", "high", "Feature Development", "proj-iristrack", "in_progress", 4, 6.0,
|
| 263 |
+
["Design alignment bar chart component", "Wire to GET /submissions endpoint", "Mobile responsive layout"],
|
| 264 |
+
),
|
| 265 |
+
(
|
| 266 |
+
"Deliver IrisTrack demo to Harare Logistics Ltd",
|
| 267 |
+
"Prepare and deliver a full product demo covering weekly reporting, leave management, and expense tracking. Use the IrisPlus internal deployment as the live demo environment.",
|
| 268 |
+
"simba@irisplus.co.zw", "high", "Revenue-Generating Activities", "proj-client-harare", "open", 7, 4.0,
|
| 269 |
+
["Prepare demo script", "Set up demo org in IrisTrack", "Rehearse with Nyasha", "Send follow-up proposal"],
|
| 270 |
+
),
|
| 271 |
+
(
|
| 272 |
+
"Write onboarding guide for new IrisTrack clients",
|
| 273 |
+
"Create a step-by-step WhatsApp and web onboarding guide for org admins. Cover: org setup, adding users, KPI model creation, and first submission walkthrough.",
|
| 274 |
+
"tatenda@irisplus.co.zw", "medium", "Knowledge Base Updates", "proj-iristrack", "open", 10, 5.0,
|
| 275 |
+
["Draft WhatsApp interaction guide", "Draft web dashboard guide", "Review with Kalpesh", "Publish to help centre"],
|
| 276 |
+
),
|
| 277 |
+
(
|
| 278 |
+
"Resolve CRM data gaps from conference leads",
|
| 279 |
+
"Clean up and enrich the 6 Harare Chamber of Commerce contacts added last week. Add company info, LinkedIn, and assign follow-up dates.",
|
| 280 |
+
"nyasha@irisplus.co.zw", "medium", "Client Relationship Management", "proj-client-harare", "open", 5, 3.0,
|
| 281 |
+
["Research company profiles", "Update CRM records", "Schedule follow-up calls"],
|
| 282 |
+
),
|
| 283 |
+
(
|
| 284 |
+
"Deploy IrisTrack to production HuggingFace Space",
|
| 285 |
+
"Final production deployment. Verify all env secrets, run health check, test WhatsApp webhook with Twilio sandbox, confirm Resend invite email flow.",
|
| 286 |
+
"tendai@irisplus.co.zw", "high", "Feature Development", "proj-iristrack", "completed", -2, 6.0,
|
| 287 |
+
["Set all HF secrets", "Deploy and run /api/health", "Test Twilio webhook", "Test Resend invite"],
|
| 288 |
+
),
|
| 289 |
+
(
|
| 290 |
+
"Close third Harare Enterprise Pilot client",
|
| 291 |
+
"Follow up with Nyaradzo Insurance β they've had a demo and requested a proposal. Negotiate terms and close before end of month.",
|
| 292 |
+
"simba@irisplus.co.zw", "high", "Revenue-Generating Activities", "proj-client-harare", "in_progress", 12, 5.0,
|
| 293 |
+
["Send revised proposal", "Schedule negotiation call", "Prepare contract draft"],
|
| 294 |
+
),
|
| 295 |
+
(
|
| 296 |
+
"Support ticket SLA review",
|
| 297 |
+
"Analyse the last 30 days of support tickets. Identify the top 3 recurring issue categories and propose process improvements.",
|
| 298 |
+
"mazvita@irisplus.co.zw", "low", "Knowledge Base Updates", "proj-iristrack", "open", 14, 4.0,
|
| 299 |
+
["Pull ticket data from Firebase", "Categorise and count issues", "Draft recommendations"],
|
| 300 |
+
),
|
| 301 |
+
]
|
| 302 |
+
|
| 303 |
+
# ββ Client meetings ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 304 |
+
MEETINGS = [
|
| 305 |
+
{
|
| 306 |
+
"title": "IrisTrack Demo β Harare Logistics Ltd",
|
| 307 |
+
"clientName": "Harare Logistics Ltd",
|
| 308 |
+
"attendee_emails": ["simba@irisplus.co.zw", "nyasha@irisplus.co.zw"],
|
| 309 |
+
"status": "completed",
|
| 310 |
+
"intelligence": {
|
| 311 |
+
"executive_summary": "A 45-minute product demo covering weekly reporting, leave management, and expense tracking. The CFO and HR Director were engaged throughout. Key concern raised was data residency β they want confirmation that data stays in-country or within Africa.",
|
| 312 |
+
"key_decisions": ["Request for pilot proposal to be sent within 48 hours", "Technical data residency question to be answered by engineering"],
|
| 313 |
+
"action_items": [
|
| 314 |
+
{"action": "Send pilot proposal with pricing", "owner": "Simba Dube", "due_date": (date.today() - timedelta(days=5)).isoformat(), "priority": "high"},
|
| 315 |
+
{"action": "Technical response on data residency", "owner": "Tendai Moyo", "due_date": (date.today() - timedelta(days=3)).isoformat(), "priority": "high"},
|
| 316 |
+
],
|
| 317 |
+
"client_sentiment": "positive",
|
| 318 |
+
"follow_up_required": True,
|
| 319 |
+
"risks_identified": ["Data residency compliance may require Firebase region configuration"],
|
| 320 |
+
"opportunities_identified": ["CFO mentioned interest in expense reconciliation feature for their 40-person field team"],
|
| 321 |
+
"employee_summary": "Strong demo. The client was particularly interested in the WhatsApp-native reporting flow β exactly the pain point they have with their field drivers. Next step is the proposal. Make sure the data residency answer is watertight before sending.",
|
| 322 |
+
"manager_summary": "Warm lead. CFO is the economic buyer. The data residency question is a real gating issue β needs engineering input within 48 hours or we risk losing momentum. The field team expense use case is a significant upsell opportunity if the pilot lands.",
|
| 323 |
+
}
|
| 324 |
+
},
|
| 325 |
+
{
|
| 326 |
+
"title": "Pilot Review β Client 1",
|
| 327 |
+
"clientName": "Harare Logistics Ltd",
|
| 328 |
+
"attendee_emails": ["simba@irisplus.co.zw", "mazvita@irisplus.co.zw"],
|
| 329 |
+
"status": "completed",
|
| 330 |
+
"intelligence": {
|
| 331 |
+
"executive_summary": "30-day pilot review meeting. Client reported 85% weekly submission compliance, up from near-zero with their previous spreadsheet process. Two feature requests raised: bulk expense upload and department-level reporting. Renewal discussed.",
|
| 332 |
+
"key_decisions": ["Client proceeding to paid subscription β Growth tier", "Feature requests logged for Q3 roadmap"],
|
| 333 |
+
"action_items": [
|
| 334 |
+
{"action": "Send Growth tier subscription agreement", "owner": "Simba Dube", "due_date": (date.today() + timedelta(days=2)).isoformat(), "priority": "high"},
|
| 335 |
+
{"action": "Log bulk expense upload in product backlog", "owner": "Tendai Moyo", "due_date": (date.today() + timedelta(days=7)).isoformat(), "priority": "medium"},
|
| 336 |
+
],
|
| 337 |
+
"client_sentiment": "positive",
|
| 338 |
+
"follow_up_required": True,
|
| 339 |
+
"risks_identified": ["Two team members still not submitting β may need WhatsApp number re-registration"],
|
| 340 |
+
"opportunities_identified": ["Client has 3 sister companies β warm referral opportunity"],
|
| 341 |
+
"employee_summary": "Great meeting. Client is happy and converting. Mention the sister company referral angle to Kalpesh β that's three potential logos from one relationship.",
|
| 342 |
+
"manager_summary": "First conversion secured. The 85% compliance stat is a strong case study data point. Referral opportunity to sister companies should be actioned immediately while sentiment is high.",
|
| 343 |
+
}
|
| 344 |
+
},
|
| 345 |
+
]
|
| 346 |
+
|
| 347 |
+
|
| 348 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 349 |
+
# SEEDING FUNCTIONS
|
| 350 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 351 |
+
|
| 352 |
+
def reset_org():
|
| 353 |
+
print(f"\nπ Resetting org {ORG_ID}...")
|
| 354 |
+
for node in ["organizations", "departments", "users", "kpi_models", "projects",
|
| 355 |
+
"tasks", "subtasks", "submissions", "leave_requests", "expenses",
|
| 356 |
+
"budgets", "notifications", "audit_log", "client_meetings"]:
|
| 357 |
+
db_ref.child(f"{node}/{ORG_ID}").delete()
|
| 358 |
+
db_ref.child(f"platform_orgs/{ORG_ID}").delete()
|
| 359 |
+
print(" β Org data wiped")
|
| 360 |
+
|
| 361 |
+
|
| 362 |
+
def seed_org(admin_uid):
|
| 363 |
+
print(f"\nπ’ Seeding organization...")
|
| 364 |
+
org_data = {
|
| 365 |
+
"orgId": ORG_ID,
|
| 366 |
+
"name": "IrisPlus Technologies",
|
| 367 |
+
"industry": "Technology",
|
| 368 |
+
"country": "Zimbabwe",
|
| 369 |
+
"createdAt": past_iso(90),
|
| 370 |
+
"reporting_day": "friday",
|
| 371 |
+
"reporting_time": "08:00",
|
| 372 |
+
"org_goals": "Build and ship IrisTrack to 10 paying customers in Harare by Q3 2026. Maintain team alignment scores above 75%. Grow revenue to $2,000 MRR.",
|
| 373 |
+
"active": True,
|
| 374 |
+
}
|
| 375 |
+
db_ref.child(f"organizations/{ORG_ID}").set(org_data)
|
| 376 |
+
db_ref.child(f"platform_orgs/{ORG_ID}").set({
|
| 377 |
+
"orgId": ORG_ID,
|
| 378 |
+
"name": "IrisPlus Technologies",
|
| 379 |
+
"adminUid": admin_uid,
|
| 380 |
+
"createdAt": past_iso(90),
|
| 381 |
+
})
|
| 382 |
+
print(" β Organization: IrisPlus Technologies")
|
| 383 |
+
|
| 384 |
+
|
| 385 |
+
def seed_departments():
|
| 386 |
+
print(f"\nπ Seeding departments...")
|
| 387 |
+
for dept_id, dept_data in DEPARTMENTS.items():
|
| 388 |
+
db_ref.child(f"departments/{ORG_ID}/{dept_id}").set({
|
| 389 |
+
**dept_data,
|
| 390 |
+
"createdAt": past_iso(89),
|
| 391 |
+
})
|
| 392 |
+
print(f" β {dept_data['name']}")
|
| 393 |
+
|
| 394 |
+
|
| 395 |
+
def seed_kpi_models(admin_uid):
|
| 396 |
+
print(f"\nπ Seeding KPI models...")
|
| 397 |
+
for model_id, model_data in KPI_MODELS.items():
|
| 398 |
+
db_ref.child(f"kpi_models/{ORG_ID}/{model_id}").set({
|
| 399 |
+
**model_data,
|
| 400 |
+
"orgId": ORG_ID,
|
| 401 |
+
"createdBy": admin_uid,
|
| 402 |
+
"createdAt": past_iso(88),
|
| 403 |
+
})
|
| 404 |
+
print(f" β {model_data['role']}")
|
| 405 |
+
|
| 406 |
+
|
| 407 |
+
def seed_projects(admin_uid):
|
| 408 |
+
print(f"\nπ Seeding projects...")
|
| 409 |
+
for proj_id, proj_data in PROJECTS.items():
|
| 410 |
+
db_ref.child(f"projects/{ORG_ID}/{proj_id}").set({
|
| 411 |
+
**proj_data,
|
| 412 |
+
"createdBy": admin_uid,
|
| 413 |
+
"createdAt": past_iso(60),
|
| 414 |
+
})
|
| 415 |
+
print(f" β {proj_data['name']}")
|
| 416 |
+
|
| 417 |
+
|
| 418 |
+
def seed_budgets():
|
| 419 |
+
print(f"\nπ° Seeding budgets...")
|
| 420 |
+
budgets = {
|
| 421 |
+
"dept-sales": {
|
| 422 |
+
"client_entertainment": {"allocated": 400, "spent": 130.00},
|
| 423 |
+
"transport": {"allocated": 200, "spent": 26.00},
|
| 424 |
+
"meals": {"allocated": 150, "spent": 28.00},
|
| 425 |
+
"other": {"allocated": 300, "spent": 200.00},
|
| 426 |
+
},
|
| 427 |
+
"dept-tech": {
|
| 428 |
+
"office_supplies": {"allocated": 200, "spent": 42.00},
|
| 429 |
+
"other": {"allocated": 500, "spent": 150.00},
|
| 430 |
+
},
|
| 431 |
+
"dept-support": {
|
| 432 |
+
"client_entertainment": {"allocated": 200, "spent": 103.00},
|
| 433 |
+
"transport": {"allocated": 100, "spent": 8.00},
|
| 434 |
+
},
|
| 435 |
+
}
|
| 436 |
+
for dept_id, budget in budgets.items():
|
| 437 |
+
db_ref.child(f"budgets/{ORG_ID}/{dept_id}").set(budget)
|
| 438 |
+
print(" β Budgets set for all departments")
|
| 439 |
+
|
| 440 |
+
|
| 441 |
+
def seed_users():
|
| 442 |
+
print(f"\nπ₯ Seeding users...")
|
| 443 |
+
uid_map = {}
|
| 444 |
+
for (display_name, email, password, phone, position, dept_id, platform_role) in TEAM:
|
| 445 |
+
uid = create_fb_user(email, password, display_name)
|
| 446 |
+
uid_map[email] = uid
|
| 447 |
+
leave_balance = {"annual": 20, "sick": 10, "study": 5}
|
| 448 |
+
|
| 449 |
+
# Simulate some used leave
|
| 450 |
+
if email == "nyasha@irisplus.co.zw":
|
| 451 |
+
leave_balance["annual"] = 15
|
| 452 |
+
elif email == "rudo@irisplus.co.zw":
|
| 453 |
+
leave_balance["sick"] = 8
|
| 454 |
+
elif email == "farai@irisplus.co.zw":
|
| 455 |
+
leave_balance["study"] = 3
|
| 456 |
+
elif email == "mazvita@irisplus.co.zw":
|
| 457 |
+
leave_balance["annual"] = 17
|
| 458 |
+
|
| 459 |
+
db_ref.child(f"users/{ORG_ID}/{uid}").set({
|
| 460 |
+
"uid": uid,
|
| 461 |
+
"email": email,
|
| 462 |
+
"displayName": display_name,
|
| 463 |
+
"phone": phone,
|
| 464 |
+
"platform_role": platform_role,
|
| 465 |
+
"department_id": dept_id,
|
| 466 |
+
"position": position,
|
| 467 |
+
"createdAt": past_iso(85),
|
| 468 |
+
"addedBy": "seed",
|
| 469 |
+
"active": True,
|
| 470 |
+
"leave_balance": leave_balance,
|
| 471 |
+
"whatsapp_state": "idle",
|
| 472 |
+
"last_alignment_score": None,
|
| 473 |
+
})
|
| 474 |
+
print(f" β {len(TEAM)} users seeded")
|
| 475 |
+
return uid_map
|
| 476 |
+
|
| 477 |
+
|
| 478 |
+
def seed_submissions(uid_map):
|
| 479 |
+
print(f"\nπ Seeding submissions (8 weeks Γ 8 employees)...")
|
| 480 |
+
dept_templates = {
|
| 481 |
+
"dept-tech": (SUBMISSION_TEMPLATES["dept-tech"], "kpi-tech", ["tendai@irisplus.co.zw", "rudo@irisplus.co.zw", "farai@irisplus.co.zw"]),
|
| 482 |
+
"dept-sales": (SUBMISSION_TEMPLATES["dept-sales"], "kpi-sales", ["simba@irisplus.co.zw", "nyasha@irisplus.co.zw", "chiedza@irisplus.co.zw"]),
|
| 483 |
+
"dept-support": (SUBMISSION_TEMPLATES["dept-support"], "kpi-support", ["tatenda@irisplus.co.zw","mazvita@irisplus.co.zw"]),
|
| 484 |
+
}
|
| 485 |
+
|
| 486 |
+
kpi_weights_map = {
|
| 487 |
+
"kpi-tech": {c["name"]: c["weight"] for c in KPI_MODELS["kpi-tech"]["kpi_categories"]},
|
| 488 |
+
"kpi-sales": {c["name"]: c["weight"] for c in KPI_MODELS["kpi-sales"]["kpi_categories"]},
|
| 489 |
+
"kpi-support": {c["name"]: c["weight"] for c in KPI_MODELS["kpi-support"]["kpi_categories"]},
|
| 490 |
+
}
|
| 491 |
+
|
| 492 |
+
for dept_id, (templates, model_id, emails) in dept_templates.items():
|
| 493 |
+
cats = [c["name"] for c in KPI_MODELS[model_id]["kpi_categories"]]
|
| 494 |
+
weights = kpi_weights_map[model_id]
|
| 495 |
+
for email in emails:
|
| 496 |
+
uid = uid_map.get(email)
|
| 497 |
+
if not uid:
|
| 498 |
+
continue
|
| 499 |
+
for week_offset in range(8):
|
| 500 |
+
# Skip current week for a couple of employees to simulate missing submissions
|
| 501 |
+
if week_offset == 0 and email in ("chiedza@irisplus.co.zw", "mazvita@irisplus.co.zw"):
|
| 502 |
+
continue
|
| 503 |
+
tmpl_idx = week_offset % len(templates)
|
| 504 |
+
raw_text, alignment_score, drift = templates[tmpl_idx]
|
| 505 |
+
wid = week_id(week_offset)
|
| 506 |
+
submitted_at = past_iso(days_ago=week_offset * 7, hours_ago=2)
|
| 507 |
+
|
| 508 |
+
# Simulate actual KPI allocation based on alignment score
|
| 509 |
+
if model_id == "kpi-tech":
|
| 510 |
+
actual = {"Feature Development": 40 if not drift else 20, "Bug Fixes & Maintenance": 25,
|
| 511 |
+
"Code Review & Mentoring": 15, "Documentation": 5 if drift else 10,
|
| 512 |
+
"Internal Admin": 15 if drift else 5}
|
| 513 |
+
elif model_id == "kpi-sales":
|
| 514 |
+
actual = {"Revenue-Generating Activities": 35 if drift else 42,
|
| 515 |
+
"Client Relationship Management": 28 if drift else 31,
|
| 516 |
+
"Product & Market Knowledge": 22, "Internal Administration": 15 if drift else 5}
|
| 517 |
+
else:
|
| 518 |
+
actual = {"Ticket Resolution": 48, "Proactive Client Check-in": 22,
|
| 519 |
+
"Knowledge Base Updates": 18, "Internal Escalations": 12}
|
| 520 |
+
|
| 521 |
+
sub_data = {
|
| 522 |
+
"submissionId": str(uuid.uuid4()),
|
| 523 |
+
"orgId": ORG_ID,
|
| 524 |
+
"uid": uid,
|
| 525 |
+
"weekId": wid,
|
| 526 |
+
"rawText": raw_text,
|
| 527 |
+
"kpiModelId": model_id,
|
| 528 |
+
"submittedAt": submitted_at,
|
| 529 |
+
"source": "seed",
|
| 530 |
+
"alignment_score": alignment_score,
|
| 531 |
+
"alignment_notes": f"Employee's time allocation {'shows significant drift from' if drift else 'broadly aligns with'} target KPI weights. Alignment score: {alignment_score}/100.",
|
| 532 |
+
"actual_allocation": actual,
|
| 533 |
+
"role_drift_detected": drift,
|
| 534 |
+
"role_drift_detail": "Significant time on out-of-role administrative tasks" if drift else None,
|
| 535 |
+
"success_events": ["Shipped feature on time", "Closed client follow-up"] if alignment_score > 80 else [],
|
| 536 |
+
"structured_activities": [{"activity": "Work activities", "kpi_category": cats[0], "estimated_percent": 45}],
|
| 537 |
+
"summary": raw_text[:200] + "...",
|
| 538 |
+
}
|
| 539 |
+
db_ref.child(f"submissions/{ORG_ID}/{uid}/{wid}").set(sub_data)
|
| 540 |
+
# Update last alignment score on user record
|
| 541 |
+
db_ref.child(f"users/{ORG_ID}/{uid}").update({
|
| 542 |
+
"last_submission_at": submitted_at,
|
| 543 |
+
"last_alignment_score": alignment_score,
|
| 544 |
+
})
|
| 545 |
+
print(" β Submissions seeded")
|
| 546 |
+
|
| 547 |
+
|
| 548 |
+
def seed_leave_requests(uid_map, admin_uid):
|
| 549 |
+
print(f"\nπ Seeding leave requests...")
|
| 550 |
+
for (email, ltype, days, status, reason) in LEAVE_REQUESTS:
|
| 551 |
+
uid = uid_map.get(email)
|
| 552 |
+
if not uid:
|
| 553 |
+
continue
|
| 554 |
+
start = date.today() + timedelta(days=14) if status == "pending" else date.today() - timedelta(days=30)
|
| 555 |
+
end = start + timedelta(days=days - 1)
|
| 556 |
+
req_id = str(uuid.uuid4())
|
| 557 |
+
req_data = {
|
| 558 |
+
"requestId": req_id,
|
| 559 |
+
"orgId": ORG_ID,
|
| 560 |
+
"uid": uid,
|
| 561 |
+
"employeeName": next(t[0] for t in TEAM if t[1] == email),
|
| 562 |
+
"leave_type": ltype,
|
| 563 |
+
"start_date": start.isoformat(),
|
| 564 |
+
"end_date": end.isoformat(),
|
| 565 |
+
"days_requested": days,
|
| 566 |
+
"reason": reason,
|
| 567 |
+
"is_urgent": False,
|
| 568 |
+
"status": status,
|
| 569 |
+
"submittedAt": past_iso(20),
|
| 570 |
+
}
|
| 571 |
+
if status in ("approved", "declined"):
|
| 572 |
+
req_data.update({
|
| 573 |
+
"decidedBy": admin_uid,
|
| 574 |
+
"decidedAt": past_iso(18),
|
| 575 |
+
"decision_note": reason if status == "declined" else "",
|
| 576 |
+
})
|
| 577 |
+
db_ref.child(f"leave_requests/{ORG_ID}/{req_id}").set(req_data)
|
| 578 |
+
print(f" β {len(LEAVE_REQUESTS)} leave requests seeded")
|
| 579 |
+
|
| 580 |
+
|
| 581 |
+
def seed_expenses(uid_map, admin_uid):
|
| 582 |
+
print(f"\nπ§Ύ Seeding expenses...")
|
| 583 |
+
for (email, description, amount, currency, category, status) in EXPENSES:
|
| 584 |
+
uid = uid_map.get(email)
|
| 585 |
+
if not uid:
|
| 586 |
+
continue
|
| 587 |
+
user_team = next(t for t in TEAM if t[1] == email)
|
| 588 |
+
dept_id = user_team[5]
|
| 589 |
+
exp_id = str(uuid.uuid4())
|
| 590 |
+
exp_data = {
|
| 591 |
+
"expenseId": exp_id,
|
| 592 |
+
"orgId": ORG_ID,
|
| 593 |
+
"uid": uid,
|
| 594 |
+
"employeeName": user_team[0],
|
| 595 |
+
"department_id": dept_id,
|
| 596 |
+
"receiptUrl": None,
|
| 597 |
+
"rawText": description,
|
| 598 |
+
"vendor": description.split(" at ")[-1].split(" β ")[0][:40],
|
| 599 |
+
"amount": amount,
|
| 600 |
+
"currency": currency,
|
| 601 |
+
"category": category,
|
| 602 |
+
"description": description,
|
| 603 |
+
"is_client_related": "client" in description.lower() or "conference" in description.lower(),
|
| 604 |
+
"status": status,
|
| 605 |
+
"submittedAt": past_iso(14),
|
| 606 |
+
}
|
| 607 |
+
if status in ("approved", "flagged"):
|
| 608 |
+
exp_data.update({
|
| 609 |
+
"decidedBy": admin_uid,
|
| 610 |
+
"decidedAt": past_iso(12),
|
| 611 |
+
"decision_note": "Flagged β amount exceeds entertainment guideline. Please provide itemised receipt." if status == "flagged" else "",
|
| 612 |
+
})
|
| 613 |
+
db_ref.child(f"expenses/{ORG_ID}/{exp_id}").set(exp_data)
|
| 614 |
+
print(f" β {len(EXPENSES)} expenses seeded")
|
| 615 |
+
|
| 616 |
+
|
| 617 |
+
def seed_tasks(uid_map, admin_uid):
|
| 618 |
+
print(f"\nβ
Seeding tasks and subtasks...")
|
| 619 |
+
for (title, description, assignee_email, priority, kpi_cat, project_id, status, due_days, est_hours, subtask_titles) in TASKS:
|
| 620 |
+
assignee_uid = uid_map.get(assignee_email)
|
| 621 |
+
if not assignee_uid:
|
| 622 |
+
continue
|
| 623 |
+
task_id = str(uuid.uuid4())
|
| 624 |
+
due_dt = date.today() + timedelta(days=due_days)
|
| 625 |
+
if due_dt.weekday() == 5: due_dt += timedelta(days=2)
|
| 626 |
+
if due_dt.weekday() == 6: due_dt += timedelta(days=1)
|
| 627 |
+
|
| 628 |
+
task_data = {
|
| 629 |
+
"taskId": task_id,
|
| 630 |
+
"orgId": ORG_ID,
|
| 631 |
+
"projectId": project_id,
|
| 632 |
+
"title": title,
|
| 633 |
+
"description": description,
|
| 634 |
+
"kpi_category": kpi_cat,
|
| 635 |
+
"priority": priority,
|
| 636 |
+
"assignedTo": assignee_uid,
|
| 637 |
+
"assigned_to_uid": assignee_uid,
|
| 638 |
+
"assignedBy": admin_uid,
|
| 639 |
+
"dueDateTime": due_dt.strftime("%Y-%m-%dT17:00:00Z"),
|
| 640 |
+
"estimated_hours": est_hours,
|
| 641 |
+
"status": status,
|
| 642 |
+
"createdAt": past_iso(days_ago=max(0, -due_days + 7)),
|
| 643 |
+
"synthesized_from": "seed",
|
| 644 |
+
"workload_assessment": "ok",
|
| 645 |
+
"workload_recommendation": "Reasonable workload based on current open tasks.",
|
| 646 |
+
}
|
| 647 |
+
if status == "completed":
|
| 648 |
+
task_data["completedAt"] = past_iso(abs(due_days))
|
| 649 |
+
|
| 650 |
+
db_ref.child(f"tasks/{ORG_ID}/{task_id}").set(task_data)
|
| 651 |
+
|
| 652 |
+
# Seed subtasks
|
| 653 |
+
for i, st_title in enumerate(subtask_titles):
|
| 654 |
+
st_id = str(uuid.uuid4())
|
| 655 |
+
st_status = "completed" if status == "completed" else ("completed" if i == 0 and status == "in_progress" else "open")
|
| 656 |
+
st_data = {
|
| 657 |
+
"subtaskId": st_id,
|
| 658 |
+
"taskId": task_id,
|
| 659 |
+
"orgId": ORG_ID,
|
| 660 |
+
"title": st_title,
|
| 661 |
+
"status": st_status,
|
| 662 |
+
"createdBy": assignee_uid,
|
| 663 |
+
"order": i,
|
| 664 |
+
"createdAt": past_iso(days_ago=max(0, -due_days + 6)),
|
| 665 |
+
}
|
| 666 |
+
if st_status == "completed":
|
| 667 |
+
st_data["completedAt"] = past_iso(abs(due_days) + 1)
|
| 668 |
+
db_ref.child(f"subtasks/{ORG_ID}/{task_id}/{st_id}").set(st_data)
|
| 669 |
+
|
| 670 |
+
print(f" β {len(TASKS)} tasks with subtasks seeded")
|
| 671 |
+
|
| 672 |
+
|
| 673 |
+
def seed_meetings(uid_map, admin_uid):
|
| 674 |
+
print(f"\nπ€ Seeding client meetings...")
|
| 675 |
+
for meeting in MEETINGS:
|
| 676 |
+
attendee_uids = [uid_map[e] for e in meeting["attendee_emails"] if e in uid_map]
|
| 677 |
+
meeting_id = str(uuid.uuid4())
|
| 678 |
+
meeting_data = {
|
| 679 |
+
"meetingId": meeting_id,
|
| 680 |
+
"orgId": ORG_ID,
|
| 681 |
+
"title": meeting["title"],
|
| 682 |
+
"clientName": meeting["clientName"],
|
| 683 |
+
"scheduledAt": past_iso(days_ago=21 if "Demo" in meeting["title"] else 7),
|
| 684 |
+
"attendeeUids": attendee_uids,
|
| 685 |
+
"status": meeting["status"],
|
| 686 |
+
"createdBy": uid_map.get("simba@irisplus.co.zw", admin_uid),
|
| 687 |
+
"createdAt": past_iso(days_ago=25 if "Demo" in meeting["title"] else 10),
|
| 688 |
+
"completedAt": past_iso(days_ago=21 if "Demo" in meeting["title"] else 7),
|
| 689 |
+
"transcript": f"[Seeded transcript for: {meeting['title']}]",
|
| 690 |
+
"intelligence": meeting["intelligence"],
|
| 691 |
+
}
|
| 692 |
+
db_ref.child(f"client_meetings/{ORG_ID}/{meeting_id}").set(meeting_data)
|
| 693 |
+
print(f" β {len(MEETINGS)} client meetings seeded")
|
| 694 |
+
|
| 695 |
+
|
| 696 |
+
def seed_notifications(uid_map, admin_uid):
|
| 697 |
+
print(f"\nπ Seeding sample notifications...")
|
| 698 |
+
samples = [
|
| 699 |
+
(uid_map.get("rudo@irisplus.co.zw"), "New Task Assigned", "Build KPI alignment dashboard chart", "info"),
|
| 700 |
+
(uid_map.get("farai@irisplus.co.zw"), "New Task Assigned", "Finalise IrisTrack meeting recording endpoint", "info"),
|
| 701 |
+
(uid_map.get("nyasha@irisplus.co.zw"), "β
Leave Approved", "Your annual leave (5 days) has been approved.", "success"),
|
| 702 |
+
(uid_map.get("tatenda@irisplus.co.zw"), "β Leave Declined", "Leave request declined β peak support period.", "warning"),
|
| 703 |
+
(uid_map.get("chiedza@irisplus.co.zw"), "β οΈ Role Drift Detected", "Your latest submission shows role drift. Check your KPI alignment.", "warning"),
|
| 704 |
+
(uid_map.get("simba@irisplus.co.zw"), "Meeting Summary Ready", "IrisTrack Demo β Harare Logistics Ltd", "success"),
|
| 705 |
+
(admin_uid, "New Leave Request", "Simba Dube has submitted a leave request.", "info"),
|
| 706 |
+
(admin_uid, "β οΈ Budget Alert: client_entertainment", "dept-sales has used 82% of client entertainment budget.", "warning"),
|
| 707 |
+
]
|
| 708 |
+
for (uid, title, message, ntype) in samples:
|
| 709 |
+
if not uid:
|
| 710 |
+
continue
|
| 711 |
+
ref = db_ref.child(f"notifications/{ORG_ID}/{uid}").push()
|
| 712 |
+
ref.set({
|
| 713 |
+
"notificationId": ref.key,
|
| 714 |
+
"title": title,
|
| 715 |
+
"message": message,
|
| 716 |
+
"type": ntype,
|
| 717 |
+
"read": False,
|
| 718 |
+
"createdAt": past_iso(days_ago=1),
|
| 719 |
+
})
|
| 720 |
+
print(f" β {len(samples)} notifications seeded")
|
| 721 |
+
|
| 722 |
+
|
| 723 |
+
def print_credentials(uid_map):
|
| 724 |
+
print(f"\n{PRINT_DIVIDER}")
|
| 725 |
+
print("IRISTRACK TEST ENVIRONMENT β CREDENTIALS")
|
| 726 |
+
print(PRINT_DIVIDER)
|
| 727 |
+
print(f"Organization ID: {ORG_ID}")
|
| 728 |
+
print(f"Login URL: https://iristrack.app/login\n")
|
| 729 |
+
print(f"{'Name':<22} {'Email':<35} {'Password':<20} {'Role'}")
|
| 730 |
+
print("β" * 100)
|
| 731 |
+
for (display_name, email, password, phone, position, dept_id, platform_role) in TEAM:
|
| 732 |
+
role_label = {"org_admin": "Org Admin", "dept_admin": "Dept Admin", "employee": "Employee"}.get(platform_role, platform_role)
|
| 733 |
+
dept_label = DEPARTMENTS.get(dept_id, {}).get("name", "β") if dept_id else "β"
|
| 734 |
+
print(f"{display_name:<22} {email:<35} {password:<20} {role_label} / {dept_label}")
|
| 735 |
+
print(PRINT_DIVIDER)
|
| 736 |
+
print("\nData seeded:")
|
| 737 |
+
print(f" β’ 8 weeks of weekly submissions per employee")
|
| 738 |
+
print(f" β’ {len(TASKS)} tasks with subtasks across both projects")
|
| 739 |
+
print(f" β’ {len(LEAVE_REQUESTS)} leave requests (mix of approved/pending/declined)")
|
| 740 |
+
print(f" β’ {len(EXPENSES)} expense claims (mix of approved/pending/flagged)")
|
| 741 |
+
print(f" β’ {len(MEETINGS)} completed client meetings with full intelligence")
|
| 742 |
+
print(f" β’ KPI models for Tech, Sales, Support")
|
| 743 |
+
print(f" β’ 2 projects: IrisTrack v1 Launch, Harare Enterprise Pilot")
|
| 744 |
+
print(f" β’ Budget allocations for all departments")
|
| 745 |
+
print(f"\nRole drift flags: Chiedza Mhaka (Sales), Tendai Moyo (Tech β week 2)")
|
| 746 |
+
print(f"Missing this week: Chiedza Mhaka, Mazvita Chirinda (simulate missing submissions)")
|
| 747 |
+
print(f"Overdue task: Deliver demo to Harare Logistics (Simba β check dueDateTime)")
|
| 748 |
+
print(PRINT_DIVIDER)
|
| 749 |
+
|
| 750 |
+
|
| 751 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 752 |
+
# MAIN
|
| 753 |
+
# ββββββββββββββββββββββββββοΏ½οΏ½οΏ½ββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 754 |
+
if __name__ == "__main__":
|
| 755 |
+
parser = argparse.ArgumentParser(description="IrisTrack seed script")
|
| 756 |
+
parser.add_argument("--reset", action="store_true", help="Wipe org data before seeding")
|
| 757 |
+
args = parser.parse_args()
|
| 758 |
+
|
| 759 |
+
if args.reset:
|
| 760 |
+
reset_org()
|
| 761 |
+
|
| 762 |
+
print(f"\n{'β' * 60}")
|
| 763 |
+
print("IRISTRACK SEED β IrisPlus Technologies")
|
| 764 |
+
print(f"{'β' * 60}")
|
| 765 |
+
|
| 766 |
+
# Admin user first
|
| 767 |
+
admin = next(t for t in TEAM if t[6] == "org_admin")
|
| 768 |
+
admin_uid = create_fb_user(admin[1], admin[2], admin[0])
|
| 769 |
+
|
| 770 |
+
seed_org(admin_uid)
|
| 771 |
+
seed_departments()
|
| 772 |
+
seed_kpi_models(admin_uid)
|
| 773 |
+
seed_projects(admin_uid)
|
| 774 |
+
seed_budgets()
|
| 775 |
+
uid_map = seed_users()
|
| 776 |
+
uid_map[admin[1]] = admin_uid
|
| 777 |
+
|
| 778 |
+
seed_submissions(uid_map)
|
| 779 |
+
seed_leave_requests(uid_map, admin_uid)
|
| 780 |
+
seed_expenses(uid_map, admin_uid)
|
| 781 |
+
seed_tasks(uid_map, admin_uid)
|
| 782 |
+
seed_meetings(uid_map, admin_uid)
|
| 783 |
+
seed_notifications(uid_map, admin_uid)
|
| 784 |
+
|
| 785 |
+
print(f"\nβ
Seed complete.")
|
| 786 |
+
print_credentials(uid_map)
|