Spaces:
Sleeping
Sleeping
Update main.py
Browse files
main.py
CHANGED
|
@@ -103,53 +103,60 @@ def _normalize_lines(text: str) -> List[str]:
|
|
| 103 |
return [raw.strip()] if raw.strip() else []
|
| 104 |
|
| 105 |
def _ensure_numbering(text: str) -> str:
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
)
|
| 119 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
|
| 121 |
-
lines = [ln.strip() for ln in (text or "").splitlines() if ln and ln.strip()]
|
| 122 |
-
if not lines:
|
| 123 |
-
return text or ""
|
| 124 |
-
merged: List[str] = []
|
| 125 |
-
i = 0
|
| 126 |
-
while i < len(lines):
|
| 127 |
-
ln = lines[i]
|
| 128 |
-
if re.fullmatch(r"\d+[.)]|[\u2460-\u2473]", ln) and (i + 1) < len(lines):
|
| 129 |
-
merged.append(lines[i + 1].strip())
|
| 130 |
-
i += 2
|
| 131 |
-
else:
|
| 132 |
-
merged.append(ln)
|
| 133 |
-
i += 1
|
| 134 |
-
para = " ".join(merged).strip()
|
| 135 |
-
if not para:
|
| 136 |
-
return ""
|
| 137 |
-
para_clean = re.sub(r"(?:((?<=^)|(?<=\s))\d+[.)])\s+", "\n\n\n", para)
|
| 138 |
-
para_clean = re.sub(r"(?:((?<=^)|(?<=\s))[\u2460-\u2473])\s+", "\n\n\n", para_clean)
|
| 139 |
-
para_clean = re.sub(r"(?i)\bstep\s*\d+\s*:\s*", "\n\n\n", para_clean)
|
| 140 |
-
segments = [seg.strip() for seg in para_clean.split("\n\n\n") if seg.strip()]
|
| 141 |
-
if len(segments) < 2:
|
| 142 |
-
tmp = [ln.strip() for ln in para.splitlines() if ln.strip()]
|
| 143 |
-
segments = tmp if len(tmp) > 1 else [seg.strip() for seg in re.split(r"(?<=[.!?])\s+|;\s+", para) if seg.strip()]
|
| 144 |
-
segments = [strip_prefix_any(seg) for seg in segments if seg.strip()]
|
| 145 |
circled = {
|
| 146 |
-
1:"\u2460",2:"\u2461",3:"\u2462",4:"\u2463",5:"\u2464",
|
| 147 |
-
|
|
|
|
|
|
|
| 148 |
}
|
| 149 |
-
out
|
| 150 |
-
for
|
| 151 |
-
marker = circled.get(
|
| 152 |
-
out.append(f"{marker} {
|
| 153 |
return "\n".join(out)
|
| 154 |
|
| 155 |
def _filter_error_lines_by_query(text: str, query: str, max_lines: int = 4) -> str:
|
|
@@ -597,19 +604,40 @@ async def chat_with_ai(input_data: ChatInput):
|
|
| 597 |
# Prefer full SOP section when we have the best_doc
|
| 598 |
escalation_line = None # SOP escalation candidate (if found)
|
| 599 |
if best_doc:
|
| 600 |
-
|
| 601 |
-
|
| 602 |
-
|
| 603 |
-
|
| 604 |
-
|
| 605 |
-
|
| 606 |
-
|
| 607 |
-
|
| 608 |
-
|
| 609 |
-
|
| 610 |
-
|
| 611 |
-
|
| 612 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 613 |
full_errors = get_best_errors_section_text(best_doc)
|
| 614 |
if full_errors:
|
| 615 |
# Extract the error lines from the SOP
|
|
|
|
| 103 |
return [raw.strip()] if raw.strip() else []
|
| 104 |
|
| 105 |
def _ensure_numbering(text: str) -> str:
|
| 106 |
+
"""
|
| 107 |
+
Robust step numbering:
|
| 108 |
+
- Split by natural delimiters (newline, end-of-sentence, semicolons, arrows, 'then', simple bullets).
|
| 109 |
+
- Strip any existing list markers (numbers, 'Step N:', bullets).
|
| 110 |
+
- Return ①..⑳ then 'N)' beyond 20.
|
| 111 |
+
"""
|
| 112 |
+
import re
|
| 113 |
+
t = (text or "").replace("\u2060", "").replace("\u200B", "")
|
| 114 |
+
|
| 115 |
+
# 1) Split to segments by common step delimiters
|
| 116 |
+
parts = re.split(
|
| 117 |
+
r"(?:\r?\n|\r|\u2028|\u2029)" # newlines
|
| 118 |
+
r"|(?<=[.!?])\s+" # sentence end + space
|
| 119 |
+
r"|;\s+" # semicolons
|
| 120 |
+
r"|(?:\s*→\s*|\s*->\s*)" # arrows
|
| 121 |
+
r"|(?:\s*\bthen\b\s*)" # 'then'
|
| 122 |
+
r"|(?:^\s*[-*•]\s*)", # simple bullets
|
| 123 |
+
t,
|
| 124 |
+
flags=re.IGNORECASE
|
| 125 |
+
)
|
| 126 |
+
|
| 127 |
+
# 2) Clean segments & drop empties
|
| 128 |
+
cleaned = []
|
| 129 |
+
for seg in parts:
|
| 130 |
+
s = (seg or "").strip()
|
| 131 |
+
if not s:
|
| 132 |
+
continue
|
| 133 |
+
# Remove ANY leading list marker (1) / 2. / Step 3: / -, *, • / circled numerals
|
| 134 |
+
s = re.sub(
|
| 135 |
+
r"^\s*(?:"
|
| 136 |
+
r"(?:\d+\s*[.)])|" # 1) or 2.
|
| 137 |
+
r"(?i:step\s*\d+:?)|" # Step 3:
|
| 138 |
+
r"[-*•]|" # bullets
|
| 139 |
+
r"[\u2460-\u2473]" # circled numerals
|
| 140 |
+
r")\s*",
|
| 141 |
+
"",
|
| 142 |
+
s
|
| 143 |
)
|
| 144 |
+
if s:
|
| 145 |
+
cleaned.append(s)
|
| 146 |
+
|
| 147 |
+
if not cleaned:
|
| 148 |
+
return (text or "").strip()
|
| 149 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 150 |
circled = {
|
| 151 |
+
1:"\u2460",2:"\u2461",3:"\u2462",4:"\u2463",5:"\u2464",
|
| 152 |
+
6:"\u2465",7:"\u2466",8:"\u2467",9:"\u2468",10:"\u2469",
|
| 153 |
+
11:"\u246a",12:"\u246b",13:"\u246c",14:"\u246d",15:"\u246e",
|
| 154 |
+
16:"\u246f",17:"\u2470",18:"\u2471",19:"\u2472",20:"\u2473",
|
| 155 |
}
|
| 156 |
+
out = []
|
| 157 |
+
for i, s in enumerate(cleaned, 1):
|
| 158 |
+
marker = circled.get(i, f"{i})")
|
| 159 |
+
out.append(f"{marker} {s}")
|
| 160 |
return "\n".join(out)
|
| 161 |
|
| 162 |
def _filter_error_lines_by_query(text: str, query: str, max_lines: int = 4) -> str:
|
|
|
|
| 604 |
# Prefer full SOP section when we have the best_doc
|
| 605 |
escalation_line = None # SOP escalation candidate (if found)
|
| 606 |
if best_doc:
|
| 607 |
+
if detected_intent == "steps":
|
| 608 |
+
# 1) Prefer action-matched steps first (update/reschedule/edit vs create)
|
| 609 |
+
preferred_actions = kb_results.get("actions", []) or []
|
| 610 |
+
full_steps = get_steps_text_by_action(best_doc, preferred_actions)
|
| 611 |
+
|
| 612 |
+
# 2) If nothing found, fallback BUT FILTER creation-only when user asked update
|
| 613 |
+
if not full_steps:
|
| 614 |
+
fallback = get_best_steps_section_text(best_doc)
|
| 615 |
+
if preferred_actions and ("update" in [a.lower() for a in preferred_actions]):
|
| 616 |
+
# Drop lines that look like pure creation verbs when user asked update
|
| 617 |
+
fallback_lines = [ln.strip() for ln in (fallback or "").splitlines() if ln.strip()]
|
| 618 |
+
keep = []
|
| 619 |
+
for ln in fallback_lines:
|
| 620 |
+
low = ln.lower()
|
| 621 |
+
is_creation_only = (
|
| 622 |
+
("create" in low or "creation" in low or " add " in low or " new " in low or " book " in low or " schedule " in low)
|
| 623 |
+
and not any(x in low for x in ("update", "modify", "change", "edit", "amend", "reschedule", "re-schedule"))
|
| 624 |
+
)
|
| 625 |
+
if is_creation_only:
|
| 626 |
+
continue # skip creation-only line
|
| 627 |
+
keep.append(ln)
|
| 628 |
+
fallback = "\n".join(keep).strip()
|
| 629 |
+
full_steps = fallback if (fallback and fallback.strip()) else full_steps
|
| 630 |
+
|
| 631 |
+
if not full_steps:
|
| 632 |
+
sec = (top_meta or {}).get("section")
|
| 633 |
+
if sec:
|
| 634 |
+
full_steps = get_section_text(best_doc, sec)
|
| 635 |
+
|
| 636 |
+
if full_steps:
|
| 637 |
+
context = _ensure_numbering(full_steps)
|
| 638 |
+
|
| 639 |
+
elif detected_intent == "errors":
|
| 640 |
+
|
| 641 |
full_errors = get_best_errors_section_text(best_doc)
|
| 642 |
if full_errors:
|
| 643 |
# Extract the error lines from the SOP
|