bd1 / core /parser.py
hoangrimo's picture
Upload folder using huggingface_hub
0331854 verified
Raw
History Blame Contribute Delete
4 kB
# core/parser.py — Itinerary text parsing: detect cities, transport modes, legs
import re
from typing import List
from models.city import DB, _norm
CONNECT = re.compile(r"\s*(?:–|—|-|→|->|>|to)\s*", re.I)
PLANE_ANY_RE = re.compile(r"\b(?:vol|flight|plane|fly)\b(?:\s*(?:pour|to|vers|à|a))?\s*([A-Za-zÀ-ỹ' \-/]{2,})", re.I)
PLANE_TAIL_RE = re.compile(r"(?:^|[&;,]|-)\\s*(?:vol|flight|plane|fly)\b[^\w]*(?:to|pour|vers|à|a)\s+([A-Za-zÀ-ỹ' \-]+)", re.I)
ARRIVE_RE = re.compile(r"\b(arriv(?:ee|ée|e)?|arrival)\b", re.I)
DEPART_RE = re.compile(r"\b(d[ée]part|departure|return flight|vol de retour|vol depart|vol départ)\b", re.I)
TRAIN_KEYS = {"train", "rail", "tàu", "tau"}
BOAT_KEYS = {"boat", "cruise", "croisiere", "croisière", "ferry", "sampan", "barque", "kayak"}
def find_cities_in_text(text: str) -> List[str]:
return DB.match_in_text(text)
def _clean_dest_token(s: str) -> str:
s = s.strip()
s = re.split(r"[.,;/()|]|(?:\s+-\s+)|(?:—)|(?:–)", s)[0].strip()
return s
def parse_itinerary(text: str):
legs = []
last_for_flight = None
arrive_hint = None
depart_hint = None
for raw in (text or "").splitlines():
line = raw.strip()
if not line:
continue
if re.match(r"^\d{1,2}\s+[A-Za-z]{3}\s+\d{2,4}$", line):
continue
if ARRIVE_RE.search(line):
cs = find_cities_in_text(line)
if cs:
arrive_hint = cs[0]
if DEPART_RE.search(line):
cs = find_cities_in_text(line)
if cs:
depart_hint = cs[-1]
for m in PLANE_ANY_RE.finditer(line):
dest_raw = _clean_dest_token(m.group(1))
dests = find_cities_in_text(dest_raw)
dest = dests[0] if dests else None
srcs = find_cities_in_text(line[:m.start()])
src = srcs[-1] if srcs else last_for_flight
if src and dest and src != dest:
legs.append({"a": src, "b": dest, "mode": "plane"})
last_for_flight = dest
def _remove_plane(seg: str) -> str:
out = seg
for m in PLANE_ANY_RE.finditer(seg):
s, e = m.span()
out = out[:s] + " " * (e - s) + out[e:]
return out
base_no_plane = _remove_plane(line)
base_can = _norm(base_no_plane)
mode = "car"
if any(k in base_can for k in TRAIN_KEYS):
mode = "train"
elif any(k in base_can for k in BOAT_KEYS):
mode = "boat"
tokens = [t for t in CONNECT.split(base_no_plane) if t.strip()]
cities = []
for tk in tokens:
found = find_cities_in_text(tk)
if found:
cities.extend(found)
if len(cities) >= 2:
for i in range(len(cities) - 1):
a, b = cities[i], cities[i + 1]
if a == b:
continue
if any(e["a"] == a and e["b"] == b and e["mode"] == "plane" for e in legs):
continue
legs.append({"a": a, "b": b, "mode": mode})
last_for_flight = cities[-1]
elif len(cities) == 1:
last_for_flight = cities[-1]
for m in PLANE_TAIL_RE.finditer(line):
dest_raw = m.group(1).strip()
dests = find_cities_in_text(dest_raw)
dest = dests[0] if dests else None
if dest and last_for_flight and last_for_flight != dest:
if not any(e["a"] == last_for_flight and e["b"] == dest and e["mode"] == "plane" for e in legs):
legs.append({"a": last_for_flight, "b": dest, "mode": "plane"})
last_for_flight = dest
legs = [e for e in legs if e["a"] != e["b"]]
markers = {"arrive_city": arrive_hint, "depart_city": depart_hint}
return legs, markers