Add QuickCreate heuristic fallback when AI unavailable

This commit is contained in:
Christian 2026-03-18 10:29:45 +01:00
parent 153eb728e2
commit 243e4375e0
2 changed files with 88 additions and 3 deletions

18
RELEASE_NOTES_v2.2.64.md Normal file
View 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`

View File

@ -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"""
@ -470,6 +470,73 @@ Returner JSON med suggested_title, suggested_description, priority, customer_hin
confidence=0.0, confidence=0.0,
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"""