0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

反復学習を自動で回す、小さな通知システム

0
Posted at

繰り返し思い出して、知識を知恵に近づける


0. この記事について

本で学んだ内容や気づきを
「忘れないように通知するだけ」 の小さな仕組みを、
Googleスプレッドシート+Pythonで作りました。


1. はじめに / 背景

業務や自己研鑽で本を読んだあと、

  • いい言葉だったのに思い出せない
  • あの時のフレーズ、また活かせず忘れている

という状態が続いていました。そこで、

  • 1回に1つ
  • 思い出すきっかけだけ作る
  • 身についたら除外できる

という割り切った仕組みで、繰り返し思い出せる仕組みを作りました。


2. 全体像・仕組み

構成はかなり単純です。

  • 各内容は、数秒で読める単位に分割
  • 各内容を Google スプレッドシートで管理
  • Python でランダムに1件取得
  • Webex に内容(フレーズ)を送信
  • 身についたものは、通知除外できるしくみ

処理の流れ


3. 構築手順・基本構成

最小構成

  1. Googleスプレッドシートを1枚用意
  2. PythonでSheets APIを読む
  3. Webex通知用モジュールを外だし

※ Windows + Python を想定


4. 各論

4-1. スプレッドシート設計

シート名:学びの言葉

内容
A 検索用ID
B 管理ID
C キーワード
D 書籍名
E 通知除外フラグ
  • 管理IDはレコードのキーID
  • 検索用IDは通知対象のみの連番(Pythonで利用)
  • 除外したいものはフラグを立てるだけ

👉 Python側の処理を単純にするためです。


4-2. Python側の考え方

Pythonでは以下だけをやっています。

  • A列(検索用ID)を読んで最大値取得
  • 1〜max の乱数を生成
  • 一致する行だけを再取得
  • 通知文を組み立てる
    (通知対象外は検索用IDが空白)

通知メッセージ表示例(ダミー)

ロボット名 14:00

繰り返し思い出せば、知識は知恵に近づく
 ~(出展:~~ダミー出版~~)~

※参考コードは後述


4-3. Webex通知は外だし

通知処理は 別Pythonファイル にしています。

from send_webex_message_utf8 import send_webex_message

本体側は、

send_webex_message(message)

と呼ぶだけ。

別ファイルなので、この通知ファイルは他にも流用が可能です。
※コードは後述


5. 注意点・運用上の工夫

このシステムは、振り返り用の通知を出すことに特化しています。
特に「身についたかどうか」を管理するシートは作っていません。
通知除外フラグが増えたら身に付いていると代替判定します。


6. まとめ・応用

  • 学びを「忘れない」だけで価値がある
  • 最小構成+外だしで保守しやすい
  • スプレッドシート×Pythonは業務に転用しやすい

この構成は、

  • 定期リマインド
  • チェックリスト通知
  • 業務ルールの再確認

などにもそのまま使えます。


付録:参考コード(最小構成)

記事中で説明した「スプレッドシートからランダムに1件選んで Webex に通知する」部分の参考コードです。
※ トークン等の秘匿情報は config.ini に外出ししています。


フォルダ構成(最小)

project/
├─ 書籍からの学びの自動通知システム.py
├─ send_webex_message_utf8.py
├─ config.ini
└─ <サービスアカウント鍵.json>

config.ini(例)

[google]
service_account_json = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.json
spreadsheet_id = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
sheet_name = 学びの言葉
id_start = 1

[sendwebexmessage]
access_token = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
room_id =
to_person_email = your_mail@example.com

[logging]
log_path =

本体:書籍からの学びの自動通知システム.py

import configparser
import logging
import os
import random
import sys
from typing import Optional, Tuple

from google.oauth2.service_account import Credentials
from googleapiclient.discovery import build

from send_webex_message_utf8 import send_webex_message


def setup_logger(log_path: Optional[str]) -> logging.Logger:
    logger = logging.getLogger("remind_notifier")
    logger.setLevel(logging.INFO)

    fmt = logging.Formatter("%(asctime)s %(levelname)s %(message)s")

    sh = logging.StreamHandler(sys.stdout)
    sh.setFormatter(fmt)
    logger.addHandler(sh)

    if log_path:
        fh = logging.FileHandler(log_path)
        fh.setFormatter(fmt)
        logger.addHandler(fh)

    return logger


def build_sheets_service(sa_json_path: str):
    scopes = ["https://www.googleapis.com/auth/spreadsheets.readonly"]
    creds = Credentials.from_service_account_file(sa_json_path, scopes=scopes)
    return build("sheets", "v4", credentials=creds, cache_discovery=False)


def format_message(keyword: str, book: str) -> str:
    text = keyword.strip()
    b = book.strip() if book.strip() else "不明"
    return f"{text}\n ~(出展:{b})~"


def main():
    base_dir = os.path.dirname(os.path.abspath(__file__))
    cfg = configparser.ConfigParser()
    cfg.read(os.path.join(base_dir, "config.ini"), encoding="utf-8")

    sa_json = os.path.join(base_dir, cfg.get("google", "service_account_json"))
    spreadsheet_id = cfg.get("google", "spreadsheet_id")
    sheet_name = cfg.get("google", "sheet_name")

    service = build_sheets_service(sa_json)

    resp = service.spreadsheets().values().get(
        spreadsheetId=spreadsheet_id,
        range=f"{sheet_name}!A2:D"
    ).execute()

    rows = resp.get("values", [])
    if not rows:
        return

    search_id = random.choice(rows)[0]

    for row in rows:
        if row[0] == search_id:
            keyword = row[2]
            book = row[3] if len(row) > 3 else ""
            message = format_message(keyword, book)
            send_webex_message(message)
            break


if __name__ == "__main__":
    main()

通知モジュール:send_webex_message_utf8.py

import configparser
import requests
from pathlib import Path


SECTION_NAME = "sendwebexmessage"


def load_config(section: str = SECTION_NAME) -> dict:
    script_dir = Path(__file__).resolve().parent
    config_file = script_dir / "config.ini"

    if not config_file.exists():
        config_file = script_dir.parent / "config.ini"

    config = configparser.ConfigParser()
    config.read(config_file, encoding="utf-8")

    s = config[section]

    return {
        "access_token": s.get("access_token", "").strip(),
        "room_id": s.get("room_id", "").strip(),
        "to_person_email": s.get("to_person_email", "").strip(),
    }


def send_webex_message(message: str, section: str = SECTION_NAME) -> None:
    config = load_config(section)

    url = "https://webexapis.com/v1/messages"
    headers = {
        "Authorization": f"Bearer {config['access_token']}",
        "Content-Type": "application/json",
    }

    payload = {"text": message}

    if config["room_id"]:
        payload["roomId"] = config["room_id"]
    else:
        payload["toPersonEmail"] = config["to_person_email"]

    response = requests.post(url, headers=headers, json=payload, timeout=10)
    response.raise_for_status()

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?