83 lines
2.2 KiB
Python
83 lines
2.2 KiB
Python
|
|
"""Intent routing — pure regex, no AI, sub-millisecond.
|
||
|
|
|
||
|
|
Loco-Soft Komm-Nr Format:
|
||
|
|
Type-Buchstabe + 4-6 stellige Zahl, optional Space dazwischen.
|
||
|
|
Type-Letter: N=Neu, T=Tageszul., V=Vorfuehr, D=Differenzbest., G=Gebraucht, L=Leihgabe.
|
||
|
|
Beispiele: 'D9094', 'd9094', 'D 9094', 'n8093', 'L9083'
|
||
|
|
|
||
|
|
Pure 4-6 stellige Zahlen werden als DVN-Lookup behandelt (DVN ist eindeutig).
|
||
|
|
"""
|
||
|
|
import re
|
||
|
|
from dataclasses import dataclass
|
||
|
|
from typing import Literal
|
||
|
|
|
||
|
|
|
||
|
|
# Komm-Nr: Type + DVN (mit oder ohne Space)
|
||
|
|
KOMM_NR_RE = re.compile(r"^[NTVDGLntvdgl]\s*\d{4,6}$")
|
||
|
|
# DVN allein: pure Zahl 4-6 Stellen
|
||
|
|
DVN_RE = re.compile(r"^\d{4,6}$")
|
||
|
|
# VIN: 17 chars, no I/O/Q
|
||
|
|
VIN_RE = re.compile(r"^[A-HJ-NPR-Z0-9]{17}$")
|
||
|
|
|
||
|
|
|
||
|
|
@dataclass
|
||
|
|
class Intent:
|
||
|
|
type: Literal[
|
||
|
|
"komm_nr",
|
||
|
|
"dvn",
|
||
|
|
"vin",
|
||
|
|
"autocomplete_only",
|
||
|
|
"keyword_search",
|
||
|
|
"empty",
|
||
|
|
]
|
||
|
|
direct_redirect: bool = False
|
||
|
|
normalized_query: str = ""
|
||
|
|
|
||
|
|
|
||
|
|
def route(raw_query: str) -> Intent:
|
||
|
|
q = (raw_query or "").strip()
|
||
|
|
|
||
|
|
if not q:
|
||
|
|
return Intent(type="empty", normalized_query="")
|
||
|
|
|
||
|
|
# Rule 1: Komm-Nr (Type + DVN, e.g. 'D9094', 'D 9094', 'd9094')
|
||
|
|
if KOMM_NR_RE.match(q):
|
||
|
|
# Normalize: remove spaces, uppercase the type letter
|
||
|
|
cleaned = q.replace(" ", "")
|
||
|
|
normalized = cleaned[0].upper() + cleaned[1:]
|
||
|
|
return Intent(
|
||
|
|
type="komm_nr",
|
||
|
|
direct_redirect=True,
|
||
|
|
normalized_query=normalized,
|
||
|
|
)
|
||
|
|
|
||
|
|
# Rule 2: Pure 4-6 digit number = DVN lookup (eindeutig)
|
||
|
|
if DVN_RE.match(q):
|
||
|
|
return Intent(
|
||
|
|
type="dvn",
|
||
|
|
direct_redirect=True,
|
||
|
|
normalized_query=q,
|
||
|
|
)
|
||
|
|
|
||
|
|
# Rule 3: 17-char alphanumeric (no IOQ) = VIN
|
||
|
|
upper = q.upper().replace(" ", "")
|
||
|
|
if VIN_RE.match(upper):
|
||
|
|
return Intent(
|
||
|
|
type="vin",
|
||
|
|
direct_redirect=True,
|
||
|
|
normalized_query=upper,
|
||
|
|
)
|
||
|
|
|
||
|
|
# Rule 4: Single char only -> autocomplete (FTS unbrauchbar bei 1 Zeichen)
|
||
|
|
if len(q) < 2:
|
||
|
|
return Intent(
|
||
|
|
type="autocomplete_only",
|
||
|
|
normalized_query=q.lower(),
|
||
|
|
)
|
||
|
|
|
||
|
|
# Rule 5: Default — keyword + fuzzy search
|
||
|
|
return Intent(
|
||
|
|
type="keyword_search",
|
||
|
|
normalized_query=q.lower(),
|
||
|
|
)
|