srilakshu012456 commited on
Commit
afd54bd
·
verified ·
1 Parent(s): 4cbfdcf

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +27 -55
main.py CHANGED
@@ -1,4 +1,4 @@
1
-
2
  # main.py
3
  import os
4
  import json
@@ -104,10 +104,6 @@ GEMINI_URL = (
104
 
105
  # ------------------ Numbering & friendly fallbacks ------------------
106
  def _normalize_lines(text: str) -> List[str]:
107
- """
108
- Safe splitter used everywhere to avoid undefined local variable bugs.
109
- Always returns a list (possibly empty), never raises.
110
- """
111
  raw = (text or "")
112
  try:
113
  return [ln.strip() for ln in raw.splitlines() if ln.strip()]
@@ -116,14 +112,9 @@ def _normalize_lines(text: str) -> List[str]:
116
 
117
 
118
  def _ensure_numbering(text: str) -> str:
119
- """
120
- Force plain-text numbering: 1) 2) 3) so it renders in any bubble.
121
- Handles bullets (-, *, •), and "Step N" formats. Splits single-paragraph procedures.
122
- """
123
  lines = _normalize_lines(text)
124
  if not lines:
125
  return text or ""
126
-
127
  normalized: List[str] = []
128
  for ln in lines:
129
  ln2 = re.sub(
@@ -133,14 +124,11 @@ def _ensure_numbering(text: str) -> str:
133
  flags=re.IGNORECASE,
134
  ).strip()
135
  normalized.append(ln2)
136
-
137
  if len(normalized) == 1:
138
  para = normalized[0]
139
- # split on ';' or '. <Upper/Digit>' boundaries
140
  parts = [p.strip() for p in re.split(r"(?:;|\.\s+(?=[A-Z0-9]))", para) if p.strip()]
141
  if len(parts) > 1:
142
  normalized = parts
143
-
144
  return "\n".join([f"{i+1}) {ln}" for i, ln in enumerate(normalized)])
145
 
146
 
@@ -253,29 +241,21 @@ PERM_QUERY_TERMS = [
253
  "permission", "permissions", "access", "access right", "authorization", "authorisation",
254
  "role", "role access", "security", "security profile", "privilege", "not allowed", "not authorized", "denied",
255
  ]
256
-
257
- # Permission-synonyms used to pick only permission-specific lines
258
  PERM_SYNONYMS = (
259
  "permission", "permissions", "access", "authorization", "authorisation",
260
  "role", "role mapping", "security profile", "not allowed", "not authorized", "denied", "insufficient"
261
  )
262
 
263
-
264
  def _normalize_for_match(text: str) -> str:
265
  t = (text or "").lower()
266
  t = re.sub(r"[^\w\s]", " ", t)
267
  t = re.sub(r"\s+", " ", t).strip()
268
  return t
269
 
270
-
271
  def _split_sentences(ctx: str) -> List[str]:
272
- raw_sents = re.split(
273
- r"(?<=([.!?]))\s+|\n+|\-\s*|\*\s*",
274
- ctx or ""
275
- )
276
  return [s.strip() for s in raw_sents if s and len(s.strip()) > 2]
277
 
278
-
279
  def _filter_context_for_query(context: str, query: str) -> Tuple[str, Dict[str, Any]]:
280
  ctx = (context or "").strip()
281
  if not ctx or not query:
@@ -284,7 +264,6 @@ def _filter_context_for_query(context: str, query: str) -> Tuple[str, Dict[str,
284
  q_terms = [t for t in q_norm.split() if len(t) > 2]
285
  if not q_terms:
286
  return ctx, {'mode': 'concise', 'matched_count': 0, 'all_sentences': 0}
287
-
288
  sentences = _split_sentences(ctx)
289
  matched_exact, matched_any = [], []
290
  for s in sentences:
@@ -295,7 +274,6 @@ def _filter_context_for_query(context: str, query: str) -> Tuple[str, Dict[str,
295
  matched_exact.append(s)
296
  elif overlap > 0:
297
  matched_any.append(s)
298
-
299
  if matched_exact:
300
  kept = matched_exact[:MAX_SENTENCES_STRICT]
301
  return "\n".join(kept).strip(), {'mode': 'exact', 'matched_count': len(kept), 'all_sentences': len(sentences)}
@@ -305,10 +283,9 @@ def _filter_context_for_query(context: str, query: str) -> Tuple[str, Dict[str,
305
  kept = sentences[:MAX_SENTENCES_CONCISE]
306
  return "\n".join(kept).strip(), {'mode': 'concise', 'matched_count': 0, 'all_sentences': len(sentences)}
307
 
308
- # ------------------ Navigation & Errors extraction ------------------
309
  NAV_LINE_REGEX = re.compile(r"(navigate\s+to|login|log in|menu|screen)", re.IGNORECASE)
310
 
311
-
312
  def _extract_navigation_only(text: str, max_lines: int = 6) -> str:
313
  lines = _normalize_lines(text)
314
  kept: List[str] = []
@@ -319,14 +296,12 @@ def _extract_navigation_only(text: str, max_lines: int = 6) -> str:
319
  break
320
  return "\n".join(kept).strip() if kept else (text or "").strip()
321
 
322
-
323
  ERROR_STARTS = (
324
  "error", "resolution", "fix", "verify", "check",
325
  "permission", "access", "authorization", "authorisation",
326
  "role", "role mapping", "security profile", "escalation", "not allowed", "not authorized", "denied"
327
  )
328
 
329
-
330
  def _extract_errors_only(text: str, max_lines: int = 12) -> str:
331
  lines = _normalize_lines(text)
332
  kept: List[str] = []
@@ -338,11 +313,7 @@ def _extract_errors_only(text: str, max_lines: int = 12) -> str:
338
  break
339
  return "\n".join(kept).strip() if kept else (text or "").strip()
340
 
341
-
342
  def _filter_permission_lines(text: str, max_lines: int = 6) -> str:
343
- """
344
- Keep only permission/access-related lines so the bot answers the exact thing user asked.
345
- """
346
  lines = _normalize_lines(text)
347
  kept: List[str] = []
348
  for ln in lines:
@@ -364,10 +335,10 @@ async def chat_with_ai(input_data: ChatInput):
364
  try:
365
  msg_norm = (input_data.user_message or "").lower().strip()
366
 
367
- # --- Yes/No handlers ---
368
  if msg_norm in ("yes", "y", "sure", "ok", "okay"):
369
  return {
370
- "bot_response": ("Great! Tell me what you'd like to do next — check another ticket, create an incident, or describe your issue."),
371
  "status": "OK",
372
  "followup": "You can say: 'create ticket', 'incident status INC0012345', or describe your problem.",
373
  "options": [],
@@ -383,7 +354,7 @@ async def chat_with_ai(input_data: ChatInput):
383
  "debug": {"intent": "end_conversation"},
384
  }
385
 
386
- # --- Resolution ack (auto incident + mark Resolved) ---
387
  is_llm_resolved = _classify_resolution_llm(input_data.user_message)
388
  if _has_negation_resolved(msg_norm):
389
  is_llm_resolved = False
@@ -438,7 +409,7 @@ async def chat_with_ai(input_data: ChatInput):
438
  "debug": {"intent": "resolved_ack", "exception": True},
439
  }
440
 
441
- # --- Incident intent ---
442
  if _is_incident_intent(msg_norm):
443
  return {
444
  "bot_response": (
@@ -457,7 +428,7 @@ async def chat_with_ai(input_data: ChatInput):
457
  "debug": {"intent": "create_ticket"},
458
  }
459
 
460
- # --- Status intent ---
461
  status_intent = _parse_ticket_status_intent(msg_norm)
462
  if status_intent:
463
  if status_intent.get("ask_number"):
@@ -511,7 +482,7 @@ async def chat_with_ai(input_data: ChatInput):
511
  except Exception as e:
512
  raise HTTPException(status_code=500, detail=safe_str(e))
513
 
514
- # --- Generic opener or very short queries ---
515
  if len(msg_norm.split()) <= 2 or any(p in msg_norm for p in ("issue", "problem", "help", "support")):
516
  return {
517
  "bot_response": _build_clarifying_message(),
@@ -526,7 +497,7 @@ async def chat_with_ai(input_data: ChatInput):
526
  "debug": {"intent": "generic_issue"},
527
  }
528
 
529
- # --- Hybrid KB search ---
530
  kb_results = hybrid_search_knowledge_base(input_data.user_message, top_k=10, alpha=0.6, beta=0.4)
531
  documents = kb_results.get("documents", [])
532
  metadatas = kb_results.get("metadatas", [])
@@ -558,7 +529,7 @@ async def chat_with_ai(input_data: ChatInput):
558
  best_doc = kb_results.get("best_doc")
559
  top_meta = (metadatas or [{}])[0] if metadatas else {}
560
 
561
- # Force errors intent for permissions (and demote steps entirely)
562
  is_perm_query = any(t in msg_norm for t in PERM_QUERY_TERMS)
563
  if is_perm_query:
564
  detected_intent = "errors"
@@ -575,14 +546,13 @@ async def chat_with_ai(input_data: ChatInput):
575
  elif detected_intent == "errors":
576
  full_errors = get_best_errors_section_text(best_doc)
577
  if full_errors:
578
- # Extract only error lines, then narrow to permission-only if the query is permission-related
579
  ctx_err = _extract_errors_only(full_errors, max_lines=12)
580
  context = _filter_permission_lines(ctx_err, max_lines=6) if is_perm_query else ctx_err
581
  lines = _normalize_lines(context)
582
  if len(lines) == 1:
583
  context = _friendly_permission_reply(lines[0])
584
 
585
- # If we STILL have no context for an errors/permission query, return clarifying + ticket option
586
  if detected_intent == "errors" and not (context or "").strip():
587
  return {
588
  "bot_response": _build_clarifying_message(),
@@ -597,11 +567,11 @@ async def chat_with_ai(input_data: ChatInput):
597
  "debug": {"intent": "errors_no_context"},
598
  }
599
 
600
- # Build LLM prompt (mirror language if possible)
601
  language_hint = _detect_language_hint(input_data.user_message)
602
  lang_line = f"Respond in {language_hint}." if language_hint else "Respond in a clear, polite tone."
603
 
604
- # Safer: skip Gemini for steps to avoid altering procedure; use Gemini only for errors/permissions.
605
  use_gemini = (detected_intent == "errors")
606
  enhanced_prompt = f"""You are a helpful support assistant. Rewrite the provided context ONLY into clear, user-friendly guidance.
607
  - Do not add any information that is not present in the context.
@@ -613,7 +583,6 @@ async def chat_with_ai(input_data: ChatInput):
613
  {input_data.user_message}
614
  ### Output
615
  Return ONLY the rewritten guidance."""
616
-
617
  headers = {"Content-Type": "application/json"}
618
  payload = {"contents": [{"parts": [{"text": enhanced_prompt}]}]}
619
 
@@ -641,7 +610,6 @@ Return ONLY the rewritten guidance."""
641
  if not bot_text.strip() or http_code == 429:
642
  lines = _normalize_lines(context)
643
  bot_text = _friendly_permission_reply(lines[0] if lines else context)
644
- # Append standard escalation line for errors/permission messages
645
  bot_text = (bot_text or "").rstrip() + "\n\nIf you want to escalate the issue, reach out to Operator – Inventory Analyst."
646
  else:
647
  bot_text = context
@@ -659,7 +627,7 @@ Return ONLY the rewritten guidance."""
659
  "bot_response": bot_text,
660
  "status": status,
661
  "context_found": True,
662
- "ask_resolved": (status == "OK" and detected_intent != "errors"), # don't ask resolved for error explanations
663
  "suggest_incident": (status == "PARTIAL"),
664
  "followup": ("Is this helpful, or should I raise a ticket?" if status == "PARTIAL" else None),
665
  "options": options,
@@ -711,7 +679,8 @@ def _set_incident_resolved(sys_id: str) -> bool:
711
  except Exception as e:
712
  print(f"[SN PATCH progress] exception={safe_str(e)}")
713
 
714
- def clean(d: dict) -> dict: return {k: v for k, v in d.items() if v is not None}
 
715
 
716
  payload_A = clean({
717
  "state": "6",
@@ -724,7 +693,8 @@ def _set_incident_resolved(sys_id: str) -> bool:
724
  "assignment_group": assign_group,
725
  })
726
  respA = requests.patch(url, headers=headers, json=payload_A, verify=VERIFY_SSL, timeout=25)
727
- if respA.status_code in (200, 204): return True
 
728
  print(f"[SN PATCH resolve A] status={respA.status_code} body={respA.text[:500]}")
729
 
730
  payload_B = clean({
@@ -738,7 +708,8 @@ def _set_incident_resolved(sys_id: str) -> bool:
738
  "assignment_group": assign_group,
739
  })
740
  respB = requests.patch(url, headers=headers, json=payload_B, verify=VERIFY_SSL, timeout=25)
741
- if respB.status_code in (200, 204): return True
 
742
  print(f"[SN PATCH resolve B] status={respB.status_code} body={respB.text[:500]}")
743
 
744
  code_field = os.getenv("SERVICENOW_RESOLUTION_CODE_FIELD", "close_code")
@@ -750,18 +721,19 @@ def _set_incident_resolved(sys_id: str) -> bool:
750
  "caller_id": caller_sysid,
751
  "resolved_at": datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S"),
752
  "work_notes": "Auto-resolve set by NOVA.",
753
- "resolved_by": resolved_by_sysid,
754
  "assignment_group": assign_group,
755
  })
756
  respC = requests.patch(url, headers=headers, json=payload_C, verify=VERIFY_SSL, timeout=25)
757
- if respC.status_code in (200, 204): return True
 
758
  print(f"[SN PATCH resolve C] status={respC.status_code} body={respC.text[:500]}")
759
  return False
760
  except Exception as e:
761
  print(f"[SN PATCH resolve] exception={safe_str(e)}")
762
  return False
763
 
764
- # ------------------ Incident creation ------------------
765
  @app.post("/incident")
766
  async def raise_incident(input_data: IncidentInput):
767
  try:
@@ -786,7 +758,7 @@ async def raise_incident(input_data: IncidentInput):
786
  except Exception as e:
787
  raise HTTPException(status_code=500, detail=safe_str(e))
788
 
789
- # ------------------ Ticket description generation ------------------
790
  @app.post("/generate_ticket_desc")
791
  async def generate_ticket_desc_ep(input_data: TicketDescInput):
792
  try:
@@ -824,7 +796,7 @@ async def generate_ticket_desc_ep(input_data: TicketDescInput):
824
  except Exception as e:
825
  raise HTTPException(status_code=500, detail=safe_str(e))
826
 
827
- # ------------------ Incident status ------------------
828
  @app.post("/incident_status")
829
  async def incident_status(input_data: TicketStatusInput):
830
  try:
 
1
+ #ver1
2
  # main.py
3
  import os
4
  import json
 
104
 
105
  # ------------------ Numbering & friendly fallbacks ------------------
106
  def _normalize_lines(text: str) -> List[str]:
 
 
 
 
107
  raw = (text or "")
108
  try:
109
  return [ln.strip() for ln in raw.splitlines() if ln.strip()]
 
112
 
113
 
114
  def _ensure_numbering(text: str) -> str:
 
 
 
 
115
  lines = _normalize_lines(text)
116
  if not lines:
117
  return text or ""
 
118
  normalized: List[str] = []
119
  for ln in lines:
120
  ln2 = re.sub(
 
124
  flags=re.IGNORECASE,
125
  ).strip()
126
  normalized.append(ln2)
 
127
  if len(normalized) == 1:
128
  para = normalized[0]
 
129
  parts = [p.strip() for p in re.split(r"(?:;|\.\s+(?=[A-Z0-9]))", para) if p.strip()]
130
  if len(parts) > 1:
131
  normalized = parts
 
132
  return "\n".join([f"{i+1}) {ln}" for i, ln in enumerate(normalized)])
133
 
134
 
 
241
  "permission", "permissions", "access", "access right", "authorization", "authorisation",
242
  "role", "role access", "security", "security profile", "privilege", "not allowed", "not authorized", "denied",
243
  ]
 
 
244
  PERM_SYNONYMS = (
245
  "permission", "permissions", "access", "authorization", "authorisation",
246
  "role", "role mapping", "security profile", "not allowed", "not authorized", "denied", "insufficient"
247
  )
248
 
 
249
  def _normalize_for_match(text: str) -> str:
250
  t = (text or "").lower()
251
  t = re.sub(r"[^\w\s]", " ", t)
252
  t = re.sub(r"\s+", " ", t).strip()
253
  return t
254
 
 
255
  def _split_sentences(ctx: str) -> List[str]:
256
+ raw_sents = re.split(r"(?<=([.!?]))\s+|\n+|\-\s*|\*\s*", ctx or "")
 
 
 
257
  return [s.strip() for s in raw_sents if s and len(s.strip()) > 2]
258
 
 
259
  def _filter_context_for_query(context: str, query: str) -> Tuple[str, Dict[str, Any]]:
260
  ctx = (context or "").strip()
261
  if not ctx or not query:
 
264
  q_terms = [t for t in q_norm.split() if len(t) > 2]
265
  if not q_terms:
266
  return ctx, {'mode': 'concise', 'matched_count': 0, 'all_sentences': 0}
 
267
  sentences = _split_sentences(ctx)
268
  matched_exact, matched_any = [], []
269
  for s in sentences:
 
274
  matched_exact.append(s)
275
  elif overlap > 0:
276
  matched_any.append(s)
 
277
  if matched_exact:
278
  kept = matched_exact[:MAX_SENTENCES_STRICT]
279
  return "\n".join(kept).strip(), {'mode': 'exact', 'matched_count': len(kept), 'all_sentences': len(sentences)}
 
283
  kept = sentences[:MAX_SENTENCES_CONCISE]
284
  return "\n".join(kept).strip(), {'mode': 'concise', 'matched_count': 0, 'all_sentences': len(sentences)}
285
 
286
+ # Navigation & errors extraction
287
  NAV_LINE_REGEX = re.compile(r"(navigate\s+to|login|log in|menu|screen)", re.IGNORECASE)
288
 
 
289
  def _extract_navigation_only(text: str, max_lines: int = 6) -> str:
290
  lines = _normalize_lines(text)
291
  kept: List[str] = []
 
296
  break
297
  return "\n".join(kept).strip() if kept else (text or "").strip()
298
 
 
299
  ERROR_STARTS = (
300
  "error", "resolution", "fix", "verify", "check",
301
  "permission", "access", "authorization", "authorisation",
302
  "role", "role mapping", "security profile", "escalation", "not allowed", "not authorized", "denied"
303
  )
304
 
 
305
  def _extract_errors_only(text: str, max_lines: int = 12) -> str:
306
  lines = _normalize_lines(text)
307
  kept: List[str] = []
 
313
  break
314
  return "\n".join(kept).strip() if kept else (text or "").strip()
315
 
 
316
  def _filter_permission_lines(text: str, max_lines: int = 6) -> str:
 
 
 
317
  lines = _normalize_lines(text)
318
  kept: List[str] = []
319
  for ln in lines:
 
335
  try:
336
  msg_norm = (input_data.user_message or "").lower().strip()
337
 
338
+ # Yes/No short handlers
339
  if msg_norm in ("yes", "y", "sure", "ok", "okay"):
340
  return {
341
+ "bot_response": "Great! Tell me what you'd like to do next — check another ticket, create an incident, or describe your issue.",
342
  "status": "OK",
343
  "followup": "You can say: 'create ticket', 'incident status INC0012345', or describe your problem.",
344
  "options": [],
 
354
  "debug": {"intent": "end_conversation"},
355
  }
356
 
357
+ # Resolution ack (auto incident + mark Resolved)
358
  is_llm_resolved = _classify_resolution_llm(input_data.user_message)
359
  if _has_negation_resolved(msg_norm):
360
  is_llm_resolved = False
 
409
  "debug": {"intent": "resolved_ack", "exception": True},
410
  }
411
 
412
+ # Incident intent
413
  if _is_incident_intent(msg_norm):
414
  return {
415
  "bot_response": (
 
428
  "debug": {"intent": "create_ticket"},
429
  }
430
 
431
+ # Status intent
432
  status_intent = _parse_ticket_status_intent(msg_norm)
433
  if status_intent:
434
  if status_intent.get("ask_number"):
 
482
  except Exception as e:
483
  raise HTTPException(status_code=500, detail=safe_str(e))
484
 
485
+ # Generic opener / short
486
  if len(msg_norm.split()) <= 2 or any(p in msg_norm for p in ("issue", "problem", "help", "support")):
487
  return {
488
  "bot_response": _build_clarifying_message(),
 
497
  "debug": {"intent": "generic_issue"},
498
  }
499
 
500
+ # Hybrid KB search
501
  kb_results = hybrid_search_knowledge_base(input_data.user_message, top_k=10, alpha=0.6, beta=0.4)
502
  documents = kb_results.get("documents", [])
503
  metadatas = kb_results.get("metadatas", [])
 
529
  best_doc = kb_results.get("best_doc")
530
  top_meta = (metadatas or [{}])[0] if metadatas else {}
531
 
532
+ # Force errors intent for permissions
533
  is_perm_query = any(t in msg_norm for t in PERM_QUERY_TERMS)
534
  if is_perm_query:
535
  detected_intent = "errors"
 
546
  elif detected_intent == "errors":
547
  full_errors = get_best_errors_section_text(best_doc)
548
  if full_errors:
 
549
  ctx_err = _extract_errors_only(full_errors, max_lines=12)
550
  context = _filter_permission_lines(ctx_err, max_lines=6) if is_perm_query else ctx_err
551
  lines = _normalize_lines(context)
552
  if len(lines) == 1:
553
  context = _friendly_permission_reply(lines[0])
554
 
555
+ # No context for errors clarifying + ticket option
556
  if detected_intent == "errors" and not (context or "").strip():
557
  return {
558
  "bot_response": _build_clarifying_message(),
 
567
  "debug": {"intent": "errors_no_context"},
568
  }
569
 
570
+ # Build LLM prompt (mirror language)
571
  language_hint = _detect_language_hint(input_data.user_message)
572
  lang_line = f"Respond in {language_hint}." if language_hint else "Respond in a clear, polite tone."
573
 
574
+ # Steps no LLM; Errors LLM paraphrase
575
  use_gemini = (detected_intent == "errors")
576
  enhanced_prompt = f"""You are a helpful support assistant. Rewrite the provided context ONLY into clear, user-friendly guidance.
577
  - Do not add any information that is not present in the context.
 
583
  {input_data.user_message}
584
  ### Output
585
  Return ONLY the rewritten guidance."""
 
586
  headers = {"Content-Type": "application/json"}
587
  payload = {"contents": [{"parts": [{"text": enhanced_prompt}]}]}
588
 
 
610
  if not bot_text.strip() or http_code == 429:
611
  lines = _normalize_lines(context)
612
  bot_text = _friendly_permission_reply(lines[0] if lines else context)
 
613
  bot_text = (bot_text or "").rstrip() + "\n\nIf you want to escalate the issue, reach out to Operator – Inventory Analyst."
614
  else:
615
  bot_text = context
 
627
  "bot_response": bot_text,
628
  "status": status,
629
  "context_found": True,
630
+ "ask_resolved": (status == "OK" and detected_intent != "errors"),
631
  "suggest_incident": (status == "PARTIAL"),
632
  "followup": ("Is this helpful, or should I raise a ticket?" if status == "PARTIAL" else None),
633
  "options": options,
 
679
  except Exception as e:
680
  print(f"[SN PATCH progress] exception={safe_str(e)}")
681
 
682
+ def clean(d: dict) -> dict:
683
+ return {k: v for k, v in d.items() if v is not None}
684
 
685
  payload_A = clean({
686
  "state": "6",
 
693
  "assignment_group": assign_group,
694
  })
695
  respA = requests.patch(url, headers=headers, json=payload_A, verify=VERIFY_SSL, timeout=25)
696
+ if respA.status_code in (200, 204):
697
+ return True
698
  print(f"[SN PATCH resolve A] status={respA.status_code} body={respA.text[:500]}")
699
 
700
  payload_B = clean({
 
708
  "assignment_group": assign_group,
709
  })
710
  respB = requests.patch(url, headers=headers, json=payload_B, verify=VERIFY_SSL, timeout=25)
711
+ if respB.status_code in (200, 204):
712
+ return True
713
  print(f"[SN PATCH resolve B] status={respB.status_code} body={respB.text[:500]}")
714
 
715
  code_field = os.getenv("SERVICENOW_RESOLUTION_CODE_FIELD", "close_code")
 
721
  "caller_id": caller_sysid,
722
  "resolved_at": datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S"),
723
  "work_notes": "Auto-resolve set by NOVA.",
724
+ "resolved_by": resolved_by_sysid",
725
  "assignment_group": assign_group,
726
  })
727
  respC = requests.patch(url, headers=headers, json=payload_C, verify=VERIFY_SSL, timeout=25)
728
+ if respC.status_code in (200, 204):
729
+ return True
730
  print(f"[SN PATCH resolve C] status={respC.status_code} body={respC.text[:500]}")
731
  return False
732
  except Exception as e:
733
  print(f"[SN PATCH resolve] exception={safe_str(e)}")
734
  return False
735
 
736
+ # Incident creation
737
  @app.post("/incident")
738
  async def raise_incident(input_data: IncidentInput):
739
  try:
 
758
  except Exception as e:
759
  raise HTTPException(status_code=500, detail=safe_str(e))
760
 
761
+ # Ticket description generation
762
  @app.post("/generate_ticket_desc")
763
  async def generate_ticket_desc_ep(input_data: TicketDescInput):
764
  try:
 
796
  except Exception as e:
797
  raise HTTPException(status_code=500, detail=safe_str(e))
798
 
799
+ # Incident status
800
  @app.post("/incident_status")
801
  async def incident_status(input_data: TicketStatusInput):
802
  try: