mcp-ticketer 0.3.0__py3-none-any.whl → 2.2.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. mcp_ticketer/__init__.py +10 -10
  2. mcp_ticketer/__version__.py +3 -3
  3. mcp_ticketer/_version_scm.py +1 -0
  4. mcp_ticketer/adapters/__init__.py +2 -0
  5. mcp_ticketer/adapters/aitrackdown.py +930 -52
  6. mcp_ticketer/adapters/asana/__init__.py +15 -0
  7. mcp_ticketer/adapters/asana/adapter.py +1537 -0
  8. mcp_ticketer/adapters/asana/client.py +292 -0
  9. mcp_ticketer/adapters/asana/mappers.py +348 -0
  10. mcp_ticketer/adapters/asana/types.py +146 -0
  11. mcp_ticketer/adapters/github/__init__.py +26 -0
  12. mcp_ticketer/adapters/github/adapter.py +3229 -0
  13. mcp_ticketer/adapters/github/client.py +335 -0
  14. mcp_ticketer/adapters/github/mappers.py +797 -0
  15. mcp_ticketer/adapters/github/queries.py +692 -0
  16. mcp_ticketer/adapters/github/types.py +460 -0
  17. mcp_ticketer/adapters/hybrid.py +58 -16
  18. mcp_ticketer/adapters/jira/__init__.py +35 -0
  19. mcp_ticketer/adapters/jira/adapter.py +1351 -0
  20. mcp_ticketer/adapters/jira/client.py +271 -0
  21. mcp_ticketer/adapters/jira/mappers.py +246 -0
  22. mcp_ticketer/adapters/jira/queries.py +216 -0
  23. mcp_ticketer/adapters/jira/types.py +304 -0
  24. mcp_ticketer/adapters/linear/__init__.py +1 -1
  25. mcp_ticketer/adapters/linear/adapter.py +3810 -462
  26. mcp_ticketer/adapters/linear/client.py +312 -69
  27. mcp_ticketer/adapters/linear/mappers.py +305 -85
  28. mcp_ticketer/adapters/linear/queries.py +317 -17
  29. mcp_ticketer/adapters/linear/types.py +187 -64
  30. mcp_ticketer/adapters/linear.py +2 -2
  31. mcp_ticketer/analysis/__init__.py +56 -0
  32. mcp_ticketer/analysis/dependency_graph.py +255 -0
  33. mcp_ticketer/analysis/health_assessment.py +304 -0
  34. mcp_ticketer/analysis/orphaned.py +218 -0
  35. mcp_ticketer/analysis/project_status.py +594 -0
  36. mcp_ticketer/analysis/similarity.py +224 -0
  37. mcp_ticketer/analysis/staleness.py +266 -0
  38. mcp_ticketer/automation/__init__.py +11 -0
  39. mcp_ticketer/automation/project_updates.py +378 -0
  40. mcp_ticketer/cache/memory.py +9 -8
  41. mcp_ticketer/cli/adapter_diagnostics.py +91 -54
  42. mcp_ticketer/cli/auggie_configure.py +116 -15
  43. mcp_ticketer/cli/codex_configure.py +274 -82
  44. mcp_ticketer/cli/configure.py +1323 -151
  45. mcp_ticketer/cli/cursor_configure.py +314 -0
  46. mcp_ticketer/cli/diagnostics.py +209 -114
  47. mcp_ticketer/cli/discover.py +297 -26
  48. mcp_ticketer/cli/gemini_configure.py +119 -26
  49. mcp_ticketer/cli/init_command.py +880 -0
  50. mcp_ticketer/cli/install_mcp_server.py +418 -0
  51. mcp_ticketer/cli/instruction_commands.py +435 -0
  52. mcp_ticketer/cli/linear_commands.py +256 -130
  53. mcp_ticketer/cli/main.py +140 -1544
  54. mcp_ticketer/cli/mcp_configure.py +1013 -100
  55. mcp_ticketer/cli/mcp_server_commands.py +415 -0
  56. mcp_ticketer/cli/migrate_config.py +12 -8
  57. mcp_ticketer/cli/platform_commands.py +123 -0
  58. mcp_ticketer/cli/platform_detection.py +477 -0
  59. mcp_ticketer/cli/platform_installer.py +545 -0
  60. mcp_ticketer/cli/project_update_commands.py +350 -0
  61. mcp_ticketer/cli/python_detection.py +126 -0
  62. mcp_ticketer/cli/queue_commands.py +15 -15
  63. mcp_ticketer/cli/setup_command.py +794 -0
  64. mcp_ticketer/cli/simple_health.py +84 -59
  65. mcp_ticketer/cli/ticket_commands.py +1375 -0
  66. mcp_ticketer/cli/update_checker.py +313 -0
  67. mcp_ticketer/cli/utils.py +195 -72
  68. mcp_ticketer/core/__init__.py +64 -1
  69. mcp_ticketer/core/adapter.py +618 -18
  70. mcp_ticketer/core/config.py +77 -68
  71. mcp_ticketer/core/env_discovery.py +75 -16
  72. mcp_ticketer/core/env_loader.py +121 -97
  73. mcp_ticketer/core/exceptions.py +32 -24
  74. mcp_ticketer/core/http_client.py +26 -26
  75. mcp_ticketer/core/instructions.py +405 -0
  76. mcp_ticketer/core/label_manager.py +732 -0
  77. mcp_ticketer/core/mappers.py +42 -30
  78. mcp_ticketer/core/milestone_manager.py +252 -0
  79. mcp_ticketer/core/models.py +566 -19
  80. mcp_ticketer/core/onepassword_secrets.py +379 -0
  81. mcp_ticketer/core/priority_matcher.py +463 -0
  82. mcp_ticketer/core/project_config.py +189 -49
  83. mcp_ticketer/core/project_utils.py +281 -0
  84. mcp_ticketer/core/project_validator.py +376 -0
  85. mcp_ticketer/core/registry.py +3 -3
  86. mcp_ticketer/core/session_state.py +176 -0
  87. mcp_ticketer/core/state_matcher.py +592 -0
  88. mcp_ticketer/core/url_parser.py +425 -0
  89. mcp_ticketer/core/validators.py +69 -0
  90. mcp_ticketer/defaults/ticket_instructions.md +644 -0
  91. mcp_ticketer/mcp/__init__.py +29 -1
  92. mcp_ticketer/mcp/__main__.py +60 -0
  93. mcp_ticketer/mcp/server/__init__.py +25 -0
  94. mcp_ticketer/mcp/server/__main__.py +60 -0
  95. mcp_ticketer/mcp/server/constants.py +58 -0
  96. mcp_ticketer/mcp/server/diagnostic_helper.py +175 -0
  97. mcp_ticketer/mcp/server/dto.py +195 -0
  98. mcp_ticketer/mcp/server/main.py +1343 -0
  99. mcp_ticketer/mcp/server/response_builder.py +206 -0
  100. mcp_ticketer/mcp/server/routing.py +723 -0
  101. mcp_ticketer/mcp/server/server_sdk.py +151 -0
  102. mcp_ticketer/mcp/server/tools/__init__.py +69 -0
  103. mcp_ticketer/mcp/server/tools/analysis_tools.py +854 -0
  104. mcp_ticketer/mcp/server/tools/attachment_tools.py +224 -0
  105. mcp_ticketer/mcp/server/tools/bulk_tools.py +330 -0
  106. mcp_ticketer/mcp/server/tools/comment_tools.py +152 -0
  107. mcp_ticketer/mcp/server/tools/config_tools.py +1564 -0
  108. mcp_ticketer/mcp/server/tools/diagnostic_tools.py +211 -0
  109. mcp_ticketer/mcp/server/tools/hierarchy_tools.py +942 -0
  110. mcp_ticketer/mcp/server/tools/instruction_tools.py +295 -0
  111. mcp_ticketer/mcp/server/tools/label_tools.py +942 -0
  112. mcp_ticketer/mcp/server/tools/milestone_tools.py +338 -0
  113. mcp_ticketer/mcp/server/tools/pr_tools.py +150 -0
  114. mcp_ticketer/mcp/server/tools/project_status_tools.py +158 -0
  115. mcp_ticketer/mcp/server/tools/project_update_tools.py +473 -0
  116. mcp_ticketer/mcp/server/tools/search_tools.py +318 -0
  117. mcp_ticketer/mcp/server/tools/session_tools.py +308 -0
  118. mcp_ticketer/mcp/server/tools/ticket_tools.py +1413 -0
  119. mcp_ticketer/mcp/server/tools/user_ticket_tools.py +364 -0
  120. mcp_ticketer/queue/__init__.py +1 -0
  121. mcp_ticketer/queue/health_monitor.py +168 -136
  122. mcp_ticketer/queue/manager.py +78 -63
  123. mcp_ticketer/queue/queue.py +108 -21
  124. mcp_ticketer/queue/run_worker.py +2 -2
  125. mcp_ticketer/queue/ticket_registry.py +213 -155
  126. mcp_ticketer/queue/worker.py +96 -58
  127. mcp_ticketer/utils/__init__.py +5 -0
  128. mcp_ticketer/utils/token_utils.py +246 -0
  129. mcp_ticketer-2.2.9.dist-info/METADATA +1396 -0
  130. mcp_ticketer-2.2.9.dist-info/RECORD +158 -0
  131. mcp_ticketer-2.2.9.dist-info/top_level.txt +2 -0
  132. py_mcp_installer/examples/phase3_demo.py +178 -0
  133. py_mcp_installer/scripts/manage_version.py +54 -0
  134. py_mcp_installer/setup.py +6 -0
  135. py_mcp_installer/src/py_mcp_installer/__init__.py +153 -0
  136. py_mcp_installer/src/py_mcp_installer/command_builder.py +445 -0
  137. py_mcp_installer/src/py_mcp_installer/config_manager.py +541 -0
  138. py_mcp_installer/src/py_mcp_installer/exceptions.py +243 -0
  139. py_mcp_installer/src/py_mcp_installer/installation_strategy.py +617 -0
  140. py_mcp_installer/src/py_mcp_installer/installer.py +656 -0
  141. py_mcp_installer/src/py_mcp_installer/mcp_inspector.py +750 -0
  142. py_mcp_installer/src/py_mcp_installer/platform_detector.py +451 -0
  143. py_mcp_installer/src/py_mcp_installer/platforms/__init__.py +26 -0
  144. py_mcp_installer/src/py_mcp_installer/platforms/claude_code.py +225 -0
  145. py_mcp_installer/src/py_mcp_installer/platforms/codex.py +181 -0
  146. py_mcp_installer/src/py_mcp_installer/platforms/cursor.py +191 -0
  147. py_mcp_installer/src/py_mcp_installer/types.py +222 -0
  148. py_mcp_installer/src/py_mcp_installer/utils.py +463 -0
  149. py_mcp_installer/tests/__init__.py +0 -0
  150. py_mcp_installer/tests/platforms/__init__.py +0 -0
  151. py_mcp_installer/tests/test_platform_detector.py +17 -0
  152. mcp_ticketer/adapters/github.py +0 -1354
  153. mcp_ticketer/adapters/jira.py +0 -1011
  154. mcp_ticketer/mcp/server.py +0 -2030
  155. mcp_ticketer-0.3.0.dist-info/METADATA +0 -414
  156. mcp_ticketer-0.3.0.dist-info/RECORD +0 -59
  157. mcp_ticketer-0.3.0.dist-info/top_level.txt +0 -1
  158. {mcp_ticketer-0.3.0.dist-info → mcp_ticketer-2.2.9.dist-info}/WHEEL +0 -0
  159. {mcp_ticketer-0.3.0.dist-info → mcp_ticketer-2.2.9.dist-info}/entry_points.txt +0 -0
  160. {mcp_ticketer-0.3.0.dist-info → mcp_ticketer-2.2.9.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,463 @@
1
+ """Semantic priority matcher for natural language ticket priority inputs.
2
+
3
+ This module provides intelligent priority matching that accepts natural language inputs
4
+ and resolves them to universal Priority values with confidence scoring.
5
+
6
+ Features:
7
+ - Comprehensive synonym dictionary (20+ synonyms per priority)
8
+ - Multi-stage matching pipeline (exact → synonym → fuzzy)
9
+ - Confidence scoring with thresholds
10
+ - Support for all 4 universal priorities
11
+ - Natural language understanding
12
+
13
+ Design Decision: Multi-Stage Matching Pipeline
14
+ ----------------------------------------------
15
+ The matcher uses a cascading approach to maximize accuracy while maintaining
16
+ flexibility:
17
+
18
+ 1. Exact Match: Direct priority name match (confidence: 1.0)
19
+ 2. Synonym Match: Pre-defined synonym lookup (confidence: 0.95)
20
+ 3. Fuzzy Match: Levenshtein distance with thresholds (confidence: 0.70-0.95)
21
+
22
+ This approach ensures high confidence for common inputs while gracefully handling
23
+ typos and variations.
24
+
25
+ Performance Considerations:
26
+ - Average match time: <5ms (target: <10ms)
27
+ - Synonym lookup: O(1) with dict hashing
28
+ - Fuzzy matching: O(n) where n = number of priorities (4)
29
+ - Memory footprint: <500KB for matcher instance
30
+
31
+ Example:
32
+ >>> matcher = SemanticPriorityMatcher()
33
+ >>> result = matcher.match_priority("urgent")
34
+ >>> print(f"{result.priority.value} (confidence: {result.confidence})")
35
+ critical (confidence: 0.95)
36
+
37
+ >>> result = matcher.match_priority("asap")
38
+ >>> print(f"{result.priority.value} (confidence: {result.confidence})")
39
+ critical (confidence: 0.95)
40
+
41
+ >>> suggestions = matcher.suggest_priorities("important", top_n=3)
42
+ >>> for s in suggestions:
43
+ ... print(f"{s.priority.value}: {s.confidence}")
44
+ high: 0.95
45
+ critical: 0.65
46
+
47
+ Ticket Reference: ISS-0002 - Add semantic priority matching for natural language inputs
48
+ """
49
+
50
+ from __future__ import annotations
51
+
52
+ from dataclasses import dataclass
53
+
54
+ try:
55
+ from rapidfuzz import fuzz
56
+
57
+ FUZZY_AVAILABLE = True
58
+ except ImportError:
59
+ FUZZY_AVAILABLE = False
60
+
61
+ from .models import Priority
62
+
63
+
64
+ @dataclass
65
+ class PriorityMatchResult:
66
+ """Result of a priority matching operation.
67
+
68
+ Attributes:
69
+ priority: Matched Priority
70
+ confidence: Confidence score (0.0-1.0)
71
+ match_type: Type of match used (exact, synonym, fuzzy)
72
+ original_input: Original user input string
73
+ suggestions: Alternative matches for ambiguous inputs
74
+
75
+ """
76
+
77
+ priority: Priority
78
+ confidence: float
79
+ match_type: str
80
+ original_input: str
81
+ suggestions: list[PriorityMatchResult] | None = None
82
+
83
+ def is_high_confidence(self) -> bool:
84
+ """Check if confidence is high enough for auto-apply."""
85
+ return self.confidence >= 0.90
86
+
87
+ def is_medium_confidence(self) -> bool:
88
+ """Check if confidence is medium (needs confirmation)."""
89
+ return 0.70 <= self.confidence < 0.90
90
+
91
+ def is_low_confidence(self) -> bool:
92
+ """Check if confidence is too low (ambiguous)."""
93
+ return self.confidence < 0.70
94
+
95
+
96
+ class SemanticPriorityMatcher:
97
+ """Intelligent priority matcher with natural language support.
98
+
99
+ Provides comprehensive synonym matching, fuzzy matching, and confidence
100
+ scoring for ticket priority assignment.
101
+
102
+ The synonym dictionary includes 20+ synonyms across all 4 universal priorities,
103
+ covering common variations, typos, and platform-specific terminology.
104
+
105
+ Ticket Reference: ISS-0002
106
+ """
107
+
108
+ # Comprehensive synonym dictionary for all universal priorities
109
+ PRIORITY_SYNONYMS: dict[Priority, list[str]] = {
110
+ Priority.CRITICAL: [
111
+ "critical",
112
+ "urgent",
113
+ "asap",
114
+ "as soon as possible",
115
+ "emergency",
116
+ "blocker",
117
+ "blocking",
118
+ "show stopper",
119
+ "show-stopper",
120
+ "showstopper",
121
+ "highest",
122
+ "p0",
123
+ "p-0",
124
+ "priority 0",
125
+ "needs immediate attention",
126
+ "immediate attention",
127
+ "very urgent",
128
+ "right now",
129
+ "drop everything",
130
+ "top priority",
131
+ "mission critical",
132
+ "business critical",
133
+ "sev 0",
134
+ "sev0",
135
+ "severity 0",
136
+ "must have",
137
+ ],
138
+ Priority.HIGH: [
139
+ "high",
140
+ "important",
141
+ "soon",
142
+ "needs attention",
143
+ "p1",
144
+ "p-1",
145
+ "priority 1",
146
+ "high priority",
147
+ "should do",
148
+ "should have",
149
+ "significant",
150
+ "pressing",
151
+ "time sensitive",
152
+ "time-sensitive",
153
+ "sev 1",
154
+ "sev1",
155
+ "severity 1",
156
+ "major",
157
+ "escalated",
158
+ "higher",
159
+ "elevated",
160
+ ],
161
+ Priority.MEDIUM: [
162
+ "medium",
163
+ "normal",
164
+ "standard",
165
+ "regular",
166
+ "moderate",
167
+ "average",
168
+ "default",
169
+ "typical",
170
+ "p2",
171
+ "p-2",
172
+ "priority 2",
173
+ "medium priority",
174
+ "could have",
175
+ "sev 2",
176
+ "sev2",
177
+ "severity 2",
178
+ "routine",
179
+ "ordinary",
180
+ ],
181
+ Priority.LOW: [
182
+ "low",
183
+ "minor",
184
+ "whenever",
185
+ "low priority",
186
+ "not urgent",
187
+ "nice to have",
188
+ "nice-to-have",
189
+ "backlog",
190
+ "someday",
191
+ "if time permits",
192
+ "when possible",
193
+ "optional",
194
+ "can wait",
195
+ "lowest",
196
+ "p3",
197
+ "p-3",
198
+ "priority 3",
199
+ "trivial",
200
+ "cosmetic",
201
+ "sev 3",
202
+ "sev3",
203
+ "severity 3",
204
+ "won't have",
205
+ "wont have",
206
+ ],
207
+ }
208
+
209
+ # Confidence thresholds
210
+ CONFIDENCE_HIGH = 0.90
211
+ CONFIDENCE_MEDIUM = 0.70
212
+ FUZZY_THRESHOLD_HIGH = 90
213
+ FUZZY_THRESHOLD_MEDIUM = 70
214
+
215
+ def __init__(self) -> None:
216
+ """Initialize the semantic priority matcher.
217
+
218
+ Creates reverse lookup dictionary for O(1) synonym matching.
219
+ """
220
+ # Build reverse lookup: synonym -> (priority, is_exact)
221
+ self._synonym_to_priority: dict[str, tuple[Priority, bool]] = {}
222
+
223
+ for priority in Priority:
224
+ # Add exact priority value
225
+ self._synonym_to_priority[priority.value.lower()] = (priority, True)
226
+
227
+ # Add all synonyms
228
+ for synonym in self.PRIORITY_SYNONYMS.get(priority, []):
229
+ self._synonym_to_priority[synonym.lower()] = (priority, False)
230
+
231
+ def match_priority(
232
+ self,
233
+ user_input: str,
234
+ adapter_priorities: list[str] | None = None,
235
+ ) -> PriorityMatchResult:
236
+ """Match user input to universal priority with confidence score.
237
+
238
+ Uses multi-stage matching pipeline:
239
+ 1. Exact match against priority values
240
+ 2. Synonym lookup
241
+ 3. Fuzzy matching with Levenshtein distance
242
+
243
+ Args:
244
+ user_input: Natural language priority input from user
245
+ adapter_priorities: Optional list of adapter-specific priority names
246
+
247
+ Returns:
248
+ PriorityMatchResult with matched priority and confidence score
249
+
250
+ Example:
251
+ >>> matcher = SemanticPriorityMatcher()
252
+ >>> result = matcher.match_priority("urgent")
253
+ >>> print(f"{result.priority.value}: {result.confidence}")
254
+ critical: 0.95
255
+
256
+ >>> result = matcher.match_priority("criticl") # typo
257
+ >>> print(f"{result.priority.value}: {result.confidence}")
258
+ critical: 0.85
259
+
260
+ Ticket Reference: ISS-0002
261
+ """
262
+ if not user_input:
263
+ # Default to MEDIUM for empty input
264
+ return PriorityMatchResult(
265
+ priority=Priority.MEDIUM,
266
+ confidence=0.5,
267
+ match_type="default",
268
+ original_input=user_input,
269
+ )
270
+
271
+ # Normalize input
272
+ normalized = user_input.strip().lower()
273
+
274
+ # Handle whitespace-only input (after normalization)
275
+ if not normalized:
276
+ return PriorityMatchResult(
277
+ priority=Priority.MEDIUM,
278
+ confidence=0.5,
279
+ match_type="default",
280
+ original_input=user_input,
281
+ )
282
+
283
+ # Stage 1: Exact match
284
+ exact_result = self._exact_match(normalized)
285
+ if exact_result:
286
+ return exact_result
287
+
288
+ # Stage 2: Synonym match
289
+ synonym_result = self._synonym_match(normalized)
290
+ if synonym_result:
291
+ return synonym_result
292
+
293
+ # Stage 3: Fuzzy match
294
+ fuzzy_result = self._fuzzy_match(normalized)
295
+ if fuzzy_result:
296
+ return fuzzy_result
297
+
298
+ # No good match found - return suggestions
299
+ suggestions = self.suggest_priorities(user_input, top_n=3)
300
+ return PriorityMatchResult(
301
+ priority=suggestions[0].priority if suggestions else Priority.MEDIUM,
302
+ confidence=suggestions[0].confidence if suggestions else 0.5,
303
+ match_type="fallback",
304
+ original_input=user_input,
305
+ suggestions=suggestions,
306
+ )
307
+
308
+ def suggest_priorities(
309
+ self,
310
+ user_input: str,
311
+ top_n: int = 3,
312
+ ) -> list[PriorityMatchResult]:
313
+ """Return top N priority suggestions for ambiguous inputs.
314
+
315
+ Uses fuzzy matching to rank all possible priorities by similarity.
316
+ Useful for providing user with multiple options when confidence is low.
317
+
318
+ Args:
319
+ user_input: Natural language priority input
320
+ top_n: Number of suggestions to return (default: 3)
321
+
322
+ Returns:
323
+ List of PriorityMatchResult sorted by confidence (highest first)
324
+
325
+ Example:
326
+ >>> matcher = SemanticPriorityMatcher()
327
+ >>> suggestions = matcher.suggest_priorities("importnt", top_n=3)
328
+ >>> for s in suggestions:
329
+ ... print(f"{s.priority.value}: {s.confidence:.2f}")
330
+ high: 0.85
331
+ medium: 0.45
332
+ critical: 0.42
333
+
334
+ Ticket Reference: ISS-0002
335
+ """
336
+ if not FUZZY_AVAILABLE:
337
+ # Without fuzzy matching, return all priorities with low confidence
338
+ return [
339
+ PriorityMatchResult(
340
+ priority=priority,
341
+ confidence=0.5,
342
+ match_type="suggestion",
343
+ original_input=user_input,
344
+ )
345
+ for priority in Priority
346
+ ][:top_n]
347
+
348
+ normalized = user_input.strip().lower()
349
+ suggestions: list[tuple[Priority, float, str]] = []
350
+
351
+ # Calculate similarity for each priority and its synonyms
352
+ for priority in Priority:
353
+ # Check priority value
354
+ priority_similarity = fuzz.ratio(normalized, priority.value.lower())
355
+ max_similarity = priority_similarity
356
+ match_text = priority.value
357
+
358
+ # Check synonyms
359
+ for synonym in self.PRIORITY_SYNONYMS.get(priority, []):
360
+ similarity = fuzz.ratio(normalized, synonym.lower())
361
+ if similarity > max_similarity:
362
+ max_similarity = similarity
363
+ match_text = synonym
364
+
365
+ # Convert similarity to confidence (0-100 → 0.0-1.0)
366
+ confidence = max_similarity / 100.0
367
+ suggestions.append((priority, confidence, match_text))
368
+
369
+ # Sort by confidence descending
370
+ suggestions.sort(key=lambda x: x[1], reverse=True)
371
+
372
+ # Convert to PriorityMatchResult
373
+ return [
374
+ PriorityMatchResult(
375
+ priority=priority,
376
+ confidence=conf,
377
+ match_type="suggestion",
378
+ original_input=user_input,
379
+ )
380
+ for priority, conf, _ in suggestions[:top_n]
381
+ ]
382
+
383
+ def _exact_match(self, normalized_input: str) -> PriorityMatchResult | None:
384
+ """Match exact priority value."""
385
+ for priority in Priority:
386
+ if normalized_input == priority.value.lower():
387
+ return PriorityMatchResult(
388
+ priority=priority,
389
+ confidence=1.0,
390
+ match_type="exact",
391
+ original_input=normalized_input,
392
+ )
393
+ return None
394
+
395
+ def _synonym_match(self, normalized_input: str) -> PriorityMatchResult | None:
396
+ """Match using synonym dictionary."""
397
+ if normalized_input in self._synonym_to_priority:
398
+ priority, is_exact = self._synonym_to_priority[normalized_input]
399
+ return PriorityMatchResult(
400
+ priority=priority,
401
+ confidence=1.0 if is_exact else 0.95,
402
+ match_type="exact" if is_exact else "synonym",
403
+ original_input=normalized_input,
404
+ )
405
+ return None
406
+
407
+ def _fuzzy_match(self, normalized_input: str) -> PriorityMatchResult | None:
408
+ """Match using fuzzy string matching."""
409
+ if not FUZZY_AVAILABLE:
410
+ return None
411
+
412
+ best_match: tuple[Priority, float, str] | None = None
413
+
414
+ for priority in Priority:
415
+ # Check priority value
416
+ priority_similarity = fuzz.ratio(normalized_input, priority.value.lower())
417
+
418
+ if priority_similarity >= self.FUZZY_THRESHOLD_MEDIUM:
419
+ if best_match is None or priority_similarity > best_match[1]:
420
+ best_match = (priority, priority_similarity, "priority_value")
421
+
422
+ # Check synonyms
423
+ for synonym in self.PRIORITY_SYNONYMS.get(priority, []):
424
+ similarity = fuzz.ratio(normalized_input, synonym.lower())
425
+ if similarity >= self.FUZZY_THRESHOLD_MEDIUM:
426
+ if best_match is None or similarity > best_match[1]:
427
+ best_match = (priority, similarity, "synonym")
428
+
429
+ if best_match:
430
+ priority, similarity, match_source = best_match
431
+
432
+ # Calculate confidence based on similarity
433
+ if similarity >= self.FUZZY_THRESHOLD_HIGH:
434
+ confidence = 0.85 + (similarity - self.FUZZY_THRESHOLD_HIGH) / 100.0
435
+ else:
436
+ confidence = 0.70 + (similarity - self.FUZZY_THRESHOLD_MEDIUM) / 200.0
437
+
438
+ return PriorityMatchResult(
439
+ priority=priority,
440
+ confidence=min(confidence, 0.95), # Cap at 0.95
441
+ match_type="fuzzy",
442
+ original_input=normalized_input,
443
+ )
444
+
445
+ return None
446
+
447
+
448
+ # Singleton instance for convenience
449
+ _default_matcher: SemanticPriorityMatcher | None = None
450
+
451
+
452
+ def get_priority_matcher() -> SemanticPriorityMatcher:
453
+ """Get the default priority matcher instance.
454
+
455
+ Returns:
456
+ Singleton SemanticPriorityMatcher instance
457
+
458
+ Ticket Reference: ISS-0002
459
+ """
460
+ global _default_matcher
461
+ if _default_matcher is None:
462
+ _default_matcher = SemanticPriorityMatcher()
463
+ return _default_matcher