Skip to content

Commit b1d200d

Browse files
committed
updates: module-level constants, logging, groupby comprehension
1 parent fd560d3 commit b1d200d

2 files changed

Lines changed: 146 additions & 0 deletions

File tree

__init__.py

Whitespace-only changes.

bug_triage.py

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
"""
2+
Automated Bug Triage Tool
3+
This script scans markdown bug reports, classifies them by severity based on
4+
keywords, and generates a summarized triage report in Markdown format.
5+
"""
6+
7+
import glob
8+
import logging
9+
import os
10+
from collections import defaultdict
11+
from datetime import UTC, datetime
12+
from pathlib import Path
13+
14+
# Configuration: Adjust these paths based on your local environment
15+
BASE_DIR = Path(__file__).parent
16+
BUG_PATH = str(BASE_DIR / "production" / "qa" / "bugs" / "*.md")
17+
OUTPUT_PATH = str(BASE_DIR / "production" / "qa")
18+
19+
SEVERITY_KEYWORDS: dict[str, list[str]] = {
20+
"S1": ["crash", "data loss", "cannot start", "fatal"],
21+
"S2": ["broken", "not working", "fail"],
22+
"S3": ["slow", "incorrect", "glitch"],
23+
}
24+
25+
logger = logging.getLogger(__name__)
26+
27+
28+
def classify_severity(content: str) -> str:
29+
"""
30+
Classifies bug severity based on specific keywords found in the content.
31+
Returns S1 (Critical) through S4 (Minor).
32+
>>> classify_severity("The application had a fatal crash on startup.")
33+
'S1'
34+
>>> classify_severity("The UI is a bit slow today.")
35+
'S3'
36+
"""
37+
content = content.lower()
38+
for severity, keywords in SEVERITY_KEYWORDS.items():
39+
if any(k in content for k in keywords):
40+
return severity
41+
return "S4"
42+
43+
44+
def classify_priority(severity: str) -> str:
45+
"""
46+
Maps the technical severity level to a business priority level.
47+
>>> classify_priority("S1")
48+
'P1'
49+
>>> classify_priority("S4")
50+
'P4'
51+
"""
52+
priority_map = {"S1": "P1", "S2": "P2", "S3": "P3"}
53+
return priority_map.get(severity, "P4")
54+
55+
56+
def read_bugs() -> list[dict]:
57+
"""
58+
Reads all markdown files in the BUG_PATH and extracts metadata.
59+
>>> read_bugs()
60+
[]
61+
"""
62+
files = glob.glob(BUG_PATH)
63+
bugs = []
64+
65+
for i, file_path in enumerate(sorted(files)):
66+
try:
67+
with open(file_path, encoding="utf-8") as f:
68+
content = f.read()
69+
70+
severity = classify_severity(content)
71+
priority = classify_priority(severity)
72+
73+
bugs.append({
74+
"id": f"BUG-{i+1:03}",
75+
"file": file_path,
76+
"severity": severity,
77+
"priority": priority,
78+
"summary": content.strip().split("\n")[0][:80]
79+
})
80+
except OSError as e:
81+
logger.warning("Could not read file %s: %s", file_path, e)
82+
83+
return bugs
84+
85+
86+
def generate_report(bugs: list[dict]) -> None:
87+
"""
88+
Groups bugs by priority and writes a summarized Markdown report.
89+
>>> generate_report([])
90+
❌ No bugs to report.
91+
"""
92+
if not bugs:
93+
print("❌ No bugs to report.")
94+
return
95+
96+
date = datetime.now(UTC).strftime("%Y-%m-%d")
97+
98+
if not os.path.exists(OUTPUT_PATH):
99+
os.makedirs(OUTPUT_PATH)
100+
101+
output_file = os.path.join(OUTPUT_PATH, f"bug-triage-{date}.md")
102+
103+
grouped: defaultdict[str, list[dict]] = defaultdict(list)
104+
for b in bugs:
105+
grouped[b["priority"]].append(b)
106+
107+
p1, p2, p3, p4 = grouped["P1"], grouped["P2"], grouped["P3"], grouped["P4"]
108+
109+
report_content = [
110+
"# Bug Triage Report",
111+
f"**Date**: {date} ",
112+
f"**Open bugs processed**: {len(bugs)}",
113+
"\n---\n",
114+
"## Triage Summary\n",
115+
"| Priority | Count |",
116+
"|----------|-------|",
117+
f"| P1 | {len(p1)} |",
118+
f"| P2 | {len(p2)} |",
119+
f"| P3 | {len(p3)} |",
120+
f"| P4 | {len(p4)} |",
121+
"\n---\n",
122+
"## P1 Bugs (Critical)"
123+
]
124+
125+
for b in p1:
126+
report_content.append(f"- {b['id']} | {b['severity']} | {b['summary']}")
127+
128+
report_content.append("\n## P2 Bugs (High)")
129+
for b in p2:
130+
report_content.append(f"- {b['id']} | {b['severity']} | {b['summary']}")
131+
132+
report_content.append("\n## Backlog (P3/P4)")
133+
for b in p3 + p4:
134+
report_content.append(f"- {b['id']} | {b['severity']} | {b['summary']}")
135+
136+
with open(output_file, "w", encoding="utf-8") as f:
137+
f.write("\n".join(report_content))
138+
139+
print(f"✅ Report successfully generated at: {output_file}")
140+
141+
142+
if __name__ == "__main__":
143+
import doctest
144+
doctest.testmod()
145+
extracted_bugs = read_bugs()
146+
generate_report(extracted_bugs)

0 commit comments

Comments
 (0)