# Manual voice-dictated district updates

Use this when the user dictates district assignments by date and surnames/pairs, e.g. “20 травня: Зозуля Шраменко — третя, Гавриленко Пилипенко — друга”.

## Interpretation rules

- In `districts.html`, `І / ІІ / ІІІ` are **districts** (`дільниці`), not work shifts.
- Normalize spoken “перша / друга / третя” or “перший / другий / третій” to:
  - first → `І` / `area-1`
  - second → `ІІ` / `area-2`
  - third → `ІІІ` / `area-3`
- Voice transcription is often distorted. Match against known controller names and common pairs:
  - Зозуля / Шраменко
  - Гавриленко / Пилипенко
  - Битко / Ніколіч
  - Добшина / Мельник
  - Андрющенко / Турчанович
  - Яремака / Ярошенко
  - Пісоцька / Романенко
  - Хорошковська / Шендрик
  - Гордійчук / Карапузова
  - Касьяненко / Мотайленко / Юрченко
  - Затокова / Тарарєєва / Перетятько
  - Бобух / Дибля

## Safe update pattern

1. Work in `/opt/data/vercel-project/districts.html` (deploy source).
2. Replace only the requested day column. The day column index is 1-based among `<td class="area...">` cells after the controller name (`day 20` = `cells[19]`).
3. Preserve existing status values unless the user explicitly dictates that person/day as a district. Manual dictation can intentionally override a prior `1/2` placeholder or existing district.
4. Verify several representative rows by printing that day’s cell before deploy.
5. Copy to mirror path: `cp /opt/data/vercel-project/districts.html /tmp/schedule_site/vercel-project/districts.html`.
6. Deploy and give a concise confirmation.

## Reusable Python snippet

```python
from pathlib import Path
import re

DAY = 20  # 1-based day of month
updates = {
    'Зозуля О.М.': 'ІІІ', 'Шраменко В.А.': 'ІІІ',
    # ... fill from dictation ...
}
cls = {'І': 'area-1', 'ІІ': 'area-2', 'ІІІ': 'area-3'}
p = Path('/opt/data/vercel-project/districts.html')
s = p.read_text(encoding='utf-8')
missing, changed = [], []

for name, val in updates.items():
    pat = re.compile(r'(<tr><td class="pairno">[^<]*</td><td class="name">' + re.escape(name) + r'</td>)(.*?)(</tr>)')
    m = pat.search(s)
    if not m:
        missing.append(name); continue
    row = m.group(2)
    cells = list(re.finditer(r'<td class="area(?: [^"]*)?">.*?</td>', row))
    if len(cells) < DAY:
        missing.append(name); continue
    out, last = [], 0
    for idx, c in enumerate(cells, start=1):
        out.append(row[last:c.start()])
        out.append(f'<td class="area {cls[val]}">{val}</td>' if idx == DAY else c.group(0))
        last = c.end()
    out.append(row[last:])
    s = s[:m.start(2)] + ''.join(out) + s[m.end(2):]
    changed.append((name, val))

p.write_text(s, encoding='utf-8')
print('changed', len(changed))
if missing:
    raise SystemExit(f'MISSING: {missing}')
```

## Manual daily schedule corrections

Use this when the user dictates a specific daily graph after/alongside district-table updates.

### Requirements

- Update `/opt/data/vercel-project/daily/DD.MM.26.html` with the exact groups the user dictated.
- A group can be a pair or a trio. Use an `area-cell` with `rowspan` so the colored district cell visually covers all people in that group.
- Then synchronize `/opt/data/vercel-project/districts.html` for the same date and same controllers. If the user corrects the daily graph, the daily graph is the source of truth for that date.
- Copy both changed files to `/tmp/schedule_site/vercel-project/...` before deployment.

### Rowspan table pattern

```python
area_cls = {'І': 'area-1', 'ІІ': 'area-2', 'ІІІ': 'area-3'}
area_txt = {'І': 'І дільниця', 'ІІ': 'ІІ дільниця', 'ІІІ': 'ІІІ дільниця'}

def make_table(groups):
    rows = []
    for gi, (num, names, dist) in enumerate(groups):
        for ni, name in enumerate(names):
            trcls = ' class="pair-separator"' if gi > 0 and ni == 0 else ''
            pair = str(num) if ni == 0 else ''
            area = (f'<td class="area-cell {area_cls[dist]}" rowspan="{len(names)}">{area_txt[dist]}</td>'
                    if ni == 0 else '')
            rows.append(f'<tr{trcls}><td class="pair-cell">{pair}</td><td class="name-cell">{name}</td>{area}</tr>')
    return '<table class="people-table"><thead><tr><th>Пара</th><th>Прізвище</th><th>Дільниця</th></tr></thead><tbody>' + ''.join(rows) + '</tbody></table>'
```

Example group list:

```python
shift1 = [
    (1, ['Шраменко В.А.', 'Зозуля О.М.'], 'ІІІ'),
    (5, ['Бобух Л.І.', 'Дибля Н.А.', 'Перетятько М.О.'], 'І'),
]
```

Verification:

```bash
python3 - <<'PY'
from pathlib import Path
import re
s = Path('/opt/data/vercel-project/daily/20.05.26.html').read_text(encoding='utf-8')
print('rowspan count', s.count('rowspan='))
for h, table in zip(re.findall(r'<h2>.*?</h2>', s), re.findall(r'<table class="people-table">.*?</table>', s, re.S)):
    print('\n', re.sub('<.*?>', '', h))
    for r in re.findall(r'<tr[^>]*>(.*?)</tr>', table, re.S):
        cells = [re.sub('<.*?>', '', c).strip() for c in re.findall(r'<td[^>]*>(.*?)</td>', r, re.S)]
        if cells: print(cells)
PY
```

## Verification snippet

```bash
python3 - <<'PY'
from pathlib import Path
import re
DAY = 20
names = ['Зозуля О.М.', 'Гавриленко Л.І.', 'Битко І.В.']
s = Path('/opt/data/vercel-project/districts.html').read_text(encoding='utf-8')
for name in names:
    m = re.search(r'<td class="name">' + re.escape(name) + r'</td>(.*?)</tr>', s)
    cells = re.findall(r'<td class="area[^"]*">([^<]*)</td>', m.group(1))
    print(name, cells[DAY-1])
PY
```
