67 lines
2.9 KiB
Python
67 lines
2.9 KiB
Python
"""Dona: heure/date du système, sans appel LLM. Fuseau Europe/Paris par défaut."""
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
import os
|
|
import re
|
|
import unicodedata
|
|
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
|
|
|
DAYS = ('lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi', 'dimanche')
|
|
MONTHS = ('janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet',
|
|
'août', 'septembre', 'octobre', 'novembre', 'décembre')
|
|
|
|
|
|
def _normalized(text: str) -> str:
|
|
text = unicodedata.normalize('NFKD', str(text or '').lower())
|
|
text = ''.join(c for c in text if not unicodedata.combining(c))
|
|
text = re.sub(r'[^a-z0-9]+', ' ', text).strip()
|
|
return re.sub(r'^(?:dona|alfred)\s+', '', text)
|
|
|
|
|
|
def intent(text: str) -> str | None:
|
|
"""Renvoie 'time', 'date', 'both' ou None; pas de faux positif trajet/durée."""
|
|
s = _normalized(text)
|
|
if not s or len(s) > 140:
|
|
return None
|
|
# Ne pas répondre avec l'heure de Paris à une question sur une autre ville.
|
|
if re.search(r'\b(?:heure|est il|est elle)\s+(?:a|en|au|aux|dans)\s+[a-z]', s):
|
|
return None
|
|
# Ne pas renvoyer la date actuelle à une question concernant une date future/passée.
|
|
if re.search(r'\bdate\s+(?:du|de|pour|a)\s+(?:\d|demain|hier)', s):
|
|
return None
|
|
t = (
|
|
bool(re.search(r'\b(?:quelle?|quel)\s+heure\s+(?:est\s+il|il\s+est|est\s+elle)\b', s))
|
|
or bool(re.search(r'\bil\s+est\s+(?:quelle?|quel)\s+heure\b', s))
|
|
or bool(re.search(r'\b(?:donne|dis|dites|affiche|montre)\s+(?:moi\s+)?(?:l\s+)?heure\b', s))
|
|
or bool(re.search(r'\b(?:heure\s+actuelle|heure\s+maintenant|il\s+est\s+quelle\s+heure)\b', s))
|
|
)
|
|
d = (
|
|
bool(re.search(r'\b(?:quelle?|quel)\s+(?:est\s+)?(?:la\s+)?date\s+(?:d\s+aujourd\s+hui|aujourd\s+hui|du\s+jour)?\b', s))
|
|
or bool(re.search(r'\b(?:on\s+est|nous\s+sommes|sommes\s+nous)\s+(?:quel|quelle|le\s+combien)\s+(?:jour|date)?\b', s))
|
|
or bool(re.search(r'\b(?:quel\s+jour\s+(?:est\s+on|sommes\s+nous|on\s+est)|quelle\s+date\s+sommes\s+nous)\b', s))
|
|
or bool(re.search(r'\b(?:donne|dis|affiche|montre)\s+(?:moi\s+)?(?:la\s+)?date\s+(?:du\s+jour)?\b', s))
|
|
)
|
|
if t and d:
|
|
return 'both'
|
|
if t:
|
|
return 'time'
|
|
if d:
|
|
return 'date'
|
|
return None
|
|
|
|
|
|
def answer(text: str, *, now: datetime | None = None) -> str | None:
|
|
which = intent(text)
|
|
if which is None:
|
|
return None
|
|
if now is None:
|
|
try:
|
|
tz = ZoneInfo(os.getenv('DONA_TIMEZONE') or os.getenv('ALFRED_TIMEZONE') or 'Europe/Paris')
|
|
except (ZoneInfoNotFoundError, ValueError, KeyError):
|
|
tz = ZoneInfo('Europe/Paris')
|
|
now = datetime.now(tz)
|
|
hour = f'Il est {now.hour} heures {now.minute:02d}.'
|
|
day = f"Aujourd'hui, nous sommes le {DAYS[now.weekday()]} {now.day} {MONTHS[now.month-1]} {now.year}."
|
|
return (hour + ' ' + day) if which == 'both' else hour if which == 'time' else day
|