LifeHacker & Elyssa
Привет, Лайфхакерша, я тут ковырялась с голосовым календарем, который сам назначает встречи, планирует перекусы и даже медитации — представь себе личного лайфхакера, но на максималках. Хочешь заглянуть в код и посмотрим, как сделать его круче, чем твои нынешние инструменты продуктивности?
Звучит как мечта. Давай посмотрим на репозиторий и начнем чистить лишние триггеры. Я сначала задокументирую текущий процесс, а потом добавим более продвинутое распознавание контекста, чтобы оно понимало, когда ты на совещании, а когда просто разговариваешь с коллегой. Покажи мне код, и мы сделаем его безупречным.
Вот небольшой фрагмент секции триггеров из `triggers.py` – пока что там полный хаос.
# triggers.py
from events import *
def register_triggers():
triggers = []
# Триггеры устарели, многие из них пересекаются
triggers.append(When('meeting_start').do(schedule_meeting))
triggers.append(When('meeting_end').do(clean_up_meeting))
triggers.append(When('email_sent').do(archive_email))
triggers.append(When('message_received').do(process_message))
triggers.append(When('task_created').do(create_task))
# Повторяющиеся и редко используемые
triggers.append(When('calendar_view').do(update_ui))
triggers.append(When('calendar_view').do(log_view))
triggers.append(When('calendar_view').do(cache_view))
return triggers
```
Мы можем избавиться от `calendar_view`, объединить `schedule_meeting` и `clean_up_meeting` в один обработчик с учетом контекста, и заменить простые проверки `When` на датчик контекста, который будет помечать события как "встреча", "вне экрана" и т.д. Давай набросаем простой `ContextSensor`, который будет отмечать события на основе текущего статуса календаря, а затем подключим его к циклу. Хочешь увидеть код датчика дальше?
Конечно, вот минимальный датчик, чтобы помечать события до срабатывания триггеров.
# context_sensor.py
class ContextSensor:
def __init__(self, calendar):
self.calendar = calendar # простой интерфейс с is_meeting() и т.д.
def tag(self, event):
if self.calendar.is_meeting():
event.context = 'in_meeting'
elif self.calendar.is_offscreen():
event.context = 'off_screen'
else:
event.context = 'idle'
return event
```
Затем в основном цикле:
```
sensor = ContextSensor(calendar)
for event in event_stream:
event = sensor.tag(event)
handle(event) # твой универсальный обработчик
```
Так ты можешь избавиться от старых проверок `When` и просто смотреть на `event.context`. Это даёт чистый и расширяемый хук для будущих меток. Скажи, если захочешь добавить более тонкие состояния или обработку неизвестных событий.
Замечательно, этот датчик поддерживает структуру цикла в порядке. Один небольшой штрих: сделай так, чтобы функция `tag` возвращала копию, а не изменяла исходное событие, чтобы избежать случайных побочных эффектов. И добавь контекст `unknown` для событий, которые не соответствуют ни одному правилу – так будет проще ловить ошибки на раннем этапе. Хочешь небольшую тестовую систему, чтобы проверить теги на основе фиктивного календаря?
Вот небольшая доработка и крошечная проверка, чтобы убедиться, что всё работает.
Выглядит здорово! Только одна маленькая поправка – давай выделим состояние "idle" в отдельную подпись, вместо того, чтобы относить его к "неизвестному". Так потом будет проще добавить специальную обработку для реального состояния простоя. В остальном, плетение должно выводить три контекста, как и ожидалось. Готова запускать его в основной цикл?
Поняла, просто переставь проверку бездействия до значения по умолчанию. Вот финальная настройка датчика:
```class ContextSensor:
def __init__(self, calendar):
self.calendar = calendar
def tag(self, event):
ev = copy.deepcopy(event)
if self.calendar.is_meeting():
ev.context = 'in_meeting'
elif self.calendar.is_offscreen():
ev.context = 'off_screen'
elif self.calendar.is_idle(): # add this in your calendar class
ev.context = 'idle'
else:
ev.context = 'unknown'
return ev
```
Обнови свой mock календаря:
```class FakeCalendar:
def __init__(self, state):
self.state = state
def is_meeting(self): return self.state == 'meeting'
def is_offscreen(self): return self.state == 'off_screen'
def is_idle(self): return self.state == 'idle'
```
Запусти тест снова, увидишь `in_meeting`, `off_screen`, `idle` и `unknown` как отдельные метки. Теперь можно напрямую подключить это к основному циклу и добавить отдельные обработчики для каждого контекста. Готова вставлять.
Поняла — твой финальный датчик — чистый switch-case. Сейчас запускаю работу с подключением:
state=meeting → context=in_meeting
state=off_screen → context=off_screen
state=idle → context=idle
state=unknown → context=unknown
Отлично. Подключи это в основной цикл событий, сопоставь каждый контекст с соответствующим обработчиком, и у тебя получится очень чёткий, контекстно-зависимый поток. Что дальше будем добавлять?
Отлично, теперь когда с датчиком всё в порядке, переходим к следующей крутой штуке: уведомления о простоях с прогнозированием. В общем, если ты находишься в состоянии "ожидания" дольше минуты, предлагай короткий перерыв или напоминание о растяжке. Чтобы работа не превращалась в скуку смертную. Добавь таймер в обработчике бездействия, который будет выдавать вежливое уведомление. Готова набросать это?
Конечно, давай добавим немного "мотивации" в режим ожидания. В обработчике бездействия запусти таймер при первом событии бездействия. Если таймер истечет через 60 секунд, выводи короткое напоминание (например, "Пора размяться!"). Если пользователь что-то делает, сбрасывай таймер. Можно использовать короткую асинхронную задержку или запланированную задачу, главное – чтобы это не блокировало основной цикл. Нужен небольшой пример кода, чтобы подвязать все это?
Вот тебе удобный асинхронный помощник, который можно интегрировать в обработчик бездействия:
```python
import asyncio
from datetime import datetime
class IdleCoach:
def __init__(self, prompt, timeout=60):
self.prompt = prompt
self.timeout = timeout
self.last_action = datetime.utcnow()
self.task = None
async def _watch(self):
while True:
await asyncio.sleep(1)
elapsed = (datetime.utcnow() - self.last_action).total_seconds()
if elapsed >= self.timeout:
self.prompt() # Например, print("Пора немного размяться!")
self.last_action = datetime.utcnow() # Сбрасываем после уведомления
def start(self):
if not self.task:
self.task = asyncio.create_task(self._watch())
def reset(self):
self.last_action = datetime.utcnow()
def stop(self):
if self.task:
self.task.cancel()
self.task = None
```
Используй это так в основном цикле:
```python
coach = IdleCoach(lambda: print("Пора немного размяться!"))
for event in event_stream:
ev = sensor.tag(event)
if ev.context == 'idle':
coach.start()
coach.reset()
else:
coach.stop()
handle(ev)
```
Теперь цикл останется быстрым, и ты получишь лёгкое напоминание после минуты бездействия.