<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: K Gann</title>
    <description>The latest articles on DEV Community by K Gann (@jubilee_happy_a567008f769).</description>
    <link>https://dev.to/jubilee_happy_a567008f769</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4020347%2F7a2e4d14-ff58-40f7-b71d-c0c65aee3826.png</url>
      <title>DEV Community: K Gann</title>
      <link>https://dev.to/jubilee_happy_a567008f769</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jubilee_happy_a567008f769"/>
    <language>en</language>
    <item>
      <title>How I Auto-Groomed 500 Jira Tickets with ML and LLM</title>
      <dc:creator>K Gann</dc:creator>
      <pubDate>Wed, 08 Jul 2026 02:42:24 +0000</pubDate>
      <link>https://dev.to/jubilee_happy_a567008f769/how-i-auto-groomed-500-jira-tickets-with-ml-and-llm-1j7k</link>
      <guid>https://dev.to/jubilee_happy_a567008f769/how-i-auto-groomed-500-jira-tickets-with-ml-and-llm-1j7k</guid>
      <description>&lt;p&gt;&lt;em&gt;A practical, end-to-end walkthrough of applying NLP, machine learning, and Gemini to automatically cluster issues, surface duplicates, and generate an executive summary — no manual grooming required.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Doesn't Jira Just Do This Already?
&lt;/h2&gt;

&lt;p&gt;If you've ever inherited a Jira project with hundreds of open tickets, your first instinct might be: &lt;em&gt;can't the tool handle this?&lt;/em&gt; It's a fair question. Atlassian has invested heavily in AI in recent years, and their answer — Atlassian Intelligence and the newer Rovo platform — is genuinely impressive for many tasks.&lt;/p&gt;

&lt;p&gt;Atlassian Intelligence's summarization tool can read through a long comment thread and provide a concise summary, and its JQL assistance lowers the barrier to writing complex queries. Rovo goes further: Rovo Agents are task-specific assistants designed to automate workflows like triaging issues and grooming backlogs.&lt;/p&gt;

&lt;p&gt;But here's what neither tool actually does: &lt;strong&gt;cluster hundreds of tickets into thematic groups, or systematically detect duplicates at scale across an entire backlog.&lt;/strong&gt; Rovo is optimized for individual ticket interactions — summarizing one issue, routing one request. It has no native capability to run a similarity computation across 500 issues or partition a backlog into coherent clusters a PM can act on.&lt;/p&gt;

&lt;p&gt;What we're building here is a &lt;strong&gt;batch analytical pipeline&lt;/strong&gt; — a fundamentally different mode of operation.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the Research Says
&lt;/h2&gt;

&lt;p&gt;The closest published work is a 2025 arXiv study, &lt;em&gt;"GenAI-Enabled Backlog Grooming in Agile Software Projects"&lt;/em&gt;, which built a Jira plugin using vector embeddings, cosine similarity, and GPT-4o to propose merges and deletions — achieving 100% precision while reducing time-to-completion by 45%. Our pipeline validates the same core approach but differs in two ways: we use classical TF-IDF (no GPU required) and introduce &lt;strong&gt;selective RAG grounding with role-differentiated context files&lt;/strong&gt; — something the academic study didn't explore.&lt;/p&gt;

&lt;p&gt;Earlier work confirmed that ML can effectively predict issue link types across large Jira repositories, with duplicate detection being one of the most studied categories. And TF-IDF + K-Means has been shown to efficiently filter large text corpora into focal topics — a pattern that maps directly onto backlog clustering.&lt;/p&gt;

&lt;p&gt;The gap this article fills is the &lt;strong&gt;integration layer&lt;/strong&gt;: combining these techniques with a selectively RAG-grounded LLM into a single end-to-end pipeline that produces a PM-ready executive summary.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Pipeline at a Glance
&lt;/h2&gt;

&lt;p&gt;Using 500 publicly available Apache ZooKeeper Jira issues as a sample dataset, the pipeline runs in two phases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Phase I:&lt;/strong&gt; NLP preprocessing → TF-IDF vectorization → K-Means clustering → cosine similarity duplicate detection&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phase II:&lt;/strong&gt; Selective RAG grounding + Gemini 2.5 Flash → enriched cluster names, age-aware duplicate insights, and an executive summary&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvyd9zya52azxm2f5p040.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvyd9zya52azxm2f5p040.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase I: Clustering and Duplicate Detection
&lt;/h2&gt;

&lt;p&gt;We pull 500 issues from Apache's public Jira REST API with fields including summary, description, status, priority, labels, and comments. Critically, we store &lt;code&gt;updated&lt;/code&gt; as a timezone-aware pandas Timestamp — this feeds the age categorization in Phase II.&lt;/p&gt;

&lt;p&gt;Text preprocessing combines each ticket's &lt;code&gt;summary&lt;/code&gt; and &lt;code&gt;description&lt;/code&gt; into a &lt;code&gt;full_text&lt;/code&gt; field, then cleans it through this pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;bs4&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BeautifulSoup&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;nltk.corpus&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;stopwords&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;nltk.tokenize&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;word_tokenize&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;clean_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BeautifulSoup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;html.parser&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;separator&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;http\S+|www\S+|https\S+&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MULTILINE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;[^a-zA-Z\s]&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;word_tokenize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;stop_words&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stopwords&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;words&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;english&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tokens&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;stop_words&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;df_jira_issues&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;processed_text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df_jira_issues&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;full_text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clean_text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From there, standard TF-IDF vectorization produces a (500 × 6,842) matrix, and K-Means with &lt;code&gt;k-means++&lt;/code&gt; initialization groups issues into 5 clusters. We then compute pairwise cosine similarity across all 500 issues and flag pairs above a 0.8 threshold as potential duplicates.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Why TF-IDF over embeddings?&lt;/strong&gt; ZooKeeper tickets are keyword-dense — error names, component labels, CVE IDs. TF-IDF is fast, interpretable, and needs no GPU. For backlogs with richer prose, &lt;code&gt;sentence-transformers&lt;/code&gt; would be worth exploring.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The cluster distribution was notably uneven: Cluster 3 contained 286 issues while Cluster 0 had just 42 — already a signal that client/server errors dominate this backlog. Cosine similarity surfaced &lt;strong&gt;12 potential duplicate pairs&lt;/strong&gt;. Phase I ends here; the raw outputs are meaningful to engineers but opaque to PMs. Phase II fixes that.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase II: Selective RAG + Gemini
&lt;/h2&gt;

&lt;p&gt;The key design decision in this pipeline is that &lt;strong&gt;RAG files are weighted differently by task&lt;/strong&gt;, not sent uniformly to every prompt. When all three context files carry equal weight, the LLM hedges between historical patterns and future priorities. Explicit weighting — via prompt instructions like &lt;code&gt;"PRIORITIZE for strategic alignment"&lt;/code&gt; — produces sharper outputs.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;Primary RAG&lt;/th&gt;
&lt;th&gt;Background&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cluster naming &amp;amp; PM recommendations&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2026_theme.txt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;project_context.txt&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Duplicate insights&lt;/td&gt;
&lt;td&gt;&lt;code&gt;release_notes.txt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;project_context.txt&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Executive summary&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2026_theme.txt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;project_context.txt&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The three files serve distinct roles. &lt;code&gt;project_context.txt&lt;/code&gt; provides the strategic frame — project goals, workflows, definitions of success — and appears in every prompt as background. &lt;code&gt;release_notes.txt&lt;/code&gt; carries the historical record of bug fixes and shipped features; it's the primary driver for duplicate detection because it can tell the LLM whether a reported bug was already fixed in a past release. &lt;code&gt;2026_theme.txt&lt;/code&gt; contains quarterly priorities (e.g., FY26Q1: "Core System Hardening") and drives cluster naming and the executive summary, keeping outputs aligned with where the team is going.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enriching Cluster Names
&lt;/h3&gt;

&lt;p&gt;The cluster prompt requests a four-field JSON output — name, theme alignment, description, and PM recommendations — grounded primarily in the 2026 themes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langchain_google_genai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ChatGoogleGenerativeAI&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langchain_core.prompts&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PromptTemplate&lt;/span&gt;

&lt;span class="n"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ChatGoogleGenerativeAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;models/gemini-2.5-flash&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                              &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;GEMINI_API_KEY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;cluster_enhancement_prompt_template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PromptTemplate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;input_variables&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;project_context&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;release_notes_context&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                     &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;theme_2026_context&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cluster_keywords&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cluster_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You are an expert in Jira issue analysis and project management.&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Project Context (use for overall understanding):&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;{project_context}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Release Notes (use for historical context):&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;{release_notes_context}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026 Themes (PRIORITIZE for theme alignment and recommendations):&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;{theme_2026_context}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Top keywords for Cluster {cluster_id}: {cluster_keywords}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Return JSON with keys: name, theme_alignment, description, pm_recommendations.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Raw keywords → LLM-enhanced names:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Cluster&lt;/th&gt;
&lt;th&gt;Raw Keywords&lt;/th&gt;
&lt;th&gt;LLM-Enhanced Name&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;support, auth, apis, admin, prometheus&lt;/td&gt;
&lt;td&gt;Admin Server APIs, Authentication, and Prometheus Metrics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;cve, netty, jetty, vulnerability&lt;/td&gt;
&lt;td&gt;Security Vulnerability Fixes and Dependency Upgrades&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;client, session, connection, ssl&lt;/td&gt;
&lt;td&gt;ZooKeeper Client Connectivity, Sessions, and Security&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;error, exception, npe, zookeeper&lt;/td&gt;
&lt;td&gt;ZooKeeper Client/Server Errors and Stability Issues&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;logging, dependency, core&lt;/td&gt;
&lt;td&gt;Core System Dependency and Logging Updates&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Cluster 1's name anchors to "Fixes and Dependency Upgrades" because &lt;code&gt;2026_theme.txt&lt;/code&gt; explicitly flags dependency remediation as a Core System Hardening priority. The LLM isn't just reading keywords — it's reading the roadmap. Each cluster also returns PM-level output: how it connects to FY2026 themes, what the issues represent in engineering terms, and what the team should prioritize next quarter.&lt;/p&gt;

&lt;h3&gt;
  
  
  Age-Aware Duplicate Detection
&lt;/h3&gt;

&lt;p&gt;Before sending duplicate pairs to the LLM, we compute a &lt;strong&gt;ticket age category&lt;/strong&gt; from each issue's &lt;code&gt;updated&lt;/code&gt; timestamp:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;categorize_age&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;updated_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;current_date&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isnull&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;updated_date&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Unknown&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;age_days&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_date&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;updated_date&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;age_days&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Active (&amp;lt; 30 days)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;age_days&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Recent (30-60 days)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;age_days&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Aging (&amp;gt; 60 days)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;age_days&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;365&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Stale (&amp;gt; 180 days)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Very Stale (&amp;gt; 365 days)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;current_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Timestamp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tz&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Age travels into the duplicate prompt alongside summaries, descriptions, and similarity scores — with &lt;code&gt;release_notes.txt&lt;/code&gt; as the primary context. Three real examples from the pipeline output show how age category produces meaningfully different recommendations across three distinct duplicate scenarios.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example 1 — True Duplicate, Stale: ZOOKEEPER-4948 &amp;amp; ZOOKEEPER-4947 (Similarity: 1.0000)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Both tickets are &lt;em&gt;Stale (&amp;gt; 180 days)&lt;/em&gt; and describe the exact same bug word-for-word: ZooKeeper's &lt;code&gt;SendThread&lt;/code&gt; and &lt;code&gt;EventThread&lt;/code&gt; fail to terminate after &lt;code&gt;close()&lt;/code&gt; is called during a SASL &lt;code&gt;AuthFailed&lt;/code&gt; state. Because the tickets are stale — not freshly filed — the LLM doesn't just say "close one." It adds a verification step first:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Both issues are Stale (&amp;gt; 180 days) and relate to a SASL authentication bug introduced in Release 3.8.0. First verify whether a subsequent patch already resolved this. If fixed, close ZOOKEEPER-4947 as a duplicate and close ZOOKEEPER-4948 as Done, referencing the fix. If still open, merge into ZOOKEEPER-4948 and reprioritize as high-severity under FY26Q1 Core System Hardening."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;An Active pair with the same similarity score would skip that verification caveat entirely. Age changes the action.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example 2 — Same Root Cause, Different Components: ZOOKEEPER-4665 &amp;amp; ZOOKEEPER-4664 (Similarity: 0.8624)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Both tickets are &lt;em&gt;Very Stale (&amp;gt; 365 days)&lt;/em&gt; and report OWASP failures from vulnerable third-party dependencies — but in different components (&lt;code&gt;zooinspector&lt;/code&gt; vs. &lt;code&gt;zookeeper-contrib-rest&lt;/code&gt;) with entirely different CVE lists. The LLM correctly avoids calling these exact duplicates:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"These are not identical duplicates but represent the same systemic security problem across two components. Both are Very Stale (&amp;gt; 365 days) — some specific CVEs may have been implicitly resolved through dependency updates in 3.6.3 or 3.8.1. Consolidate under a parent epic 'Address Third-Party Security Vulnerabilities' aligned with FY26Q1 Core System Hardening, and verify current CVE status before prioritizing each sub-task."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The Very Stale age category surfaces a nuance a simple similarity check would miss: the specific CVEs may no longer exist. The recommendation is to verify before acting, not close blindly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example 3 — Scope Diverged, Keep Both: ZOOKEEPER-4634 &amp;amp; ZOOKEEPER-4633 (Similarity: 0.9412)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Both tickets are &lt;em&gt;Very Stale (&amp;gt; 365 days)&lt;/em&gt; and request authentication support for the same Admin Server APIs, referencing an identical design document. But they diverge on implementation: ZOOKEEPER-4634 targets &lt;strong&gt;x509 auth&lt;/strong&gt;, ZOOKEEPER-4633 targets &lt;strong&gt;digest auth&lt;/strong&gt;. The LLM recognizes they share an origin but have grown into distinct workstreams:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"These are not duplicates — they are two implementation paths for a single feature. Both are Very Stale (&amp;gt; 365 days), and the Admin Server APIs they target were only introduced in Release 3.7.0 (February 2026), likely after these tickets were created. Verify whether either method has already been implemented. If not, consolidate under a parent epic 'Admin Server API Authentication Strategy' and determine whether both authentication types are still required or if the 3.8.0 SASL implementation has superseded them."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here, age + release note context together expose something critical: the tickets were written &lt;em&gt;before the feature they reference even existed&lt;/em&gt;, making their requirements potentially obsolete. Neither the similarity score nor the LLM alone could surface that — it required the &lt;code&gt;release_notes.txt&lt;/code&gt; primary context combined with the age signal.&lt;/p&gt;

&lt;p&gt;The three examples cover the full spectrum a real backlog surfaces:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;What age adds&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ZOOKEEPER-4948/4947&lt;/td&gt;
&lt;td&gt;True duplicate&lt;/td&gt;
&lt;td&gt;Triggers "verify fix before closing" on stale tickets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ZOOKEEPER-4665/4664&lt;/td&gt;
&lt;td&gt;Same root cause, different components&lt;/td&gt;
&lt;td&gt;Flags possible implicit resolution for very stale CVEs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ZOOKEEPER-4634/4633&lt;/td&gt;
&lt;td&gt;Scope diverged&lt;/td&gt;
&lt;td&gt;Exposes requirements written before the feature existed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  The Output: Executive Summary for PMs
&lt;/h2&gt;

&lt;p&gt;The executive summary prompt intentionally excludes &lt;code&gt;release_notes.txt&lt;/code&gt; — the summary is forward-looking, not a historical audit — and synthesizes cluster theme alignments and duplicate insights into 3–5 strategic paragraphs saved as &lt;code&gt;executive_summary.txt&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The file covers: methodology overview, 5 named clusters with FY2026 theme alignment and PM recommendations, all 12 duplicate pairs with age categories and specific actions, and a conclusion tied to quarterly priorities. It's the artifact that bridges engineering analysis and program management, readable without touching any code.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Tradeoffs and Lessons Learned
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Selective RAG weighting is the highest-leverage design choice.&lt;/strong&gt; Moving from uniform context injection to task-specific prioritization made cluster names more strategically forward-looking and duplicate recommendations more historically precise — without adding complexity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ticket age is a low-cost, high-value addition.&lt;/strong&gt; A "Very Stale" duplicate pair warrants confident closure; an "Active" pair needs investigation first. One function call adds a dimension that meaningfully changes the LLM's recommendations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cluster imbalance is a finding, not a failure.&lt;/strong&gt; 286 issues in Cluster 3 vs. 42 in Cluster 0 tells you something real about where engineering effort is concentrated. For a production pipeline, run silhouette scoring to find a better &lt;code&gt;k&lt;/code&gt; — 5 may be too few for this dataset.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;60-second API delays add up.&lt;/strong&gt; With 12 duplicate pairs and a rate-limit delay between calls, Phase II takes ~12 minutes end-to-end. Batching or async calls would speed this up significantly at scale.&lt;/p&gt;




&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Auto-triage new tickets:&lt;/strong&gt; Apply the trained K-Means model to incoming issues at creation time for automatic cluster routing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Threshold tuning dashboard:&lt;/strong&gt; A Streamlit tool letting PMs adjust the 0.8 similarity threshold and preview duplicates in real time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic RAG updates:&lt;/strong&gt; Automate refresh of &lt;code&gt;release_notes.txt&lt;/code&gt; and &lt;code&gt;2026_theme.txt&lt;/code&gt; from Confluence via API, so context stays current without manual maintenance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Richer embeddings:&lt;/strong&gt; Replace TF-IDF with &lt;code&gt;sentence-transformers&lt;/code&gt; for semantically richer clustering on backlogs with more conversational language.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;What started as a 500-ticket mess became a structured, LLM-annotated backlog report — ready for a PM to act on — in a single Jypyter notebook.&lt;/p&gt;

&lt;p&gt;The core insight: &lt;strong&gt;classical ML handles the heavy lifting&lt;/strong&gt; (TF-IDF + K-Means + cosine similarity, fast and GPU-free), and &lt;strong&gt;a selectively RAG-grounded LLM handles the last mile&lt;/strong&gt; — turning keyword lists into strategically aligned, age-aware, historically informed language that both engineers and program managers can use.&lt;/p&gt;

&lt;p&gt;The selective RAG weighting is the detail that makes the difference. Routing the right knowledge source to the right task — release notes for historical duplicate reasoning, 2026 themes for forward-looking cluster strategy — produces outputs that are genuinely actionable rather than generically descriptive.&lt;/p&gt;

&lt;p&gt;Full code is available on &lt;a href="https://github.com/wkgann/jira-backlog-analyzer" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;p&gt;[1] &lt;em&gt;A clustering approach for topic filtering within systematic literature reviews&lt;/em&gt; — NCBI, 2020. &lt;a href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC7078380/" rel="noopener noreferrer"&gt;https://www.ncbi.nlm.nih.gov/pmc/articles/PMC7078380/&lt;/a&gt;&lt;br&gt;
[2] &lt;em&gt;Apache ZooKeeper Public Jira Issue Tracker&lt;/em&gt; — Apache Software Foundation. &lt;a href="https://issues.apache.org/jira/projects/ZOOKEEPER/issues" rel="noopener noreferrer"&gt;https://issues.apache.org/jira/projects/ZOOKEEPER/issues&lt;/a&gt;&lt;br&gt;
[3] Atlassian Rovo — Feature overview. &lt;a href="https://www.atlassian.com/software/rovo/features" rel="noopener noreferrer"&gt;https://www.atlassian.com/software/rovo/features&lt;/a&gt;&lt;br&gt;
[4] &lt;em&gt;Automated Detection of Typed Links in Issue Trackers&lt;/em&gt; — arXiv, 2022. &lt;a href="https://arxiv.org/pdf/2206.07182" rel="noopener noreferrer"&gt;https://arxiv.org/pdf/2206.07182&lt;/a&gt;&lt;br&gt;
[5] &lt;em&gt;GenAI-Enabled Backlog Grooming in Agile Software Projects&lt;/em&gt; — arXiv, 2025. &lt;a href="https://arxiv.org/pdf/2507.10753" rel="noopener noreferrer"&gt;https://arxiv.org/pdf/2507.10753&lt;/a&gt;&lt;br&gt;
[6] &lt;em&gt;Rovo by Atlassian: What the AI can and can't do&lt;/em&gt; — eesel AI blog. &lt;a href="https://www.eesel.ai/blog/rovo" rel="noopener noreferrer"&gt;https://www.eesel.ai/blog/rovo&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>llm</category>
      <category>python</category>
    </item>
  </channel>
</rss>
