Add QuickCreate heuristic fallback when AI unavailable
This commit is contained in:
parent
153eb728e2
commit
243e4375e0
18
RELEASE_NOTES_v2.2.64.md
Normal file
18
RELEASE_NOTES_v2.2.64.md
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Release Notes v2.2.64
|
||||||
|
|
||||||
|
Dato: 18. marts 2026
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
|
||||||
|
- Forbedret QuickCreate robusthed når AI/LLM er utilgængelig.
|
||||||
|
- Tilføjet lokal heuristisk fallback i `CaseAnalysisService`, så brugeren stadig får:
|
||||||
|
- foreslået titel
|
||||||
|
- foreslået prioritet
|
||||||
|
- simple tags
|
||||||
|
- kunde-match forsøg
|
||||||
|
- Fjernet afhængighed af at Ollama altid svarer, så QuickCreate ikke længere ender i tom AI-unavailable flow ved midlertidige AI-fejl.
|
||||||
|
|
||||||
|
## Berørte filer
|
||||||
|
|
||||||
|
- `app/services/case_analysis_service.py`
|
||||||
|
- `RELEASE_NOTES_v2.2.64.md`
|
||||||
@ -67,12 +67,12 @@ class CaseAnalysisService:
|
|||||||
|
|
||||||
return analysis
|
return analysis
|
||||||
else:
|
else:
|
||||||
logger.warning("⚠️ Ollama returned no result, using empty analysis")
|
logger.warning("⚠️ Ollama returned no result, using heuristic fallback analysis")
|
||||||
return self._empty_analysis(text)
|
return await self._heuristic_fallback_analysis(text)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"❌ Case analysis failed: {e}", exc_info=True)
|
logger.error(f"❌ Case analysis failed: {e}", exc_info=True)
|
||||||
return self._empty_analysis(text)
|
return await self._heuristic_fallback_analysis(text)
|
||||||
|
|
||||||
def _build_analysis_prompt(self) -> str:
|
def _build_analysis_prompt(self) -> str:
|
||||||
"""Build Danish system prompt for case analysis"""
|
"""Build Danish system prompt for case analysis"""
|
||||||
@ -471,6 +471,73 @@ Returner JSON med suggested_title, suggested_description, priority, customer_hin
|
|||||||
ai_reasoning="AI unavailable - fill fields manually"
|
ai_reasoning="AI unavailable - fill fields manually"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def _heuristic_fallback_analysis(self, text: str) -> QuickCreateAnalysis:
|
||||||
|
"""Local fallback when AI service is unavailable."""
|
||||||
|
cleaned_text = (text or "").strip()
|
||||||
|
if not cleaned_text:
|
||||||
|
return self._empty_analysis(text)
|
||||||
|
|
||||||
|
lowered = cleaned_text.lower()
|
||||||
|
|
||||||
|
# Priority heuristics based on urgency wording.
|
||||||
|
urgent_terms = ["nede", "kritisk", "asap", "omgående", "straks", "akut", "haster"]
|
||||||
|
high_terms = ["hurtigt", "vigtigt", "snarest", "prioriter"]
|
||||||
|
low_terms = ["når i får tid", "ikke hastende", "lavprioriteret"]
|
||||||
|
|
||||||
|
if any(term in lowered for term in urgent_terms):
|
||||||
|
priority = SagPriority.URGENT
|
||||||
|
elif any(term in lowered for term in high_terms):
|
||||||
|
priority = SagPriority.HIGH
|
||||||
|
elif any(term in lowered for term in low_terms):
|
||||||
|
priority = SagPriority.LOW
|
||||||
|
else:
|
||||||
|
priority = SagPriority.NORMAL
|
||||||
|
|
||||||
|
# Basic title heuristic: first non-empty line/sentence, clipped to 80 chars.
|
||||||
|
first_line = cleaned_text.splitlines()[0].strip()
|
||||||
|
first_sentence = re.split(r"[.!?]", first_line)[0].strip()
|
||||||
|
title_source = first_sentence or first_line or cleaned_text
|
||||||
|
title = title_source[:80].strip()
|
||||||
|
if not title:
|
||||||
|
title = "Ny sag"
|
||||||
|
|
||||||
|
# Lightweight keyword tags.
|
||||||
|
keyword_tags = {
|
||||||
|
"printer": "printer",
|
||||||
|
"mail": "mail",
|
||||||
|
"email": "mail",
|
||||||
|
"vpn": "vpn",
|
||||||
|
"net": "netværk",
|
||||||
|
"wifi": "wifi",
|
||||||
|
"server": "server",
|
||||||
|
"laptop": "laptop",
|
||||||
|
"adgang": "adgang",
|
||||||
|
"onboarding": "onboarding",
|
||||||
|
}
|
||||||
|
suggested_tags: List[str] = []
|
||||||
|
for key, tag in keyword_tags.items():
|
||||||
|
if key in lowered and tag not in suggested_tags:
|
||||||
|
suggested_tags.append(tag)
|
||||||
|
|
||||||
|
# Try simple customer matching from long words in text.
|
||||||
|
candidate_hints = []
|
||||||
|
for token in re.findall(r"[A-Za-z0-9ÆØÅæøå._-]{3,}", cleaned_text):
|
||||||
|
if token.lower() in {"ring", "kunde", "sag", "skal", "have", "virker", "ikke"}:
|
||||||
|
continue
|
||||||
|
candidate_hints.append(token)
|
||||||
|
customer_id, customer_name = await self._match_customer(candidate_hints[:8])
|
||||||
|
|
||||||
|
return QuickCreateAnalysis(
|
||||||
|
suggested_title=title,
|
||||||
|
suggested_description=cleaned_text,
|
||||||
|
suggested_priority=priority,
|
||||||
|
suggested_customer_id=customer_id,
|
||||||
|
suggested_customer_name=customer_name,
|
||||||
|
suggested_tags=suggested_tags,
|
||||||
|
confidence=0.35,
|
||||||
|
ai_reasoning="AI service unavailable - using local fallback suggestions"
|
||||||
|
)
|
||||||
|
|
||||||
def _get_cached_analysis(self, text: str) -> Optional[QuickCreateAnalysis]:
|
def _get_cached_analysis(self, text: str) -> Optional[QuickCreateAnalysis]:
|
||||||
"""Get cached analysis if available and not expired"""
|
"""Get cached analysis if available and not expired"""
|
||||||
text_hash = hashlib.md5(text.encode()).hexdigest()
|
text_hash = hashlib.md5(text.encode()).hexdigest()
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user