Столкнувшись со шквалом задач разной степени важности, 3 года назад я принял решение начать записывать задачи в планер/to do list который было бы удобно вести и с телефона, и с ноутбука. Выбор пал на Notion, как на популярную межоперационную платформу. За время использования планера было выполнено множество разных задач, и стало интересно провести некоторый анализ того, как и на что уходило время.
1. Устройство Notion планера
Notion планер является одной большой страничкой Database
с форматом просмотра By Status
, наполненной задачами, реализованными как page
. Именно Status является ключевой фичей, характеризующей то, в каком состояние находиться каждая задача.
Для удобства статусы у меня принимают разные значение, главные 6 из которых это:
Top Priority для короткой задачи требующей незамедлительного внимания
Goal как долгая задача/цель, требующая последовательного подхода, но приводящая к серьезному результату
и соответвующие им 4 скрытых статуса:
Succesfully accomplished / Unaccomplished
Achieved Projects / Unachieved Projects.
Чтобы не плодить лишние сущности, прямо в планере есть статусы для задач второго плана, персональных дел, возможных идей для проектов и планов на совместный досуг с девушкой. А также, с недавнего времени добавилась колонка повторяющихся задач, которую нужно было добавить, чтобы она вечно напоминала о себе, но не засоряла поток Top priority
По началу я также заполнял такие параметры как Date и Tag, чтобы фильтровать задачи по их профилю, но со временем надобность в последнем почти отпала, так что Tag носит только декоративный характер, позволяя визуально отделить задачи друг от друга. А вот Date показывает себя особенно удобно в случае необходимости в кратчейшее сроки решить сразу много задач с близким дедлайном.
Наиболее удобно это использовать следующим образом: в базу данных можно добавить способ просмотра Timeline
, где каждую задачу представялет прямоугольник интервал времени. Из overlap'а задач можно составить оптимальный маршрут решения, тем самым уменьшив возможные издержки и сохранив хрупкий баланс (в моем случае между учебой, работой и наукой).
2. Наработка данных
Выгрузить данные из Notion можно разными способами. Первый, и наверное самый простой способ, это скачать их напрямую через сайт. Так можно наработать .csv
файл со всеми основными фичами. Второй способ, это использовать официальный API Notion. Мне нравиться именно второй способ, т.к помимо базовых фич страничек в планере, он умеет парсить и два не менее важных показателя: created_time
, last_edited_time
.
Для парсинга использовался python + requests, за выгрузку в .csv отвечал pandas
Для взаимодействия с официальным API Notion необходимо создать специальный токен. Как это делается детально описанно в спойлере:
Создание секретного токена API Notion
Для получения уникального ключа для API необходимо создать интеграцию по ссылке
Создав интеграцию и получив код:
Его нужно подключить к страничке с планером. Перед этим рекомендую установить вашей интеграции какую-то узнаваемую картинку, так чтобы не спутать с чужой при добавлении. Для этого на странице с планером жмем на 3 точки, потом на Connect to, где в выпадающем списке ищем нашу интеграцию.
Получив токен, смело заполняем headers
для requests:
headers = {
"Authorization": "Bearer " + "YOUR TOKEN",
"Content-Type": "application/json",
"Notion-Version": "2022-02-22"
}
Стоит отметить, что "Notion-Version" должен быть строго определенный, если с ним ошибиться, с сайта вернеться с ошибкам с указанием на возможные верные версии.
Парсинг выполняется с помощью requests.request('POST', url, json=payload, headers=headers)
, где url
это адресс на базу данных, а payload
указывает с какой page
нужно записывать. По умолчанию Notion выдает максимум 100 page
, что можно решить простым циклом.
Полная функция парсера db
def readEntireDatabase(databaseID, headers):
url = f"https://api.notion.com/v1/databases/{databaseID}/query"
page_size = 100
payload = {"page_size": page_size}
response = requests.request('POST', url, json=payload, headers=headers)
data = response.json()
results = data["results"]
while data["has_more"]:
payload = {"page_size": page_size, "start_cursor": data["next_cursor"]}
response = requests.request("POST", url, json=payload, headers=headers)
data = response.json()
results.extend(data["results"])
return results
Полученный файл содержит себе множество json
'ов страничек, у каждой из которой в наличие следующие данные (ключи словаря):
['object', 'id', 'created_time', 'last_edited_time', 'created_by', 'last_edited_by', 'cover', 'icon', 'parent', 'archived', 'in_trash', 'properties', 'url', 'public_url']
Из них особенный интерес для меня представяют created_time
, last_edited_time
, in_trash
, а также основной словарь лежащий по ключу properties
. Именно он содержит все основые фичи страничек, в том числе и название со статусом.
Из json основные фичи вытаскиваются следующим образом (try-except соотвествует случаю когда нужное свойство не было указано в Notion):
if not page['in_trash']:
try:
names.append(page['properties']['Name']['title'][0]['plain_text'])
except:
names.append('')
try:
statuses.append(page['properties']['Status']['select']['name'])
except:
statuses.append('')
try:
tags.append(page['properties']['Tags']['multi_select'])
except:
tags.append('')
try:
dates.append(page['properties']['Date']['date'])
except:
dates.append('')
Собрав из полученных листов pd.DataFrame
и сохранив в .csv
можем переходить к анализу.
3. Анализ данных и визуализация
Для анализа полученных данных воспользуемся datetime, nltk и pymorphy2. Для визуализации и построения графиков matplotlib и wordcloud.
3.1 Временной анализ
Для начала разберемся с датами полученными из created_by
и last_edited_by
. Записываются они в нечитабельном формате 2024-07-06T18:18:00.000Z
. Обрежем ненужное количество значащих цифр для миллисекунд с правого конца и заменем символы T
на -
для consistency
def format_datetime(string : str):
return string[:-5].replace('T', '-')
Такой формат даты уже можно считать с помощью datetime.
datetime.strptime(date_str, '%Y-%m-%d-%H:%M:%S')
Переписав в pd.DataFrame
колонки CREATION_TIME и EDITION_TIME в читабельном формате и получив их datetime репрезентации можем немного поиграть с анализом данных, посмотрев на то, как выглядит распределение решенных/заваленных задач на масштабах в дни/месяцы/годы.
Построив распредление задач от месяца можно увидеть закономерный провал летом, и в принципе ожидаемый подъем в апреле и ноябре (эти два пика совпадают с максимумами учебной нагрзуки в институте). Подсчитав среднее время выполнения и провала (изменения статуса на Unaccomplished) задачи получились соотвественно 13 и 57 дней.
Код построения графика
creation_months = np.zeros(12)
completion_months = np.zeros(12)
for date in data[data['STATUS'] == 'Succesfully accomplished'].CREATION_TIME:
creation_months[datetime.strptime(date, '%Y-%m-%d-%H:%M:%S').month - 1] += 1
for date in data[data['STATUS'] == 'Succesfully accomplished'].EDITION_TIME:
completion_months[datetime.strptime(date, '%Y-%m-%d-%H:%M:%S').month - 1] += 1
plt.figure(figsize=(7, 5))
plt.style.use('dark_background')
plt.bar(x=np.arange(1, 13), height=creation_months, color='white', label='начато')
plt.bar(x=np.arange(1, 13), height=completion_months, color='lightgray', label='сделано', alpha=0.5)
plt.xlabel('Месяц',fontsize=24)
plt.xticks(np.arange(1, 13)[::2], fontsize=14)
plt.ylabel('Кол-во задач',fontsize=24)
plt.yticks(np.arange(0, 151, 30), fontsize=14)
plt.legend(fontsize=14)
plt.tight_layout()
plt.savefig('months_tasks.jpg')
На масштабе в 4 года, видна явная тендция роста количества заверешнных задач (и проектов) от года к году. За начало 2024 года выполнено в 2.9 раз больше задач и в 3.33 раза больше проектов чем за соответствующий период 2023-го.
Код построения графика
# data_years_i полученно как value_counts to_dict()
fig, ax = plt.subplots(1, 2, figsize=(20, 7))
plt.rcParams.update({'font.size': 20})
ax[0].barh(width=list(data_years_1.values()), y=list(data_years_1.keys()), color='white')
ax[0].set_yticks([key for key in sorted(data_years_1.keys())])#, fontsize=20)
ax[0].set_xticks(range(0, 451, 90))#, fontsize=20)
ax[0].set_ylabel('Год', fontsize=24)
ax[0].set_xlabel('Количество задач', fontsize=24)
ax[1].barh(width=list(data_years_2.values()), y=list(data_years_2.keys()), color='white', )
ax[1].set_yticks([key for key in sorted(data_years_2.keys())])#, fontsize=20)
ax[1].set_xticks(range(0, 11, 2))#, fontsize=20)
ax[1].set_ylabel('Год', fontsize=24)
Самым приятным глазу, конечно же, является карта активности за целый год, постоенная в маштабе дней (источником вдохновения послужил GitHub).
Код построения графика
data_for_map = sc_data[sc_data['year'] == 2024]
activity = np.zeros((7, 53))
for day in np.arange(7):
for week in np.arange(53):
activity[day][week] += len(data_for_map[data_for_map['week'] == week][data_for_map['weekday'] == day + 1])
fig, ax = plt.subplots(figsize=(14, 11))
ax.set_aspect("equal")
plt.style.use('dark_background')
orig_map = plt.cm.get_cmap('Grays') # инверсия чтобы maximum совпадал с белым
reversed_map = orig_map.reversed()
plt.title('Карта активности 2024', fontsize=24)
plt.pcolormesh(activity, cmap=reversed_map, edgecolor="w")
plt.xticks(np.arange(0, 53 ,5), fontsize=16)
plt.yticks(np.arange(0, 7, 2), fontsize=16)
plt.ylabel('Номер дня', fontsize=20)
plt.xlabel('Номер недели', fontsize=20)
3.2 Текстовый анализ
Проведем первичный анализ текстов задач. Посчитаем: 1) среднее числов слов в задаче, 2) среднее число символов в задаче и 3) средню длину слова.
sc_data = data[data['STATUS'] == 'Succesfully accomplished']
average_number_words = sc_data.NAME.str.split().str.len().mean()
average_number_symbols = sc_data.NAME.str.replace(' ', '').str.len().mean() # пробелы не считаем
def avg_word_length(sentence : str):
words = sentence.split()
return (sum(len(str(word)) for word in words) / len(words))
average_word_length = sc_data.NAME.apply(lambda x: avg_word_length(str(x))).mean()
В решенных задачах в среднем 4.2 слова и 24 символа. Средняя длина слова 6.7 букв.
Для анализа текстов задач проведем следующий препроцессинг.
Заменим все знаки препинания на пробелы
Приведем все слова к нижнему регистру
Разобьем предложение на списки, а все списки объединим в один
Удалим все слова с длиной меньше 4
Код препроцессинга. Часть 1
def clear_punctuation(string : str):
for punct in ['.', ':', ',', '!', '?', ';', '\\', '/', '-', ')', '(']:
string = string.replace(punct, ' ')
return string
def filter(text : str):
filtered = [word for word in text if len(word) > 3]
return filtered
text = sc_data.NAME.astype(str)
text = text.apply(lambda x: clear_punctuation(x))
text = text.str.lower()
text = ' '.join(text.to_list()).split()
text = filter(text)
Полученный список слов можно поместить в pd.Series
и используя value_counts()
получить словарь с частотностью. Полученный словарь красиво представить в качестве wordcloud:
Код Wordcloud
mask = np.array(Image.open('coffee.jpg'))
wordcloud = WordCloud(mask=mask).generate(' '.join(text))
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 7))
plt.imshow(wordcloud.recolor(color_func=grey_color_func, random_state=3),
interpolation="bilinear")
plt.axis("off")
plt.savefig('completed.png')
И из картинки, и из словаря частот видно, что самыми популярным словами являются глаголы, призывающие к незамедлительному действию. И вместо того, чтобы удалять самые частые слова, можно определить для каждого слова его часть речи (c помощью pymorphy2), и удалить все ненужные.
Итого, добавим в препроцессинг еще два этапа:
Удаление стоп-слов
Приведение к начальной форме и удаление глаголов
Код препроцессинга. Часть 2
from nltk.corpus import stopwords
import nltk
import pymorphy2
morph = pymorphy2.MorphAnalyzer()
# Create a set of stop words
stop_words = set(stopwords.words('russian'))
def remove_stop_words(sentence : list, filter_array=stop_words):
filtered_words = [word for word in sentence if word not in filter_array]
return filtered_words
def filter_verbs(text: list):
filtered = [word for word in text if morph.parse(word)[0].tag.POS != 'INFN' and morph.parse(word)[0].tag.POS != 'VERB']
return filtered
Воспользовавшись препроцессингом, получим вот такое симпатичное лингвистическое облако решенных задач:
Самые популярные слова: неделя, билет, лекция, статья.
4. Планы на будущее
Продолжая темы, поднятые в этом мини-проекте, можно выделить два основных возможных направления развития идеи.
Первое - это автоматизация сбора и представления данных, возможно в формате Streamlit платформы. Это бы приятно дополнило опыт работы с Notion, особенно если придумать как добавить элементы такого анализа в качестве виджетов прямо на страничку с базой данных.
Второе - это работа с лингвистическими моделями для семантической классификации проделанных задач на категории, к примеру на науку (мои подкатегории были бы физика и ML) искусство (кино и литература), здоровье и т.д
С такой задачей справился бы слегка доубученный BERT, но я не нашел датасетов с задачами по типу to-do, а среди моих 1500 задач, размечено от силы 60 (и то ~20 это reading и ~40 Stuying), так что оставим это на будущее.
Комментарии (18)
dyadyaSerezha
19.07.2024 22:16+31) Не понял смысл анализа. Ну выяснилось, что задачи выполняются за 13, а фейлятся за 57 дней - и что? Что дальше? Какой из этого полезный вывод? А какая польза от среднего кол-ва слов и прочего? Не понимаю. И все графики на мой взгляд абсолютно бесполезные, так как не ведут ни к какой корректировке себя или к новому осознанию. Ну разве что процент неудачных дел хоть что-то бы показал, точнее, его изменение во времени. Но даже это для меня лично было бесполезной инфой.
2) вместо лингвистических моделей в миллион раз проще ставить тэг для каждой задачи.
hK04 Автор
19.07.2024 22:16+21) Ну, тут даны избыточные методы для анализа Вашего Notion, на основе которых Вы сможете построить собственные инсайты. Все же Хабр про прогу, а не про личностый рост, и на самом деле не все результаты вошли в статью.
К примеру, распределение слов только за 2024 (после лингвистического анализа) дало забавные результаты: наиболее популярное словосочетание это "Аналитическая механика" (и соответсвующее ему наибошьшее количество потраченного времени), за которую у меня наименьший результата в учебную сессию. Вполне себе полезное знание о том, как не надо распределять время.
2) Не считаю, что ставить тэги проще По крайней мере для задач, которые надо делать оперативно.
dyadyaSerezha
19.07.2024 22:16Если уж про прогу, то 4 похожих try-except подряд, это очень плохой стиль. В цикле гораздо лучше.
А написать короткий тэг в дополнение к названию, приоритету и датам задачи, это очень просто, по-моему.
Kahelman
19.07.2024 22:16Больше графиков- богу графиков.
Для меня основная идея замёток это хранить нужную информацию - сей час к сожалению, интернет заспамлен и что легко искалось пару лет назад - сей час не найдёшь.
Лет десять сидел на Evernote - premium последние лет пять переехал на Joplin. Основная проблема Evernote -его превратили в тормозной маркетинговый ужас.
Joplin тоже периодически косячит - отваливаются attached документы. Но в общем жить можно.
Только хардкор- только текст :)
Aquahawk
19.07.2024 22:16+1Obsidian, 2 с копейками года, 1643 заметки, 1670 ссылок. Синхронизирую syncthing. На телефоне начал подтормаживать при открытии obsidian. Но вообще штука мега крутая. Думал что страницы без ссылок это проблема, но пока вроде как нет. Я гораздо чаще ищу по базе по именам (и алиасам) или полнотекстовым поиском, чем перемещаюсь по линкам. Что интересно графовый вид тоже бесполезен, только вот такие картинки показывать.
IAmThat
19.07.2024 22:16Пока для меня лучшее для трекинга задач - Zenkit. Прямо сильно лучше и удобнее чем все что я до этого пользовал! Ещё сидел на Oneclick, но там слишком сложная система и слишком много всего - перегружает доски сильно!
Лучшее для ведения конспектов OneNote. На компьютере веду конспекты курсов, видео с экранами , вебинаров, статей, планов, личных сессий, конференций, дневники разные ит. Д.
OneNote конспектирует оооочень быстро, с экранами и в пару колонок по-ширине. С мобильного тоже можно, но интерфейс форматирования очень куцый. Так что практически текст с буллетами - и все. Но читает и синхронизирует на телефоне как часы!
Единственное что-данные лежат на серверах майкрософта.
digtatordigtatorov
Obsidian? Почему не он? Вот тут есть простор полета фантазий. Разобраться в графах, вытянуть много интересного.
mc2
У автора задача что бы и с устройства, и с телефона можно было бы вводить. Обсидиан хорош, но постоянно синхронизацию делать...
Ryav
Syncthing для вас шутка какая-то?
NibiruanChild
Костыль. За неимением другого пользуюсь для обсидиан, но много раз подводил
Из-за конфликта версий не синхронились отдельные заметки
Из-за режимов экономии энергии на мобильных устройствах не синхронил. Дописал заметку, закрыл ноутбук и ушел, а синка не произошло. А именно последние заметки чаще всего нужны
Вне дома когда устройства хоть и с интернетом, но не в одной сети, синхронизации нет.
Если надо открыть на устройстве, на котором был не в сети на момент написания заметки. У меня есть походные ноутбук и планшет, которые беру в командировки например. Если забыл синкнуть перед поездкой, то все.
Ryav
У меня ни одной проблемы не было за всё время использования, правда у меня есть медиа-сервер, который 99% времени в сети и туда в любом случае всё долетает, ну а с него уже и другие устройства могут забрать.
По 3 должно быть без разницы, хоть за NATом у вас устройства.
А по 4 вообще никакого решения нет. Как вы без сети синхронизируете?
NibiruanChild
4 у того же ноушн решается загрузкой на их сервера. Потому и говорю что синкфин увы костыль, хоть многим и хватает
rustavelli
Так всего лишь нужно поднять свою файлопомойку, которая всегда онлайн и доступна для синхронизации заметок.
NibiruanChild
Всего лишь :D
Я же и говорю что костыль. Я не сказал что костыли не могут работать, но это костыль.
Хотя идеология обсидиан вся такая. Из коробки им пользоваться невозможно и надо тратить много времени на допил под себя. Иногда вместо того чтобы пользоваться.
Увы, я признаю что обсидиан одно из лучших решений. Сам на него ушел с ноушена. Ок быстрый и локальный, ссылки и поиск очень удобные. Но я абсолютно не понимаю тех кто его идеализирует, потому что у него огромная куча недостатков и недоработок до идеального инструмента.
Ryav
Так если вы так и так синхронизируете (сеть есть), то и через Syncthing с другим устройством проблем не вижу. Если у вас устройства одновременно в сети не могут быть, то добавьте ещё одно, которое всегда будет в сети (та же VPSка, например, или старый телефон, который всегда дома включённый и подключённый к сети лежит).
LeshaRB
В платной версий там есть синхронизация
shikhov
Использую Self-hosted LiveSync, синхронизация мгновенная, проблем пока не было. https://github.com/vrtmrz/obsidian-livesync
NibiruanChild
Пользуюсь, но без баз данных и централизованного управления большим количеством заметок грустно очень. Про DB Folder знаю, но у него куча багов, мало возможностей по сравнению с ноушн, и таблички выглядят отвратительно и не настраиваются