Update assistant features and docs
This commit is contained in:
@@ -12,6 +12,7 @@ Handles complex number-to-text conversion for Russian language.
|
||||
import re
|
||||
import pymorphy3
|
||||
from num2words import num2words
|
||||
from .roman import roman_to_int
|
||||
|
||||
# Инициализация морфологического анализатора (для определения падежей)
|
||||
morph = pymorphy3.MorphAnalyzer()
|
||||
@@ -334,6 +335,50 @@ def numbers_to_words(text: str) -> str:
|
||||
return text
|
||||
|
||||
|
||||
def roman_numerals_to_words(text: str) -> str:
|
||||
"""
|
||||
Преобразует римские цифры в порядковые числительные с учетом
|
||||
морфологии предыдущего слова.
|
||||
Пример: "Ивана III" -> "Ивана третьего".
|
||||
"""
|
||||
if not text:
|
||||
return ""
|
||||
|
||||
def replace_roman_match(match):
|
||||
prev_word = match.group(1)
|
||||
roman = match.group(2)
|
||||
|
||||
number = roman_to_int(roman)
|
||||
if number is None:
|
||||
return match.group(0)
|
||||
|
||||
case = "nominative"
|
||||
gender = "m"
|
||||
|
||||
try:
|
||||
parsed = morph.parse(prev_word)[0]
|
||||
case_tag = parsed.tag.case
|
||||
gender_tag = parsed.tag.gender
|
||||
|
||||
if case_tag:
|
||||
case = PYMORPHY_TO_NUM2WORDS.get(case_tag, "nominative")
|
||||
if gender_tag:
|
||||
gender = PYMORPHY_TO_GENDER.get(gender_tag, "m")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
ordinal = convert_number(
|
||||
str(number), context_type="ordinal", case=case, gender=gender
|
||||
)
|
||||
return f"{prev_word} {ordinal}"
|
||||
|
||||
return re.sub(
|
||||
r"(?i)\b([А-Яа-яЁё]+)\s+([IVXLCDM]+)\b",
|
||||
replace_roman_match,
|
||||
text,
|
||||
)
|
||||
|
||||
|
||||
def clean_response(text: str, language: str = "ru") -> str:
|
||||
"""
|
||||
Основная функция очистки.
|
||||
@@ -408,9 +453,11 @@ def clean_response(text: str, language: str = "ru") -> str:
|
||||
flags=re.IGNORECASE | re.MULTILINE,
|
||||
)
|
||||
|
||||
# Convert numbers to words only for Russian, and only if digits exist
|
||||
if language == "ru" and re.search(r"\d", text):
|
||||
text = numbers_to_words(text)
|
||||
# Convert Roman numerals and Arabic digits to words for Russian.
|
||||
if language == "ru":
|
||||
text = roman_numerals_to_words(text)
|
||||
if re.search(r"\d", text):
|
||||
text = numbers_to_words(text)
|
||||
|
||||
# Remove extra whitespace
|
||||
text = re.sub(r"\n{3,}", "\n\n", text)
|
||||
|
||||
@@ -33,6 +33,8 @@ DEEPGRAM_API_KEY = os.getenv("DEEPGRAM_API_KEY")
|
||||
PORCUPINE_ACCESS_KEY = os.getenv("PORCUPINE_ACCESS_KEY")
|
||||
# Путь к файлу модели ключевого слова (.ppn), который лежит в папке assets/models
|
||||
PORCUPINE_KEYWORD_PATH = BASE_DIR / "assets" / "models" / "Alexandr_en_linux_v4_0_0.ppn"
|
||||
# Чувствительность wake word (0..1). Выше = ловит легче, но больше ложных срабатываний.
|
||||
PORCUPINE_SENSITIVITY = float(os.getenv("PORCUPINE_SENSITIVITY", "0.8"))
|
||||
|
||||
# --- Параметры аудио ---
|
||||
# Частота дискретизации для микрофона (стандарт для распознавания речи)
|
||||
|
||||
43
app/core/roman.py
Normal file
43
app/core/roman.py
Normal file
@@ -0,0 +1,43 @@
|
||||
"""Roman numeral parsing helpers."""
|
||||
|
||||
import re
|
||||
|
||||
_ROMAN_VALID_RE = re.compile(
|
||||
r"^M{0,3}(CM|CD|D?C{0,3})"
|
||||
r"(XC|XL|L?X{0,3})"
|
||||
r"(IX|IV|V?I{0,3})$"
|
||||
)
|
||||
_ROMAN_TOKEN_RE = re.compile(r"(?<![A-Za-zА-Яа-яЁё0-9])[IVXLCDMivxlcdm]+(?![A-Za-zА-Яа-яЁё0-9])")
|
||||
_ROMAN_VALUES = {"I": 1, "V": 5, "X": 10, "L": 50, "C": 100, "D": 500, "M": 1000}
|
||||
|
||||
|
||||
def roman_to_int(token: str) -> int | None:
|
||||
if not token:
|
||||
return None
|
||||
|
||||
roman = token.strip().upper()
|
||||
if not roman or not _ROMAN_VALID_RE.fullmatch(roman):
|
||||
return None
|
||||
|
||||
total = 0
|
||||
prev = 0
|
||||
for char in reversed(roman):
|
||||
value = _ROMAN_VALUES[char]
|
||||
if value < prev:
|
||||
total -= value
|
||||
else:
|
||||
total += value
|
||||
prev = value
|
||||
return total
|
||||
|
||||
|
||||
def replace_roman_numerals(text: str) -> str:
|
||||
if not text:
|
||||
return text
|
||||
|
||||
def _repl(match: re.Match) -> str:
|
||||
token = match.group(0)
|
||||
value = roman_to_int(token)
|
||||
return str(value) if value is not None else token
|
||||
|
||||
return _ROMAN_TOKEN_RE.sub(_repl, text)
|
||||
Reference in New Issue
Block a user