Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.mavera.io/llms.txt

Use this file to discover all available pages before exploring further.

Epic Progress → Campaign Alignment

Scenario

Epics represent major product initiatives — the kind marketing needs to build campaigns around. This job pulls active epics via JQL, calculates progress as percentage of child issues completed, then sends the data to Mave Agent for a campaign strategy aligned with engineering velocity. Flow: Jira POST /search (type=Epic) → child issue progress → Mavera POST /api/v1/mave/chat → Campaign strategy

Code

import os, requests, time, base64

DOMAIN, EMAIL = os.environ["JIRA_DOMAIN"], os.environ["JIRA_EMAIL"]
TOKEN, MV = os.environ["JIRA_API_TOKEN"], os.environ["MAVERA_API_KEY"]
JB, MB = f"https://{DOMAIN}.atlassian.net/rest/api/3", "https://app.mavera.io/api/v1"
cred = base64.b64encode(f"{EMAIL}:{TOKEN}".encode()).decode()
JH = {"Authorization": f"Basic {cred}", "Content-Type": "application/json"}
MH = {"Authorization": f"Bearer {MV}", "Content-Type": "application/json"}
PROJECT_KEY = "PROJ"

r = requests.post(f"{JB}/search", headers=JH, json={
    "jql": f"project = {PROJECT_KEY} AND issuetype = Epic AND statusCategory != Done ORDER BY rank ASC",
    "startAt": 0, "maxResults": 20,
    "fields": ["summary", "status", "duedate"],
})
if r.status_code == 429:
    time.sleep(int(r.headers.get("Retry-After", 30)))
r.raise_for_status()
epics = r.json().get("issues", [])
print(f"Found {len(epics)} active epics")

epic_progress = []
for epic in epics:
    time.sleep(0.5)
    cr = requests.post(f"{JB}/search", headers=JH, json={
        "jql": f'"Epic Link" = {epic["key"]}', "startAt": 0, "maxResults": 100, "fields": ["status"],
    })
    if cr.status_code == 429:
        time.sleep(int(cr.headers.get("Retry-After", 30)))
        continue
    children = cr.json().get("issues", [])
    total = len(children)
    done = sum(1 for c in children if c["fields"]["status"]["statusCategory"]["key"] == "done")
    pct = round((done / total) * 100, 1) if total else 0
    f = epic["fields"]
    epic_progress.append({"key": epic["key"], "summary": f["summary"],
        "due": f.get("duedate", "No date"), "total": total, "done": done, "pct": pct})

progress_txt = "\n".join(
    f"  {e['key']}: {e['summary']} | {e['pct']}% ({e['done']}/{e['total']}) | Due: {e['due']}"
    for e in epic_progress)

time.sleep(0.3)
strategy = requests.post(f"{MB}/mave/chat", headers=MH, json={
    "message": f"Campaign strategist. Align marketing with {len(epic_progress)} epics.\n\n"
               f"EPIC PROGRESS:\n{progress_txt}\n\n"
               "Produce: 1) Ship date estimates 2) Campaign priority ranking 3) Pre-launch "
               "checklist for >75% epics 4) Week-by-week timeline 5) Content matrix per epic "
               "6) Risk assessment 7) Stagger vs big-bang recommendation",
}).json()

print(f"\n{'='*60}\nCAMPAIGN ALIGNMENT\n{'='*60}")
print(strategy.get("content", "")[:3000])

Example Output

Found 6 active epics

## Ship Date Estimates
| Epic | Progress | Ship Date | Confidence |
|------|----------|-----------|------------|
| PROJ-135: SSO/SCIM | 91.7% (11/12) | Mar 19 | Very High |
| PROJ-100: API v3 | 88.5% (23/26) | Mar 24 | High |
| PROJ-112: Dashboard | 62.0% (31/50) | Apr 14 | Medium |

## Campaign Priority
1. SSO/SCIM (91.7%) — Start content NOW. Security angle.
2. API v3 (88.5%) — Developer blog + migration guide this week.

## Recommendation: STAGGER
Ship SSO first, API v3 one week later. Dashboard in April.

Error Handling

Epics with zero child issues return 0% progress. Filter them out or flag as “not yet scoped” in the campaign strategy.
Jira status categories (done, indeterminate, new) differ from status names. The code uses statusCategory.key for reliable progress regardless of custom workflows.