File size: 12,823 Bytes
b484b4d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
"""
Launch Desk β€” Function Tools
=============================
Four @function_tool decorated tools for the Launch Planner agent.
Each does simple but realistic logic without external API calls.
"""

from __future__ import annotations

import re
from agents import function_tool


# ---------------------------------------------------------------------------
# 1. extract_tasks
# ---------------------------------------------------------------------------

@function_tool
def extract_tasks(brief: str) -> list[dict]:
    """Parse a product brief and extract a list of launch tasks.

    Each task includes a name, priority (high/medium/low), owner role,
    and a relative deadline expressed as days before launch.

    Args:
        brief: Free-form product brief text.

    Returns:
        A list of task dicts: {task, priority, owner, deadline}
    """
    if not brief or not brief.strip():
        return [
            {"task": "Define launch goals and metrics", "priority": "high",
             "owner": "Product", "deadline": "T-30 days"},
        ]

    text = brief.lower()

    # Heuristic keyword β†’ task mapping. We scan the brief for domain keywords
    # and build a prioritised task list from whatever matches.
    task_templates: list[dict] = [
        {"keywords": ["api", "backend", "server", "database", "infra"],
         "task": "Freeze code and tag release candidate",
         "priority": "high", "owner": "Engineering", "deadline": "T-14 days"},
        {"keywords": ["qa", "test", "bug", "regression", "testing"],
         "task": "Complete QA regression and smoke testing",
         "priority": "high", "owner": "QA", "deadline": "T-7 days"},
        {"keywords": ["doc", "documentation", "guide", "readme", "manual"],
         "task": "Write user-facing documentation and API guides",
         "priority": "medium", "owner": "Technical Writing", "deadline": "T-10 days"},
        {"keywords": ["market", "marketing", "campaign", "ad", "social", "blog", "press"],
         "task": "Prepare marketing campaign assets and schedule posts",
         "priority": "high", "owner": "Marketing", "deadline": "T-5 days"},
        {"keywords": ["support", "customer", "help", "ticket", "oncall"],
         "task": "Staff support team and prepare runbooks",
         "priority": "medium", "owner": "Customer Success", "deadline": "T-3 days"},
        {"keywords": ["security", "compliance", "audit", "gdpr", "soc"],
         "task": "Complete security review and compliance sign-off",
         "priority": "high", "owner": "Security", "deadline": "T-21 days"},
        {"keywords": ["pricing", "billing", "plan", "tier", "subscription"],
         "task": "Finalise pricing tiers and billing configuration",
         "priority": "medium", "owner": "Finance", "deadline": "T-12 days"},
        {"keywords": ["analytics", "tracking", "metric", "dashboard", "kpi"],
         "task": "Set up analytics dashboards and success metrics",
         "priority": "low", "owner": "Data", "deadline": "T-8 days"},
        {"keywords": ["design", "ui", "ux", "figma", "mockup", "brand"],
         "task": "Finalise UI polish and brand assets",
         "priority": "medium", "owner": "Design", "deadline": "T-10 days"},
        {"keywords": ["legal", "contract", "terms", "privacy", "policy"],
         "task": "Review legal terms and privacy policy updates",
         "priority": "medium", "owner": "Legal", "deadline": "T-15 days"},
    ]

    tasks: list[dict] = []
    for tmpl in task_templates:
        if any(kw in text for kw in tmpl["keywords"]):
            tasks.append({
                "task": tmpl["task"],
                "priority": tmpl["priority"],
                "owner": tmpl["owner"],
                "deadline": tmpl["deadline"],
            })

    # Always include goal definition as the first task
    always = {
        "task": "Define launch goals and success metrics",
        "priority": "high",
        "owner": "Product",
        "deadline": "T-30 days",
    }
    if not any(t["task"] == always["task"] for t in tasks):
        tasks.insert(0, always)

    # Always include a launch-day verification task
    launch_day = {
        "task": "Launch-day verification and monitoring",
        "priority": "high",
        "owner": "Engineering",
        "deadline": "Launch day (T-0)",
    }
    if not any(t["task"] == launch_day["task"] for t in tasks):
        tasks.append(launch_day)

    return tasks


# ---------------------------------------------------------------------------
# 2. check_readiness
# ---------------------------------------------------------------------------

@function_tool
def check_readiness(tasks: str, launch_date: str) -> dict:
    """Check launch readiness against a five-point rubric.

    The rubric covers: code frozen, docs written, QA passed, marketing
    ready, and support staffed. We look for evidence of each in the
    provided task list text.

    Args:
        tasks: The extracted task list (as a string or JSON).
        launch_date: Target launch date in YYYY-MM-DD format.

    Returns:
        {ready: bool, score: int (0-100), gaps: list[str]}
    """
    text = (tasks or "").lower()

    rubric = [
        ("code frozen", ["code", "freeze", "tag", "release candidate", "engineering"]),
        ("docs written", ["doc", "documentation", "guide", "readme", "writing"]),
        ("QA passed", ["qa", "test", "regression", "smoke", "bug"]),
        ("marketing ready", ["market", "campaign", "social", "ad", "press", "blog"]),
        ("support staffed", ["support", "customer", "runbook", "oncall", "help"]),
    ]

    covered: list[str] = []
    gaps: list[str] = []

    for label, keywords in rubric:
        if any(kw in text for kw in keywords):
            covered.append(label)
        else:
            gaps.append(label)

    score = int((len(covered) / len(rubric)) * 100)
    ready = len(gaps) == 0 and score >= 100

    # If launch date is missing we flag it as a gap too
    if not launch_date or not launch_date.strip():
        gaps.append("launch date not set")
        score = max(0, score - 10)

    return {
        "ready": ready,
        "score": score,
        "gaps": gaps,
        "covered": covered,
    }


# ---------------------------------------------------------------------------
# 3. generate_checklist
# ---------------------------------------------------------------------------

@function_tool
def generate_checklist(tasks: str) -> list[dict]:
    """Generate per-owner checklists from a task list.

    Parses the tasks string and groups items by owner, producing a
    checklist for each owner with concrete action items.

    Args:
        tasks: The extracted task list (string or JSON).

    Returns:
        A list of {owner, items: [str]} dicts.
    """
    # Try to parse as JSON list of dicts; fall back to line parsing
    import json

    task_items: list[dict] = []
    stripped = (tasks or "").strip()

    if stripped:
        try:
            parsed = json.loads(stripped)
            if isinstance(parsed, list):
                task_items = parsed
        except (json.JSONDecodeError, TypeError):
            # Fall back: split by newline, treat each line as a task
            for line in stripped.splitlines():
                line = line.strip().strip("-").strip()
                if line:
                    task_items.append({"task": line, "owner": "General"})

    # If nothing parsed, provide a default checklist
    if not task_items:
        task_items = [
            {"task": "Define launch goals and metrics", "owner": "Product"},
            {"task": "Freeze code and tag release candidate", "owner": "Engineering"},
            {"task": "Complete QA regression testing", "owner": "QA"},
            {"task": "Write documentation", "owner": "Technical Writing"},
            {"task": "Prepare marketing campaign", "owner": "Marketing"},
            {"task": "Staff support team", "owner": "Customer Success"},
        ]

    # Group tasks by owner
    by_owner: dict[str, list[str]] = {}
    for t in task_items:
        owner = t.get("owner", "General")
        task_name = t.get("task", str(t))
        by_owner.setdefault(owner, []).append(task_name)

    # Build owner checklists with concrete action items
    checklists: list[dict] = []
    for owner, task_names in by_owner.items():
        items: list[str] = []
        for tn in task_names:
            items.append(f"Confirm: {tn}")
            items.append(f"Sign off: {tn}")
        # Add a shared pre-launch check for every owner
        items.append("Review final launch checklist with launch lead")
        checklists.append({"owner": owner, "items": items})

    # Ensure there is always a Launch Lead checklist
    if not any(c["owner"] == "Launch Lead" for c in checklists):
        checklists.append({
            "owner": "Launch Lead",
            "items": [
                "Confirm all owners have signed off",
                "Verify go/no-go criteria are met",
                "Schedule launch-day standup",
                "Monitor dashboards during launch window",
            ],
        })

    return checklists


# ---------------------------------------------------------------------------
# 4. draft_copy
# ---------------------------------------------------------------------------

@function_tool
def draft_copy(channel: str, product: str, audience: str) -> dict:
    """Draft marketing copy for a specific channel.

    Generates a headline, body, and call-to-action tailored to the
    channel, product, and audience.

    Args:
        channel: Marketing channel (e.g. "email", "twitter", "blog",
                 "linkedin", "landing_page").
        product: Product name or short description.
        audience: Target audience description.

    Returns:
        {headline: str, body: str, cta: str}
    """
    ch = (channel or "").lower().strip()
    prod = (product or "").strip() or "our new product"
    aud = (audience or "").strip() or "your team"

    # Channel-specific copy templates
    if ch in ("twitter", "x", "tweet"):
        headline = f"πŸš€ {prod} is live β€” built for {aud}."
        body = (
            f"We just shipped {prod}, designed from the ground up for {aud}. "
            "Faster, simpler, and ready for production today. "
            "Check it out πŸ‘‡"
        )
        cta = "Try it now β†’"

    elif ch in ("email", "newsletter", "drip"):
        headline = f"Introducing {prod} β€” now available for {aud}"
        body = (
            f"Hi there,\n\n"
            f"We're excited to announce {prod}, purpose-built for {aud}. "
            "This release brings a refined experience, improved performance, "
            "and the features you've been asking for.\n\n"
            "Here's what's new:\n"
            "  β€’ Streamlined workflow that saves you time\n"
            "  β€’ Built-in reliability for production use\n"
            "  β€’ Thoughtful design choices throughout\n\n"
            "We'd love your feedback as you try it out."
        )
        cta = "Get started today"

    elif ch in ("blog", "blog post", "announcement"):
        headline = f"{prod}: A new standard for {aud}"
        body = (
            f"Today we're launching {prod}, our most refined release yet. "
            f"Built specifically for {aud}, it combines the features you need "
            "with the reliability you expect.\n\n"
            "In this post we'll walk through what's new, why we built it, "
            "and how to get started in under five minutes."
        )
        cta = "Read the full announcement"

    elif ch in ("linkedin", "li"):
        headline = f"We just launched {prod} β€” here's why it matters for {aud}"
        body = (
            f"After months of iteration, {prod} is now generally available. "
            f"We built it for {aud} who need a solution that's both powerful "
            "and dependable. Here's what went into it and what it means for you."
        )
        cta = "Learn more"

    elif ch in ("landing", "landing page", "landing_page", "homepage"):
        headline = f"{prod} β€” purpose-built for {aud}"
        body = (
            f"{prod} gives {aud} the tools they need to move faster, "
            "with the reliability required for real production workloads. "
            "No noise, no bloat β€” just what matters."
        )
        cta = "Start building for free"

    else:
        # Generic fallback for unknown channels
        headline = f"Introducing {prod}"
        body = (
            f"{prod} is now available. Designed for {aud}, it delivers "
            "the features and reliability you need to succeed."
        )
        cta = "Learn more"

    return {
        "headline": headline,
        "body": body,
        "cta": cta,
        "channel": ch or "general",
    }