OmniAICreator/Qiita-1.07M huggingface dataset:
野球観戦を楽しむためのプロダクト開発   ~AR.jsを使用した簡易検証~

## 私が目指しているもの
野球観戦を盛り上げるツールを作りたいと考えています。
自宅で独り、試合観戦をしているとナイスプレーや悔しいシーンなどで他人と気持ちを共有できないもどかしさを感じることはありませんか？
そこで下記サイトに記載しているようなプロダクトを作りたいと考えています。

https://note.com/yopi0901/n/nfb6d77c70afe

その中の一つに、スマートグラスを通してテレビ画面の脇にX（Twitter）等のタイムライン画面を表示させて、野球観戦をしているみんなのコメントを試合と一緒に確認できるツールを考えています。
今回はそのツールの簡易検証として、「**スマホカメラでテレビ脇のARマーカーを読み込んだらサイト画面が出現して、試合を観ながら目線を変えずにサイト画面を確認できる**」ということを目指して、簡易ツールを作ります。

## 検証に利用したもの
### 出現させるサイト
ARマーカーを読み込んで出現させるのは、今回はX（Twitter）ではなくこちらのサイトにします。

https://www.rakuteneagles.jp/gamelive/live/index.html

理由としては、Twitter画面を表示させるのはハードルが高いと感じたからです。（ちょっと試したのですが、上手くできなかったため今回は諦めました。）
※なぜ多くのサイトの中からこのサイトを選んだか？私が楽天イーグルスのファンだからです。

### ARマーカー
カメラで読み取るマーカーはこちらのサイトで作成しました。

https://jeromeetienne.github.io/AR.js/three.js/examples/marker-training/examples/generator.html

![pattern-marker.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3770353/ddd74462-82fc-cfe4-4d84-ecb20ba411ab.png)


### 開発したツール
↓のサイトを開くとカメラが起動します。起動したカメラで上のマーカーを読み込みます。PCのWEBカメラでもできますが、おすすめはスマートフォンのカメラです。

https://elaborate-haupia-a19ea3.netlify.app/



## 実際に動かしたときの状態
- テレビでパ・リーグSpecialの試合映像をリアルタイムで投影
- テレビ脇にARマーカーを表示しているスマホを設置
- ツールを起動したスマホをソファに座っている自分の目線の高さに調整

![RPReplay_Final1719117711.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3770353/1cc2eeea-7684-b52b-7f5a-23dad879c2ea.gif)

期待通りの動きができました！



## ソース　(CodePen)

<p class="codepen" data-height="300" data-default-tab="html" data-slug-hash="GRaxLxQ" data-pen-title="display" data-user="taka-the-sasster" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/taka-the-sasster/pen/GRaxLxQ">
  display</a> by taka (<a href="https://codepen.io/taka-the-sasster">@taka-the-sasster</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

見ての通り、3Dオブジェクトを出現させるわけではなく、単純にマーカーを読み取ったら、iframe（インラインフレーム）でサイト画面表示させているだけです。



## 今後の課題
お気づきの方もいるかもしれませんが、今の状態だと、野球中継アプリの画面をピクチャー イン ピクチャー（PiP）で表示させて、WEBサイトを開けば良いだけなのです。なんならこっちの方が安定して見やすい！？

<img width="200" alt="IMG_0385.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3770353/bba5d678-99f3-99aa-570c-c46139091753.png">

プロダクトのコンセプトである「**スマートグラスを通してテレビ画面の脇にX（Twitter）等のタイムライン画面を表示させて、野球観戦をしているみんなのコメントを試合と一緒に確認できるツール**」を変えないのであれば、今後はテレビ画面をわざわざ見るメリットを考えていく必要があるのかなと思います。

あと、やはり時差の問題もありそうです。テレビの映像と表示している画面の情報が、タイムラグにより一致していません。もちろんラグは無い方が良いので、こちらの対応策を考える必要があります。

これらを考慮して、今度は少々時間をかけてX（Twitter）のようなものを表示させて検証してみたいですね。
Next.js+Leafletで地図を表示する

Next.jsの導入は完了している物とします。

## 必要なモジュールをインストール
```bash
yarn add leaflet react-leaflet
yarn add -D @types/leaflet
```


## マップコンポーネントとページの作成
ファイルはこんな感じに `src/app/testmap` 配下へ作成するイメージです。
![nextjs_leaflet_project_file.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622558/f2c17de7-fb55-3e44-c093-3810255a2d23.png)

### マーカーアイコンの読み込み不良の解消
マップコンポーネントを作成する前に、マーカーの読み込みが上手くいかない問題を解消する。
```initLeaflet.ts
import Leaflet from 'leaflet';
import markerIcon2x from 'leaflet/dist/images/marker-icon-2x.png';
import markerIcon from 'leaflet/dist/images/marker-icon.png';
import markerIconShadow from 'leaflet/dist/images/marker-shadow.png';

const DefaultIcon = Leaflet.icon({
  iconUrl: markerIcon.src,
  iconRetinaUrl: markerIcon2x.src,
  shadowUrl: markerIconShadow.src,
  iconAnchor: [12, 41], // アイコンのオフセット
  popupAnchor: [0, -32], // ポップアップのオフセット
});
Leaflet.Marker.prototype.options.icon = DefaultIcon;
```

### マップコンポーネント
スタイルシートが無いと表示されないので作成する。
```simpleMap.css
/* 地図を画面全体に表示 */
.leaflet-container {
  width: 100vw;
  height: 100vh;
}

/* 通常カーソル */
.leaflet-grab {
  cursor: auto;
}
```

マップコンポートネントを作成。マーカーは福岡タワーに設定。
```simpleMap.ts
import { LatLng } from 'leaflet';
import 'leaflet/dist/leaflet.css';
import { MapContainer, Marker, Popup, TileLayer } from 'react-leaflet';
import './initLeaflet';
import './simpleMap.css';

const simpleMap = () => {
  const position = new LatLng(33.59337, 130.35152); // 福岡タワー

  return (
    <MapContainer center={position} zoom={13}>
      <TileLayer
        attribution='© <a href="https://maps.gsi.go.jp/development/ichiran.html">国土地理院</a>'
        url="https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png"
      />
      <Marker position={position}>
        <Popup>
          PopUp!! PopUp!! PopUp!! <br /> ポップアップ
        </Popup>
      </Marker>
    </MapContainer>
  );
};

export default simpleMap;
```


### ページ
作成したマップコンポーネントを読み込むわけですが、クライアントサイドでしか動かないので、ダイナミックインポートする。
```page.ts
'use client';

import dynamic from 'next/dynamic';

import { useMemo } from 'react';

export default function Home() {
  const SimpleMap = useMemo(
    () =>
      dynamic(() => import('./simpleMap'), {
        loading: () => <p>map loading...</p>,
        ssr: false,
      }),
    []
  );
  return <SimpleMap />;
}
```


## 実行する
実行して `http://localhost:3000/testmap` にアクセスするとこんな感じに表示されます。
![nextjs_leaflet.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622558/187453d7-23ac-fe11-1999-e605f7d20e9b.png)
統計士　資格取得の記録

# 統計士　とは
一般財団法人　実務教育研究所が実施している通信講座「現代統計実務講座」を修了すると認定してもらえる資格になります💡

**統計分野で日本唯一の文部科学省認定の通信講座**　です！

https://www.jitsumu.or.jp/courselist/statistics

しかし、統計検定の情報はたくさんあるのですが、「統計士」については公式HP以外の情報がないので、参考になればと記録したいと思います💡

# 通信講座「現代統計実務講座」の概要
・1カ月に1単元を学習し8カ月で修了
・４単元分３００ｐ程度の冊子２冊を自分のペースで進めていきます
・毎月、課題が送付されてくる　⇒　返送する　（アナログな郵送方式）
・学習は、地道な手計算を中心としており、考え方のプロセスが分かり、統計的考え方の原理を実感することが可能
・入学金5,000円 / 受講料 54,800円　ちょっとお高め。


## 現代統計実務講座 カリキュラム概要

### 第1単元: 統計とは何か
- **概要**: 統計の基礎と統計的考え方を学ぶ。
1. 統計の利用
2. 統計は何を表わすか
3. 統計的規則性
4. 統計的推論

### 第2単元: 集団構造の記述
- **概要**: 集団の特徴を統計的に記述する方法を学ぶ。
1. 比率
2. 平均とバラツキ
3. 度数分布
4. 相関

### 第3単元: 母集団と標本
- **概要**: 母集団と標本の関係と統計的推測の基本を学ぶ。
1. 母集団と標本
2. 離散分布からの標本抽出
3. 連続分布からの標本抽出
4. 誤差法則と中心極限定理

### 第4単元: 推定と検定
- **概要**: 標本から母集団を推測する統計的推論の手法を学ぶ。
1. 推定と検定
2. 平均値に関する推定と検定
3. 分散に関する推定と検定
4. 離散量に関する推定と検定

### 第5単元: 回帰と相関
- **概要**: 回帰分析と相関の検定・推定を学ぶ。
1. 直線回帰
2. 回帰と相関
3. 重回帰分析
4. 曲線回帰と順位相関

### 第6単元: 統計調査
- **概要**: 実践的な統計調査の手法を学ぶ。
1. 調査の企画と方法
2. 調査票の設計
3. 調査の組織と運営
4. 解釈と報告

### 第7単元: 標本調査法
- **概要**: 標本調査法を詳細に学ぶ。
1. 標本設計の基本問題
2. 単純無作為抽出法
3. 各種の抽出方法
4. 複合標本方式

### 第8単元: 品質管理と実験計画
- **概要**: 品質管理と実験計画法を学ぶ。
1. 品質管理と抜取検査
2. 実験計画法（１）
3. 実験計画法（２）
4. 官能検査と嗜好調査


# 受講の感想
・毎週、かかさずに本を開いて進めないときついペースなので、統計知識はもちろんのこと、**学習習慣**が身に付きます。
※半年は延長も可能とのこと
・本の内容や事例が、昭和から進んでないものが多々あり、日本の歴史を感じることができます。一般家庭の熱源のグラフに、薪、炭など見慣れないものがありました。笑
・統計知識は、統計検定２級程度の知識を身に着けることができます。非常にわかりやすく、初学者でもついていけると思います。
・毎月の課題の中で、先生や事務局からたくさんの激励の言葉が届くので、モチベーションは維持しやすいです。

# 最後に
８か月間、めげずに統計の勉強を続けることができたのは、とても自身につながります！
引き続き、インプット／アウトプット習慣を維持したいと思います✨
Node.js: ヘッダーを考慮した csv ファイルの読み込み

こちらのプログラムを Node.js に変換しました。
[Python3: ヘッダーを考慮した csv ファイルの読み込み](https://qiita.com/ekzemplaro/items/aabc6e3b9ac71e242d06)

## 準備

```bash
npm install --save csv-parser
```

確認したバージョン

```text
$ node --version
v22.3.0
```

## プログラム

```js:csv_header.js
#! /usr/bin/env node

// csv_header.js

const fs = require('fs');
const csv = require('csv-parser');

// Function to write to a file
function fileWriteProc(fileName, strOut) {
    fs.writeFileSync(fileName, strOut);
}

// Command line arguments
const args = process.argv.slice(2);
const fileCsv = args[0];
const fileJson = args[1];

// Read CSV file
const rows = [];
fs.createReadStream(fileCsv)
    .pipe(csv())
    .on('data', (row) => {
        rows.push(row);
    })
    .on('end', () => {
        // Convert rows to JSON string
        const outStr = JSON.stringify(rows);
        
        // Write JSON string to file
        fileWriteProc(fileJson, outStr);

        console.log(`Conversion successful. JSON data written to ${fileJson}`);
    });
```

## 実行方法

```bash
./csv_header.js cities_header.csv cities.json
./csv_header.js cities_ex02.csv cities_ex02.json
```
二要素認証追加でPushできなくなった。

## 状況
SourceTreeでコミットをGithubのプライベートリポジトリにプッシュしようとしたところ、以下のエラーが発生して、pushできなかった。
なおfetchやpull, cloneでも同じエラーが発生した。

`fatal: repository 'https://github.com/リポジトリ名' not found`


## 原因
最近行ったGitHubの設定変更といえば、二要素認証を追加したことだった。
二要素認証を追加したことで、リポジトリへのアクセスに必要な認証情報も変わったのが原因だった

これまで、httpsプロトコルで接続をしてきた。
この際の認証情報は**ユーザー名**と**パスワード**だった。
二要素認証を追加したことで必要な認証情報が**ユーザー名**と**アクセストークン**に変更されたのだ。
そのため、二要素認証を追加以前のSourceTreeの設定では、認証情報が異なりアクセスできなかったのである。

## 対処
1. GitHubでアクセストークンを発行する
2. アクセストークンをSourceTreeの認証情報に設定する

これらを正しく行うことで、無事プッシュできた。

久々にGitHubへコミットしたものだから、焦ったが割と昔から二要素認証ではアクセストークン利用が当たり前だった。
面倒がって二要素認証追加していなかった自分だからこそ、起きた自称だね。

### 参考
https://qiita.com/ponsuke0531/items/4fe191b7bed03342cff2
scpやsftpを使わずにsshだけでファイルをアップロードする

## 単一ファイルのみの場合

ローカル側のファイル`local.txt`をcatで読み込み、それをsshにパイプして、サーバー側でファイル`upload.txt`にリダイレクトすることでアップロードができます。

※Windowsの場合、git-bash(gitをインストールするとついてくる)が必要です

```bash
cat local.txt | ssh -i *****.pem user@server "cat > ~/upload.txt"
```


## 複数ファイルをアップロードする場合

複数ファイルをアップロードしたい場合は、`cat`の代わりに`tar`を使いアーカイブしてから転送します
1. ローカル側でtarを使ってファイルをまとめる
1. サーバーへアップロード(パイプで転送)する
1. サーバー側で解凍する

```bash
# ローカルの転送ディレクトリ、リモートの出力先ディレクトリを指定する
LOCAL_DIR=*****   # ./test_upload
UPLOAD_DIR=*****  # ./
tar -zcf - $LOCAL_DIR | ssh -i *****.pem user@server "tar zxf - -C $UPLOAD_DIR"
```
* tar の`-`は「標準入力／出力」を入出力先として利用するためのオプション
pearを実行するとエラーが出た時のメモ

## 昨今のpearやpecl

最近はあんまり使わなくなりましたよね。
でも、今回はちょっと面倒な処理を行うのにpearがいるので久しぶり実行すると、以下の様なエラーがでた。

## エラー

```
# pear version

Warning: foreach() argument must be of type array|object, null given in Command.php on line 249
PHP Warning:  foreach() argument must be of type array|object, null given in /usr/share/php/PEAR/Command.php on line 249
PHP Stack trace:
...
```

## 結論

設定ファイルのxmlが読めなくなっていたようです。
大分前に、php自体をバージョン上げた時に、xmlが動かなくなっていたのかも？
いや、そもそもxmlを使わなくなってきたから入れてなかったのか？
兎にも角にも、xmlをインストールしました。

```
sudo apt install php-xml
```
Dockerのビルドを復習する

Dockerの基本をおさらいするためにイメージのビルドについて説明します。

## イメージとコンテナ

コンテナを実行するためには、イメージをビルドする必要があります。

イメージは、コンテナの実行に必要なファイルや設定をまとめたものでイメージはDockerfileというファイルにビルドの手順を記述することで作成できます。

```Dockerfile
FROM ubuntu

CMD ["echo", "Hello, Docker!"]
```

このファイルでは、Ubuntuのイメージをベースにして、`echo "Hello, Docker!"`を実行するイメージを作成しています。

## イメージのビルド

イメージをビルドするには、`docker build`コマンドを使います。

```sh
docker build -t my-image .
```

このコマンドは、カレントディレクトリにあるDockerfileを使ってイメージをビルドし、`my-image`という名前でタグ付けします。

## イメージの実行

イメージを実行するには、`docker run`コマンドを使います。

```sh
docker run my-image
```

このコマンドは、`my-image`という名前のイメージを実行します。

## レイヤー

イメージは、複数のレイヤーから構成されています。

レイヤーはFROM, RUN, COPY, ADD, CMDなどのDockerfileの命令によって追加され、変更差分を保持しています。


## キャッシュ

buildの高速化のために、Dockerはビルドの各ステップの結果をキャッシュしています。

Dockerfileの命令が変更されない限りキャッシュされた結果を再利用することができ、ビルドの高速化につながります。

## マルチステージビルド

ビルドのステップを分割することに最終的なイメージに不要なファイルや設定を含めないようにすることができます。

これをマルチステージビルドと呼び、Dockerfileに複数のFROM命令を使って複数のステージを定義することで実現できます。

例えばGoのアプリケーションをビルドする場合、バイナリをビルドするステージと実行するためのイメージを作成するステージを分けることができます。

サンプルは次に記載します。

### イメージサイズの比較

ここからはGoのアプリケーションをビルドする例を使って、マルチステージビルドの効果を確認します。

対象のGoのアプリケーションは以下のようなものとします。

```go
package main

import "fmt"

func main() {
	fmt.Println("Hello, playground")
}
```

```go.mod
module main

go 1.22.4
```

このアプリケーションをビルドするためのDockerfileは以下のようにします。

```Dockerfile
FROM golang:1.22
WORKDIR /work/app
COPY main.go go.mod ./
RUN go mod tidy
RUN go build .
CMD ["./main"]
```

このDockerfileを使ってイメージをビルドし、サイズを確認します。

```sh
$ docekr build . -t normal_build_sample
$ docker images

REPOSITORY          ~ SIZE
normal_build_sample ~ 859MB
```

では、マルチステージビルドを使ってイメージをビルドし、サイズを確認します。

Goのコードは同一としてDistrolessイメージを使ってマルチステージビルドを行います。

```Dockerfile
FROM golang:1.22 AS builder
WORKDIR /work/app
COPY main.go go.mod ./
RUN go mod tidy
RUN go build .

FROM gcr.io/distroless/static:nonroot
COPY --from=builder /work/app ./
CMD ["./main"]
```

この例では、Goのアプリケーションをビルドするステージと実行するためのイメージを作成するステージを分けています。

バイナリを実行するイメージには、Distrolessという軽量なイメージを使っています。

Distrolessイメージについては[こちら](https://qiita.com/tsukasaI/items/34302c89b3136be1b5c9)で解説していますのでぜひ参考にしてください。

```sh
$ docker build . -t multi_stage_build_sample
$ docker images
REPOSITORY               SIZE
multi_stage_build_sample 3.93MB
```

このように、マルチステージビルドを使うことで、不要なファイルや設定を含めないようにすることができ、イメージのサイズを小さくすることができます。
Kotlin Fest2024で見たセッションのメモ

昨日kotlin festに行ってまいりました。スピード重視で記憶があるうちにセッションを聞いた感想や学びを書いていきたいと思います。割と雑な部分もあると思いますがご了承ください。

## kotlinconf 2024 を後から256倍楽しむためのヒント
jetbrains japanの営業の方のセッション。
kotlin conf聞きましたか？というQから始まりますが、自分は聞けてません、、
そんな方のためにタイパ重視でカンファレンスを楽しむ方法。
生成aiを使うとサマリーがなかなかの精度で作れるとのことです。
kotlin conf以外のカンファレンスでも使えそうな気がしました。
まずはkotlin conf聞けてないので、やってみたいです。
またfleetというxcode開発もできてしまうideがあるとのことで、kmp開発時にすごく役立ちそうだと思いました。
トップバッターにふさわしい内容で、とてもためになりました。


## kotlinで愉しむクリエイティブコーディング
プログラミング教室で実践されている、初心者のための講義内容を紹介されていました。
openrndrというライブラリを使うと、簡単に高度なグラフィックが作れます。
（大昔にpcのミュージックプレイヤーを再生すると変な波形のグラフィックが表示されましたが、それを思い出しました。）
完成されたものは少ないコードからできたと思えないほどいいものでした。
数学とか物理学の現象をつかって面白いグラフィックを作っていました。
そういうの話好きなので、楽しかったです。普段やらない領域だったこともあり、面白かった。


## パフォーマンスと可読性を両立：kotlinのcollection関数をマスター
今回聞いた中で一番やさしい内容かと思います。kotlinのcollectionってたくさんあって、うろ覚えの物もあり、
改めて整理できました。
あと個人的にはcopilotなど生成aiをうまく使うと、使うべきcollectionがsuggestされてよいと思っています。
collection多くて覚えられない人におすすめです。



## okioに愛を込めて
あまりkmp最近できてないですが、kotlinx-datetimeとか出ているんですね。
かなり昔やってたときはios用とandroid用に車輪の再開発をしていました、、
kmp改めてやってみたいと思いました。
プレゼンの構成もとてもよく参考にしたいです。


## kotlin coroutinesで共有リソースに正しくアクセスする
flowとかコルーチンとか学びたてのときにmori atsushiさんのブログには大変お世話になりました。
今回の内容もとてもわかりやすかったです。
改めてmutexやupdate関数、競合に対する考え方の解像度を上げることができました。
atomicintegerとかも知らなくて、勉強になりました。


## もっとKotlinを好きになる！K2時代のKotlin Compiler Plugin開発
なかなか上級者向けですが、Compilerの構造の概要を知ることができ、面白かったです。
このセッションのお陰で、あとのセッションも見やすくなりました（後述）
実際に自分がCompiler Pluginを開発することは今のところ想像できないですが、
Koltlinの情報収集やビルド関連のトラブルシューティングをする際に、少しでも概要を知っておくことで良い方向に働くのではないかと思います。


## まだ JUnit を使ってるの？ kotest を使って快適にテストを書こう
Kotestは知っていたが試せていなかったので、試したい！と思いました。
Fieldごとに比較できる良いマッチャーがあるのはよさそうです。
Truthライブラリでそんな困っていないし、Junitの書き方で慣れてしまっているのでこれでいいんじゃないかと思っていましたが、
食わず嫌いはだめなので試してみることにします。


## Jetpack Compose: 効果的なComposable関数のAPI設計
Composableで開発しているときのあるあるがつまっていました。
個人的にはできるだけシンプルにし、拡張性や設計や最低限にして、
必要になったときに考えるというのが良いと思っています。
どんな状況にでも当てはまる指針は存在せず、
その都度いい方法を柔軟に考えないといけないということだと思いました。私も賛同です。

## K2のKotlin IDEプラグインの中を覗いてみよう♪
JetBrainsの中の人ということで、K2で何が変わったか、変化の背景や目的などの話で、
面白かったです。
意味解析の部分が大きく変わったとのことでした。
IDEのindexを作る時間が短くなるということで、これは嬉しいことですね。
Kotlinを楽しく使えているのもIDE開発者の努力のおかげなんだなということを改めて感じ、頭が上がらないです。
k2にするとたまに見かけたsyntax highlight errorもなくなるようです。
あと、Android StudioにもK2モード追加されていますね。


## Kotlinの歴史を可視化する
githubのKotlinのリポジトリのコミット数などを分析していく話。
どちらかというとKotlinの話よりデータ分析の話でした。
世間には大量のデータがあり、なんとなくデータを評価しがちですが、
論理的に、数学的な方法を使って評価していかないと信頼できないよね、と思いました。
Python3: ヘッダーを考慮した csv ファイルの読み込み

## csv ファイル

ヘッダーを考慮すれば次の2つのファイルは、同じデータになります。

```text:cities_header.csv
id,name,population,date_mod
t1271,千葉,59178,2003-9-21
t1272,勝浦,47215,2003-10-15
t1273,市原,83654,2003-6-14
```

```text:cities_ex02.csv
id,date_mod,population,name
t1271,2003-9-21,59178,千葉
t1272,2003-10-15,47215,勝浦
t1273,2003-6-14,83654,市原
```

ふたつ目のファイルは次のようにして作成しました。

```bash
awk -F, '{print $1","$4","$3","$2}' cities_header.csv > cities_ex02.csv
```

## csv を読むプログラム

```py:csv_header.py
#! /usr/bin/python
#
#	csv_header.py
#
#						Jun/23/2024
# ------------------------------------------------------------------
import sys
import csv
import json

# ------------------------------------------------------------------
def file_write_proc(file_name,str_out):
	fp_out = open(file_name,mode='w')
	fp_out.write(str_out)
	fp_out.close()
#
# ------------------------------------------------------------------
file_csv=sys.argv[1]
file_json=sys.argv[2]
#
with open(file_csv, "r") as f:
	csvreader = csv.DictReader(f)
	rows = list(csvreader)
#
out_str = json.dumps(rows)
file_write_proc(file_json,out_str)
# ------------------------------------------------------------------
```

## 実行

```bash
./csv_header.py cities_header.csv cities.json
./csv_header.py cities_ex02.csv cities_ex02.json
```

実行結果
>ヘッダーが考慮されて読み込まれていることを確認

```text
$ jq . cities.json
[
  {
    "id": "t1271",
    "name": "千葉",
    "population": "59178",
    "date_mod": "2003-9-21"
  },
  {
    "id": "t1272",
    "name": "勝浦",
    "population": "47215",
    "date_mod": "2003-10-15"
  },
  {
    "id": "t1273",
    "name": "市原",
    "population": "83654",
    "date_mod": "2003-6-14"
  }
]
```

```text
$ jq . cities_ex02.json 
[
  {
    "id": "t1271",
    "date_mod": "2003-9-21",
    "population": "59178",
    "name": "千葉"
  },
  {
    "id": "t1272",
    "date_mod": "2003-10-15",
    "population": "47215",
    "name": "勝浦"
  },
  {
    "id": "t1273",
    "date_mod": "2003-6-14",
    "population": "83654",
    "name": "市原"
  }
]
```
[Amazon Web Services]awscliv2とSSMエージェントのインストール

AWSCLIおよびSSMエージェントの、Linuxサーバへのインストールスクリプトを作成しました。
個人的には、AmazonLinux以外のインスタンスにも標準で設定しておいてほしい...

```install.sh
#!/bin/bash

# AWS CLIインストール
echo "awscliv2.zipをダウンロードします..."
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "/tmp/awscliv2.zip"
check_exists_unzip=$(yum list installed | grep unzip)
if [ "$check_exists_unzip" == "" ]; then
    echo "解凍パッケージunzipを新規インストールします..."
    yum install -y unzip > /dev/null
    if [ "$?" == 0 ]; then
        echo "unzipのインストールに成功しました..."
    else
        echo "エラー: unzipのインストールに失敗しました。" >&2
        exit 1
    fi
fi
echo "awscliv2.zipを解凍します..."
unzip /tmp/awscliv2.zip -d /tmp > /dev/null

echo "awscliv2をインストールします..."
/tmp/aws/install > /dev/null
aws --version
if [ "$?" == 0 ]; then
    echo "awscliのインストールに成功しました。"
else
    echo "エラー: awscliインストールに失敗しました。処理を中断します。" >&2
    exit 1
fi

rm -rf /tmp/awscliv2.zip
rm -rf /tmp/aws

#ユーザーのリージョンで RHEL 9 用のSSMカスタムエージェントインストール
echo "SSMエージェントをインストールします..."
dnf install -y https://s3.ap-northeast-1.amazonaws.com/amazon-ssm-ap-northeast-1/latest/linux_amd64/amazon-ssm-agent.rpm > /dev/null
if [ "$?" == 0 ]; then
    echo "SSMエージェントのインストールに成功しました..."
else
    echo "エラー: SSMエージェントのインストールに失敗しました。処理を中断します。" >&2
    exit 1
fi

echo "SSMエージェントの稼働ステータスを確認します..."
check_status=$(systemctl status amazon-ssm-agent | awk '/Active:/ {print $2}')
if [ "$check_status" == "active" ]; then
    echo "SSMエージェントは正常に稼働しています。"
else
    echo "エラー: Amazon SSM Agent が正常に動作していません。処理を中断します。"
    exit 1
fi

echo "全ての処理が完了しました。"
```

ということで初めての投稿でした。
投稿を続けられるようコメントは最小限です..
Cloud Run + Gorm + Cloud SQLで詰まった

## 前提
`Go` + `Echo` + `Gorm`で作成したWebアプリを`Google Cloud Run`にデプロイしました。その際に、`Cloud SQL`との接続に詰まりました。

## 詰まったところ
### DB接続
DBが中々接続できずに公式ドキュメントを読みましたが、
https://cloud.google.com/sql/docs/mysql/samples/cloud-sql-mysql-databasesql-connect-connector?hl=ja
以下の2つのライブラリを用いるものでした。
```go
import (
    "cloud.google.com/go/cloudsqlconn"
    "github.com/go-sql-driver/mysql"
)
```
私の場合には`Gorm`のSQLドライバーを使っていたので、大幅な改修が必要そうなこの方法を回避したかったので調べてみると以下のページに解決方法が載ってました。
https://qiita.com/sky0621/items/d91ffd1faf63dbf34e77
googleのドライバー以外から接続するときには`unix`でソケットと`Cloud SQL`のファイル名？(接続名)を指定して接続するみたいです。
```db.go
var (
		dbUser = os.Getenv("MYSQL_USER")
		dbPwd  = os.Getenv("MYSQL_PW")
		dbHost = os.Getenv("MYSQL_HOST") // project_id:region/db_instance_name
		socket = "/cloudsql"
		dbName = os.Getenv("MYSQL_DB")
	)

	path := fmt.Sprintf("%s:%s@unix(%s/%s)/%s?parseTime=true",
		dbUser,
		dbPwd,
		socket,
		dbHost,
		dbName,
	)
```
しかしここでまたエラー。具体的なエラーは忘れてしまいましたが、`unix`が接続名からファイルを見つけられない、的なエラーでした。どうやら`Cloud SLQ`に接続するには、`SQL admin API`を有効にする必要があるようです。
https://console.cloud.google.com/apis/enableflow?apiid=sqladmin&_ga=2.66654765.-2015653629.1511582598&project=crested-century-425512-p0
## 結果
DBに接続できました。
Youtube の動画、Commandprompt から .mp3 で DL できる？


# 初めに
Hello Youtube !!

ということで、（？）ゆーちゅーぶのミュージック動画を自分の PC のコマンドプロンプトから DL できたらなんかかっこよくないかい？~~（ッフ）~~

ということで、（汗

この記事ではあなたの PC のコマンドプロンプトからどのようにして Youtube の動画を .mp3（ミュージックファイル）でダウンロードすることができるのか、記載されています。

※Youtube の利用規約には次のように記載されています。悪しからず♪

本サービスの利用には制限があり、以下の行為が禁止されています。
1. 本サービスまたはコンテンツのいずれかの部分に対しても、アクセス、複製、**ダウンロード**、配信、送信、放送、展示、販売、ライセンス供与、改変、修正、またはその他の方法での使用を行うこと。ただし、（a）本サービスによって明示的に承認されている場合、または（b）YouTube および（適用される場合）各権利所持者が事前に書面で許可している場合を除きます。
# 使い方
実際のところ、非常に簡単に環境を用意することができます。

1. Node.js をインストール
1. [ytdl-mp3 が存在する git repository](https://github.com/joshunrau/ytdl-mp3)より、npm を介してのインストールコマンドを参照

以上で、あなたの PC のコマンドプロンプトは 世界の Youtube から 動画音源をダウンロードするマジックぱわーを手に入れるのでした…

・・・具体的に説明すると、
Node.js 入れる
コマンドプロンプトに以下のコマンド入力してエンター
```
npm install -g ytdl-mp3
````

これで、あとは動画の音源を入れる方法ですが…

以下を参照にしてコマンドプロンプトに入力して Enter すれば OK です。
```:参照
Usage: ytdl-mp3 [options] <url>

A NodeJS package and command-line tool for downloading music from YouTube, including automatic retrieval of ID3 tags and album art via iTunes.

Arguments:
url                     url of video to download

Options:
-V, --version           output the version number
-o --output-dir <path>  path to output directory
-n --no-get-tags        skip extracting/applying id3 tags
-v --verify-tags        verify id3 tags fetched from itunes
-h, --help              display help for command
````

```:コマンドの例
>>>ytdl-mp3 https://www.youtube.com/watch?v=uRE8oEP9_bI
````

# 最後に

以上のことをしてしまったあなた、もうこちら側の人間です

悪しからず♪
git hub の src をチラ見してみると、どうやら ytdl-core を一緒に使って、そっちでまず DL してたぶんその後 mp3 にフォーマットして返す…みたいなことをしてるのかなと思います。

多分です＿・・＿

最後まで見てくれてありがとう！！
CesiumJS で地下にある 3D Tiles などを非表示にする方法

もうね。しょっちゅうこのプロパティーを忘れちゃうんです。

```
viewer.scene.globe.depthTestAgainstTerrain = true;
```

underground hide とかでググってもなかなか正解にたどり着けないのです。

たしかちょっと検索ワードをチョイスする時のの感覚がちがったよなぁといううろ覚えの感覚はあっても、depth test 系にたどりつきにくいのです。

ということで、メモです。

ついでに地面の衝突をしないようにして地下にカメラを移動させたい時はこちら。

```
viewer.scene.screenSpaceCameraController.enableCollisionDetection = false;
```
Cloud Runへのデプロイでハマった、ModuleNotFoundErrorと.env参照の仕方

個人開発しているwebアプリがやっと形になったので、Cloud Runを使って「よしデプロイしよう！」と意気込んだものの、わからないことだらけでハマりました。


## `ModuleNotFoundError`にハマる

まずハマったのが、デプロイした直後の`ModuleNotFoundError: No module named 'config'`というエラー。

gunicornを用いて`wsgi.py`からDjangoプロジェクトを起動していくため、Dockerfileに以下を記述しています。

```docker
FROM python:3.11

ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

WORKDIR /code

COPY requirements.txt ./
RUN python3 -m pip install --upgrade pip setuptools
RUN pip install -r requirements.txt

COPY . ./

CMD exec gunicorn --bind :$PORT --workers 1 --threads 8 --timeout 0 config.wsgi:application
```

gunicornで起動する先を指定するのに`config.wsgi:application`としていますが、ここの`config`が見つけられないらしい。

ディレクトリ構成として`config`からの指定で合っているはずなのになぜ…と悩んでいたら閃きました。


### シークレットを記述したファイルのマウントの仕方を間違えていた

Clooud Runでは、機密情報を記載した[シークレットを作成](https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets?hl=ja)して、それを各プロジェクトで参照します。

[シークレットを使用](https://cloud.google.com/run/docs/configuring/secrets?hl=ja#access-secrets)する際にファイルベースで読み込むには、マウントパスを指定するのですが、ここで間違いが。

**マウントパスを指定した先は、ファイル等が存在する場合上書きされてしまう**ようです。

https://stackoverflow.com/questions/77902545/getting-modulenotfound-error-when-deploying-a-gunicorn-flask-app-in-cloud-run-w

私はそのパスをプロジェクトルートに設定していたため、すべて上書きされ存在しないものになっていました…。

マウントパスを`/conf/.env`として新規ディレクトリを指定し、無事Dockerfileが`config`を参照できるようになりました！


## 今度はpython側で`.env`が認識できない

無事起動できたと思いきや、今度は`.env`が認識されず、
```
KeyError: 'SECRET_KEY'
```
とログに記載が…。


### `.env`を読み込むためのパスを忘れず設定すべし
結論、`.env`を読み込むパスの設定が間違っていました。
環境変数周りは、[dotenv](https://pypi.org/project/python-dotenv/)を使っているので、`load_dotenv`で以下のようにパスの設定が必要でした。

```python:wsgi.py
import os

from django.core.wsgi import get_wsgi_application
from dotenv import load_dotenv

# wsgi.pyから見て、親の親ディレクトリがルートになるのでdirnameを２回適用している
dotenv_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "environment/.env")
load_dotenv(dotenv_path=dotenv_path)
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.production")

application = get_wsgi_application()
```

```
project-root
├── Dockerfile
├── Dockerfile.dev
├── environment
│   └── .env
├── config
│   ├── __init__.py
│   ├── asgi.py
│   ├── settings
│   │   ├── __init__.py
│   │   ├── base.py
│   │   ├── devlopment.py
│   │   └── production.py
│   ├── urls.py
│   └── wsgi.py
├── manage.py
├── media
├── app
├── requirements.txt
├── static
├── staticfiles
└── templates
```

無事読み込めて、デプロイできました！！
【ABC359】AtCoder Beginner Contest 359【C++】

### コンテスト名
ユニークビジョンプログラミングコンテスト2024 夏（AtCoder Beginner Contest 359）

### コンテストURL
https://atcoder.jp/contests/abc359

### 開催日
2024/06/22 21:00-22:40

---

## [A: Count Takahashi](https://atcoder.jp/contests/abc359/tasks/abc359_a)
**解法**
- 文字列が等しいか判定する
```cpp:ABC359A.cpp
#include <iostream>
#include <string>
using namespace std;

int main(){
    int n;
    cin >> n;

    string s;
    int cnt = 0;
    for(int i=0; i<n; i++){
        cin >> s;
        if(s=="Takahashi"){
            cnt++;
        }
    }

    cout << cnt << endl;
    return 0;
}
```

## [B: Couples](https://atcoder.jp/contests/abc359/tasks/abc359_b)
**解法**
- $A_i = A_{i+2}$ を満たす $i$ の個数を求める
```cpp:ABC359B.cpp
#include <iostream>
#include <vector>
using namespace std;

int main(){
    int n;
    cin >> n;

    vector<int> A(2*n);
    for(int i=0; i<2*n; i++){
        cin >> A[i];
    }

    int cnt = 0;
    for(int i=0; i<2*n-2; i++){
        if(A[i]==A[i+2]){
            cnt++;
        }
    }

    cout << cnt << endl;
    return 0;
}
```

## [C: Tile Distance 2](https://atcoder.jp/contests/abc359/tasks/abc359_c)
**解法**
- 処理を簡単にするためにスタートとゴールの座標はタイルの一方の端に揃える
- $y$ 軸方向に移動すると、移動できる $x$ 軸方向の範囲が増えることに注意する
    - $x$ 軸方向の移動と $y$ 軸方向の移動の大小によって計算が異なる
```cpp:ABC359C.cpp
#include <iostream>
#include <cmath>
using namespace std;

int main(){
    long long int sx, sy, tx, ty;
    cin >> sx >> sy >> tx >> ty;

    if((sx+sy)%2==0){
        sx++;
    }
    if((tx+ty)%2==0){
        tx++;
    }

    long long int ans = 0;
    if(abs(sx-tx)<=abs(sy-ty)){
        ans = abs(sy-ty);
    }else{
        ans = abs(sx-tx)/2 - abs(sy-ty)/2 + abs(sy-ty);
    }
    
    cout << ans << endl;
    return 0;
}
```

## [D: Avoid K Palindrome](https://atcoder.jp/contests/abc359/tasks/abc359_d)
**解法**
- 動的計画法 (DP)
- $\text{dp} [i][s]$ := $i$ 文字目まで決定して、直前の長さ $K - 1$ の文字列が $s$ である場合の数
    - 文字列を状態として持つ 
- `map<string, long long int>` で管理する
```cpp:ABC359D.cpp
#include <iostream>
#include <string>
#include <algorithm>
#include <map>
using namespace std;

bool kaibun(string s){
    string s2 = s;
    reverse(s2.begin(), s2.end());
    return s == s2;
}

int main(){
    const int MOD = 998244353;

    int n, k;
    cin >> n >> k;

    string s;
    cin >> s;

    map<string, long long int> dp;
    dp[""] = 1;
    for(int i=0; i<n; i++){
        map<string, long long int> tmp;
        swap(tmp, dp);
        for(auto [t, num] : tmp){
            for(char c='A'; c<='B'; c++){
                if(s[i]!='?' && s[i]!=c){
                    continue;
                }

                string t2 = t + c;

                if(t2.size()==k && kaibun(t2)){
                    continue;
                }

                if(t2.size()==k){
                    t2.erase(t2.begin());
                }

                dp[t2] += num % MOD;
                dp[t2] %= MOD;
            }
        }
    }

    long long int ans = 0;
    for(auto [t, num] : dp){
        ans += num;
        ans %= MOD;
    }

    cout << ans << endl;
    return 0;
}
```
【Python】音声ファイル変換・webmからwavに変換

# 概要
Pythonのffmpegを用いて、webmファイルをwavファイルに変換する
ffmpegはほかにもいろんな音声ファイルに変換できる

## 事前準備
1. 以下の公式サイトからffmpegをダウンロード
https://ffmpeg.org/download.html

2. 解凍したらbinのなかに入っているexeファイルを適当なフォルダに入れる

3. システム環境変数にそのフォルダのパスを通す
3. VScodeのターミナルなどでffmpegをインストールする

## コード

```python
import ffmpeg

input_file = 'aiu.webm'
output_file = 'aiu.wav'

(
    ffmpeg
    .input(input_file)
    .output(output_file)
    .run()
)
```

## 注意

```shell
Error opening input: No such file or directory
```
このようなエラーが出たときは、実行した際のカレントディレクトリを確認してみて。
そのpythonのファイルからたどった音声ファイルのパスは正しくても、実行時のターミナルのカレントディレクトリが違うとエラーがでたよ


## ちなみに
直接データを変更する場合、

aiu.webmのバイナリデータを受け取る
↓
バイナリデータからwavのデータに変換する
↓
wavファイルとして保存する

```python

import ffmpeg
import subprocess

# バイナリデータを読み込む
with open('aiu.webm', 'rb') as f:
    input_data = f.read()

# ffmpegプロセスの実行
process = (
    ffmpeg
    .input('pipe:0', format='webm')  # 'pipe:0' は標準入力からデータを受け取る指定
    .output('pipe:1', format='wav')  # 'pipe:1' は標準出力にデータを出力する指定
    .run_async(pipe_stdin=True, pipe_stdout=True, pipe_stderr=True)
)

# ffmpegにデータを渡して、出力データを取得
output_data, err = process.communicate(input=input_data)

# 出力データをファイルに書き込む
with open('aiu.wav', 'wb') as f:
    f.write(output_data)

# エラーメッセージを表示（必要に応じて）
if process.returncode != 0:
    print("Error:", err.decode())

````
BEMを3分間で理解する

## はじめに

BEM（ブロック、エレメント、モディファイア）は、フロントエンド開発において再利用可能なコンポーネントとコード共有を実現するための方法論です。
BEMは、Block（ブロック）、Element（エレメント）、Modifier（モディファイア）の頭文字を取ったもので、それぞれの部分を詳しく見ていきましょう。

## ブロック (Block)

ブロックは、それ自体で意味を持つ独立したエンティティです。例えば、ヘッダー、メニュー、ボタン、入力フィールドなどがブロックに該当します。

```html
<div class="block">
    ブロックの内容
</div>
```

### 使用時のポイント
- ブロックの名前は目的を表していること
    - 以下の質問に対しての答えがブロックの名前になっている
        - 例）Q. これは何？ -> A. これは`メニュー`です
- 他のブロックに影響を与えないようにすること
    - ブロックにはpaddingやmarginを使わないこと
- CSSタグ名やIDは使わないこと

## エレメント (Element)

エレメントは、特定の機能を果たすブロックの一部です。エレメントはそのブロックに結びついています。例えば、メニューブロック内のメニューアイテムや、フォームブロック内の送信ボタンがエレメントに該当します。

```html
<div class="block">
    <div class="block__element">
        エレメントの内容
    </div>
</div>
```
### 使用時のポイント
- エレメント名は目的を表していること
    - 以下の質問に対しての答えがエレメントの名前になっている
        - 例）Q. これは何？ -> A. これは`項目`です
- エレメント名は`ブロック名__エレメント名`のようにアンダースコアを2つブロック名とエレメント名の間につける

## モディファイア (Modifier)

モディファイアは、ブロックやエレメントの見た目や動作、状態を変えるためのフラグです。例えば、ボタンの無効状態や、メニューアイテムのハイライト状態がモディファイアに該当します。

```html
<div class="block">
    <div class="block__element block__element_modifier">
        変更されたエレメントの内容
    </div>
</div>
```

### 使用時のポイント
- モディファイア名は見た目を表していること
    - 以下の質問に対しての答えがモディファイアの名前になっている
        - 例）Q. これの色は何？ -> A. この色は`red`です
- モディファイア名はアンダースコアを1つブロック名またはエレメント名とモディファイア名の間につける


## 実践例

BEMの使い方を実践的に理解するために、簡単なカードコンポーネントを作成してみましょう。

```html
<div class="card">
    <div class="card__header">
        カードのヘッダー
    </div>
    <div class="card__body">
        カードのボディ
    </div>
    <div class="card__footer card__footer_highlighted">
        カードのフッター
    </div>
</div>
```

この例では、
- `card`がブロックです。
- `card__header`、`card__body`、`card__footer`がカードブロックのエレメントです。
- `card__footer_highlighted`がカードフッターの見た目を変えるモディファイアです。

## BEMの利点

1. **再利用性**: コンポーネントはプロジェクトの異なる部分で再利用可能です。
2. **可読性**: コードが読みやすく、理解しやすくなります。
3. **保守性**: コードベースの保守と更新が容易になります。

## まとめ

BEMは、クリーンで再利用可能かつ保守しやすいコードを書くための強力な方法論です。CSSをブロック、エレメント、モディファイアに構造化することで、スケーラブルで扱いやすいコードベースを作成することができます。

---
> 1. [BEM Quick start
](https://en.bem.info/methodology/quick-start/)
GStreamerのインストールおよびバージョン共存


# 動作環境
| モノ | バージョン | 
|:-----------|:------------|
| マシン       | x86_64        |
| Ubuntu     | 24.04      |
| meson     | 1.3.2      |
| ninja      | 1.11.1          |
| ホストマシンGStreamer    | 1.24.2       |
| インストールGStreamer    | 1.22.5       |

# はじめに
アプリのリソース使用率などを調査する中、GStreamerのバージョンで差分ある説が浮上。切り替えたい。でもホストマシンに元来入っているGStreamerは壊したくないし、Dockerとアプリの相性もわるそう。

# 問題
ググるも、ホストマシンにGStreamerのバージョン共存させる記事は中々見つからず。仕方ないのでChatGPTくんの力も借りつつ、よしなに調整したら解決したっぽいので共有する。

# 解決方法
```shell:コマンド
# 各種ダウンロード
wget https://gstreamer.freedesktop.org/src/gstreamer/gstreamer-1.22.5.tar.xz
wget https://gstreamer.freedesktop.org/src/gst-plugins-base/gst-plugins-base-1.22.5.tar.xz
wget https://gstreamer.freedesktop.org/src/gst-plugins-bad/gst-plugins-bad-1.22.5.tar.xz
wget https://gstreamer.freedesktop.org/src/gst-plugins-good/gst-plugins-good-1.22.5.tar.xz
wget https://gstreamer.freedesktop.org/src/gst-plugins-ugly/gst-plugins-ugly-1.22.5.tar.xz

#解凍
tar xf gstreamer-1.22.5.tar.xz
tar xf gst-plugins-base-1.22.5.tar.xz
tar xf gst-plugins-bad-1.22.5.tar.xz
tar xf gst-plugins-good-1.22.5.tar.xz
tar xf gst-plugins-ugly-1.22.5.tar.xz

#各種ビルドおよびインストール
cd gstreamer-1.22.5
meson build/
ninja -C build/
cd build
ninja install

cd gst-plugins-base-1.22.5
meson build/
ninja -C build/
cd build
ninja install

cd gst-plugins-bad-1.22.5
meson build/
ninja -C build/
cd build
ninja install

cd gst-plugins-good-1.22.5
meson build/
ninja -C build/
cd build
ninja install

cd gst-plugins-ugly-1.22.5
meson build/
ninja -C build/
cd build
ninja install

#各種設定の反映。これ実行時点で、GStreamer Core Library version 1.22.5になる
sudo ldconfig
#~/gst-plugins-ugly-1.22.5/build$ gst-launch-1.0 --gst-version
#GStreamer Core Library version 1.22.5

#パス設定
nano ~/.bashrc

#別画面。末尾に以下を追加
#ホストマシンのバージョン使用時は下3行、インストールのバージョン使用時は上3桁をコメントアウト
# host GSt ver
export PATH=/usr/bin:$PATH
export LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH
export PKG_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu/pkgconfig:$PKG_LIBRARY_PATH

# installed GSt ver
export PATH=/usr/local/bin:$PATH
export LD_LIBRARY_PATH=/usr/local/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH
export PKG_LIBRARY_PATH=/usr/local/lib/x86_64-linux-gnu/pkgconfig:$PKG_LIBRARY_PATH

#パス設定の反映
source ~/.bashrc
```


# 終わりに
パス設定でバージョン切り替えられたら成功です。時間のロスが少しでも減ればと思います
# 参考
https://qiita.com/yh1224/items/18f5db443938f2885015
Tobila Phoneで受付電話を使う話

# 概要

クラウド電話のTobila Phone、便利ですよね。スマホを内線代わりに使えます。
リモートワークだとそれで十分なのですが、オフィスができて、受付電話も欲しくなりました。

いわゆるタブレットの受付って、大手企業や、大量の来客があるならアポイントの整理ができて便利なのですが、
中小企業は、受付電話で来客対応した方が、シンプル＆合理的です。

受付に電話機を置き、受話器を取ったら、自動で内線を呼び出す設定を説明します。

# 電話系の機器構成

受け付け用電話（おしゃれ機)

https://amzn.asia/d/0iBmPUwv

VoIPアダプタ HT801

https://amzn.asia/d/03mnD5dR

# 設定

HT801は、Tobila Phoneでは公式では対応していませんので、ご自身の責任で対応してください。

## Tobila Phone事前準備

システム設定＞ユーザ管理で、ユーザーを増やしてください。
ユーザーの右欄に「SIP設定」という項目があるので、確認してください。
こちらの内容を設定に使います。

![スクリーンショット 2024-06-23 13.09.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654875/747afc36-1c10-fb1b-8666-5ed4bf2d1d53.png)

## HT801基本設定

公式マニュアルなどを見て、ログインしてください。

### BASIC SETTING

- Time Zone: GMT+09:00(Japan, Korea, Yakutsk)
- Allow DHCP server to set Time Zone: NO

### ADVANCED SETTING

設定変更しなかった

### FXS PORT

- Primary SIP Server: cloud-pbx01.tobila.cloud:5061 (SIP設定を参考にしてください)
- Outbound Proxy: cloud-pbx01.tobila.cloud:5061 (SIP設定を参考にしてください)
- SIP Transport: TLS (default is UDP)
- SIP User ID: Tobila Phone事前準備で確認した、認証IDを入れてください。
- Authenticate ID: Tobila Phone事前準備で確認した、認証IDを入れてください。SIP User IDと同じです。
- Authenticate Passwod: Tobila Phone事前準備で確認した、認証パスワードを入れてください。
- Preferred DTMF method: ３つともRFC2822に設定しました。
- Send Hook Flash Event: Yes
- Preferred Vocoder：　全てPCMUに設定してください。
- G723 Rate：5．3KBps
- SRTP Mode: Enabled and forced
- SLIC Setting: JAPAN PBX
- Caller ID Scheme: Bellcore/Telcordia
- Enable Hook Flash: No

### 設定＆リブート

ここで、画面下部のUpdateボタン、Applyボタン、Rebootボタンを押して再起動してみてください。
電話機を繋いでみて、内線に繋げるか、確認してください。

### 自動ダイヤル設定

受話器を取ったら、自動で特定の内線番号をコールする設定です。

Offhook Auto-Dial: 内線番号（例：200)

例えば、複数の内線番号に同時に鳴らしたい場合には、Tobila Phone側の「内線グループ管理」で、例えば内線番号200を追加し、そこに所属する内線番号を追加してください。例えば、事務の内線を同時に鳴らすなどです。

# Enjoy!!

それでは快適な内線オフィスライフをお楽しみください。

# Memo

私が詰まった箇所。
Tobila PhoneのアプリをインストールしているiPhoneを「仕事モード」にして通知を切ってると、Busyになります。
これで100年無駄にしました。
Salesforce関連レコード更新で気を付けること～レコード画面で項目が表示されない場合の注意点

本橋孝昭です

今回は

Salesforce関連レコード更新で気を付けることとして「参照のみ」レコード権限の場合は関連レコードコンポーネントには正しい設定の項目は表示されずコンパクトレイアウトが出る
という仕様をお伝えします


元々の前提設定としてはこちら

https://qiita.com/Takaa/items/cb928573492b17abfb3a

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531631/f57dd0af-cb11-5627-c5c6-1b00a831575c.png)


通常であればこのように電話項目のみ表示され、鉛筆マークが出て編集が可能です

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531631/20e84a1a-b908-b0be-cb61-08d357e14d42.png)


しかしながら、取引先の編集権限がない場合（参照のみの場合）には、このようにコンポーネント内で見える項目はコンパクトレイアウト項目となります

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531631/a7a8a2a5-465d-64d0-4135-485f46bc5fa0.png)



詳細はこちらのヘルプに記載されております

関連レコードコンポーネントの項目が表示されない

https://help.salesforce.com/s/articleView?id=000380203&type=1

以下ヘルプ抜粋
>関連レコードコンポーネントは、クイックアクションにリンクされた標準の Lightning ページコンポーネントです。関連レコードについての情報を表示し、ユーザが他のレコードを編集、作成、関連付けできるようにします。ユーザによっては、関連レコードコンポーネントの項目セットとして表示される内容が、システム管理者に表示されるものと異なる場合があります。

>これは、関連レコードに対する権限が "参照のみアクセス" であるユーザが関連レコードを表示すると、項目はコンパクトレイアウトを使用して表示されるためです。
解決策
ユーザにシステム管理者と同じ項目セットが表示されるよう、関連オブジェクトに対する作成と編集の権限をユーザに付与します。手順は次のとおりです。


現状は権限付与しか回避策は無いようですね
Vue.jsでの関数切り出しとPiniaエラーの回避方法

# ■はじめに
　Vue.jsを学習中のエンジニアです👩🏻‍💻
今回は、関数をmodulesに切り出す際のPiniaに関するエラーについて記事を書こうと思います。
同じように困っている方の助けになればと思います☺️

※この記事ではPiniaのStoreの知識があることを前提で記事を記載していますので、ご了承ください。

# ■前提条件
- フロント：Vue.js,TypeScriptを使用
- Store使用
- 関数切り出しを行うためにsrc/modulesフォルダにファイルを作成して関数切り出し

# ■エラーが起きた事象
addNumbers関数、multiplyNumbers関数をmodulesフォルダのCalculationFunctions.tsファイルに切り出しました！

```.store/useCalculationStore.ts
import { ref } from 'vue'
import { defineStore } from 'pinia'

export const useCalculationStore = defineStore('calculation', () => {
  const numberA = ref<number>(2)
  const numberB = ref<number>(3)

  return {
    numberA,
    numberB
  }
})
```

```.modules/CalculationFunctions.ts
import { storeToRefs } from 'pinia'
import { useCalculationStore } from '@/store/useCalculationStore'

const { numberA, numberB } = storeToRefs(useCalculationStore())

export const addNumbers = (): number => {
  return numberA.value + numberB.value
}

export const multiplyNumbers = (): number => {
  return numberA.value * numberB.value
}
```
結果以下のエラーがコンソールに表示されるようになりました🤔

このメッセージは、getActivePiniaという関数が呼び出されたが、アクティブなPiniaインスタンスが存在しないということを示しています。
```
pinia.js?v=77070775:1355 Uncaught Error: [🍍]: "getActivePinia()" was called but there was no active Pinia. Are you trying to use a store before calling "app.use(pinia)"?
See https://pinia.vuejs.org/core-concepts/outside-component-usage.html for help.
This will fail in production.
    at useStore (pinia.js?v=77070775:1355:13)
    at CalculationFunctions.ts:4:42
```

# ■エラー原因
### Vueコンポーネント外でのストアの使用
- PiniaのStoreは、Vueのライフサイクルに依存しているため、コンポーネント外で直接使用するとVue初期化が完了していないタイミングでストアが使用される可能性があります。
その結果、Storeのインスタンスが存在しない状態でStoreなどの関数を呼び出すとエラーになります。

### VueのアプリケーションとPiniaの初期化
通常、Vueアプリケーションでは以下のようにしてPiniaを初期化し、Vueのアプリケーションインスタンスに組み込んで使用します。
```main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

const app = createApp(App)
const pinia = createPinia()

app.use(pinia)
app.mount('#app')
```
しかし、Vueコンポーネント外でStoreを使用する場合、この初期化の文脈が失われるため、エラーが発生します。

# ■解決方法
### 関数内でStoreから定義された変数や状態を呼び出すように変更する
以下のように関数内で必要な変数等をStoreから呼び出すことで解決しました！
```CalculationFunctions.ts
import { storeToRefs } from 'pinia'
import { useCalculationStore } from '@/store/useCalculationStore'

export const addNumbers = (): number => {
  const { numberA, numberB } = storeToRefs(useCalculationStore())
  return numberA.value + numberB.value
}

export const multiplyNumbers = (): number => {
  const { numberA, numberB } = storeToRefs(useCalculationStore())
  return numberA.value * numberB.value
}
```
# ■おわりに
　ここまで読んでいただきありがとうございます🙇🏻‍♀️今回のエラーの原因と解決方法を共有することで、同じような問題に直面している方の参考になれば幸いです。関数の切り出しやStoreの使用に関して、引き続き学んでいきたいと思います。
[Dify]Unstrucured ETLを使ってみよう


## UnstructuredのAPIキー取得
以下のURLからアカウント登録を行なってAPIキーを取得してください。
https://unstructured.io/api-key-hosted

あとドキュメントはここ
https://docs.unstructured.io/api-reference/api-services/free-api


## Difyの設定
環境変数に以下を追加してください。APIキーに関しては先ほど取得したものを使ってください。
docker-compose.yamlを使っている方はapiとworkerの下に配置するのです。

```
ETL_TYPE: Unstructured
UNSTRUCTURED_API_URL: https://api.unstructured.io/general/v0/general
UNSTRUCTURED_API_KEY: your-api-key
```

## DifyETLとUnstructured ETLのサポート形式の違い

- Unstructured ETL
ファイル解析のサポート形式: `txt、markdown、md、pdf、html、htm、xlsx、xls、docx、csv、eml、msg、pptx、ppt、xml、epub`
![スクリーンショット 2024-06-23 12.58.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/376115/f1e77729-c456-5e05-ca6c-868c48ddb0fe.png)

- DIFY ETL
ファイル解析のサポート形式: `txt、markdown、md、pdf、html、htm、xlsx、xls、docx、csv`
![スクリーンショット 2024-06-23 12.55.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/376115/f0d3be9a-cc49-0040-5314-f503fb564e6a.png)


ファイル解析のサポート形式の違いが出ていますね！Unstructuredの方がファイルの種類は多いですね。パワポとかもこれで行けるようになりますね！

ちなみにDifyのクラウド版はデフォルトでUnstructuredを使っているようです。

ちなみに私はUnstructuredで好きな機能はPDFからの画像切り抜き用のOCRです。
PDFを与えた時に表や図を一緒に切り抜いてくれれば最高なんですがね。。。

## 最後に
Xやってるので気になる方はフォローお願いします。
https://x.com/hudebakonosoto
THE ストレスマネジメント

# はじめに
最近ストレスマネジメントについて学んだので、学んだ内容と自分なりに色々調べた内容をミックスさせて、自分の中で刺さった要点だけ絞って記事にします。

# そもそもストレスマネジメントとは？
厚生労働省の意味を引用します。

>身体や心に悪影響を起こすストレスに対し、どのように対処しどのように付き合っていくかを考えることを、ストレスマネジメントと呼びます。
ストレスマネジメントでは、ストレスがかかったときの対処法（コーピング）やストレスに対する認知の変容を中心に指導をしていきます。

引用: https://www.e-healthnet.mhlw.go.jp/information/dictionary/tobacco/yt-049.html

# 超ざっくり要点
## ストレッサーの種類
・物理のストレッサー（物理的、生物的、科学的ストレッサー）
・心理のストレッサー（精神的ストレッサー）
・社会のストレッサー（社会的ストレッサー）
・環境のストレッサー（気候、災害ストレッサー）

## ストレス反応の種類
・メンタル面
・フィジカル面

## ストレスを生じさせる原因
・仕事以外（育児、家事、生活環境など）
・仕事（人間関係、仕事の内容など）
・個人要因（受け止め方、行動パターン）

## 受け止め方
"ネガティブに捉えてしまう"ことも出来れば、
"ポジティブに捉えて考える"ことも出来る

レビューで指摘をされた場合のことを例として、受け止め方について少々極端に記載してみる
　例）
 　ネガティブに捉えてしまうパターン
 　　→指摘をした人はきっと私を嫌っているからだ
　ポジティブに捉えるパターン
   　　→指摘をしてくれてありがとう！
     　　　次から指摘をされないよう気をつけ、かつ他の人にも共有して同じような指摘をされないようにするわ！

## コーピング
①問題焦点型
　ストレッサー自体を取り除いて、ストレスをなくしていく
 
②情動焦点型
　ストレッサー自体を取り除くのではなく、不安や恐れといった感情を間接的にコントロールしてストレスを緩和する

# 個人的に響いた点
受け止め方一つで考え方は変わってくるため、ポジティブに捉えることが重要。

コーピングンの情動焦点型も重要な点で、感情を間接的にコントロールすることを考えてみる。
例）
・気晴らしをする
・問題を静観する
・問題について考えるのを止める

上記のように、考えてもしょうがない場合も時にはある。
そのため、時には「問題について考えるのを止める」というのも一つの手であることを覚えておく。

仕事をする時はする、休憩や休息を取るときはとる、という頭の切り替えをし、ストレスに対する感情のコントロール法をしっかりしておく必要がある。
これだけ抑えておくVim編

# はじめに
超初心者向けの最低限これだけ抑えておけばとりあえず使えるVim操作について記載しておきます。
個人的には滅多に使用せず、毎回調べながら使っていたので超初心者向けの記事を記載しておきます。

# 最低限の操作
| コマンド | 意味 | 
|:-----------|:------------|
| i       | 挿入モード        | 
| o       | カーソル直下行に新しい行を追加し挿入モード        | 
| ESC       | ノーマルモード        | 
|  vim XXX.txt      | xxx.txtという名前の新規ファイルを作成し、同時にVimエディタを起動。既に同名のファイルが存在する場合はそのファイルを起動する | 
| :wq       |  変更内容を保存して終了       | 
| :q!       |  保存せずに終了       | 

# でもやっぱりvimは慣れないんだな
私も最初はそう思っていました。
癖ははっきり言って強いですが、便利で覚えると非常に早くタイピングできる強力なツールです。
※ガチVim使いは信じられないくらいタイピング早いです。

で、やっぱりvimはちょっと難しそう、、という方は
以下コマンドででデフォルトのテキストツールが起動します。
<font color="Red">**「open xxx.txt」**</font>
これで乗り切りましょう。
【Java】ArrayListクラスを継承する

#### ▶はじめに
ArraiListクラスの継承について最近学んだのでアウトプットのために記事を書こうと思いました。
初学者のため、間違いなどありましたご指摘いただけますと幸いです。

#### ▶（前提）ArrayListとは
そもそもArrayListってなに？というところから説明できればと思います。

ArrayListとは、Listインターフェースを実装したクラスの一つです。
実装には配列が使われており、要素の追加や削除ができます。
配列ではないのか？と思う方もいるかもしれませんが
配列はサイズが宣言時に固定されている一方で、ArrayListは後から必要に応じて拡張されます。
```Java:宣言方法
ArrayList<-> 変数名 = new ArrayList<->();

//上記に書き方よりも下記で書かれていることの方が多い
List<-> 変数名 = new ArrayList<->();
```
なぜList<->という書き方なのかについては今回触れないので、調べていただければと思います。
(QiitaでもListとArrayListについて書かれている記事はたくさんありました）


実際のコードとしては、下記のような書き方になります
```Java: 実際のコード例

List<String> sports = new ArrayList<String>();

sports.add("サッカー");
sports.add("野球");
sports.add("テニス");

//取り出すときは拡張for文
for(String s : sports){
    System.out.println(s);
}
```
上記を前提として、ArrayListクラスの継承について書いていきます。

#### ▶ArrayListクラスの継承

ArrayListクラスの継承は下記のようにします
```Java: ArrayList継承サンプル
public class Sample extends ArrayList<T>{

    // ここに追加のメソッドやフィールドを定義することができる
    // ArrayListクラスのメソッドや機能は自動的に継承される

}
```

ここで注意するべき点があります。
ArrayListはSelializableインターフェースを実装しているので、下記の一文を書かないと警告がでるそう
```
private static final long serialVersionUID = 1L;
```
ちなみに私を書かなくても警告もでず実行できてしまった（どうやら事前に警告が出るように設定してないとそのままいけてしまうぽい）ので、上記の必要性やシリアライズってなに？というところはこれから勉強していきます:sweat:

#### ▶実際に使ってみよう

簡単なショッピングカートを実装する際にArrayListクラスがどう使われるのかをみていきましょう。
Itemクラス,ShoppingCartクラスを使って上記のArrayListの継承をやっていきます。
※Itemクラスを継承する商品クラスは今回記載を省きます。

```Java: Itemクラス

public class Item {

	/** 商品名 */
	private String name;
	/** 値段 */
	private Integer price;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Integer getPrice() {
		return price;
	}

	public void setPrice(Integer price) {
		this.price = price;
	}

}
```

```java: ShoppingCartクラス
public class ShoopingCart extends ArrayList<Item>{

	
    private static final long serialVersionUID = 1L;

    /**
     * 商品の合計金額を取得する.
     * @return 商品の合計金額
     */
    public int getTotalPrice() {
        int totalPrice = 0;
        for (Item item : this) {
            totalPrice += item.getPrice();
        }
        return totalPrice;
    }

    /**
     * 商品の平均金額を取得する.
     * @return 商品の平均金額
     */
    public double getAverage() {
        return getTotalPrice() / size();
    }
}
```

ShoppingCartの実装を見てみるとArrayList<Item>をしていることによって、ArrayListクラスのメソッドを継承することができsize()メソッドをそのまま使えていますね。
ArrayListクラス他にもを継承することにより、add(),remove()メソッドなども使用可能になります。

#### ▶最後に
オブジェクト指向や継承をしっかり理解しておかないとなかなかイメージがつきづらいなと感じる一方で、逆に理解できていれば継承などを用いてよりスマートなコードが書けるのではないかと思いました。
（記事を書くのは難しい、、）
Spring Data Jpaの@OneToMany @ManyToOne　Entityテーブル結合基礎　備忘録＃５

### ER図

![スクリーンショット 2024-06-23 12.28.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3807467/7d3c3f50-9c83-5a6a-d92d-81599f62b29e.png)


### 成果物イメージ
![スクリーンショット 2024-06-23 12.53.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3807467/f1d999dc-eeab-966c-ae14-d4153be1e560.png)



### schema.sql

```schema.sql
-- テーブル削除
DROP TABLE IF EXISTS company CASCADE;
DROP TABLE IF EXISTS employee CASCADE;

-- カテゴリーテーブル
CREATE TABLE  company
(
   id SERIAL PRIMARY KEY,
   name TEXT
);

-- アイテムテーブル
CREATE TABLE employee
(
  id SERIAL PRIMARY KEY,
  name TEXT,
  company_id Integer
  );
  
```

### data.sql

```data.sql
-- カテゴリーテーブルデータ
INSERT INTO company(name) VALUES('aaa株式会社');
INSERT INTO company(name) VALUES('bbb株式会社');
INSERT INTO company(name) VALUES('ccc株式会社');
INSERT INTO company(name) VALUES('ddd株式会社');
INSERT INTO company(name) VALUES('eee株式会社');


-- 商品テーブルデータ
INSERT INTO employee(name, company_id) VALUES('田中太郎', 1);
INSERT INTO employee(name, company_id) VALUES('鈴木一郎', 1);
INSERT INTO employee(name, company_id) VALUES('佐藤健', 2);
INSERT INTO employee(name, company_id) VALUES('渡辺順一', 2);
INSERT INTO employee(name, company_id) VALUES('斎藤真由美', 3);
INSERT INTO employee(name, company_id) VALUES('佐々木信人', 3);
INSERT INTO employee(name, company_id) VALUES('武田昌幸', 4);
INSERT INTO employee(name, company_id) VALUES('石川小百合', 4);
INSERT INTO employee(name, company_id) VALUES('太田雅子', 5);
INSERT INTO employee(name, company_id) VALUES('橋本寮', 5);
```

:::note info
schema.sqlとdata.sqlは特に変える必要はない
:::

### Entity


```Company.java
@Entity
@Table(name = "company")
public class Company {
     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)
     private Integer id;
     private String name;


     @OneToMany()//1対多
    @JoinColumn(name = "company_id")
     private List<Employee> favoriteHotels = new ArrayList<>();
     
 //ゲッター、セッター、コンストラクタ省略
```

:::note warn
フィールドとは別で、OneToManyとする
:::



```Employee.java
@Entity
@Table(name = "employee")
public class Employee {

     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)
     private Integer id;
     private String name;
     
     @ManyToOne //多対1
     @JoinColumn(name = "company_id")
     private Company company;
     
//ゲッター、セッター、コンストラクタ省略
```

:::note warn
ManyToOneの方は、フィールドの方に記述する。
:::

### Controller

```EmployeeController.java
 @GetMapping("/employee")
    public String employee(Model model) {
        List<Employee> employee = employeeRepository.findAll();
        model.addAttribute("employee" , employee);
        
        return "employeeList";
    }
```

### HTML


```employee.html
 <h3>全社員一覧</h3>
  <table border="1">
    <tr>
      <th>社員ID</th>
      <th>名前</th>
      <th>会社名</th>
    </tr>
    <tr th:each="emp:${employee}">
      <td th:text="${emp.id}"></td>
      <td th:text="${emp.name}"></td>
    <!--companyIdから会社名（name）を持ってくる-->
      <td th:text="${emp.company.name}"></td>
    </tr>
  </table>
  <a href="/main">会社一覧</a>
```

参考サイト

https://qiita.com/frost_star/items/855e7fb52dca9de7566e
Salesforceでフローを作成しよう～一致レコードを確認設定

本橋孝昭です

今回はSalesforceフローの機能「レコードを作成する前の重複の確認」について解説します。

こちらの設定は2024年6月（Summer'24）から登場した新機能ですね


フローの詳細はこちらのヘルプを確認してください

https://help.salesforce.com/s/articleView?id=release-notes.rn_automate_flow_builder_check_for_duplicate_records_with_the_updated_create_records_element.htm&release=250&type=5


今回は例として
・取引先の選択リスト値「評価」が「Hot」の場合「Opportunity1」という商談名の商談レコード作成する
・しかし、取引先にすでに「Opportunity1」という商談名の商談が作成されている場合には「評価」が「Hot」に何度もなっても商談レコードが作成されない
（つまり「Opportunity1」という商談名の商談レコードの重複を防ぐ）

通常のフローの場合は
・例えば、取引先の選択リスト値「評価」が「Hot」に変更してからまた別の値に変更され、また「評価」が「Hot」に変更すると商談レコード「Opportunity1」　がずっと作成される

という動きになってしまいますが、今回の新機能を使えばそのような重複を防ぎます

フロー画面はこちら

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531631/004c2baa-afbb-a24f-0e5f-4a75d19e82b3.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531631/fb829059-129a-5359-2145-573ec3a0fd24.png)


ポイントはレコード作成設定のこちらですね

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531631/6ec57ab1-4a14-be31-4526-9ae4d9f1c579.png)

こちらを有効にしておくことで、すでに商談レコード「Opportunity1」　が作成されている場合商談レコード作成を防ぐことが可能です

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531631/41ee6411-c3f9-6883-b96f-fc70363360e4.png)