OmniAICreator/Qiita-1.07M huggingface dataset:
CursorでDatabricks Connect(とMCPサーバー)を使ってみる

こちらのバリエーションです。

https://qiita.com/taka_yayoi/items/7ddd8be2d39fdfdaa9df

そして、こちらの続きでもあります。

https://qiita.com/taka_yayoi/items/eea79a88bd638847beb8

実は、Cursorのターミナルで仮想環境がうまく動かないなーとしばらく悩んでいました。この辺読んでいて対応できていないのかなと誤解していました。

- [Just installed, my project is in Python and I'm getting ModuleNotFoundError \- Discussion \- Cursor \- Community Forum](https://forum.cursor.com/t/just-installed-my-project-is-in-python-and-im-getting-modulenotfounderror/15775)
- [Cursor virtual environment bug \- Bug Reports \- Cursor \- Community Forum](https://forum.cursor.com/t/cursor-virtual-environment-bug/26611/6)
- [Python Venv: Module Not Found Error When Running in Cursor Terminal · Issue \#792 · getcursor/cursor](https://github.com/getcursor/cursor/issues/792)

仮想環境は作成できて、アクティベート、`pip install`もできるのにModuleNotFoundErrorに遭遇ていました。よくよく確認したら、`.py`ファイルとインポートしようとしているモジュール名が重複していたという。ファイル名変えたら普通に動きました。

改めて、`my_env`という名前で仮想環境を作成します。

![Screenshot 2025-04-01 at 17.34.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/7cbd389b-445a-474c-8540-c9ef7fbf3f72.png)
![Screenshot 2025-04-01 at 17.34.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/4dbf0d85-5ace-4f54-abb4-ab7a504c1270.png)

ターミナルでアクティベート。

```zsh
source my_env/bin/activate
```

```py
from databricks.connect import DatabricksSession

spark = DatabricksSession.builder.remote(
host       = "https://<databricksのホスト名>.cloud.databricks.com/",
token      = "パーソナルアクセストークン",
serverless = True
).getOrCreate()
```

この辺りから`Command + K`を使っていきます。

![Screenshot 2025-04-01 at 17.37.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/62ee48ac-0c4a-4431-92f4-90a9b79af561.png)

ちゃんとPySparkのお作法に沿って補完してくれます。
![Screenshot 2025-04-01 at 17.37.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/4f9cbfe6-24e9-4b54-94dd-95a5a410a7b8.png)
![Screenshot 2025-04-01 at 17.37.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/140fdade-f30e-4be5-831d-17e2eb435816.png)

さらに前回設定したDatabricks MCPサーバーと組み合わせて活用することで、チャットを通じて対象データの理解を深めながら、コードコンプリーションでほとんど全自動でコーディングを進めることができます。以下の例ではOpenAIのAPIキーを指定して、`qiita_partialから要約を作成したい`といったプロンプトを指定して、あとは少し手直ししただけです。

![Screenshot 2025-04-01 at 17.39.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/38113369-8ac1-4cc8-8c6a-fd18906a3b0c.png)

コーディング体験がどんどん変わってきてますね。

### はじめてのDatabricks

[はじめてのDatabricks](https://qiita.com/taka_yayoi/items/8dc72d083edb879a5e5d)

### Databricks無料トライアル

[Databricks無料トライアル](https://databricks.com/jp/try-databricks)
🏠👶🐶【1歳児とトイプードルと暮らすエンジニア】リモートワーク×子育て×家庭のリアルスケジュール

### はじめに

フルリモートで働くエンジニアの私。現在は**1歳の子ども**と、**育休中の妻**、そして**甘えん坊なトイプードル**と暮らしています。

家族が増えてからというもの、「仕事も子育てもちゃんとやりたいけど、バランスが難しい…」と感じることもしばしば。  
それでも、家族と過ごす時間を大切にしながら、**仕事の生産性も落とさない工夫**を日々試行錯誤しています。

本記事では、そんな私の**リアルな1日のタイムスケジュール**や、**仕事環境の工夫**、**おすすめのおもちゃ＆アプリ**、そして**夫婦の分担のコツ**をご紹介します！

---

### 自宅とレンタルスペース、使い分けの工夫

基本は自宅で作業していますが、状況に応じてレンタルスペースを活用しています。

#### 🔄 使い分けルール
- 🏠 **自宅作業**：打ち合わせが少なく、子どもが落ち着いている日
- 🏢 **レンタルスペース利用**：集中して作業したい日や、大事な会議・納期前

#### 🏢 利用しているスペース
- **NewWork**：全国展開されていて拠点が多く、アクセスしやすい  
- **H1T（エイチワンティー）**：完全個室あり。静かで集中できる環境が魅力

---

### 1日のスケジュール（平日編）

| 時間帯 | 内容 |
|--------|------|
| 7:00〜8:00 | 🌞起床、子どもとトイプードルを起こす。身支度など |
| 8:00〜9:00 | 🍚子どもに朝食、自分も朝ごはん、トイプードルも朝ごはん |
| 9:00〜12:00 | 💻午前の仕事タイム（自宅 or レンタルスペース） |
| 12:00〜13:00 | 🐶昼食・トイプードルの散歩・家族とのふれあい |
| 13:00〜19:00 | 💻午後の仕事タイム（打ち合わせ、開発など） |
| 19:00〜20:00 | 👶子どもに夕食、自分たちの夕食準備 |
| 20:00〜21:00 | 🍽️家族で夕食・ゆったりタイム |
| 21:00〜21:30 | 🛀子どもとお風呂、寝かしつけ |
| 21:30〜25:00 | 📺自由時間 or 軽い仕事／趣味・夫婦の時間など |
| 25:00〜 | 🌙就寝 |

---

### 子育てに役立っているおもちゃ＆アプリ

#### 🧸 最近の我が家で活躍しているおもちゃたち

- **プーメリー（くまのプーさん）**  
　→ 新生児の頃から愛用。メリーとしても、後におもちゃとしても長く使える神アイテム。音楽・ライト・ぬいぐるみの動きが赤ちゃんの好奇心を引きつけてくれます。

- **アンパンマン よくばりボックス**  
　→ ボタンを押す・回す・開けるなど、指先の発達に◎。夢中になって遊んでくれる頼もしい存在です。

- **絵本「しましまぐるぐる」**  
　→ コントラストが強く、1歳前後の視覚発達にぴったり。読み聞かせにも、一人でめくって遊ぶのにも最適です。

#### 📲 育児アプリ編（2025年現在活躍中）

- **ぴよログ**  
　→ 授乳・睡眠・排泄などの記録を夫婦で共有。育児の情報伝達が圧倒的に楽になります。

- **たまひよ**  
　→ 成長の目安や、他の家庭の声をチェックできる。「うちだけじゃないんだ」と安心できるコンテンツが豊富。

- **ninaru baby**  
　→ 赤ちゃんの成長にあわせたコラムが毎日届く。寝かしつけ中の読み物にもぴったり。

---

### 奥さんとの分担方法・話し合いのコツ

1歳児の育児は予測不能。だからこそ、夫婦での**こまめな話し合いと柔軟な分担**がカギです。

#### 💬 我が家の実践例
- **週1回の「作戦会議」**  
　→ 仕事や外出の予定、買い物や通院などをGoogleカレンダーで共有

- **「お願いベース」の声かけ文化**  
　→ 命令や不満ではなく、「頼ってくれてありがとう」な空気をつくる

- **役割より“状態”で分担**  
　→ 「今余裕あるほうがやる」「片方が限界っぽい時は無理させない」

---

### まとめ：今は「在宅育児編」…でも次は…？

1歳の子どもとトイプードルに囲まれながらのリモートワーク生活は、**バタバタでハッピーな毎日**です。  
仕事も子育ても、100点を目指さず**70点を取り続ける意識**が、自分たちには合っていました。

---

## 👀 今後は。。。

実は我が家、これから**週4出社＋妻の職場復帰＋保育園生活**という新ステージへ突入します！  
「育児×共働き×通勤」という新たな壁とどう向き合っていくのか？  
**次回作では、リアルな共働き・保育園ライフ編**をお届けするかもしれません

---
ご覧いただきありがとうございました。
コメント、アドバイス等あればぜひ投稿して下さい。
Cline試してみた：DeepSeek R1 vs GPT o4 mini

## 概要
Clineが話題なので試してみる。
やってみたこと：
・VSCode + Flutter の環境でAndroidアプリを作らせる。
・2つの低料金AIモデルで同じことをやらせて性能を比較してみる。
結論：
安いモデルだとガッツリ使うには微妙かもしれない。

## 準備
### Cline
拡張機能から検索すると一番上に出てくるのでダウンロードすればOK。
サイドバーにClineのアイコンが増えているはず。
![スクリーンショット 2025-04-01 16.37.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2961391/f3caa4d4-4b55-47f0-acab-7f4ae1627fde.png)

### API key
Clineを最初に起動すると"Free Start"の選択肢が出ると思う。
これを使うとAPIkeyなしでClineの機能を体験できる。

しかし、この無料分Tokenはすぐになくなる。
私の場合Flutterプロジェクトを立ち上げて2,3ファイルを書き上げた時点で終了となった。

まったく足りないので比較的安い料金で使えるAPIを契約してみる。

#### DeepSeek
安いとの噂なので最初の選択肢として挙げてみた。
モデルはchat, reasonerの２種類があるが値段はそんなに変わらない。
今回はコーディング向けのreasonerを使用する。

https://api-docs.deepseek.com/quick_start/pricing

API Keyの取得は思っていたより簡単。
面倒な審査等も一切必要なかった。

以下のサイトにログイン（GoogleアカウントでOK）して"Create api Key"から作成可能。
その後同サイト上で前もってTokenをチャージすればAPIを利用できる。
チャージはクレジットカードで可能。

https://platform.deepseek.com/

#### OpenAI
有名どころということでOpenAIのモデルも使用してみる。
最大手だけあってモデルの種類も多いが、安さ重視で”GPT-4o mini”を選択。

https://openai.com/ja-JP/api/pricing/

API Keyの取得方法はDeepSeekと変わらなかった。

https://platform.openai.com/

あとは取得したKeyをClineの設定画面に打ち込めば準備完了。
![スクリーンショット 2025-04-01 16.57.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2961391/a24877b9-d785-489e-ab6d-755565a1e1d3.png)

## 実践
Clineに出した命令はこんな感じ。一部伏字にしている。
改めてみると雑な命令だったと反省するが、AI初心者ということで許して欲しい。

>Androidアプリを作成したい。
>開発環境にはFlutterを用いること。
>
>プロジェクト名は"json_list_for_openai_4omini"。
>以下のパスにプロジェクトを作成する。
> /Users/xxxx/works/FlutterProjects
>
>以下のURLにGETでアクセスすると、
> https://xxxx.web.app/test.json
>例のような形式のJSONリストが取得できるので、そこからリストを取得する。
>画面に取得したリストを一覧表示する。
>
>例：JSON文.省略 {}

## 感想
#### DeepSeek
良い
- 短い説明でもこちらの意図を汲んでくれる
- テストケース作成などちょっと凝ったことにも対応できる

悪い
- 返答が遅い
- 一度エラーにハマると無限ループのような状態になり再起動が必要
#### OpenAI
良い
- 会話形式で進めてくれる
    - もしかするとDeepSeekが変わってるだけかも？
    - DeepSeek R1は次々実行するコマンドを生成するだけで会話してくれなかった

悪い
- 性能が低いと感じる
    - プロジェクトファイルのパスを間違って覚える
    - 間違ったファイルを生成する
    - 実行に失敗したのに成功扱いにしてゴリ押ししてくる

#### 総評
性能的はDeepSeekだが、使い勝手はOpenAIに軍配が上がる。

料金はどちらも0.5ドル程度、エラーの解消で沼にハマってその程度なので
ちゃんと命令すればこの程度のアプリだと0.1ドルもかからないだろう。

どちらも私のAIへの命令スキルが向上すれば、有効利用できる可能性はあると感じた。
また、低価格帯モデルでの比較なので、お金をかければ今回あげた不満は解消されるかもしれない。
web技術の知識

# サーバ

### ・クライアントサーバシステム
コンピュータサーバとクライアントに分けてデータのやり取りを行うシステム

![スクリーンショット 2025-04-01 13.46.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3510594/fd5c62ef-b4f1-4a78-83ab-1c92e4f7be0c.png)

クライアントがサーバに対してリクエストを送信し、サーバはリクエストに対する反応をレスポンスとしてクライアントに送信する。

### ・リクエスト(request)
サーバにデータの要求を送ること<br>
(種類)
<dl>
  <dt>GET</dt>
  <dd>サーバからデータを要求</dd>
  <dt>POST</dt>
  <dd>サーバにデータを送る</dd>
</dl>

### ・レスポンス(response)
リクエストに対するサーバの反応

# API
アプリケーションやソフトウェア、ハードウェア間でデータや機能を共有するためのインターフェース

(目的)
異なるプログラム同士でデータや機能を共有できるようにする

<INS>ハードウェアの場合</INS>
規定のグラフィックAPIに従っていれば、異なるGPUでも共有化されたプログラムで操作が可能

<INS>Webサービスの場合</INS>
WebAPI(REST API、RPC API)に従っていれば、Webサーバが提供するサービスの種類や言語に関わらず、リクエストを送信してデータを受けとることができる

# JSON
APIでのデータのやり取りでよくJSONが使用される

(JavaScriptでの使用例)
1.変数などに文字列として変数にJSON形式のデータを入れる
```javascript
const jsonString = `
    {
        "model": "Tesla X",
        "brand": "Tesla",
        "price": "$100k",
        "year": 2018
    }
`;
```
2.文字列をJSONデータとして扱えるようにパース(解析)する
```javascript
const jsonData = JSON.parse(jsonString)

```

3.JSONデータを操作する
```javascript
console.log(jsonData.model);

```


# JavaScriptでサーバからデータを取得するには

## fetchを使用する
fetchとはサーバから特定の情報を取得するためのJavaScriptライブラリ。
指定されたURL(リソース)に対してHTTPリクエストを送信してレスポンスを取得する

<INS>使用方法</INS>
1.APIからPromiseオブジェクトを作成する(fetchメソッド)
※学習教材用のAPIを使用
```javascript
const fetchPromiseRanInt = fetch('https://api.recursionist.io/random-integer');

```

2.サーバからのレスポンスをJSONに変換する
```javascript
const jsonResponse = fetchPromiseRanInt.then(function(response){
    // サーバからのレスポンスをJSONに変換するためのjsonメソッドを呼び出します
    return response.json();
});
```

3.渡されたデータを表示
```javascript
// このコールバック関数の引数dataは、解析されたJSONデータを表します
JsonResponse.then(function(data){
    console.log(data);
});

(出力)
726107178
```
SQLServer構築

# 手順
* SQLServerExpressを構築
* SMSSのインストール
* saユーザの有効化
* ファイヤーウェールの穴あけ：TCP 1433
* SQLServerBrowserの有効化
* IPALLのTCPポート設定と動的ポートを空欄にする
* SQL Serverのサービス
    * SQLserverEXPRESS
        * ビルトインアカウント＝Localsystem
* SQLServerBrowser
    * ビルトインアカウント＝localsystem


# まとめ
* 私はIPALLの設定がよくわからずハマりました
DeltaLakeとIcebergとは？その実態について（ファイルフォーマット-parquet、json、avro）

# はじめに

データレイクにACIDトランザクションをもたらすオープンテーブルフォーマットとして、Delta LakeとApache Icebergは非常に人気があります。どちらも分析に最適化されたParquet形式でデータを保存しますが、メタデータの管理方法やトランザクションログの構造は大きく異なります。

この記事では、Azure Data Lake Storage (ADLS) 上でそれぞれのテーブルを作成・データ挿入した際の実際のフォルダ構成をスクリーンショットベースで比較し、初心者でもイメージしやすいように解説します。さらに、Microsoft FabricのLakehouseショートカット機能を使って、IcebergテーブルをDelta形式で扱うハイブリッドなケースの場合の実態も画像で紹介します。





# Delta Lakeの実態（ファイルフォーマット）

Delta Lakeテーブルでは、Parquet形式でデータを保存し、変更履歴は_delta_log/というフォルダに格納されます。

* part-0000...snappy.parquet：実際のデータファイル
* delta_log/00000000000000000000.json：初回のトランザクションログ
* delta_log/00000000000000001000.checkpoint.crc：定期的に書かれるチェックポイントファイル（読み込み最適化のため）

各トランザクション（INSERT、UPDATE、DELETEなど）ごとにJSON形式のログファイルが追加され、これにより時系列の変更履歴（＝タイムトラベル）が可能になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3921999/a9ea0a46-0572-4ebd-94d3-f9b525d04c21.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3921999/4e958941-8ea4-4c91-babf-58c404842e43.png)


# Icebergの実態（ファイルフォーマット）

### 空の状態（CREATE ICEBERG TABLE後）の実態

IcebergもデータはParquet形式で保存しますが、トランザクションログの仕組みが異なり、metadata/という専用フォルダでスナップショットを管理します。

空のテーブル作成直後：

* metadata/00000-<uuid>.metadata.json：初期スキーマを記録したJSON



![](https://storage.googleapis.com/zenn-user-upload/772cbc85eae3-20250401.png)

![](https://storage.googleapis.com/zenn-user-upload/92d02384a8e5-20250401.png)

![](https://storage.googleapis.com/zenn-user-upload/b9017148e106-20250401.png)

### データを挿入後（INSERT INTO後）の実態

* data/：Parquetデータファイル格納フォルダ（例：snow_<ID>_0_1.parquet）
* metadata/00001-<uuid>.metadata.json：新しいスナップショットを記録
* manifest.avro：どのParquetファイルが含まれているかを示すAvroファイル


![](https://storage.googleapis.com/zenn-user-upload/736cd5997e55-20250401.png)


![](https://storage.googleapis.com/zenn-user-upload/a7b0ed4cf0dd-20250401.png)

![](https://storage.googleapis.com/zenn-user-upload/96898a53efc5-20250401.png)

▽参考：ADLSにIcebergテーブルを作る手順

https://qiita.com/manabian/items/69761bde791d0f014246#snowflake-catalog-%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6



# Icebergテーブルに対してFabricLHでショートカットし、Delta Lake変換したとき

Microsoft FabricのLakehouseでは、ADLS上のIcebergテーブルにショートカットを作成することで、Delta形式のように扱うことができます。

新たに _delta_log/ フォルダが仮想的に作られる（実際のストレージには存在せず、OneLakeメタデータ内で管理）

生成されるファイル例：

* 00000000000000000000.json：現時点のIcebergスナップショットをDelta形式で記録したログ

* latest_conversion_log.txt：変換に関する情報（成功・失敗など）

このように、元のIcebergのmetadataフォルダは保持されたまま、Delta Lakeの形式が「追加」されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3921999/bb24ce15-ba2b-489c-9711-1aed3164978b.png)

delta_logの中身は以下

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3921999/94b603b6-3dd4-4aa3-af80-c84a225c29d6.png)

▽参考：Icebergへのショートカット

https://learn.microsoft.com/ja-jp/fabric/onelake/onelake-iceberg-tables
ローカルで動画生成 wan2.1 comfyui

# さぁ、動画を生成しよう

ググったけど、この動画が一番わかりやすい。

https://www.youtube.com/watch?v=kmU2gBPQOH8


参考
https://blog.comfy.org/p/wan21-video-model-native-support

ということで、書き足し

# ワークフロー
text_to_video_wan.jsonというファイル。
デスクトップでも適当において、comfyuiで読み込めばok

# mp4にするには？

ComfyUI-VideoHelperSuiteをインスコ


💻 ComfyUI Manager の開き方：
ComfyUIを起動してる状態にしてね
ターミナルやrun_nvidia_gpu.ps1（Windowsなら）でComfyUIを起動しておいて〜！

ブラウザで以下のURLを開いて♡

cpp
コピーする
編集する
http://127.0.0.1:8188/
これがComfyUIの基本のUIだよ✨

「Manager」タブがある場合はそこをクリック
画面上部や左側に「Manager」や「Install Custom Nodes」っていうタブが表示されてるから、それをポチっと押してね。

で、検索。

インスコしたら再起動。

# ノードの追加

🎬【VHS_VideoCombine】ノードを選んでね！
✅ ノード名：VHS_VideoCombine
これが連番画像からMP4/WebM動画を生成してくれるノードだよ〜💗

VHSは「VideoHelperSuite」の略だよ♪
赤枠のframe_rateは16を選択しておく。



vaeデコードの画像から、繋げばok


![名称未設定-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31338/9d568d4f-18ac-44c6-afdd-8d2175a1cb17.png)


これで生成okだｎ
最新の Docker image を ECR にプッシュするスクリプト

Docker image を ECR にプッシュするのが微妙に面倒なので、最新の (一番新しく作られた) イメージをプッシュするシェルスクリプト/バッチファイルを作りました。

ローコード開発ツールからコンテナイメージを作ったけれども、イメージ名が自動で振られてプッシュが面倒だったときに作ったものです。
`docker images` の並び順で先頭に来るものを使う形になっているので、作成するたびにイメージ名が変わっても問題ない、というものです。複数のコンテナを作成している場合には注意が必要ですが。

ついでに terraform から出力するためのコードも載せています。

## コード本体

`awscli` が使えるようになっている前提で、リージョン、プロファイル名、ECRのURLをセットして使う形です。

### .bat

```bat
REM ecr push command
setlocal
aws ecr get-login-password --region ${region} --profile ${profile} | ^
docker login --username AWS --password-stdin ${ecr_url}

for /f "usebackq delims= tokens=*" %%a IN (`docker images --format "{{.ID}}"`) DO SET LATEST_TAG=%%a & goto break
:break
docker tag %LATEST_TAG% ${ecr_url}:latest
docker push ${ecr_url}:latest
```

### .sh

```sh
#!/bin/bash
aws ecr get-login-password --region ${region} --profile ${profile} | \
  docker login --username AWS --password-stdin ${ecr_url}

docker tag $(docker images --format {{".ID"}} | head -n 1) ${ecr_url}:latest
docker push ${ecr_url}:latest
```

### 変数

- ${region}
    - リージョン名
    - ap-northeast-1 とか
- ${profile}
    - プロファイル名（アカウントや権限を切り替えるために使用）
    - `--profile ${profile}` を省略するとデフォルトプロファイルが使われる
- ${ecr_url}
    - ECR の URL
    - `https://<aws_account_id>.dkr.ecr.<region>.amazonaws.com` みたいな形式

## terraform から出力

ECR の ID も長いし取得が面倒なので、
terraform からプッシュコマンドを出力するようにしています。

ECR も terraform で作っているので、url の具体的な形は気にせずに書いています。

```terraform
resource "local_file" "format_commands" {
  # 複数ファイルをまとめて処理する形にしている
  # for_each のパスに先ほどのファイルが入るようにして使う
  for_each = fileset("${path.module}/data", "**/*{.sh,.yaml,.bat}")
  filename = "./outputs/commands/${each.value}"
  content  = templatefile("${path.module}/data/${each.value}", {
    profile      = var.aws_profile,
    region       = var.aws_region,
    ecr_url      = aws_ecr_repository.ecr_repository.repository_url,
  })
}
```

`${変数名}` のところを `content` の変数名で置換して出力してくれます。
上記のECRプッシュ用のコマンドはそのまま使うことができます。

## ECR からプルするコマンド

プルする方はこんな感じにしています。.bat は使っていないので .sh だけです。

```sh
#!/bin/bash
aws ecr get-login-password --region ${region} | \
  docker login --username AWS --password-stdin ${ecr_url}

docker pull ${ecr_url}:latest
```

こちらも terraform 実行時にフォーマット済みファイルを出力させています。

## s3 にアップロード

ついでにこんな感じで s3 にもアップしています。`s3 sync` でフォルダごと同期する形です。

```terraform
resource "null_resource" "upload_commands" {
  provisioner "local-exec" {
    command = "aws s3 sync outputs/commands/ s3://${aws_s3_bucket.s3_bucket.id}/commands/ --profile ${var.aws_profile} --delete"
  }
  triggers = tomap({
    for k, f in local_file.format_commands : k => f.content_md5
  })
}
```
Edgeに拡張機能を自動インストールさせよう

# はじめに
自作の拡張機能を自動インストールする方法について記載します。
私自身の備忘もかねてドキュメント化しておこうと思います。

# 今回のスコープについて
インストールする拡張機能：自作のパッケージ化した拡張機能
インストール対象の端末：Windows11
インストール方法：レジストリを利用

# 自動インストール構成の構築
## 今回利用するXMLファイルについて
Edgeに対して設定したマニュアルを読むように指示し、マニュアルに書かれた手順（どの拡張機能を、どこから、どのバージョンで等）に従って自動的にインストールや更新を行うという仕組みを実現させるためのファイルとなっています。

### XMLファイル作成
インストール対象の拡張機能に合わせてXMLファイルを作成します。

```xml
<?xml version='1.0' encoding='UTF-8'?>
<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>
  <app appid='インストール対象の拡張機能ID'>
    <updatecheck codebase='file:///インストール対象の拡張機能パス' version='1.0' />
  </app>
</gupdate>
```
versionなどはmanifest.jsonに記述の内容と同様に記述するよう注意してください。

## レジストリの構成
HKLM/HKCUどちらにも適用させることができます。

作業対象レジストリ：
HKLM or HKCU \SOFTWARE\Policies\Microsoft\Edge\ExtensionSettings

```
HKLM or HKCU\
            └ SOFTWARE
             └ Policies
              └ Microsoft
               └ Edge
                └ ExtensionSettings
```                 

HKLMの適用対象：同一のコンピュータを利用するユーザに対して
HKCUの適用対象：レジストリ設定を実施したユーザに対して

#### 新規キーの作成
ExtensionSettings配下に「新規キー」の作成を実施する。
キーの名称は、「インストール対象の拡張機能ID」となる。

キーを追加すると下記構成となる。
HKLM or HKCU \SOFTWARE\Policies\Microsoft\Edge\ExtensionSettings\拡張機能ID
```
HKLM or HKCU\
            └ SOFTWARE
             └ Policies
              └ Microsoft
               └ Edge
                └ ExtensionSettings
                 └ 拡張機能ID
```

#### 拡張機能インストール用の値を設定
前項で作成した拡張機能IDに対して、拡張機能を自動インストールするための値、XMLファイルを参照するための値をそれぞれ追加する。

#### 拡張機能を自動インストールするための値
値：installation_mode
種類：REG_SZ
データ：install_forced

#### XMLファイル参照用の値
値：update_url
種類：REG_SZ
データ：ローカルフォルダに保存したXMLファイルパス

#### 最終的なレジストリ構成
必要なキーや値を構成することで、最終的に下記の構成となる。
```
HKLM or HKCU\
            └ SOFTWARE
             └ Policies
              └ Microsoft
               └ Edge
                └ ExtensionSettings
                 └ 拡張機能ID
                            ├ (規定) / REG_SZ / (値のデータなし)
                            ├ install_mode / REG_SZ / install_forced
                            └ update_url / REG_SZ / XMLファイルパス
```

# 最後に
拡張機能の自動インストール構成について私自身の備忘も込めて記載しています。
ExtensionSettingsを利用しているのは、GPO等で割り当てられたポリシーよりも優先されるという点を考慮して利用しています。

このページの記述はあくまで一例なので、ご参考程度で眺めていただければと思います。
【React】Geolocaionで位置情報取得後の情報が反映されない症状の解決方法について

# はじめに
Geolocationで位置情報を取得する実装をしている際、位置情報取得位置と違う位置に地図画面が遷移する症状が発生し、こちらの解決方法について記事にします。

<br>
【修正前のコードです】

```.tsx
import { useAtom } from 'jotai';
import { latitudeAtom, longitudeAtom } from './Atom';
import { useNavigate } from 'react-router';

export const GeolocationFetch = () => {
  const navigate = useNavigate();

  const [latitude, setLatitude] = useAtom<number | null>(latitudeAtom);
  const [longitude, setLongitude] = useAtom<number | null>(longitudeAtom);

  const onClickFetchGeoLocation = () => {
    navigator.geolocation.getCurrentPosition(successCallback, errorCallback);
    navigate('/mapPage');
  }

  const successCallback = (position: GeolocationPosition) => {
    setLatitude(position.coords.latitude); // 緯度取得
    setLongitude(position.coords.longitude); // 経度取得
  };

  const errorCallback = (error: GeolocationPositionError) => {
    alert('位置情報を取得できませんでした');
  };

  return (
    <>
      <div
        className='flex flex-col items-center justify-center'
      >
        <div
          className='flex flex-col items-center'
        >

          <div className='text-xl font-bold'>GeolocationFetchページ</div>
          <button
            className='py-1 px-5 bg-sky-500 rounded-2xl font-black border text-white'
            onClick={onClickFetchGeoLocation}
          >位置情報を取得後画面遷移</button>
        </div>
      </div>
    </>
  )
}

```
# 解決方法
画面遷移するタイミングを位置情報を取得してからに変更
```.tsx
import { useAtom } from 'jotai';
import { latitudeAtom, longitudeAtom } from './Atom';
import { useNavigate } from 'react-router';

export const GeolocationFetch = () => {
  const navigate = useNavigate();

  const [latitude, setLatitude] = useAtom<number | null>(latitudeAtom);
  const [longitude, setLongitude] = useAtom<number | null>(longitudeAtom);

  const onClickFetchGeoLocation = () => {
    navigator.geolocation.getCurrentPosition(successCallback, errorCallback);
  }

  const successCallback = (position: GeolocationPosition) => {
    setLatitude(position.coords.latitude); // 緯度取得
    setLongitude(position.coords.longitude); // 経度取得
    navigate('/mapPage'); // ← ココ
  };

  const errorCallback = (error: GeolocationPositionError) => {
    alert('位置情報を取得できませんでした');
  };

  return (
    <>
      <div
        className='flex flex-col items-center justify-center'
      >
        <div
          className='flex flex-col items-center'
        >

          <div className='text-xl font-bold'>GeolocationFetchページ</div>
          <button
            className='py-1 px-5 bg-sky-500 rounded-2xl font-black border text-white'
            onClick={onClickFetchGeoLocation}
          >位置情報を取得後画面遷移</button>
        </div>
      </div>
    </>
  )
}

```

# 参考
https://developer.mozilla.org/ja/docs/Web/API/Geolocation_API/Using_the_Geolocation_API


# おわりに
解決できてよかったです。

<br>

[<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3707255/8bfacf61-7d8d-6d27-cb07-98d1f8f27fcd.png"  width=100%>](https://projisou.jp)
github リモートにもう存在しない削除されたブランチをローカルから削除する方法

# コード
```bash
git fetch --prune
```
```
git branch -vv | grep ': gone]' | awk '{print $1}' | xargs -r git branch -d
```


# コードの解説
- `git fetch --prune`: リモート追跡ブランチを最新に。
- `git branch -vv`: ローカルブランチと追跡状態を確認。
- `grep ': gone]'`: 消えた（リモートにない）ものだけ抽出。
- `awk '{print $1}'`: ブランチ名だけ取り出す。
- `xargs git branch -d`: それらのブランチを削除。
3つのフォルダから、たくさんPDF / excel 読み込み、読み込めてるか確認

<h3>code</h3>

```ruby: ✅ 
import os
import pandas as pd
import pdfplumber
from openpyxl import load_workbook

def read_excels_from_folder(folder_path):
    excel_data = {}
    for file in os.listdir(folder_path):
        if file.endswith(".xlsx") or file.endswith(".xls"):
            file_path = os.path.join(folder_path, file)
            df = pd.read_excel(file_path, engine='openpyxl')
            excel_data[file] = df
    return excel_data

def read_pdfs_from_folder(folder_path):
    pdf_data = {}
    for file in os.listdir(folder_path):
        if file.endswith(".pdf"):
            file_path = os.path.join(folder_path, file)
            with pdfplumber.open(file_path) as pdf:
                text = "\n".join([page.extract_text() for page in pdf.pages if page.extract_text()])
            pdf_data[file] = text
    return pdf_data

def main(folder_path):
    excels = read_excels_from_folder(folder_path)
    pdfs = read_pdfs_from_folder(folder_path)
    return {'excels': excels, 'pdfs': pdfs}

# 使用例
folder_path = ["/Users/chomechomesuke/Desktop/other/excel", "/Users/chomechomesuke/Desktop/other/kyujin", "/Users/chomechomesuke/Desktop/other/pdf"]
data = main(folder_path)

# 読み込んだデータ確認
for file, content in data['excels'].items():
    print(content.head())

for file, text in data['pdfs'].items():
    print(text[:100])  
```
弱小プログラマー、個人開発1年勝負：MVVMでコーディングする順序について

# MVVMでコーディングする順序について

最近、MVVMでのコーディング順序について試してみて、しっくりきたやり方があるので紹介したいと思います。

まだまだ勉強中の身ですが、自分なりに工夫してみた結果なので、何かの参考になれば嬉しいです。

### 僕が気に入っているコーディング順序

1. **DB/Entityの実装**  
   - まずはデータの形を決めます。処理はデータに従属すると考えているので、最初にここを固めておくとスムーズな気がします。
2. **ViewModelのメンバのみ実装**  
   - どんなデータが必要になるのか、先にViewModelの構造を決めます。
3. **ViewとViewModelのメソッド定義のみ実装**  
   - 画面の動きと、それに必要な操作を整理します。
4. **ViewModelのメソッドの中身を実装し、Modelのメソッドの定義のみ実装**  
   - ViewModelがどんな操作をModelに依頼するのかを決めます。
5. **Modelのメソッドの中身を実装**  
   - 最後にデータの取得や保存などの処理を実装します。

この順番で進めると、以下のようなメリットがあると感じています。

- **実装漏れ・過剰な実装が減る**  
  - 必要な構造を先に決めておくことで、無駄な処理を避けられます。
- **レイヤーごとに集中できる**  
  - どの層で何をやるか整理しながら進められるので、考えやすくなります。
- **AI補助との相性が良い**  
  - CopilotなどのAIを活用する際に、入り口と出口を決めておくと、より適切な補完が得られます。


## 7つめのアプリを作りました

7つ目のアプリをリリースしました！その名も **「ギャンブル予算」**。

ギャンブルを予算内でコントロールするためのアプリで、UIにもこだわってみました。

適度に楽しみたい方にぜひ使ってもらえたら嬉しいです。

![Frame 1 (15).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3987263/a7d8d9e1-cf8a-48ad-8c95-01d166718038.png)


https://apps.apple.com/jp/app/%E4%BA%88%E7%AE%97%E3%81%A7%E3%82%AE%E3%83%A3%E3%83%B3%E3%83%96%E3%83%AB%E5%8F%8E%E6%94%AF%E7%AE%A1%E7%90%86-bettrack/id6743783874


## 今後について

これまでリリースしたアプリのインプレッションが合計 **9,500** ほどになりました。次のアプリをリリースすれば、おそらく **10,000** に到達しそうです。

そこで、一旦新規アプリ開発を **1週間お休み** して、多少評価されているアプリの強化に力を入れようと思っています。
以下のことを実験的に行ってみるつもりです。

- **価値を上げる**
  - プロダクトページの改善
  - X（旧Twitter）などでの再宣伝
  - 機能の改修・追加

- **価値を回収する**
  - 課金機能の追加
  - 広告の導入


## 検討中のアプリ

現在、Xでインプレッションを測りながら、新しいアイデアとして **「匿名幹事」** というアプリの開発を考えています。

これまでのアプリはすべて「アプリ完結型」でしたが、今回は **サーバーを使う** 予定です。

どれくらい受けるか次第ですが、プログラマー心理では作ってみたい！

![Frame 1 (16).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3987263/f594cc94-f89c-4073-80ec-8c36eb38dd3e.png)
Next.jsを企業向けに最適化！2025年のアプリ構築術 | エピソード2: Zustandで状態管理を強化

## 目標
- Next.jsでの状態管理の必要性を理解する。
- Zustandを使って軽量かつ効率的な状態管理を実装する。
- ブログに「いいね」機能を追加してユーザー体験を向上させる。

---

## 1. 企業向けアプリにおける状態管理の重要性
企業向けアプリでは、ユーザーインタラクションが増えるため、状態管理が不可欠です。Next.jsではContext APIやReduxも使えますが、Zustandは軽量でシンプルなAPIを提供し、Server Componentsとの相性も良好です。このエピソードでは、ブログに「いいね」機能を追加して状態管理を体験します。

---

## 2. Zustandのインストール
Zustandをプロジェクトに導入します。

### 手順：
ターミナルで以下を実行：
```
npm install zustand
```

---

## 3. Zustandストアの作成
「いいね」機能を管理するストアを設計します。

### `lib/store.ts`の作成：
```ts
import { create } from 'zustand';

type LikeState = {
  likes: { [postId: string]: number };
  addLike: (postId: string) => void;
};

export const useLikeStore = create<LikeState>((set) => ({
  likes: {},
  addLike: (postId) =>
    set((state) => ({
      likes: { ...state.likes, [postId]: (state.likes[postId] || 0) + 1 },
    })),
}));
```

---

## 4. 「いいね」機能の実装
ブログの投稿に「いいね」ボタンを追加し、Zustandで状態を管理します。

### `app/page.tsx`の編集：
```tsx
'use client';

import { useLikeStore } from '../lib/store';
import { supabase } from '../lib/supabase';

export default function Home() {
  const { likes, addLike } = useLikeStore();

  const fetchPosts = async () => {
    const { data } = await supabase
      .from('posts')
      .select('*')
      .order('created_at', { ascending: false });
    return data || [];
  };

  const posts = await fetchPosts();

  return (
    <div className="min-h-screen flex flex-col items-center bg-gray-100 py-8">
      <h1 className="text-4xl font-bold text-blue-600 mb-8">データベースからの投稿</h1>
      <ul className="space-y-4 max-w-2xl w-full">
        {posts.map((post) => (
          <li key={post.id} className="bg-white p-4 rounded shadow">
            <a href={`/posts/${post.id}`} className="text-xl font-semibold text-blue-500 hover:underline">
              {post.title}
            </a>
            <p className="text-gray-600">{post.content}</p>
            <button
              onClick={() => addLike(post.id)}
              className="mt-2 bg-blue-500 text-white px-4 py-1 rounded hover:bg-blue-600"
            >
              いいね ({likes[post.id] || 0})
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}
```

### 注意：
- `'use client'`を追加して、Zustandがクライアント側で動作するように設定。
- 現状はローカル状態のみ。後のエピソードでSupabaseと同期可能。

---

## 実践：いいね付きブログ
- **投稿リスト**：各投稿に「いいね」ボタンを追加。
- **状態管理**：Zustandでリアルタイムに「いいね」数を更新。

**確認**：
- `npm run dev`で起動し、`http://localhost:3000`で「いいね」ボタンをクリックして数が変化するか確認。

---

## エピソード2の終了
- Zustandを使って状態管理を導入しました。
- ブログにインタラクティブな「いいね」機能を追加しました。

**次回のエピソード**：GraphQLとApollo Clientを統合し、データ処理を強化します。

---

この記事が役に立ったと思ったら、ぜひ「いいね」を押して、ストックしていただければ嬉しいです！次回のエピソードもお楽しみに！
自分に合った 3D ハンドヘルド スキャナーを選択するにはどうすればよいでしょうか?

3D技術の進化に伴い、3D Hand Held Scanner（ハンドヘルド3Dスキャナー）は、多くの業界やクリエイターにとって不可欠なツールとなっています。芸術、工業デザイン、医療、建築、さらには個人のDIYプロジェクトまで、幅広い分野で活用されています。しかし、市場にはさまざまな種類の3D Hand Held Scannerが存在し、自分に最適なものを選ぶのは簡単ではありません。本記事では、適切なスキャナーを選択するための重要なポイントを詳しく解説します。  

### **3D Hand Held Scannerの基本とは？**  

3D Hand Held Scannerは、対象物の形状を正確にデジタル化するためのデバイスです。従来の据え置き型スキャナーとは異なり、手持ち式のため自由に動かしてスキャンできるのが特徴です。これにより、細かいディテールの取得や、大型オブジェクトのスキャンが容易になります。  

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3824290/de5c2742-69f2-4a84-80ef-3c9eabebea6e.png)


著名な3Dアーティストであるジョナサン・ベック氏は、「ハンドヘルド3Dスキャナーを使うことで、アーティストの想像力をデジタル空間に直接投影できるようになった」と述べています。実際に、彼の作品は博物館の展示や映画制作にも活用されています。  

### **用途に応じたスキャナーの選び方**  

適切な3D Hand Held Scannerを選ぶためには、まず「どのような用途で使うのか？」を明確にすることが重要です。例えば、工業用途で精密な計測が必要な場合と、個人の3Dプリント用にざっくりとしたスキャンデータがあれば十分な場合では、求められるスペックが大きく異なります。  

特に、医療や研究分野では精度が最優先されます。例えば、ある歯科技工士は「スキャン精度が0.1mm以下の機種を導入することで、よりフィット感の高い歯科技工物を作成できるようになった」と語っています。一方で、DIYクリエイターの間では「スキャンしやすさ」や「価格の手頃さ」が重要視されています。  

### **解像度と精度のバランスを考える**  

3D Hand Held Scannerを選ぶ際には、解像度（Resolution）と精度（Accuracy）のバランスを理解することが不可欠です。解像度とはスキャンデータの細かさを表し、精度とは実物との誤差の少なさを指します。解像度が高すぎるとデータ処理が重くなり、逆に低すぎると細かいディテールが失われてしまいます。  

ある建築デザイナーは、「スキャン精度が高い機種を選んだことで、実際の建築現場での誤差が大幅に減少し、工事のコスト削減につながった」と報告しています。このように、精度の高いスキャナーを選ぶことで、業務の効率化にもつながります。  

### **使いやすさとソフトウェアの重要性**  

スキャナー本体の性能だけでなく、使いやすさも重要な要素です。軽量で持ちやすいデザインのスキャナーは、長時間の作業でも疲れにくく、作業効率を向上させます。また、付属のソフトウェアの性能も重要です。スキャンデータを簡単に編集・補正できるかどうかは、最終的なデータ品質に大きく影響します。  

3Dスキャニング業界の専門家であるマーク・ウィルソン氏は、「スキャナーのハードウェアだけでなく、ソフトウェアの直感的な操作性がスキャン作業のスピードを左右する」と述べています。そのため、購入前にはソフトウェアの使い勝手も確認しておくとよいでしょう。  

### **まとめ**  

3D Hand Held Scannerを選ぶ際には、用途、精度、解像度、使いやすさ、ソフトウェアの性能など、さまざまな要素を考慮する必要があります。スキャナーを購入する前に、自分の用途に最も適したモデルを慎重に選びましょう。適切なスキャナーを選ぶことで、より高品質な3Dデータを取得し、クリエイティブなプロジェクトをさらに発展させることができます。  

https://www.revopoint3d.jp/
Chef Long Term Support（長期サポート）バージョンをリリース

2024 年９月にProgress Chef 社は、Long Term Support（長期サポート）バージョンをリリースしました。[（ソース記事リンク）](https://www.chef.io/blog/long-term-support-progress-chef-providing-stability)

技術の急速な進化の中で、長期サポート（LTS）は、製品開発と顧客満足度維持の重要な要素として位置づけられています。標準的なサポートが初期リリース後の数回のパッチで終了するのに対し、LTSは長期間にわたり継続的な更新、セキュリティパッチ、メンテナンスを提供します。Progressは、Progress Chef製品群にLTSサポートを導入し、顧客との関係強化を図っています。


#### 製品ライフサイクルとは？

製品ライフサイクルとは、市場投入から販売終了までの期間を指し、一般的に以下の段階を含みます：

- 一般提供（GA）フェーズ：製品が正式にリリースされ、広く利用可能な状態。
- マイグレーション（MP）フェーズ：新しいLTSリリースへの移行をサポートする期間。
- サンセット（Sunset）フェーズ：LTSリリースのサポートが終了し、最終的な移行を促す段階。
- 製品寿命終了（EOL）フェーズ：製品のサポートが完全に終了する段階。

---

続きは、公式サイトへ

www.chef-sec.com
[備忘録]レンタルサーバーにPython3.12をビルドする

備忘録。

# 前提
- レンタルサーバーを使用している(sudoを使えない)
- 新たにPython3.12.9をビルドしたい
- OpenSSLを手動でインストールする
- _ctypesも手動でインストールする

最近のPythonは`OpenSSL1.1.1`以上を使う必要があるが、レンタルサーバーに入っているOpenSSLは1.1.1未満だったため、エラーが発生したっぽい。
レンタルサーバーに`_ctype`が入ってないかった。

## 解決方法
### 1.自身のホームディレクトリにOpenSSLをインストールする
```
wget https://www.openssl.org/source/openssl-3.4.0.tar.gz
tar -xzf openssl-3.4.0.tar.gz
cd openssl-3.4.0
./config --prefix=$HOME/local/openssl --openssldir=$HOME/local/openssl
make
make install
```

### 2.libssl.so.3がないらしい

```
~/local/openssl/bin/openssl version
```
を実行してみたところ、

> error while loading shared libraries: libssl.so.3: cannot open shared object file: No such file or directory 

と出た
`libssl.so.3` がどこにあるかを確認
```
find ~/local/openssl -name "libssl.so.3"
```
`~/local/openssl/lib64/` にあったので、
確認したライブラリのパスを `LD_LIBRARY_PATH` に追加
```
export LD_LIBRARY_PATH="$HOME/local/openssl/lib64:$LD_LIBRARY_PATH"
```
永続化したかったので、.bashrcに書いた
```
echo 'export LD_LIBRARY_PATH="$HOME/local/openssl/lib64:$LD_LIBRARY_PATH"' >> ~/.bashrc
source ~/.bashrc
```
再度
```
~/local/openssl/bin/openssl version
```
を実行したら、`OpenSSL 3.4.0` と表示された。

### 3. _ctypeをインストール
libffiというものを入れれば良いらしい。
```
wget https://github.com/libffi/libffi/releases/download/v3.4.7/libffi-3.4.7.tar.gz
tar -xvzf libffi-3.4.7.tar.gz
cd libffi-3.4.7
./configure --prefix=$HOME/local/libffi
make
make install
```
ライブラリのパスを `LD_LIBRARY_PATH`に指定する。
```
export LD_LIBRARY_PATH=$HOME/local/libffi/lib64:$LD_LIBRARY_PATH
```
これも永続化したかったので、`.bashrc`に追加した。
```
echo 'export LD_LIBRARY_PATH="$HOME/local/libffi/lib64:$LD_LIBRARY_PATH"' >> ~/.bashrc
source ~/.bashrc
```

### 3.インストールしたSSL, libffiを指定し、Pythonを再ビルドする
```
wget https://www.python.org/ftp/python/3.12.9/Python-3.12.9.tgz
tar xvf Python-3.12.9.tgz
cd ~/Python-3.12.9
```
```
./configure --prefix=$HOME/local \
  --with-openssl=$HOME/local/openssl \
  --with-system-ffi \
  CPPFLAGS="-I$HOME/local/libffi/include -I$HOME/local/openssl/include" \
  LDFLAGS="-L$HOME/local/libffi/lib64 -L$HOME/local/openssl/lib64"
make
make install
```

### 4.仮想環境を作る
```
cd
./local/bin/python3.12 -m venv venv
source venv/bin/activate
```

### 5.ちゃんと入ってるかの確認
```
python
import ssl
import _ctypes
```
何も出なければOK!
Vue 3 defineEmits の基本と活用方法まとめ

# Vue 3 `defineEmits` の基本と活用方法まとめ

Vue 3 の Composition API と共に登場した `<script setup>` 構文は、簡潔で直感的な記述を可能にしました。この記事では、その中でも子コンポーネントから親コンポーネントへイベントを発行するための仕組みである `defineEmits` について解説します。

---
## ✨ `defineEmits` とは

`defineEmits` は、Vue 3のコンポーネントの中で「親コンポーネントにイベントを通知する」ための関数です。

```ts
const emit = defineEmits(['custom-event']);
```

上記のように使うと、`emit('custom-event')`で親にイベントを発行できるようになります。

---

## ✅ 基本な使い方

### 【子コンポーネント】MyButton.vue
```vue
<script setup>
const emit = defineEmits(['click']);

const handleClick = () => {
  emit('click');
};
</script>

<template>
  <button @click="handleClick">Click me</button>
</template>
```

### 【親コンポーネント】App.vue
```vue
<template>
  <MyButton @click="onChildClick" />
</template>

<script setup>
import MyButton from './MyButton.vue';

const onChildClick = () => {
  alert('Button clicked!');
};
</script>
```

子が emit した `click` イベントを、親が `@click` で受け取っています。

---

## ▶ `v-model` との連携

Vue 3 で `v-model` を使用する場合、子コンポーネントは `modelValue` を prop として受け取り、`update:modelValue` を emit する必要があります。

### 【例】InputComponent.vue
```vue
<script setup>
const props = defineProps({ modelValue: String });
const emit = defineEmits(['update:modelValue']);

const updateValue = (e) => {
  emit('update:modelValue', e.target.value);
};
</script>

<template>
  <input :value="modelValue" @input="updateValue" />
</template>
```

### 【使用側】App.vue
```vue
<template>
  <InputComponent v-model="text" />
</template>

<script setup>
import InputComponent from './InputComponent.vue';
import { ref } from 'vue';

const text = ref('');
</script>
```

---

## 🌟 TypeScript で型指定する

```ts
const emit = defineEmits<{
  (e: 'submit', payload: { name: string }): void;
}>();

emit('submit', { name: 'John' });
```

これによって、間違った型のペイロードを渡した場合は TypeScript が検出してくれます。

---

## ⚠ よくある間違い

### ❌ `@click="emit('click')"` はダメ
- これはテンプレートのレンダリング時に実行されてしまいます。
- 正しくは `@click="() => emit('click')"` または `@click="handleClick"`にしましょう。
Raspberry pi4 を Google Drive と共有する

***はじめに***
Raspberry pi4 を用いて Google Drive を共有フォルダーとして使う方法について記す。

外部から Raspberry pi に取り込んだデータをサーバ上に置くことを目的としている。調査する中で [Rclone](https://rclone.org/) が比較的ハードルが低い様に感じたので このツールを用いて取り組むことにした。

尚、このツールは Google Drive だけでなく One Drive など 多くのサービスに対応している。

参考：[Rcloneのページ](https://rclone.org/)

----
1：Rclone を Raspberry pi にインストール
~~~
$ curl https://rclone.org/install.sh | sudo bash
~~~

2：Rclone のメニューを起動
~~~
$ rclone config
~~~

3:以下の設定を行う
~~~
No remotes found, make a new one?
n) New remote
s) Set configuration password
q) Quit config
n/s/q>n <-新規に作成

Enter name for new remote.
name>TestDrive <-Raspberry pi が認識するドライブ名(任意)

Option Storage.
Type of storage to configure.
Choose a number from below, or type in your own value.
<省略>
Storage> 20 <-20が Google Drive /　36がOne Drive (Rclone のVersionにより異なるので注意!)
<省略>
client_id>     [Enter] <-何もしない(※)
client_secret> [Enter] <-何もしない(※)
<省略>
scope> 1 <- 1:Full access all files / 2:Read-only access to file .......
service_account_file> [Enter] <-何もしない

Edit advanced config?
y) Yes
n) No (default)
y/n> n

Use web browser to automatically authenticate rclone with remote?
y) Yes (default)
n) No
y/n> y
~~~
ここで、Google への Login 画面が表示されるので Loginする
※ client_id> / client_secret> に Googleで あらかじめ指定した値を入れると自動でLoginされると思われる。

Google Drive と共有されると 以下の画面が出る。
![P001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2056854/39bb1ba5-9732-4fd0-8966-93a8355b6834.png)


~~~
Configure this as a Shared Drive (Team Drive)?

y) Yes
n) No (default)
y/n> n <- Team Drive とは、何かは不明

Keep this "TestDrive" remote?
y) Yes this is OK (default)
e) Edit this remote
d) Delete this remote
y/e/d> y

Current remotes:

Name                 Type
====                 ====
TestDrive            drive

e) Edit existing remote
n) New remote
d) Delete remote
r) Rename remote
c) Copy remote
s) Set configuration password
q) Quit config
e/n/d/r/c/s/q> q <-設定終了!!
~~~
----
Rclone 主なコマンドの使い方
参考：[Rclone コマンド一覧](https://rclone.org/commands/)

◎Google Drive 内 全ての フォルダーと ファイルの一覧を表示
~~~
$rclone ls TestDrive:
~~~

◎Googleドライブ内に TestFolder を作成
~~~
$ rclone mkdir TestDrive:TestFolder
~~~

◎ファイル(Type001.py) をGoogleドライブに作成したフォルダー(TestFolder)へコピー
~~~
$ rclone copy Test001.py TestDrive:TestFolder
~~~

◎Googleドライブ内にあるファイル(TestFolder/Test001.py)をRaspberry pi の MyWork フォルダーにコピーする
~~~
$ rclone copy TestDrive:TestFolder/Test001.py ./MyWork
~~~

---
☆2025年 4月1日(火) 午後14時50分 初版(Ver1.00) 作成

---
LeetCode 2721. Execute Asynchronous Functions in Parallel

# Pomise
非同期作業において、(例:ネットワーク要求、ファイル読み取り)が完了したら、結果を返すという **Promise(約束)** をするObjectです。

# Promiseの状態

| 状態 | 説明　 | 
|:-----------:|:------------:|
| **Pending**      | まだ作業中|
| **Fulfilled**     |   作業成功    | 
| **Rejected**      | 作業失敗        | 


# フローチャート
1.functions.map(fn => fn()
2.Promise.all()

# Code
```typescript
type Fn<T> = () => Promise<T>

function promiseAll<T>(functions: Fn<T>[]): Promise<T[]> {
    return Promise.all(functions.map(fn => fn()));
};

/**
 * const promise = promiseAll([() => new Promise(res => res(42))])
 * promise.then(console.log); // [42]
 */
```

# Promise.allSettled()
Promise.allSettled()を使うと、全部の結果をReturnするので、失敗があっても問題ないです。

```typescript
Promise.allSettled(functions.map(fn => fn()))
    .then(console.log);
```

✅ 実行結果
```json
[
  { "status": "fulfilled", "value": 1 },
  { "status": "rejected", "reason": "Error!" },
  { "status": "fulfilled", "value": 3 }
]
```

:::note info
`Promise.all()` は **1つでも失敗するとすべて失敗になる**  
`Promise.allSettled()` は **成功したものと失敗したものを分けて結果を返す**
:::
iPhoneショートカットで寝落ち防止！ワンタップでバイブのみのタイマーを設定する方法

## **はじめに**  

子どもの寝かしつけ、どうしていますか？  
我が家はとにかく「寝たふり」！  

でも、そのまま一緒に**寝落ちしてしまうことがあります**…。  
寝かしつけ前に**iPhoneの時計アプリでタイマーを設定するのは手間**なので、**ショートカットでワンタップでバイブのみのタイマーをセットできる仕組み**を作りました。  



## **ショートカットの流れ**  

![IMG_6226.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1868789/d2f982fd-9856-471d-a57d-adbd85ca975d.jpeg)



1. **待機**（指定時間）  
2. **デバイスを振動させる**（一定回数繰り返す）  
3. **ショートカットを止めると振動も止まる**  

### **なぜ「タイマーを開始」ではないのか**  

iPhoneの「ショートカット」には **「タイマーを開始」** というアクションがあります。  
しかし、**このアクションでは音の指定ができず、バイブのみのタイマーを作れません**でした。  


### **ポイント**  

- **待機アクションの単位は秒のみ**なので、1分（60秒）ごとにループさせる  
- **バイブの回数は自分が起きられる回数**を設定するとよい  
- **ワンタップでタイマーをかけられる**ので、毎回手動で設定する手間が省ける  

## **まとめ**  

iPhoneのショートカットでは「バイブのみのタイマーを設定できない」ため、**待機＋振動の組み合わせ**で実現しました。  
**寝かしつけ中の寝落ち防止にぴったり**なので、同じ悩みを持つ方はぜひ試してみてください！
Google Ads と BigQuery を連携する方法

## はじめに
Google AdsのデータをBigQueryに連携することで、より詳細な分析やカスタムレポートの作成が可能になります。本記事では、Google AdsとBigQueryの連携手順を体系的に解説します。

## 1. 前提条件
連携を行う前に、以下の条件を満たしていることを確認してください。

- Google Adsの管理者権限を持っている
- Google Cloud Platform（GCP）プロジェクトを持っている
- BigQuery API が有効になっている
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3976506/96429423-8ed2-4e05-96a2-6f917c8b012a.png)


- Google Ads Data Hubの利用資格を持っている（必要に応じて）

## 2. Google Ads と BigQuery の連携方法

### 2.1 Google Cloud プロジェクトの準備

1. [Google Cloud Console](https://console.cloud.google.com/) にアクセスし、適切なプロジェクトを選択。
2. **BigQuery API** を有効化。
3. **Google Ads API** を有効化（必要に応じて）。

### 2.2 Google AdsのデータをBigQueryにインポートする方法

#### 方法: Google Ads Data Transfer Service
Google Cloudの **BigQuery Data Transfer Service (DTS)** を利用すると、Google Adsのデータを定期的にBigQueryに取り込むことができます。

1. Google Cloud Console の **BigQuery** にアクセス。
2. **データ転送** セクションに移動。
3. **転送を作成** をクリック。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3976506/a02d4f7d-84c0-4a5f-84ce-39d732af4ba3.png)


4. **データソース** として **Google Ads** を選択。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3976506/bccf3787-6ee1-4ba4-9d9f-03e340c5a892.png)


5. **Google Ads アカウント** を入力し、適切なデータを取り込む設定を行う。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3976506/05eca868-8b7e-4d80-a172-02bd768cddd5.png)

6. スケジュールを設定し、転送を作成。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3976506/6fbc1d4e-14ea-4697-a779-5a18ca7436c2.png)

### 2.3 BigQuery でデータを確認
転送が完了したら、BigQuery の **Google Ads データセット** にアクセスしてデータを確認できます。

```sql
SELECT *
FROM `プロジェクトID.データセット名.テーブル名`
LIMIT 10;
```

## 3. 連携後の活用方法

### 3.1 Google Looker Studio で可視化
BigQueryに保存されたGoogle Adsのデータを [Google Looker Studio（旧Data Studio）](https://datastudio.google.com/) で可視化できます。

1. Looker Studio にアクセス。
2. **BigQuery コネクタ** を使用してデータセットを選択。
3. ダッシュボードを作成し、クリック数やコンバージョン数を可視化。

### 3.2 SQL を活用した詳細分析
BigQueryではSQLを使用して、Google Adsのデータを自由に分析できます。

- 広告ごとのクリック数・コンバージョン数を分析
- 地域やデバイス別のパフォーマンスを評価
- 過去のデータと比較してトレンドを分析

## 4. まとめ
Google AdsとBigQueryの連携により、大量の広告データを効率的に管理し、分析の幅を広げることができます。特に **BigQuery Data Transfer Service** を活用すれば、自動的にデータを更新できるため、手間を省きつつ高度な分析が可能になります。

ぜひ、Google Adsのデータ活用をさらに進めてみてください！
Google Search ConsoleとBigQueryの連携手順

## はじめに
Google Search Console（GSC）とBigQueryを連携すると、検索データを詳細に分析できるようになります。本記事では、GSCのデータをBigQueryに統合する手順を解説します。

---

## 1. 前提条件
連携を行う前に、以下の条件を満たしていることを確認してください。

- Google Cloud プロジェクトを作成済みである
- BigQuery API を有効にしている
- Google Search Console の管理者権限を持っている
- Google Cloud IAM で適切な権限（BigQuery Data Editor 以上）を持っている

---

## 2. Google Search Console のデータを BigQuery に取り込む手順

### **2.1 Google Cloud プロジェクトの設定**

1. **Google Cloud Console にアクセス**
   - [Google Cloud Console](https://console.cloud.google.com/) にログインし、プロジェクトを選択。
   
2. **BigQuery API の有効化**
   - 「APIとサービス」→「ライブラリ」→「BigQuery API」を検索し、有効化。

3. **必要なIAM権限を付与**
   - 「IAMと管理」から、GSCデータをBigQueryに格納できるアカウントに「BigQuery Data Editor」または「BigQuery Admin」権限を追加。
   - Google Search Console のデータをBigQueryにエクスポートするには、**「BigQuery Job User」** 権限も必要になる場合があります。

---

### **2.2 Google Search Console 側の設定**

1. **Google Search Console にアクセス**
   - [Google Search Console](https://search.google.com/search-console/) にログインし、連携するプロパティを選択。

2. **BigQuery へのデータエクスポートを設定**
   - 「設定」→「データエクスポート」から BigQuery へのエクスポート設定を行う。
   - Google Cloud プロジェクトを選択し、データセットを指定する。
   - **エクスポートの頻度を設定** し、日次で自動更新されるようにする。
   - 設定完了後、エクスポートが正しく機能しているか確認する。

---

### **2.3 IAM 権限の設定**

1. **Google Cloud IAM の設定**
   - Google Cloud Console で「IAMと管理」→「IAM」に移動。
   - Search Console のエクスポートを行う Google アカウントに、以下の権限を付与する。
     - **BigQuery Data Editor**（データセットの編集権限）
     - **BigQuery Job User**（データのエクスポートジョブ実行権限）

2. **Google Search Console にサービスアカウントを登録**
   - 必要に応じて、Search Console の設定画面でサービスアカウントを登録し、エクスポートを許可する。

---

## 3. BigQuery でのデータ分析
BigQuery に GSC のデータを取り込んだ後、SQL クエリを使用して分析できます。

### **検索クエリの上位10件を取得するSQL**
```sql
SELECT query, SUM(clicks) AS total_clicks
FROM `your_project.your_dataset.your_table`
GROUP BY query
ORDER BY total_clicks DESC
LIMIT 10;
```

### **特定のページに対する検索パフォーマンスを調査するSQL**
```sql
SELECT page, SUM(impressions) AS total_impressions, SUM(clicks) AS total_clicks
FROM `your_project.your_dataset.your_table`
WHERE page LIKE '%your-page-slug%'
GROUP BY page
ORDER BY total_clicks DESC;
```

---

## まとめ
Google Search Console のデータを BigQuery に連携することで、
- **検索パフォーマンスの詳細分析**
- **カスタムレポート作成**
- **GA4のデータと統合した分析**
が可能になります。

BigQuery のデータエクスポート機能を活用することで、スケジュール設定なしで自動更新が可能となります。

BigQuery × Google Search Console の活用を進めて、より詳細なSEO分析を実現しましょう！ 🚀
fastapi の FileResponse のファイルタイプ推定が動かなかった時のメモ。

`xlsx` ファイルをレスポンスで返そうとした時に MIMETYPE が自動で設定されたりされなかったりした。
実行環境にエクセルが入っているかどうかで変わるっぽい。

## 背景

fastapi でファイルをダウンロードする API を作成した。
`Content-Disposition` ヘッダーを設定しておけばファイルレスポンスがいい感じになるので設定していたが、
テスト用サーバに上げるとうまく設定されない（`text/plain` になる）場合があったので調べた。

フロント側の処理を色々書いても解決できそうだが、 `Content-Disposition` ヘッダーを正しく使うのが良さそう。

## fastapi の FileResponse

fastapi の FileResponse はファイルの MIMETYPE を推定してくれるが、`xlsx` とかで推定できないことがある。

```python
from fastapi.responses import FileResponse

# res.media_type は 'text/csv' になる
res = FileResponse('test.csv')

# res.media_type が 'text/plain` になる場合がある
# `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` になって欲しい
res = FileResponse('test.xlsx')
```

基本的には `FileResponse` でレスポンスを作っておけば `Content-Disposition` ヘッダーもいい感じにしてくれる。
(type も推定されたものがセットされる)

今回推定できなかったのは `xlsx`。
mac でローカル実行したときは推定できたが、 windows サーバに上げると推定できなかった。

## 解決策

`media_type` を明示的に指定する。

```python
from fastapi.responses import FileResponse
res = FileResponse('test.xlsx', media_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
```

自動で推定してくれるところはそのままにしたかったので、実際に使ったのは以下のような形。

```python
from fastapi.responses import FileResponse
res = FileResonse('test.xlsx')

if res.media_type == 'text/plain':
    res = FileResponse('test.xlsx', media_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')

# インスタンス作成後に更新してもレスポンスに反映されなかった
# res.media_type = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
```

インスタンス作成後に`media_type` を変更すると、インスタンスの更新は行われるが
レスポンスに反映されなかった。

## 原因

python の mimetype 推定は環境によって結果が異なる。

`.txt`, `.csv` などの一般的なものはデフォルトで設定されているが、その他の mimetype 情報は
システム環境から取ってきている模様。

筆者の macbook だと `/etc/apache2/mime.types` のデータが参照されていた。windows だとレジストリ情報から取ってくるらしい。

今回は windows サーバで `xlsx` の推定ができなかった。
エクセルを入れていなかったのでレジストリに情報がなかったのかもしれない。

```python
from mimetypes import guess_type
guess_type('test.xlsx')

# 推定できるとき
# ('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', None)

# 推定できないとき
# (None, None)
```

## 参考サイト

- [starlette のソース](https://github.com/encode/starlette/blob/master/starlette/responses.py)
  - 302行目あたり。fastapi の FileResponse のページにもあったので調べてみた。

- [python の mimetypes のソース](https://github.com/python/cpython/blob/3.12/Lib/mimetypes.py)
  - `.xls` はライブラリのデフォルトで設定されているが、`.xlsx` は設定されていない。

- https://zenn.dev/ncdc/articles/113571f31e73fb
[Linux][system][directry] 初期化システム_systemdのUnit_systemed

# 各Unitの拡張子
| 拡張子         | 説明      |
|:--------|:---------|
| `.service`    | システムサービスやデーモン                              |
| `.target`     | 他のユニットをまとめて管理するためのグループ化ユニット              |
| `.device`     | カーネルが認識するデバイス                              |
| `.mount`      | ファイルシステムのマウントポイント                          |
| `.swap`       | スワップデバイスやスワップファイル                         |

 


# Ping-t
- [3591_service, target](https://mondai.ping-t.com/question_subjects/31/question_sessions/31719854/progressing_questions/486088055)
- [3621_swap, device, mount](https://mondai.ping-t.com/question_subjects/31/question_sessions/31585258/progressing_questions/483986851)
IT未経験から2週間でAWS Cloud Practitioner 合格｜完全攻略ガイド

## はじめに
私はIT未経験の状態からIT企業への入社を機にクラウド技術に強い興味を持ち、AWSクラウドプラクティショナーの受験を決めました。

この記事では、私が2週間でAWSクラウドプラクティショナーに合格するまでの勉強方法や試験のポイントを紹介します。

## AWSクラウドプラクティショナーとは

AWSクラウドプラクティショナーは、AWSクラウドに関する基本的な知識とスキルを証明する資格です。

試験では、AWSの主要なサービスや概念、セキュリティ、料金体系などの初歩的ではありますが幅広い知識が問われます。

**資格取得のメリットは以下の通りです。**

* キャリアアップ
* 市場価値向上
* AWSに関する知識の体系的な習得

**AWSクラウドプラクティショナーは、以下のような方におすすめです。**

* AWSクラウドの基礎を学びたい方
* クラウドエンジニアを目指す方
* IT業界でキャリアアップしたい方

**公式ホームページには受験対象者として以下のように書かれています。**

この認定は、AWSクラウドの設計、実装、オペレーションの経験が 6 か月までの受験者が対象です。この認定は、 IT 以外のバックグラウンドを持つ受験者に適しています。そういった受験者として、AWSクラウドのキャリアの初期段階にある人や、AWSクラウドで役割を担う人々と一緒に仕事をしている人が挙げられます。

しかし**試験勉強のコツと正しいポイントを押さえれば2週間での合格も難しくありません。** そのやり方を詳しくお伝えしていきたいと思います。

## 試験勉強の準備
私が実際に使用した教材を紹介します。
* 学習期間：2週間
* 学習スケジュール：1日1時間、試験直前の土日 1日3時間
* 使用教材：
    * 教材：[一夜漬け AWS認定 クラウドプラクティショナー 直前対策テキスト]
    * youtube：AWS資格 対策講座
    * 問題集：CloudLisense

## 具体的な勉強方法

1.  教材で基礎知識を習得（上記のテキストの場合は講義部分が約120ページほどありますので、1日20ページ、1周/1週間するペースで読み進めていきました。）
2.  youtubeで通勤中の隙間時間に主要サービスをインプット
3.  **問題集で知識のアウトプット**
  ★ここが最重要！なるべく早く何度も解き直すことが大切になります。特にCloudLisenseだと本番にとても似た問題が出るので丸暗記する気合で進めていいと思います。
4.  模擬試験で実力チェック
5.  苦手分野を重点的に復習

### 試験対策のポイント：

* とにかく**サービス名とその概要を1対1対応させる**
  ★サービス名を見た時に、瞬時にそれがどんなサービスなのかを思い出せるようにする
  ★似たサービス名（IGWとNGWやECSとEKSなど）の違いを理解しておく
* 問題文をよく読む
  ★正解を判断するポイントは問題文のどのワードなのか明確にしながら解き進めていく
* 選択肢を一つずつ検討する
  ★他の選択肢のサービスがどんなものかわからなければ必ず調べ直す

## 試験当日の流れと感想

試験会場は横浜テストセンターでした。
試験は90分で65問出題されますが、問題文が短く選択肢もサービス名を答える問題が多いため時間自体は余裕をもって答えることができると思います。私の場合には見直し含めて45分ほどでとき終わりました。
結果は716/1000で合格でした。（合格ラインが700点以上なのでだいぶギリギリではありました笑）

## おわりに

AWSクラウドプラクティショナーの取得を通して、体系的にAWSのサービスを学ぶことができました。

現在はAWSの他の資格取得に向けて学習を進めており、今後も資格勉強含めAWSサービスの情報共有をしていきたいと考えています。

この記事が、これからAWSクラウドプラクティショナーを受験する方の参考になれば幸いです。ご質問やコメントがありましたら、お気軽にご連絡ください。
2025年4月 AIOps 定点観測

# 概要
AIOps の動向や学習のため、Google で AIOps を検索した結果を記録している記事です。


# 2025年4月1日の検索結果 (AI による概要)

![スクリーンショット 2025-04-01 143814.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2345504/8c7883aa-8fe9-457a-85ff-58e582b62757.png)


TOP に AI による概要が表示されるようになりました。



# 2025年4月1日の検索結果 (広告(スポンサー))

![スクリーンショット 2025-04-01 143836.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2345504/027f072d-48c4-4b9c-8cb4-b59c0b5004b3.png)


* ServiceNow
* IBM
* Dynatrace
* Splunk

今月は ServiceNow が TOP で、IBM と Dynatrace が続いています。

先月の結果はこちら --> [2025年3月 AIOps 定点観測](https://qiita.com/mkodera/items/405a89b8da8527bffafa)



# 2025年4月1日の検索結果 (広告以外)

![スクリーンショット 2025-04-01 143903.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2345504/643adf86-50cb-43c7-9c6a-c25c4e2e9ab8.png)


* AWS
* 日立ソリューションズ・クリエイト
* PagerDuty
* IBM
* ServiceNow

今月は AWS が TOP で、日立ソリューションズ・クリエイト と PagerDuty が続いています。

先月の結果はこちら --> [2025年3月 AIOps 定点観測](https://qiita.com/mkodera/items/405a89b8da8527bffafa)