# Спецификация 01: VoiceCommandParser — грамматика Ф-1.2 Источники: Ф-1.2 (грамматика), Ф-1.3 (отклонение), О-2-Б («с половиной» → +0,5 кг), tech-req 5 (постобработка чисел). Единица 1. Чистая функция JVM — без Android-зависимостей. ## 1. Интерфейс ```kotlin sealed interface VoiceCommand { data class Approach(val weightKg: Double, val reps: Int) : VoiceCommand data class Weight(val weightKg: Double) : VoiceCommand data class Rest(val seconds: Int) : VoiceCommand data class Rpe(val rpe: Int) : VoiceCommand data class Rejected(val reason: String, val attempted: JsonObject?) : VoiceCommand } object VoiceCommandParser { fun parse(alternatives: List): VoiceCommand? // null — ни одна гипотеза не содержит распознаваемой команды fun semantics(cmd: VoiceCommand): JsonObject // для журнала Ф-1.5 } ``` `parse` получает список финальных гипотез Vosk (спец 02, до 3; режим одного варианта — список из одного) и возвращает первую успешно разобранную команду; если все гипотезы мусор — `Rejected` из первой гипотезы. Отклонённая команда — тоже результат (журнал Ф-1.3/1.5), «не было команды вообще» (тишина/чужая речь) — `null`, в журнал не пишется. ## 2. Предобработка текста - нижний регистр, ё→е, удаление знаков препинания, сжатие пробелов; - нормализация аббревиатуры RPE: «эр пэ э», «эрпэ» → «рпэ» (свободное распознавание слышит буквы по отдельности); - токены — слова и цифровые группы. ## 3. Числа (`NumberRu`, 0–999) - **словами**: [сотни] [десятки] [единицы] → сумма. Словари: `ноль`; `один/одна/одно`, `два/две`, `три…девятнадцать`; десятки `двадцать…девяносто`; сотни `сто, двести, триста, четыреста, пятьсот…девятьсот`. Составное число — подряд стоящие разряды («сто двадцать три» → 123); постобработка «сто двадцать» → 120 (tech-req 5); - **цифрами**: `120`, дробное `120,5` / `120.5` (= только 5 после разделителя — шаг 0,5); - **«с половиной»** после числа → +0,5 (О-2-Б); допустимо ТОЛЬКО в позиции веса `W` (команды Approach/Weight); «с половиной» в позиции `R`/`S`/`N` — грамматическая ошибка (отклонение); - порядок разрядов нарушен («двадцать сто») — грамматическая ошибка. ## 4. Грамматика команд (Ф-1.2) | Команда | Шаблон | Диапазоны | |---|---|---| | Approach | `подход` `W` [`с половиной`] `на` `R` | W 0–500 (шаг 0,5), R 1–100 | | Weight | `вес` `W` [`с половиной`] | W 0–500 (шаг 0,5) | | Rest | `отдых` `S` [`секунд` / `секунды`] | S 5–600 | | Rpe | `рпэ` `N` | N 1–10 | - ключевые слова обязательны и однозначны; `на`, `секунд` — по шаблону (секунды склоняются: «девяносто секунд» / «тридцать секунд» — Vosk может выдать и «секунды» — принимать `секунд|секунды`); - лишние слова до/после шаблона — грамматическая ошибка (узкая грамматика, Ф-1.3); - число разобрано, но вне диапазона → `Rejected(reason= "out_of_range", attempted={…значения…})`; - шаблон не совпал → `Rejected(reason="out_of_grammar", attempted=null)`. ## 5. Семантика для журнала (Ф-1.5, контракт m09 spec 04) ```json {"cmd":"set","weight_kg":120.0,"reps":8} {"cmd":"weight","weight_kg":62.5} {"cmd":"rest","sec":90} {"cmd":"rpe","rpe":7} {"cmd":"rejected","reason":"out_of_range","weight_kg":600.0,"reps":8} ``` - `semantics` — объект (как в PayloadFactory.commandLog m09); `rejected` сохраняет распознанные значения — разбор ошибок при анализе журнала; - исходный текст гипотезы пишется в `recognized_text` исполнителем (спец 03). ## 6. Тесты (unit, pure JVM) 1. Четыре команды — числа словами: «подход сто двадцать на восемь» → Approach(120,8); «вес шестьдесят два с половиной» → Weight(62.5); «отдых девяносто секунд» → Rest(90); «рпэ семь» / «эр пэ э семь» → Rpe(7). 2. Цифрами: «подход 120 на 8», «вес 62,5», «отдых 90», «120.5». 3. Границы: W=0 и W=500 приняты; W=500,5 / R=0 / R=101 / S=4 / S=601 / N=0 / N=11 → out_of_range (attempted заполнен). 4. Отклонения: «погнали» → out_of_grammar; «подход сто двадцать» (нет «на») → out_of_grammar; «отдых тридцать с половиной» → out_of_grammar («с половиной» не у веса); «подход двадцать сто на пять» → out_of_grammar. 5. NumberRu: крайние числа — ноль, сто, девятьсот девяносто девять, «двести двадцать два»; регистр и пунктуация («Подход 120, на 8!»); формы «два»/«две». 6. Alternatives: первая гипотеза мусор, вторая валидна → команда из второй; все мусор → Rejected из первой.