109 lines
6.3 KiB
Markdown
109 lines
6.3 KiB
Markdown
# Спецификация 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<String>): 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 из первой.
|