業務の隙間を埋める技術メモ。

「それ、作れるか?」より 「それ、作って大丈夫か?」を考えたい。 業務で“ちゃんと使える”かどうかを、 実際に手を動かして確かめたログを残しています。

1on1を、面白く愉快に。これ大事。なんでアプリ作ったよ的な

IT業界の売上の源泉、それは――
水でも石油でもAIでもなく、最終的に“人間”がコードを書き、仕様に泣き、バグに祈り、リリース後にそっとモニターを見守る…そんな情緒的産業、それがITなのです。

もちろんどの業界でも人は大切ですが、ITはとくに“人そのものがほぼ全コスト”という究極のシンプル構造。材料費ゼロ、在庫ほぼゼロ、買ってくるのはカフェイン飲料ぐらい。
言い換えるなら、自動化が一番進んでいない業界なのに、自動化を作ってる業界という、壮大なセルフツッコミの世界でもあります(自嘲ポイント加算)。

そんな業界なので、人の働き=生産性にダイレクト影響。
スーパーマーケットでもおいしい鮮魚を並べるには大型冷蔵庫とピカピカの氷が必要なように、すばらしいソフトをつくるには、つくる“人”に十分なケアとフォローが必要という話になります。

 

で、そこで出てくるのが「1on1」。
ご存じない方のために超ざっくり説明すると――

上司と部下が、密室(会議室)で、逃げ場なく、じっくり話すアレです。

と聞くとなんだか取り調べ感がありますが、実際はもう少し平和で、
「最近どう?」「困ってることある?」「じゃあ改善しよっか?」
みたいな、言ってしまえば仕事版ゆるふわカウンセリングみたいな時間です。

会社によっては「1on1」じゃなく「ワンワン」と呼ばれ、
犬の散歩かな?という疑念が生まれることもありますし、
実際はただの週次しゃべり場だったり、心理安全性向上ミッションだったり、
要するに “人をちゃんと見ようぜ” の時間だと思っておけば大体合ってます。

 

私も、1on1をやってます。やるときに気を付けているのは、普段の厳しさを一切出さないことは当然として、そもそも仕事の話をあまりしません。雑ネタを探し出しては投げかけてみて反応をうかがってみる的なアプローチをしてきました。話を受ける相手も笑ったりしてくれるので、まあそんなに悪くはない話し合いができているのかなと勝手に決めつけておきます。

 

ただ、話のネタ探しはいつも困るんですよね。「最近どうよ?」的にまるごと相手にネタだしをぶん投げてしまってもいいんですが、ちょっと芸がないよねってことで、思わず勢いで作ってしまいましたよ。その名もジャジャーン!「ネタトロくん」

 

なんとな~く話してもいいカテゴリを選んで、出てきたテーマから一つ選択したらトーク開始!無駄にビジュアルに凝ったところがポイントっす。

 

テーマを選んで・・・



 

ネタ抽出のボタンを押せば・・・

そのカテゴリからなんと、3つほど適当にテーマを見繕ってくれます笑

 

テーマを選ぶと、風船が上がっていって・・・

 

 

めでたくトークテーマ決定です。どうです?とことん無駄でしょう?笑

 

常に生産性向上を問われてるエンジニアだからこそ、1週間に1回くらいはこういうゆるい装置でお互い笑いながら話し合おうかなということで、ホントに勢い余って作ってしまったのでソースを全部あげときますね。

 

python入れれば使えます。

わずかなコードでインタラクティブなUIを実現できるStreamlitを使用してみました。

  • UIの高速構築: Streamlitのおかげで、HTML/CSSの知識がほとんど不要でした。

  • データの外部管理: テーマデータはPythonコードと分離し、themes.json ファイルで管理しています。テーマの追加や編集が非常に簡単です。

  • 頑健な読み込み: utf-8-sig エンコーディングを使用することで、環境によって発生しがちなBOM(バイトオーダーマーク)問題にも対応済みです。

 

次のようにファイルを構成してください。

/(アプリのルートディレクトリ)
├── one_on_one_app.py   <-- Streamlitのメインコード (実行ファイル)
└── themes.json         <-- トークテーマのデータファイル (JSON形式)

 

one_on_one_app.py

Python

 

import streamlit as st
import
random import json import os # --- 1. JSONファイルからのテーマデータ読み込み処理 --- THEME_FILE_PATH = "themes.json" @st.cache_data def load_themes(file_path): """JSONファイルからテーマデータを読み込む""" # ファイル存在チェック if not os.path.exists(file_path): st.error(f"エラー: テーマファイルが見つかりません。'{file_path}'を作成してください。") return {} # 'utf-8-sig' を使用し、UTF-8 BOM (Byte Order Mark) の問題を回避 try: # with open のインデントが正しいか再確認してください! with open(file_path, 'r', encoding='utf-8-sig') as f: data = json.load(f) return data except json.JSONDecodeError as e: # JSON形式エラーの場合 st.error(f"エラー: JSONファイルの形式が正しくありません。'{file_path}'を確認してください。詳細: {e}") return {} except Exception as e: # その他のIOエラー(パーミッションエラーなど)の場合 st.error(f"データの読み込み中に予期せぬエラーが発生しました: {e}") return {} # テーマデータを読み込む THEME_DATA = load_themes(THEME_FILE_PATH) # テーマデータが空の場合、以降の処理を中断 if not THEME_DATA: st.stop() # --- 2. UIの基本設定 --- st.set_page_config( page_title="🚀 1on1 トークテーマジェネレーター ネタトロくん", layout="centered" ) st.title("🤝 1on1 トークテーマ・ジェネレーター ネタトロくん") st.markdown("話したいテーマのカテゴリを選び、**「ネタ抽出!」**ボタンを押しましょう!") # 初期セッションステートの設定 if 'selected_themes' not in st.session_state: st.session_state.selected_themes = [] if 'final_theme' not in st.session_state: st.session_state.final_theme = None # --- 3. テーマカテゴリの選択 --- available_categories = list(THEME_DATA.keys()) selected_categories = st.multiselect( "1️⃣ テーマカテゴリを選択してください(複数選択可)", options=available_categories, default=available_categories ) # --- 4. ネタ抽出ロジック --- def extract_themes(): """選択されたカテゴリからランダムに3つのテーマを抽出する""" if not selected_categories: st.session_state.selected_themes = [] return # 選択されたカテゴリの全テーマを結合 all_themes = [] for category in selected_categories: if category in THEME_DATA: all_themes.extend(THEME_DATA[category]) # テーマが3つ未満の場合はすべて表示、そうでない場合は3つランダム抽出 if len(all_themes) == 0: st.session_state.selected_themes = [] st.warning("選択されたカテゴリにテーマがありません。テーマを追加してください。") return elif len(all_themes) <= 3: extracted = all_themes else: # ランダムで3つ抽出 extracted = random.sample(all_themes, 3) # セッションステートに保存 st.session_state.selected_themes = extracted st.session_state.final_theme = None st.success(f"🎉 {len(extracted)}個のテーマが抽出されました!") # --- 5. 「ネタ抽出」ボタン --- st.button( "🔥 ネタ抽出!", on_click=extract_themes, type="primary", use_container_width=True ) # --- 6. 抽出されたテーマの表示(ボタン形式) --- if st.session_state.selected_themes: st.subheader("2️⃣ 抽出されたテーマ") st.info("この中から一番気になるものを1つ選んで、トークを始めましょう!") cols = st.columns(3) # 選択済みのテーマがない場合のみボタン表示 if not st.session_state.final_theme: for i, theme in enumerate(st.session_state.selected_themes): with cols[i]: # 抽出されたテーマをボタンとして表示 if st.button(theme, key=f"theme_button_{i}"): # ユーザーがボタンをクリックしたら、final_themeをセット st.session_state.final_theme = theme st.rerun() # st.experimental_rerun() から st.rerun() に修正済 # --- 7. テーマ選択後のダイアログ表示 --- if st.session_state.final_theme: final_theme = st.session_state.final_theme # 選択されたテーマを大きく強調表示 st.markdown("---") st.header("✨ トークテーマ決定! ✨") # ダイアログ形式(ここではst.successとst.headerを組み合わせて強調) with st.container(border=True): st.subheader("今日のテーマはこちら!") st.markdown(f"## **{final_theme}**") # テーマタイトルを大きく掲出 st.success("このテーマで1on1を始めましょう!") # 楽しい演出の追加 st.balloons()

 

 

themes.json(アレンジOKですよ~)

 


JSON

 
{
   "🚀 キャリア・成長": [ "最近特に学びがあった(成長した)と思うことは?", "3年後のキャリアプランと、そのために今できることは?", "仕事のモチベーションが上がるとき/下がるときは?", "今の仕事で挑戦してみたい、まだ手をつけていないことは?", "上司(またはあなた)に期待していることは?" ], "💡 仕事・改善": [ "今の業務フローで、一番ムダだと感じる工程は?", "チーム(またはプロジェクト)の成功のために、個人的にできる貢献は?", "最近「これ、うまくいった!」と感じた、小さな成功体験は?", "他のメンバーの仕事で、興味がある・もっと知りたい領域は?", "仕事で「時間があれば…」と思うとき、何をしたいですか?" ], "😊 プライベート・リフレッシュ": [ "最近ハマっている(癒されている)趣味や関心事は?", "ストレス解消法やリフレッシュ方法はありますか?", "仕事とプライベートのバランスについて、何か困っていることは?", "もし1週間休みが取れたら、何をしたいですか?", "最近、誰かに感謝した出来事はありますか?" ] }

 

 

 

 

ちなみに開発上で起こったトラブルもメモっときます。

 

😱 困ったこと:まさかの「JSON形式エラー」の罠

アプリは完成し、いざ Streamlit で実行!...と思いきや、予期せぬエラーに遭遇しました。

エラー: JSONファイルの形式が正しくありません。詳細: Unexpected UTF-8 BOM...

JSON形式が正しくない」と言われても、themes.json ファイルの中身はカンマや引用符のミスもなく、完璧に見えました。これは一体なぜでしょうか?

原因は目に見えない特殊なマーク:BOM (Byte Order Mark)

原因は、ファイルの内容ではなく、ファイルがどのように保存されているかにありました。

多くのテキストエディタは、日本語などのマルチバイト文字を扱う際、エンコーディングとして UTF-8 を使用します。しかし、UTF-8には「BOMあり」と「BOMなし」の二種類が存在します。

  • BOM (Byte Order Mark) あり: ファイルの先頭に、このファイルはUTF-8ですよ、という目に見えない印が付与されます。

  • BOM なし (推奨): 特殊な印は付かず、純粋なテキストデータのみで構成されます。

Pythonの標準的な json ライブラリは、通常、BOMなしのファイルを想定しています。そのため、themes.json の先頭にBOMが付いていると、それをJSONの開始記号 { ではない**「不正な文字」**と判断してしまい、「JSON形式が不正」というエラーを吐き出してしまっていたのです。


💡 解決策:「utf-8-sig」という救世主

この問題を解決するために、私には二つの選択肢がありました。

  1. エディタの設定を変える: themes.json を手動で「UTF-8 (BOMなし)」形式に変換し直す。

  2. コードで対応する: Python側でBOMを無視して読み込むようにする。

今回は、環境やエディタに依存せず、誰がファイルを作っても動作するように、コード側で対応することにしました。ううっいいヤツじゃん僕笑っ

 

PythonでBOMを処理してUTF-8として読み込むには、標準の 'utf-8' ではなく、'utf-8-sig' という特別なエンコーディングを指定します。

修正箇所(one_on_one_app.py 内)

 

Python
 
# 修正前 (BOMでエラー):
# with open(file_path, 'r', encoding='utf-8') as f:

# 修正後 (BOMを安全に処理):
with open(file_path, 'r', encoding='utf-8-sig') as f:
    data = json.load(f)

 

このわずか 'utf-8' から 'utf-8-sig' への変更で、アプリは無事に themes.json を読み込み、エラーは解消しました!


📝 まとめ

シンプルなアプリ開発でも、データの外部ファイル化を行うと、インデントエンコーディングといった基礎的な部分で予期せぬ壁にぶつかります。

この utf-8-sig のテクニックは、外部ファイル(特に日本語を含むJSONCSV)を扱う際の強力な防御策となります。ぜひ皆さんも、Streamlitアプリ開発の参考にしてください!