OmniAICreator/Qiita-1.07M huggingface dataset:
Common Lisp風のLISPを作ってみる（２．データ型）

データ型を作っていきましょう。`type.h`, `type.c`というファイルで管理します。
[ソースコードへのリンク](https://github.com/haruo-wakakusa/my-lisp2/tree/main/chapter02)
# オブジェクト型の識別方法
これは、私が以前関わっていたKuin言語の方式にならってそうしているのですが、オブジェクトの先頭にヘッダー部をつけています。例えばコンスオブジェクトなら、次のような感じになります。
```type.h
typedef struct HEADER {
    TYPE type;
} HEADER;

typedef struct CONS {
    HEADER h;
    void *car;
    void *cdr;
} CONS;
```
# 型の種類
型の種類は以下７種類になります。
```type.h
typedef unsigned char TYPE;

#define TYPE_SYMBOL 0
#define TYPE_CONS 1
#define TYPE_STRING 2
#define TYPE_NUMBER 3
#define TYPE_BUILT_IN_FUNC 4
#define TYPE_USER_DEFINED_FUNC 5
#define TYPE_SPECIAL_OPERATOR 6
```
ここで、どうして`enum`型を使わないのか、ということになりますが、笹川賢一さんのMonolisに対するAmazonレビューで、型の識別に４バイトも使うなんて贅沢すぎるといった意見を書いていた人がいたためです。ちなみに、もともと`const TYPE`型の定数で定義していたのですが、`#define`で書き直しているのはリンクエラーを回避するためです。
## シンボル型
シンボルは、主に変数や関数の名前に用いられます。TとNILもシンボル型にしています（@masagl07s さん、ご教示頂きありがとうございます）。
```type.h
typedef struct SYMBOL {
    HEADER h;
    char str[];
} SYMBOL;
```
この構造体にはサイズが指定されていない配列メンバ`str`があります。これはフレキシブル配列メンバというもので、実体はサイズ0の配列です(@SaitoAtsushiさん、ご教示頂きありがとうございます）。フレキシブル配列メンバは以下のように使用します。
```C
SYMBOL * make_symbol(char *str1) {
    size_t len;
    SYMBOL *symbol;

    len = strlen(str1) + 1;
    symbol = malloc(sizeof(SYMBOL) + len);
    symbol->h.type = TYPE_SYMBOL;
    strcpy(symbol->str, str1);

    return symbol;
}
```
## 文字列型
文字列型は、他の言語と同様の文字列の型です。シンボル型同様、可変長データとなります。
```type.h
typedef struct STRING {
    HEADER h;
    char str[];
} STRING;
```
## コンス型
LISPのリストはコンスセルの集合からなります。
```type.h
typedef struct CONS {
    HEADER h;
    void *car;
    void *cdr;
} CONS;
```
## 数値型
my-lisp2の数値型はdoubleだけです。数値型のサブタイプを増やすとシステムが複雑になるのでそのようにしています。
```type.h
typedef struct NUMBER {
    HEADER h;
    double num;
} NUMBER;
```
## 組み込み関数型
組み込み関数のオブジェクトは関数ポインタを保持します。
```type.h
typedef struct BUILT_IN_FUNC {
    HEADER h;
    void *(*f)(void *);
} BUILT_IN_FUNC;
```
## ユーザー定義関数型
ユーザー定義関数のオブジェクトは、ラムダ形式と環境を保持します。
```type.h
typedef struct USER_DEFINED_FUNC {
    HEADER h;
    void *body;
    void *func_env;
    void *var_env;
} USER_DEFINED_FUNC;
```
## スペシャルオペレーター型
Common Lispには関数でもマクロでもないスペシャルオペレーターと呼ばれるものがあります。スペシャルオペレーター自体をオブジェクトとして扱うのはおかしいかもしれませんが、関数・マクロと同じ環境の中に入れて一元管理したかったので型を用意しています。
```type.h
typedef struct SPECIAL_OPERATOR {
    HEADER h;
    void *(*op)(void *, void *, void *);
} SPECIAL_OPERATOR;
```
## TとNILはグローバル変数として公開する
T値とNIL値はグローバル領域にオブジェクトを持たせ、必要があればそれらのオブジェクトを参照させるようにします。
```type.h
extern void *T;
extern void *NIL;
```

```type.c
char T_BODY[] = { TYPE_SYMBOL, 'T', '\0' };
char NIL_BODY[] = { TYPE_SYMBOL, 'N', 'I', 'L', '\0' };

void *T = (void *)&T_BODY;
void *NIL = (void *)&NIL_BODY;
```
[Node.js][Jest] 単体テストにおける jest.spyOn の使い方

# 環境
MacBook Pro (2.3 GHz 8コアIntel Core i9)
macOS 14.0（23A344）
Homebrew 4.3.8
gh 2.52.0

# 目次

# `jest.spyOn(object, methodName)`の基本構文
> jest.fnと同様の関数を作成しますが、引数に与えられたobject[methodName]へのコールも実装します。 Jestの モック関数を返します。


~~~ts:xx.test.ts
const spy = jest.spyOn(object, methodName);
~~~

# `mockFn.mockResolvedValueOnce(value)`の基本構文
> モック関数を1回呼び出したときに返す値を受け取ります。 次のモック関数へのコールが異なる値を返せるようチェーンすることができます。

~~~ts:xx.test.ts
const spy = jest.spyOn(object, methodName);
spy.mockResolvedValueOnce(value);
~~~

# 活用例1
## テスト対象の関数
APIからデータを取得する関数`fetchUserData`があり、それをテストするとします。この関数は、`getUser`という外部の関数を使ってデータを取得します。
~~~ts:userService.ts
// テスト対象の関数
export const getUser = async (id: number) => {
    // 実際にはAPIからユーザー情報を取得する処理がある（実際のコードではなく、構造や流れを示すためのサンプル関数）
    return { id, name: "John Doe" };
};

export const fetchUserData = async (id: number) => {
    const user = await getUser(id);
    return user.name;
};
~~~

## テストデータ
~~~ts:userService.fixture.ts
// テストデータ
const mockUserData = { id: 1, name: "Alice" };
~~~

## 単体テストコード

~~~ts:userService.test.ts
import { fetchUserData, getUser } from './userService';
import { mockUserData } from './userService.fixture';

test('fetchUserData should return user name', async () => {
    // Arrange
    // getUser関数をスパイして、モックデータを返すように設定
    const getUserSpy = jest.spyOn(require('./userService'), 'getUser')
    getUserSpy.mockResolvedValueOnce(mockUserData);

    // Act
    // fetchUserDataを実行
    const userName = await fetchUserData(1);

    // Assert
    // getUserが正しく呼び出されているかを確認
    expect(getUserSpy).toHaveBeenCalledWith(1);
    // fetchUserDataが返す値がモックデータの名前であることを確認
    expect(userName).toBe("Alice");
});
~~~

# 活用例1のアルゴリズムイメージ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2955010/1a56af41-7d52-0c0b-ebc8-70b995c75832.png)

# 参考リンク
## メイン
- [jest.spyOn(object, methodName)](https://jestjs.io/ja/docs/jest-object#jestspyonobject-methodname)
- [mockFn.mockResolvedValueOnce(value)](https://jestjs.io/ja/docs/mock-function-api#mockfnmockresolvedvalueoncevalue)

## AAA
> - 単体テストメソッドのArrangeセクションでは、オブジェクトを初期化し、テスト対象のメソッドに渡されるデータの値を設定します。
> - Actセクションでは、設定されたパラメーターでテスト対象のメソッドを呼び出します。
> - Assertセクションでは、テスト対象のメソッドの操作が予測どおりに動作することを検証します。.NETでは、多くの場合、Assertクラスのメソッドが検証に使用されます。
- [単体テストの基本](https://learn.microsoft.com/ja-jp/visualstudio/test/unit-test-basics?view=vs-2019#write-your-tests)
解いてみた。「Aランク：お菓子の詰め合わせ」

# 問題
[「Aランク：お菓子の詰め合わせ」](https://paiza.jp/works/mondai/a_rank_skillcheck_archive/school_hiking)

# コード
Javaで解いてみました。

``` Java
import java.util.*;

public class Main {
    private static final int MAX_VAL = 99999;
    private static final int MIN_VAL = 0;
    
    private static int nN;
    private static int nX;
    private static int[] anx;

    private static int nMaxKind;
    private static int nMinChange;
    
    public static void main(String[] args) {

        Scanner sc = new Scanner(System.in);
        
        // 事前準備
        nN = sc.nextInt();
        nX = sc.nextInt();
        
        anx = new int[nN];
        for (int i = 0; i < nN; i++) {
            anx[i] = sc.nextInt();
        }

        nMaxKind = MIN_VAL;
        nMinChange = MAX_VAL;
        
        // 商品購入開始
        func(0, 0, 0);  
        
        // 結果出力
        System.out.println(nMinChange);
    }
    
    private static void func(int n, int kind, int sum) {
        if (n >= nN) {
            int nChange = nX - sum;
            if (nChange < 0) { return; }
            
            if (kind > nMaxKind) {
                nMaxKind = kind;
                nMinChange = nChange;
            } else if (kind == nMaxKind) {
                if (nMinChange > nChange) {
                    nMinChange = nChange;
                }
            }
            
            return;
        }
        
        func(n+1, kind, sum);           // 商品nを買わなかった場合
        func(n+1, kind+1, sum+anx[n]);  // 商品nを買った場合
    }
}

```

# 解説
## 標準入力からの数値の読み取り
Scanner インスタンスを作り、nextInt()メソッドで数値を取得しています。
使用する変数は、グローバル変数的に使用したいので、基本、クラスのメンバ変数として定義しています。

## func()メソッドの処理
### n < N の場合
次の2つの場合について、func()メソッドを呼び出します。
+ n番目の商品を買わなかった場合、nだけ１増やして、商品の種類k、合計額sはそのまま、func()メソッドに渡す
+ n番目の商品を買った場合、nを１増やし、商品の種類kも１増やす。合計額に商品の値段anx[n]を足し、func()メソッドに渡す

### n = N の場合
終了処理として、以下の判定を行います。
+ 購入合計額がXを超えていたら、何もしない。
+ 購入した種類が、暫定種類最大値より大きい場合、暫定種類最大値と、暫定最小釣銭を、現在の値で更新する
+ 購入した種類が、暫定種類最大値と等しい場合、お釣りが暫定最小釣銭より小さければ、暫定最小釣銭を更新する
【学習記録　2024-08-18】　　　JavaScript

## 教材を使用する
Udemy「超JavaScript完全ガイド 2024」

## 学習内容
### 学習時間：0.5h
- エラーオブジェクトについて
- 今後のキャリアプランについてご相談

## 所感
　エラーオブジェクトはJavaにもあったのですぐ理解できました！
　あと学習ではないですが、MENTAにて今後のキャリアプランについて相談させていただきました。かなりためになる内容だったので学習のモチベーションアップになりました！
CSS｜要素の間に余白をつける

要素が並んでいて、間に余白をつけるとき、あなたは何派ですか？

1. それぞれクラス名を別々につける
2. gap
3. :not:(first-child）
4. 隣接セレクタ
5. その他

今まで隣接セレクタを使う機会が少なかったので、練習しておきます。

<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="OJeQXZG" data-pen-title="Untitled" data-user="hiroro-d" 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/hiroro-d/pen/OJeQXZG">
  Untitled</a> by Hiroyuki Doke (<a href="https://codepen.io/hiroro-d">@hiroro-d</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

隣接セレクタの説明

<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="rNEJLQy" data-pen-title="Untitled" data-user="hiroro-d" 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/hiroro-d/pen/rNEJLQy">
  Untitled</a> by Hiroyuki Doke (<a href="https://codepen.io/hiroro-d">@hiroro-d</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

ちなみにSassを使えば「＆ ＋ ＆」で実現できます。
```css:アンバサンド.scss
      & + & {
        margin-top: 20px;
      }
```
他の人が見たら「なんだこれ？」ってなりそうですね。
VSCodeでEC2にRemote - SSH接続するときの注意

Amazon Linux 2023のEC2にVSCodeからリモートSSHで接続をすると最初は普通に使えていても数分後に接続が途絶え、応答がなくなる事態が起きていました

解決策として以下のようなことを設定しました
### ファイル監視によるリソースの負荷
VSCodeには「ファイル監視」機能があり、これにより大量のファイルが監視されると、EC2インスタンスのリソースが消費され、サーバーに過度な負荷がかかる可能性があります。この問題を防ぐためには、以下の手順を実行しました。

1. VSCodeの設定を調整:

    メニューから「ファイル」→「ユーザー設定」→「設定」を選択。
    「設定の検索」に「Watcher Exclude」と入力し、「パターン追加」 から ** を追加する。これにより、全てのファイルの監視が無効化され、リソース消費を抑えることができます。
    設定を反映させるために、VSCodeを再起動します。

1. ファイル監視の上限設定:

    現在のファイル監視上限を確認するために、以下のコマンドを実行します:
    ```
    cat /proc/sys/fs/inotify/max_user_watches
    ```
    この上限を引き上げるには、/etc/sysctl.confファイルに次の行を追加し、その後、設定を適用します:
    ```
    fs.inotify.max_user_watches=524288
    ```
    ```
    sudo sysctl -p
    ```
    これにより、最大524,288個のファイルを監視できるようになります。ただし、リソースが限られている環境では、この数値を調整します。
    https://code.visualstudio.com/docs/setup/linux#_visual-studio-code-is-unable-to-watch-for-file-changes-in-this-large-workspace-error-enospc

### 同一ユーザーでの接続問題
複数のユーザーが同じEC2インスタンスに同一ユーザーで接続する場合、プロセスの競合やゾンビプロセスの発生が問題となることがあります。これを防ぐために、ユーザーごとに適切にアカウントを分け、リソース管理をすることが推奨されます。TeraTermでの接続と同時にVSCodeでも接続をしている場合などは注意が必要です。
JavaScript で 3D の地球の描画：ライブラリなどを雑に調べてみる【その1】

この記事は、JavaScript で 3D の地球の描画ができるライブラリなどを、網羅的ではなく雑に調べてみるという内容です。

とりあえず、以下と別のもう 1つを試してみてたのですが、それを探す過程で見つけた情報をメモとして残してみます。

https://x.com/youtoy/status/1825062055973711934

## ライブラリなど

### Gio.js
最初は、冒頭でも試した Gio.js です。

●Gio.js
　https://giojs.org/

描画部分で Three.js を使うようで、使い方のところで Three.js も読みこむようにと書かれています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/3365b94f-7077-b894-d49d-439f2c4ba725.png)

データの可視化をしたときの描画で、光るエフェクトが素敵です。

[ライブラリのリリース履歴](https://github.com/syt123450/giojs/releases)を見ると、最近はあまり更新されてなさそうな雰囲気もありそうでした。

あと、Web検索をしてみると、日本語で書かれた記事がいくつか出てきたりもしました。

●"gio.js" - Google 検索
　https://www.google.com/search?q=%22gio.js%22

### Globe.GL
次に Globe.GL です。

●Globe.GL | globe.gl
　https://globe.gl/

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/7d9bded7-69e0-51ca-fb11-4261847dd2db.png)

公式の GitHubリポジトリを見ると、「ThreeJS/WebGL用の three-globe のラッパー」のようです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/950b8c92-c56d-ad3e-2158-82036e10daef.png)

GitHubリポジトリを見ると、最近も更新が行われているようです。

あと、ざっくり検索をしてみた感じだと、日本語の情報は少なめかもしれません。

#### three-globe
上記の three-globe もリンクだけ掲載しておきます。

●vasturiano/three-globe: WebGL Globe Data Visualization as a ThreeJS reusable 3D object
　https://github.com/vasturiano/three-globe

### CesiumJS
こちらは CesiumJS です。

●CesiumJS – Cesium
　https://cesium.com/platform/cesiumjs/

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/5232196e-5af2-af6e-5ffa-b01b37c71d76.png)

https://x.com/CesiumJS

ざっくり検索してみた感じだと、日本語の情報もけっこうでてきます。

## おわりに
まだ他にもありそうですが、自分がざっくり調べて気になったものを、まずは 3つ紹介してみました。
paiza * qiita記事投稿キャンペーンに参加してみた：Aランクスキル「本の整理 C編」

# はじめに
paizaとqiitaの記事投稿キャンペーンに参加してみました。の流れでBランクの問題にもチャレンジ内容を解説しました。
・関連記事
https://qiita.com/hyonkichi/items/b17aef92bb560d6a53f3
https://qiita.com/hyonkichi/items/33890d18cf8d2ff57e0e
https://qiita.com/hyonkichi/items/a483c3bea4942be878ca

# 問題
https://paiza.jp/works/mondai/real_event/book_sort

#解答コード
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void){
    // 自分の得意な言語で
    // Let's チャレンジ！！
    char str[1000000];
    char *pstr;
    int i,j;
    int book_num;
    int book_list[100000]={0};
    int chg_cnt=0;
    int id_buf, id_min_pos;
    
    //冊数取得
    fgets(str, sizeof(str), stdin);
    sscanf(str, "%d\n", &book_num);
    //printf("book_num=%d\n",book_num);
    
    //本棚情報取得
    fgets(str, sizeof(str), stdin);
    pstr = strtok((char*)str, " ");
    for(i=0; i<book_num; i++)
    {
        book_list[i] = atoi(pstr);
        //printf("book_list[%d]=%d\n",i,book_list[i]);
        pstr = strtok(NULL, " ");
    }    
    
    //本の入れ替え回数算出
    for(i=0; i<(book_num-1); i++)
    {
        id_min_pos = i;
        for(j=(i+1); j<book_num; j++)
        {
            if(book_list[id_min_pos] > book_list[j])
            {
                id_min_pos = j;
            }
        }
        
        if(i!=id_min_pos)
        {
            id_buf = book_list[i];
            book_list[i] = book_list[id_min_pos];
            book_list[id_min_pos] = id_buf;
            chg_cnt++;
        }
    }

    printf("%d",chg_cnt);
    return 0;
}
```
# 解説
与えられた本のリストをソートし、その過程で本の入れ替えが行われた回数を計算します。具体的には、選択ソートというアルゴリズムを使用して本のリストを昇順に並べ替え、その際に必要となった入れ替えの回数をカウントします。
プログラムの流れを以下示します。

### 変数の定義と初期化:
str[1000000]は、標準入力から取得した文字列を格納するためのバッファです。
book_list[100000]は、与えられた本のリストを格納するための配列です。最大100,000冊の本がリストに含まれることを前提としています。
chg_cntは、本の入れ替えが行われた回数をカウントするための変数です。
id_bufは、ソート中に本の位置を一時的に入れ替えるために使用する変数です。
id_min_posは、現在の範囲で最小の本の位置を保持する変数です。

### 本の冊数と本棚情報の入力:
最初に、入力から本の冊数を取得し、book_numに格納します。
次に、本棚にある本のリストを取得し、スペース区切りで分割して整数に変換し、book_list配列に格納します。この際、strtok関数を使用して文字列を分割しています。

### 選択ソートによる本の入れ替え回数の計算:
選択ソートは、リストの中から最小の要素を選び、それを現在の位置に移動させるアルゴリズムです。このプログラムでは、外側のforループがリストの各要素を走査し、その要素に対応する最小値を見つけます。
内側のforループでは、現在の要素とそれ以降の要素を比較し、最小の要素の位置を見つけます。
最小の要素が現在の位置にない場合、2つの要素を入れ替え、chg_cntを増加させます。これにより、入れ替えが発生した回数をカウントできます。

###  結果の出力:
ソートが完了すると、chg_cntの値を出力します。この値は、与えられた本のリストをソートするために行われた入れ替えの回数を表します。
選択ソートアルゴリズムについて
選択ソートは、非常にシンプルなソートアルゴリズムであり、以下の手順で動作します。
リストの最初の要素を基準にし、それ以降の要素と比較して最小の要素を見つける。
最小の要素を現在の位置と入れ替える。
この操作をリスト全体に対して繰り返す。
選択ソートの計算量はO(n^2)であり、特に大きなリストに対しては効率的ではありませんが、入れ替えの回数が明示的に計算されるため、この問題のように入れ替えの回数が重要な場合には適しています。

### ちなみに
はじめにコードを提出した際、いくつか"ランタイムエラー"になったので、見直したところ、char str[x];のサイズが足りておらず標準入力を受けきれていませんでした。
デフォルトでchar str[1000];が記述されるので、ここは触らないものだと決めつけてました..
単体テスト計画書の記載事項（目的・観点等）について整理してみる

## はじめに
担当プロジェクトで単体テスト計画書をつくることがあったので、記載項目等を共有したいと思います。
これから単体テスト計画書を作成される方の参考になれば幸いです。

## 該当工程
![単体テスト計画](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2739239/c36a4497-2710-e325-154e-ad872bc5f8ef.png)

単体テストは、「ソフトウェアレベルの単体テスト」と「機能レベルの単体テスト」に分かれます。
これらのテストは、実装作業と並行して行われることが多いので、「実装工程実行計画書」の中に含めて書かれる場合もあると思います。

この記事ではそれぞれ分けて記載しますので、プロジェクト特性に合わせるようにしてください。

## ソフトウェア単体テスト

### テストの目的
各ソフトウェア単体が詳細設計書で定義された仕様通りに動作することを検証する。

### テスト範囲・テスト対象
※プロジェクトにおけるテスト対象範囲を記載してください。システム構成図があると分かりやすいです。
※また、詳細設計書の管理単位（詳細機能一覧）を列挙しておくと、進捗管理に使えます。

### テスト観点
・エラーやログ出力などの共通詳細仕様について、仕様通りにソフトウェアが動作することを確認する。
・詳細設計書の個別詳細機能仕様について、仕様通りにソフトウェアが動作することを確認する。

### テスト手法
・ホワイトボックステスト手法を用いて、詳細設計書の分岐を網羅するようにテストケースを設定する。
・テストケース設定においては、C1カバレッジが100%であることを確認する。（何かしらの理由によって実施できないものはその理由を明記する）

### テスト環境
・各開発者のローカル環境にて検証を行う。
・また、テストデータはテスト条件を満たすデータを手作成する。

### 開始条件・完了条件
開始条件：　
・テスト対象のソフトウェアについて、実装が完了していること。
・テスト対象のソフトウェアについて、テスト仕様書が完成していること。
完了条件：
・テスト対象のソフトウェアについて、テストケースが全て実施されていること。
・検出した不具合が全て解消されていること。

### 成果物
※顧客に納品が必要と定義されている成果物を列挙してください。

### 管理方針
#### 進捗管理
・詳細機能毎にテストの予実を管理する。
（例： 8/12時点 予定5本に対して、実績4本 遅延1本。遅延1人日）

#### レビュー計画
※ソースコードレビュー、テスト仕様書レビュー、テスト結果レビュー等のレビュー予定を記載してください。

#### バグ管理
※バグ検出時のフローや管理項目を記載してください。
ソフトウェア単体テスト段階では起票しない（管理しない）プロジェクトもあるかと思います。

#### 評価
※ソフトウェア単体テストでの品質指標（テスト密度やバグ密度）があれば、その数値に対しての良し悪しを評価します。

#### 体制・役割
※プロジェクトの体制図をペタッと貼る
※基本的にベンダー側の作業になりますが、ベンダー内で詳細な役割分担がある場合はそれを明記しておく
※また、「実装者とテスト実施者は別の担当者とする」といったルールがある場合も記載しておく。




## 機能単体テスト

### テストの目的
機能単体が基本設計書で定義された仕様通りに動作することを検証する。


### テスト範囲・テスト対象
※プロジェクトにおけるテスト対象範囲を記載してください。システム構成図があると分かりやすいです。
※また、基本設計書の管理単位（詳細機能一覧）を列挙しておくと、進捗管理に使えます。

### テスト観点
・基本設計書の個別機能仕様について、仕様通りに各機能が動作することを確認する。

※機能テストについては、TIS社が公表しているテスト観点カタログが素晴らしいので、ぜひ使ってみてください。

![テスト観点カタログ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2739239/e1f62082-91a4-1b9e-587b-054c6918e08c.png)

出典：[TIS『テスト観点カタログ』](https://fintan.jp/page/1456/)

### テスト手法
・ホワイトボックステスト手法を用いて、基本設計書の個別機能仕様を網羅するようにテストケースを設定する。
・テストケース設定においては、C1カバレッジが100%であることを確認する。（何かしらの理由によって実施できないものはその理由を明記する）

### テスト環境
・各開発者のローカル環境にて検証を行う。
　※テーブルは、プロジェクトで共通のスキーマを利用する場合もあると思います。
・また、テストデータはテスト条件を満たすデータを手作成する。

### 開始条件・完了条件
開始条件：　
・テスト対象の機能について、ソフトウェア単体テストが完了していること。
・テスト対象の機能について、テスト仕様書が完成していること。
完了条件：
・テスト対象の機能について、テストケースが全て実施されていること。
・検出した不具合が全て解消されていること。

### 成果物
※顧客に納品が必要と定義されている成果物を列挙してください。
※顧客企業によりますが、単体テスト仕様書やテスト結果エビデンスなどが考えられます。

### 管理方針
#### 進捗管理
・機能毎にテストの予実を管理する。
（例： 8/12時点 予定5本に対して、実績4本 遅延1本。遅延1人日）

#### レビュー計画
※テスト仕様書レビュー、テスト結果レビュー等のレビュー予定を記載してください。

#### バグ管理
※バグ検出時のフローや管理項目を記載してください。
※機能単体テスト段階では、ほぼ全てのプロジェクトでバグ管理をすると思います。

#### 評価
※機能単体テストでの品質指標（テスト密度やバグ密度）があれば、その数値に対しての良し悪しを評価します。

#### 体制・役割
※プロジェクトの体制図をペタッと貼る
※基本的にベンダー側の作業になりますが、ベンダー内で詳細な役割分担がある場合はそれを明記しておく
※また、「実装者とテスト実施者は別の担当者とする」といったルールがある場合も記載しておく。

## おわりに
単体テスト計画書の記載事項について整理してきました。
小規模なプロジェクトであれば、こういった計画書は不要かもしれませんが、ある程度の規模を超えてくるとプロジェクト関係者の共通認識を持って進めるためには計画書が必要になると思います。
【Unity】既存メニューのpriorityを確認する方法

UnityのEditor拡張でMenuを作成する場合、

```C#
[MenuItem("Assets/Create/TestMenu", priority = 100)]
private static void OpenWindow()
{
    Debug.Log("Open Window");
}
```

このようなコードでMenuを作れますが、`priority`の値をどのぐらいにしたら既存メニューの下に配置できるのかわかりません。

こういった場合に、既存メニュー(自分が作ったものも見える)の`priority`の値を表示する方法があったので紹介します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/608797/0bd2b3b1-cd91-3725-988f-8426a9ebb31c.png)
Edit ＞ Preferencesから

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/608797/19099317-d920-7080-9a6b-249cd7bc17fe.png)
Diagnosticsを選択
注意書きが書いてありますが中央の`I understand, show me the settings`を押します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/608797/c4dcee6e-828a-8233-11d3-d37bbed8f1ac.png)
そこから`MenuDisplayPriority`にチェックを打ちます。

チェックを打ったらUnityを再起動します。

するとほとんどのMenuにpriorityが表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/608797/b67e3559-4347-204d-a461-9322d1ccaded.png)
上のメニュ－

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/608797/da09a5f1-65f4-20a3-81ad-c3fa57cce37e.png)
上のメニューを開いたとき

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/608797/16f5db7e-456e-6d1b-8a53-83df1ea27207.png)
自作メニュー

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/608797/0eae506f-d1dd-e751-d3b1-9aee9c8cbafa.png)
右クリックメニュー

こうすれば自分の作りたいメニューのpriorityをどれぐらいに設定すればいいのかわかりやすくなります。
XCOPYで共有違反が発生

# 環境・事象
すべてWindows2022サーバー

複数台のサーバーから1台に対して定期的にログを転送する。

JP1/AJS3からバッチファイルを呼び出し、バッチファイルの中でxcopyを実行する。

コピー元

`D:¥～¥log¥yyyymmdd¥[hostname].log`

コピー先

`¥¥server¥～¥log¥`

xcopyが異常終了し、バッチファイルのログには「共有違反」が出力される。

発生頻度および発生サーバーはランダム。

# 原因
xcopy実行時、コピー先のフォルダーが存在しない場合は暗黙でフォルダー作成が実行される。

同名のフォルダー作成が同時に複数実行された場合、一件のみ成功し、残りは異常終了する。

xcopyの実行タイミングの重複が原因であり、AJS3は無関係。

仮にタスクスケジューラーからバッチ実行していても、実行時刻が同じなら同様の事象が発生していたと思われる。

# 対応
## 案1
xcopyではなく、リトライ機能のあるrobocopyを使う（＝バッチファイルの修正）

## 案2
ジョブネットの実行タイミングをずらす。

# 結論
他にxcopyを使っている処理が無いかといった類似確認や、ツール修正後の単体テストのやり直しなどの「お作法」をやる時間が無く、案2を採用。

類似確認は保守課題に引き継ぎとする。

以上
ExcelでCSVファイルを「0落ち」や「文字化け」なしで開く方法

# はじめに
CSVファイルをExcelで開こうとしたとき、電話番号や郵便番号の先頭のゼロ（0）が自動的に消えてしまったり、文字化けしてしまう...。そんな悩みをお持ちの方はいませんか？この記事では、データを正確に保持しながらCSVファイルを開く方法を簡潔にご紹介します。
# 1.CSVファイルとは
そもそもCSVファイルって何？というところから説明します。

CSVはComma Separated Value（カンマで区切られた値）の略であり、CSVファイルはテキストデータの各項目を、カンマ（,）で区切って表現したファイルです。
言葉では理解しにくいので、画像で見てみましょう。

まず、こちらがExcelでCSVファイルを開いたときの画像です。

![csv_important_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3663762/16f28dcd-e3b3-a5de-caa9-70d6dae19d37.png)
普段、Excelでデータを作成・編集しているときと同じように見えますよね？CSVといっても、特に変わったことはなさそうです。

では、Windowsの「メモ帳」のような、テキストエディタと呼ばれるソフトでCSVファイルを開いてみましょう。
以下はメモ帳を使った場合の画像です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3663762/f12bd7e8-2b2a-e1cf-3bb5-dbd18ce8c5ff.png)

先程のExcelを使った場合と比較してみると、2つの違いが見つかります。
・メモ帳では項目の区切りが「,（カンマ）」になっている
・メモ帳ではメンバーコード「001」だが、Excelでは「1」になってしまっている

Excelで開いた際には、閲覧・編集しやすいように、表形式でデータを表示しますが、実は、CSVファイルは、各データの列を「,（カンマ）」で、行を「改行」で区切った形式の、ただのテキストファイルです。そのため、扱いが容易で互換性が高く、色々なシステム・ソフトで採用されています。
しかし、ExcelでCSVファイルを編集する際、発生しがちな問題です。ここでは、本来「001」のデータが、Excelで開くと意図せずゼロが消えてしまい、「1」になっています。
次のセクションでは、どのような問題が起きる可能性があるか、ご紹介します。
# 2.ExcelでCSVファイルを編集するときの注意点

システムから出力したCSVファイルをダブルクリックして、Excelで開いてしまうと、以下のような問題が起きる場合があります。

・電話番号「080-1234-5678」が「80-1234-5678」になってしまった（ゼロ落ち、0落ち）
・時間数「27:00」が「1900/01/01 03:00:00」になってしまった（シリアル値）
・日付「7/1」が「7月1日」になってしまった（表示形式の変換）
・文字化けしてしまった（文字コードの不一致）　

次に、これらの問題への対処方法をご紹介します

# 3.ExcelでCSVファイルを正しく開く方法
Excelには、CSVファイルのインポート（取り込み）用の機能が用意されていて、それを活用すれば、元の形式を保ったまま、CSVファイルを開くことができます。

<strong>Step 1</strong>：空の Excel を開きます

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3663762/2c91f021-9b80-441e-95b6-c5cf284c6828.png)

<strong>Step 2</strong>：メニューから「データ」→「データの取得」→「テキストまたは CSV から」を開き、該当の CSV ファイルを選択します。
※Excel のバージョンによってはメニューの表記が異なる場合があります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3663762/c38cb1c2-5b98-0b29-57ac-4029adf12233.png)

<strong>Step 3</strong>：以下の設定値にします。
・元のファイル：932：日本語（シフトJIS）
・区切り文字：コンマ
・データ型検出：データ型を検出しない

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3663762/b0a787fa-2cc8-df9c-88c8-c0ab34be1ceb.png)

<strong>Step 4</strong>:「読み込み」を押します。
CSV データを読み込むことができます。
# 4.まとめ
本記事ではExcelでCSVファイルを取り扱うときの注意点、対処方法を紹介しました。CSVファイルを取り扱っていて、「あれ？」と思うことがあれば、是非この記事で紹介した方法を試してみてください。
# 一緒に働く仲間を募集しています！
株式会社コネクター・ジャパンでは一緒に働いてくれる仲間を募集しています！

事業拡大に伴い、エンジニアを大募集しています。
興味のある方は下記リンクから弊社のことをぜひ知っていただき応募してもらえると嬉しいです。
▼会社について
https://www.wantedly.com/companies/cnctor/about
▼代表メッセージ
https://cnctor.jp/10years-anniversary/
▼応募はこちら
https://www.wantedly.com/companies/cnctor/projects
killしても別のPIDで復活するポートを開ける方法

## 環境
- macOS Sonoma 14.4.1 

## この問題に至った経緯

Laravel学習用にsail環境を構築するため、Dockerをインストール
→sailコマンドを実行したところ80番ポートが使用されている旨のエラー発生...

## 結論
以下のコマンドで80番ポートが解放された！
```html: ターミナル
sudo apachectl stop
```

## 調べた内容
ググってみると大体が「killコマンド」で解決するという記事でしたが、自分の場合は効果なし。

試した内容が以下になります。

```html: ターミナル
sudo lsof -i:80

COMMAND   PID USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
httpd   59967 root    4u  IPv6 0x2517fa95a8b5fd1b      0t0  TCP *:http (LISTEN)
httpd   59984 _www    4u  IPv6 0x2517fa95a8b5fd1b      0t0  TCP *:http (LISTEN)
```


killコマンドを実行すると、、、
```html: ターミナル
sudo kill 59967
```

異なるPID（プロセスID）でまたポートが占有されてしまう。
```html: ターミナル
sudo lsof -i:80

COMMAND   PID USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
httpd   60355 root    4u  IPv6 0x2517fa95a8b60d1b      0t0  TCP *:http (LISTEN)
httpd   60360 _www    4u  IPv6 0x2517fa95a8b60d1b      0t0  TCP *:http (LISTEN)
```

httpdが占有しているため、apacheが原因と推測。
以下のコマンドで成功！
```html: ターミナル
sudo apachectl stop
```

無事sailが実行できました。
```html: ターミナル
./vendor/bin/sail up -d

[+] Running 6/6
 ✔ Container sample-app-meilisearch-1   Running                                                                      0.0s
 ✔ Container sample-app-mailpit-1       Running                                                                      0.0s
 ✔ Container sample-app-mysql-1         Running                                                                      0.0s
 ✔ Container sample-app-redis-1         Running                                                                      0.0s
 ✔ Container sample-app-selenium-1      Running                                                                      0.0s
 ✔ Container sample-app-laravel.test-1  Started        
```

## おわりに
以前XAMPPでも同様にポートの競合が起こったため、そのときはいろいろ調べて1日費やしてしまいました。
今回は早めに解決できてよかったです。

## 参考記事
[【Mac】占有portの調べ方と空け方](https://zenn.dev/json_hardcoder/articles/5925798786a07a)
Tips: AWS SDK for JavaScriptを使ってAmazon Pollyで生成した音声データをローカルにファイルとして保存する

## この記事は何
AWSにはAmazon Pollyという、テキストから読み上げデータを生成する（いわゆるText to Speech）ためのサービスが存在します。

https://aws.amazon.com/jp/polly/

Amazon PollyにもJavaScript SDKは存在するのですが、ExampleにはS3に生成したデータをアップロードする方法しかなく、ローカルにファイルとして保存する方法がわかりづらかったので、記事としてまとめました。

https://docs.aws.amazon.com/ja_jp/sdk-for-javascript/v3/developer-guide/polly-examples.html

## 前準備
まずは`@aws-sdk/client-polly`をインストールします。

```sh
$ npm i @aws-sdk/client-polly
```

認証についても以下の記事などを参考にセットアップを行なってください。

https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/getting-your-credentials.html

今回の例はTypeScriptで実装するので、実行環境として今回は`ts-node`を利用しようと思います。

```sh
$ npm i ts-node
```

## コード

以下のようなコードでローカルにファイルとして保存することが可能です。

```ts:script.ts
import fs from "fs";
import path from "path";

import {
  PollyClient,
  SynthesizeSpeechCommand,
  SynthesizeSpeechInput,
} from "@aws-sdk/client-polly";

const main = async () => {
  const client = new PollyClient({ region: "ap-northeast-1" });
  const speechFile = path.resolve(`path/to/file/output.mp3`,);
  const params: SynthesizeSpeechInput = {
    OutputFormat: "mp3",
    Text: 'こんにちは、私の名前はともこです。',
    VoiceId: "Tomoko",
    SampleRate: "22050",
    TextType: "text",
    LanguageCode: "ja-JP",
    Engine: "neural",
  };
  const command = new SynthesizeSpeechCommand(params);

  const response = await client.send(command);

  if (response.AudioStream) {
    const buffer = Buffer.from(
      await response.AudioStream.transformToByteArray(),
    );
    fs.writeFileSync(speechFile, buffer);
  }

  console.log(speechFile);
};

main();
```

以下のように実行することで音声データをローカルに保存することができます。

```sh
$ npx ts-node --compiler-options '{"module":"CommonJS"}' script.ts
```
S3のバージョニング機能有効後のサイズ使用量をみてみる

# はじめに
S3バケットにバージョニング機能を有効にした場合、ファイル更新の頻度が高いとその分ファイルが増えていくのでバケットサイズも倍増するものと考えているのですが、念の為動作を担保するために確認してみます。

# やってみる
### 下準備
まず、バケットのバージョニングを有効にします![スクリーンショット 2024-08-16 20.23.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478546/47b5cc26-d6a7-447e-1183-52ad6c5fc717.png)

そして、ファイルを格納
![スクリーンショット 2024-08-16 20.25.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478546/6a915fd7-96c8-c2e4-45aa-406f2909b0bd.png)
![スクリーンショット 2024-08-16 20.35.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478546/47d14d59-9363-5deb-37c8-6e4af2b42a1d.png)

### ファイルを更新して再度アップロード
新しいバージョンのファイルが増えました。
複製されたような感じですね。
![スクリーンショット 2024-08-16 20.38.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478546/17f2d828-c205-327e-5caa-2c66c488b1a1.png)
![スクリーンショット 2024-08-16 20.39.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478546/d5cb13c7-7bce-a7dc-dc12-ce59a15e15ef.png)

### サイズを見てみる
想定では、1.6KBが二つあるので、大体3.2〜3.3KBくらいです。
確認してみると、想定通りの結果でした。
![スクリーンショット 2024-08-18 20.58.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478546/9910a64c-dfd6-cb66-0740-cdc0f6a84961.png)

一応、CloudWatch側はこんな感じです。
少しでも参考になれば。
![スクリーンショット 2024-08-18 21.01.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478546/3803fd00-ac9f-41a6-a5be-53b5321d91d7.png)
JavaScriptでLaravelアプリに削除確認モーダルを実装する

## はじめに
Laravel学習の一環でBreezeを利用した簡単な記事投稿アプリを作成しました。
その際、投稿削除時に確認モーダルを表示する機能をJavaScriptで実装してみたので、備忘のため記事に残します。

## 完成イメージ
![スクリーンショット 2024-09-01 19.05.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3362317/3683c372-30a6-2a14-8be6-83c03abb42a2.png)

## 実装フロー
1. HTMLでモーダル本体と暗転用の要素を作成
1. CSSでモーダルと暗転効果のスタイルを定義
1. JavaScriptでモーダル表示等のイベントリスナーを設定

## 1. HTMLでモーダル本体と暗転用の要素を作成
削除ボタンと同じビューファイルに記述します。

```php:モーダル本体
<div id="js-delete-modal" class="hidden top-60 left-1/2 -translate-x-1/2 bg-white rounded-2xl fixed z-10">
    <div class="p-4">
        <p class="pb-4">本当に削除してよろしいですか？</p>
        <div class="text-right flex">
            <form method="post" action="{{ route('post.destroy', $post) }}" class="flex-1">
                @csrf
                @method('delete')
                <x-primary-button class="bg-red-700 ml-2">
                    はい
                </x-primary-button>
            </form>
            <x-primary-button id="js-cancel-button" class="ml-2">
                キャンセル
            </x-primary-button>
        </div>
    </div>
</div>
```

```php:暗転用
<div id="js-overlay" class="hidden top-0 left-0 w-full h-full bg-black/60 fixed z-9"></div>
```

## 2. CSSでモーダルと暗転効果のスタイルを定義
1で作成したモーダル本体と暗転用の要素に`display: none;`を設定して非表示にします。
今回はTailwind CSSを使用しているので`hidden`クラスを設定しておきます。

## 3. JavaScriptでモーダル表示等のイベントリスナーを設定
削除ボタンをクリックした時にモーダルを表示し、モーダル内のキャンセルボタンや暗転部分をクリックした時にモーダルを消すようにします。

```js:モーダル表示用
document.getElementById('js-delete-button').addEventListener('click', () => {
    document.getElementById('js-delete-modal').classList.remove('hidden');
    document.getElementById('js-overlay').classList.remove('hidden');
});
```

```js:モーダル非表示用
document.getElementById('js-cancel-button').addEventListener('click', () => {
    document.getElementById('js-delete-modal').classList.add('hidden');
    document.getElementById('js-overlay').classList.add('hidden');
});

document.getElementById('js-overlay').addEventListener('click', () => {
    document.getElementById('js-delete-modal').classList.add('hidden');
    document.getElementById('js-overlay').classList.add('hidden');
});
```

jsファイルはpublic\jsディレクトリに設置し、ビューファイル内でasset関数を用いて読み込ませています。
```php
<script src="{{ asset('js/modal.js') }}"></script>
```

## おわりに
ベストなやり方ではないと思いますが、自力でやってみたことでLaravelアプリでJavaScriptを活用するイメージがわきました。
Zodでスキーマバリデーション

 # Zodのファイルスキーマを作成

``` typescript
import { z } from "zod";

// PDFファイルの検証スキーマ
const schema = z.object({
  /** 名前 */
  nickname: z.string().min(1, { message: "名前を入力してください" }),
  /** 年齢 */
  age: z
    .number({ message: "年齢を半角数字で入力してください" })
    .int({ message: "年齢を整数で入力してください" })
    .gte(12, { message: "年齢を12歳以上で入力してください" }),
  /** メールアドレス */
  email: z.union([
    z
      .string()
      .email({ message: "メールアドレスの形式で入力してください" })
      .nullish(),
    z.literal(""),
  ]),
  /** JSONデータ */
  jsonData: z
    .string()
    .min(1, { message: "JSONデータを入力してください" })
    .refine((val) => {
      try {
        JSON.parse(val);
        return true;
      } catch {
        return false;
      }
    }, { message: "正しいJSON形式で入力してください" }),
  /** PDFファイル */
  pdfFile: z
    .any()
    .refine((file) => file instanceof File && file.type === "application/pdf", {
      message: "PDFファイルを選択してください",
    }),
});
```

 # React Hook Formとの統合
``` typescript
import { FC } from "react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";

// スキーマから型を生成
type Inputs = z.infer<typeof schema>;

export const SchemaFormSample: FC = () => {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<Inputs>({
    mode: "onTouched",
    resolver: zodResolver(schema),
  });

  const onSubmit = (data: Inputs) => {
    // フォームの送信処理
    console.log("Validated data:", data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <label>
          名前
          <input type="text" {...register("nickname")} />
        </label>
        {errors.nickname && <p>{errors.nickname.message}</p>}
      </div>
      <div>
        <label>
          年齢
          <input
            type="number"
            {...register("age", { valueAsNumber: true })}
          />
        </label>
        {errors.age && <p>{errors.age.message}</p>}
      </div>
      <div>
        <label>
          メールアドレス
          <input type="email" {...register("email")} />
        </label>
        {errors.email && <p>{errors.email.message}</p>}
      </div>
      <div>
        <label>
          JSONデータ
          <input type="text" {...register("jsonData")} />
        </label>
        {errors.jsonData && <p>{errors.jsonData.message}</p>}
      </div>
      <div>
        <label>
          PDFファイル
          <input type="file" {...register("pdfFile")} />
        </label>
        {errors.pdfFile && <p>{errors.pdfFile.message}</p>}
      </div>
      <div>
        <button type="submit">submit</button>
      </div>
    </form>
  );
};
```
【アップデート】Amazon NeptuneがPropertyGraphStoreを有効にすることで、LlamaIndexと組み合わせてGraphRAGアプリケーションを構築できるようになりました！

こんばんは。Koderaです。
Amazon Neptuneがより信頼性のあるRAGアプリケーション構築のために、PropertyGraphStoreのサポートを開始しました。
https://aws.amazon.com/jp/about-aws/whats-new/2024/08/amazon-neptune-propertygraphstore-graphrag-applications/

## アップデート内容
Amazon Neptuneに保存されたナレッジ グラフを「LlamaIndex」と組み合わせることで、Graph Retrieval-Augmented Generation (GraphRAG) アプリケーションを構築できるようになりました。

::: note info
LlamaIndexは、大規模言語モデル (LLM) を使用してアプリケーションを構築するオープンソース フレームワークです。
大規模言語モデル（LLM） と外部データを接続するためのインターフェースを提供します。
:::

TextToCypher Retrieverを介した自然言語クエリ処理、Cypher Template Retrieverを介したナレッジ グラフ取得、およびサポートされているエクストラクターとリトリーバーを介したナレッジ グラフ拡張 RAGの作成とクエリの追加ができるようになっています。

PropertyGraphIndex を使用することで、ナレッジ グラフ内のノードおよび関係プロパティの効率的なインデックス作成とクエリが可能になり、特定の属性に基づいて関連データを迅速に取得できるようになりました。


## Amazon Neptuneのナレッジグラフとは
まず、今回のアップデートに関連する「ナレッジグラフ」とは何かを考えてみます。
Amazon Neptuneには、2つの機能があります。
・Amazon Neptune Database
・Amazon Neptune Analytics

「ナレッジグラフ」の前に「グラフデータベース」の定義を確認してみます。

>グラフデータベースは、データ項目間の関係を格納し照会するために最適化されています。
>データ項目自体をグラフの頂点として、それらの間の関係はエッジとして格納します。各エッジにはタイプがあり、ある頂点 (始点) から別の頂点 (終点) に向けられます。関係はエッジだけでなく述語とも呼ばれ、頂点はノードと呼ばれることもあります。いわゆるプロパティグラフでは、頂点とエッジの両方がそれらに関連付けられた追加のプロパティを持つことができます。
>> https://docs.aws.amazon.com/ja_jp/neptune/latest/userguide/graph-get-started.html
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3858908/c8451a25-de7d-5d49-a2d4-b2ddac40b924.png)

エッジは名前の付いた矢印で表示され、頂点はつながりのある特定の人物や趣味を表します。
グラフは単純な探索により、Justin の友だちについて知ることができます。

次に「ナレッジグラフ」とは何でしょうか。
#### ナレッジグラフとは
ナレッジグラフを使用すると、DB接続されているあらゆる種類の情報を整理して照会し、一般的な質問に答えられます。
リレーショナルDBだけではなく、半構造化ソース (メディア メタデータ、スプレッドシートなど) や非構造化ソース (テキスト ドキュメント、電子メール、ニュース記事など)も対象です。

概念の定義、プロパティ、情報の関係性、および保持されると予想される論理制約のセットを使用して、特定のドメインの情報や意味合いをキャプチャします。ナレッジグラフを利用することで、組織で保有している情報資産を統合することができ、組織のすべてのメンバーが情報資産をより簡単に利用できるようにします。
異種のデータ ソースからの情報をリンクしてアクセスできるようにすると、さらに精度を上げて質問に答えることができます。


## PropertyGraphStoreとは
プロパティグラフは、プロパティ (メタデータ) を含むラベル付きノード (エンティティ カテゴリ、テキスト ラベルなど) のナレッジ コレクションです。
LlamaIndex では、PropertyGraphIndex が主要なオーケストレーションとして、グラフの構築とグラフに対するクエリ機能を提供しています。
>https://docs.llamaindex.ai/en/latest/module_guides/indexing/lpg_index_guide/


### まとめ
本アップデートにより、PropertyGraphIndexの有効化を行い、回答の迅速な取得により、GraphRAG ソリューションがより効率的に活用できるようになったかと思います。
rswagでAPI仕様書自動生成するときにindexアクションの扱いに困った

# 前提

[rswagの公式Doc](https://github.com/rswag/rswag?tab=readme-ov-file#getting-started)を見ながらAPI仕様書の自動作成に取り組んでいた時の話。
自動作成までの手順として、RSpecでAPIのテストを書いた後、ターミナルで`RAILS_ENV=test rake rswag:specs:swaggerize`を実行する。
すると、`swagger/v1/swagger.yaml` が自動で作成されて、`api-docs/index.html` にアクセスするとswaggerのAPIDocが確認できる。

# うまく行ったこと

createアクションはドキュメント通りに以下のようなテストを作った。（ユーザーに関するAPI）

```ruby: users_spec.rb
require 'swagger_helper'

RSpec.describe 'api/v1/users', type: :request do
  path '/api/v1/users' do
    post('create user') do
      # リクエストボディがJSON形式であることを示す
      consumes 'application/json'
      parameter name: :user, in: :body, schema: {
        type: :object,
        properties: {
          name: { type: :string },
          email: { type: :string },
          password: { type: :string }
        },
        required: [:name, :email, :password]
      }

      response '201', 'user created' do
        let(:user) { { name: 'test_user', email: 'test@example.com', password: 'test_pass' } }
        run_test!
      end

      response '422', 'パスワード無しでリクエスト' do
        let(:user) { { name: 'test_user', email: 'test@example.com' } }
        run_test!
      end
    end
  end
end
```

`RAILS_ENV=test rake rswag:specs:swaggerize`を実行すると、

`swgger.yaml`が作成された。

```yaml: swagger.yaml
---
openapi: 3.0.1
info:
  title: API V1
  version: v1
paths:
  "/api/v1/users":
    post:
      summary: create user
      parameters: []
      responses:
        '201':
          description: user created
        '422':
          description: パスワード無しでリクエスト
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                email:
                  type: string
                password:
                  type: string
              required:
              - name
              - email
              - password
servers:
- url: https://{defaultHost}
  variables:
    defaultHost:
      default: localhost:3000
```

`api-docs/index.html` にアクセスしてみると、ちゃんとプロパティも反映された状態で確認できた。

![Swagger_UI.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2629116/2da9a1c8-81d3-16cb-3c1f-7526d8e10004.png)


# 困ったこと

indexアクションのテストの書き方の参考が公式Docにはありませんでした。自分流に書いたのですが、上画像のプロパティ欄がなかなか表示されませんでした。
僕に公式Docを読み解く力があれば解決できたかもしれないのですが。。。まだまだ苦手です。
一人で試行錯誤しました。

# 解決策

海外で同じことで苦しんだ人がいました。バージョンダウンするだけでした、何じゃそりゃ、、、

https://stackoverflow.com/questions/66588082/using-rswag-in-rails-what-is-the-syntax-for-index-responses-arrays

> Just replace opeanapi: '3.0.1' for swagger: '2.0'. I've faced this same issue and this is the only workaround I've found so far.

```ruby: swagger_helper.rb
RSpec.configure do |config|
  config.openapi_root = Rails.root.join('swagger').to_s

  config.openapi_specs = {
    'v1/swagger.yaml' => {
      swagger: '2.0',
      info: {
        title: 'API V1',
        version: 'v1'
      },
      paths: {},
      servers: [
        {
          url: 'https://{defaultHost}',
          variables: {
            defaultHost: {
              default: 'localhost:3000'
            }
          }
        }
      ]
    }
  }
```
Rubyプログラミング考え方｛その７：カンマ区切りのN個のデータの入力 (paizaランク D 相当)｝

https://paiza.jp/works/mondai/stdin/stdin_comma_n

**ステップ1: 要件の分析**
- 最初の行で整数 `N` が与えられます。
- 次の行で `N` 個の文字列がカンマ区切りで1行で与えられます。
- これらの文字列を順に `N` 行で出力するプログラムを作成します。

**ステップ2: 擬似コードの設計**
1. 最初の行で `N` を取得します。
2. 次の行でカンマ区切りの文字列を取得し、分割します。
3. 各文字列を順に出力します。

**ステップ3: コメント付きRubyコードの作成**

```ruby
# 1行目でNを取得
n = gets.to_i

# 2行目でN個の文字列を取得し、カンマ区切りで分割する
strings = gets.chomp.split(",")

# 各文字列を順に出力
strings.each do |str|
  puts str
end
```

**ステップ4: コードの解説**
- **`N` の取得:** 最初に `gets.to_i` を使って、入力から整数 `N` を取得します。
- **文字列の取得と分割:** `gets.chomp.split(",")` を使って、カンマ区切りの文字列を分割し、文字列の配列を生成します。
- **出力:** `each` ループを用いて、配列の各要素を順に出力します。

このコードをRuby環境で実行すると、入力された `N` 個の文字列がそれぞれ1行ずつ出力されます。
Rubyプログラミング考え方｛その７：カンマ区切りの3つのデータの入力 (paizaランク D 相当)｝

https://paiza.jp/works/mondai/stdin/stdin_comma_3

**ステップ1: 要件の分析**
- 標準入力で3つの文字列がカンマ区切りで1行で与えられます。
- これらの文字列を順に3行で出力するプログラムを作成します。

**ステップ2: 擬似コードの設計**
1. 標準入力から1行の文字列を取得します。
2. 取得した文字列をカンマで分割し、3つの文字列に分けます。
3. 各文字列を順に出力します。

**ステップ3: コメント付きRubyコードの作成**

```ruby
# 標準入力から1行の文字列を取得し、カンマで分割して3つの文字列に分ける
strings = gets.chomp.split(",")

# 各文字列を順に出力する
strings.each do |str|
  puts str
end
```

**ステップ4: コードの解説**
- **文字列の取得と分割:** `gets.chomp.split(",")` を使って、標準入力から取得した文字列をカンマで分割し、3つの文字列の配列にします。
- **出力:** `each` ループを使って、配列の各要素を順に出力します。

このコードをRuby環境で実行すると、入力された3つの文字列がそれぞれ1行ずつ出力されます。
Rubyプログラミング考え方｛その６：N個のデータの入力 (paizaランク D 相当)｝

https://paiza.jp/works/mondai/stdin/stdin_n

**ステップ1: 要件の分析**
- 最初の行で整数 `N` が与えられます。
- 次の行で `N` 個の文字列が半角スペース区切りで与えられます。
- これらの文字列を順番に `N` 行で出力するプログラムを作成します。

**ステップ2: 擬似コードの設計**
1. 標準入力から最初の行で整数 `N` を取得します。
2. 次の行で与えられた `N` 個の文字列を取得し、半角スペースで分割します。
3. 分割された各文字列を順に出力します。

**ステップ3: コメント付きRubyコードの作成**

```ruby
# 1行目でNを取得
n = gets.to_i

# 2行目でN個の文字列を取得し、スペース区切りで配列にする
strings = gets.chomp.split(" ")

# 各文字列を順に出力
strings.each do |str|
  puts str
end
```

**ステップ4: コードの解説**
- **`N` の取得:** 最初に `gets.to_i` を使って、入力から整数 `N` を取得します。
- **文字列の取得と分割:** `gets.chomp.split(" ")` を使って、2行目の入力を半角スペースで分割し、文字列の配列にします。
- **出力:** `each` ループを使って、配列の各要素を順に出力します。

このコードをRuby環境で実行すると、入力された `N` 個の文字列がそれぞれ1行ずつ出力されます。
AWS SAA ソリューションアーキテクトアソシエイト　合格体験記

# 背景

AWSを業務で使用していますが、体系だった理解が不十分だな、と日々感じていたので学習の一環として資格取得を目指すことにしました。

# 勉強で使った資料、サイト、参考書

使用した参考書は以下の通りです。

- 徹底攻略 AWS認定 ソリューションアーキテクト − アソシエイト教科書 第3版(https://book.impress.co.jp/books/1122101088)
- AWS認定　ソリューションアーキテクトアソシエイト　問題集  第2版(https://amzn.asia/d/7RePyeb)

前者は「黒本」と呼ばれているシリーズで教科書的な参考書になっており、1度通読した後はわからないところがあれば都度参照するような使用方法でした。
後者の参考書は問題や要点がよくまとめられており、短期集中で詰め込むのが得意な人間としてはありがたい構成でした。

あとは演習として[ping-t](https://mondai.ping-t.com)とudemyの模擬問題集を使用しました。

# 勉強スケジュール

## 1週～2週目

- 黒本を通読&ping-tの問題を1周する

とにかく試験範囲で問われるサービスの概要を把握するために基本問題ばかりこなしていました。
普段の業務で触らないサービスばかりだったのでとにかくわからないサービスについては都度調べる、の繰り返しでした。

- ハンズオンで実際にサービスを触ってみる

AWSはハンズオンを公開しており、特に手軽に始めやすいLambdaやDynamoDBを使うハンズオンをやってみたりEC2を立ててサーバとして触ってみるなどをやったりしました。
なかなか書籍だけで勉強してもイメージが湧きづらかったので、実際に触ることで学習内容の定着が少しでも進めば…という狙いでした。

## 3週目～本番

- 問題演習の参考書通読

問われやすいシナリオごとに問題がまとめられており、コスト/セキュリティ/弾力性　などの様々な視点で問題をとらえる感覚を鍛えられたような気がします。

- udemyの模擬問題集

6回分の演習セットをセール時に1800￥で購入しました。1周やってみましたが1度も合格基準の72%に達成しなかったのでこれはまずい、とかなり焦っていました…

## 本番

8/18 テストセンターで受験しました。

12時過ぎぐらいに受験して、1時間半ぐらいで見直しを済ませ退出しました。
手ごたえはあまりなく、不合格かもな…と思っていましたが20:00前ぐらいに合格通知のメールが来たので一安心でした。


# 試験結果

スコアは783点だったのでちょっとギリギリでした。が、なんとか合格。
コンピテンシーも基準を満たしていたようです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3817138/b0b23ebb-7861-c69c-d3c4-2b355afd4768.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3817138/2f97bc5b-1250-1289-9ed5-24821dc6447f.png)


勉強時間は平均で平日2時間、土日に4時間ぐらいかけていたのでトータルで50～60時間ぐらいでしょうか？
何はともあれ無事合格できて一安心でした。
試験を通じて普段触らないサービスやあやふやだった理解がある程度確かなものにできたような気がするので試験を受けてよかったなと思います。

次は応用情報の勉強に切り替えて頑張ります。
Rubyプログラミング考え方｛その５：3つのデータの入力 (paizaランク D 相当)｝

https://paiza.jp/works/mondai/stdin/stdin_3

**ステップ1: 要件の分析**
- 標準入力で3つの文字列が1行で、半角スペース区切りで与えられます。
- それらの文字列を順に3行で出力するプログラムを作成します。

**ステップ2: 擬似コードの設計**
1. 標準入力から1行の文字列を取得します。
2. 取得した文字列を半角スペースで分割し、3つの文字列に分けます。
3. それぞれの文字列を順に出力します。

**ステップ3: コメント付きRubyコードの作成**

```ruby
# 標準入力から1行の文字列を取得し、スペースで分割して3つの文字列に分ける
strings = gets.chomp.split(" ")

# 各文字列を順に出力する
strings.each do |str|
  puts str
end
```

**ステップ4: コードの解説**
- **文字列の取得と分割:** `gets.chomp.split(" ")` を使って、標準入力から取得した文字列を半角スペースで分割し、3つの文字列の配列にします。
- **出力:** `each` ループを使って、配列の各要素を順に出力します。

このコードをRuby環境で実行すると、入力された3つの文字列がそれぞれ1行ずつ出力されます。
【Lambda】pythonの画像処理ライブラリのPillowをインポートするのに手間取った話

どうもこんにちは。

今回は、AWS Lambdaを使用してPillowをインポートしようとした時につまづいたのでメモしておきます。

# Pillowってなに？

そもそもPillowって何って話ですが、以下の記事にはこのように記載されていました。

> OpenCVのようにコンピュータービジョン系の高度な画像処理（顔検出やオプティカルフローなど）はできないが、リサイズ（拡大・縮小）や回転、トリミング（部分切り出し）のような単純な処理が簡単にできる。

https://note.nkmk.me/python-pillow-basic/

ほうほう...

今回は、base64エンコードされた画像データをバイナリデータに変換し、さらにバイナリデータから画像を読み込んで画像オブジェクトに変換してS3に保存するというなんともめんどくさい処理をする必要があるので、Pillowを使用しています。

```python
# 画像生成試してます
response = bedrock_runtime.invoke_model(
    body=body,
    modelId="amazon.titan-image-generator-v2",
    accept="application/json",
    contentType="application/json"
)

response_body = json.loads(response['body'].read())

# ここやで
images = [Image.open(BytesIO(base64.b64decode(base64_image))) for base64_image in response_body.get("images")]
```

# 早速、LambdaでPillowをインポートしてみる

```python
from PIL import Image
```

これを実行すると...

```python
[ERROR] Runtime.ImportModuleError: Unable to import module 'lambda_function': No module named 'PIL'
Traceback (most recent call last):
```

やめてぇ...ってなるわけですよね。

よし、**Lambda Layer**使いましょ。

## Lambda Layerを知らない人向けに...

Lambda Layerは、**Lambdaにデフォルトでインストールされていないライブラリを使用したい時**に使用します。

例えば、Pythonでよく使用する`pandas`などは、Lambdaでデフォルトインストールされていないので、**ローカルPCにダウンロードして、Zip圧縮して、Lambda Layerに登録して、インポート**という流れを踏む必要があります。

使い方さえわかれば簡単でっす。

# ローカルにダウンロード

```sh
mkdir pillow
cd pillow
```

```sh
pip install \
    --platform manylinux2014_x86_64 \
    --target=python \
    --implementation cp \
    --python-version 3.12 \
    --only-binary=:all: \
    --upgrade Pillow
```

:::note warn
`--platform`については、今後のバージョンアップで変更になる気がしていますが、本記事を書いている時点(2024年8月)では`manylinux2014_x86_64`で問題ないです。
:::

これでローカルにダウンロードはできました。

# Zip圧縮

`pip install`を実行すると、`pillow`ディレクトリに`python`ディレクトリが作成されていると思います。
（上のコマンドで`--target=python`を指定したからです。）

```sh
% ls
python
```

この`python`ディレクトリの中に`Pillow`ライブラリが入っています。

というわけで、`python`ディレクトリをZip圧縮します。

```sh
zip -r9 layer.zip python
```

:::note info
`layer.zip`のところは、自分の好きなファイル名にしていただいてOKです。
:::

これで、Zip圧縮完了です。

# Lambda Layerにアップロード

AWSコンソールの**レイヤー**から新しくZipをアップロードします。

# Lambda関数でレイヤーを追加

最後に、Lambda関数が登録したレイヤーを参照できるように、レイヤーの追加をしてください。

これで、以下のようなエラーとはおさらばです。

```python
[ERROR] Runtime.ImportModuleError: Unable to import module 'lambda_function': No module named 'PIL'
Traceback (most recent call last):
```
時間のバグ

蟹さんのmrubyで安定度をチェックするためにInternet of Test(IoT)でThingSpeakにアクセスし確認しました。

ある時点でアクセスはしているのですがデータが入らなくなりました。

ThingSpeakからデータをダウンロードして確認してみます。

開始：2024-07-04T15:27:55+09:00,1,1,,,,

最終：2024-07-31T04:11:53+09:00,72042,72045,,,,

コンソールを見てみたところSSLの証明書のエラーが発生していました。

証明書の中には有効時間が入っていて、それとローカルの時間を比較しています。

```
        Validity
            Not Before: Jul 19 00:00:00 2024 GMT
            Not After : Aug  4 23:59:59 2025 GMT
```

蟹さんは最初にSNTPで時間を確認して、それ以降をミリ秒の割り込みで作った値を足して現在の時刻にしています。

BareSSLはこれで確認しています。

開始から最終までの秒数は2,292,238秒で、ミリ秒だとsigned int(31ビット)をオーバーフローしたようです。

証明書の有効期間は普通くらい1年くらいはあるのですが、ちょうど張替らえて、その期間より前の時間になったものと思われます。

このため定期的にSNTPして時間を合わせるようにしたところ、不定期に再起動するようになりました。

再起動はwatchdogによるものと思われ、一旦watchdogを外して動かしてみました。

![スクリーンショット 2024-08-18 7.39.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104066/001193cf-7e39-7163-3ecd-f97220050654.png)

横にずれているところのデータです。

2024-08-18T03:00:57+09:00,90524,721,,,,

2024-08-18T03:45:00+09:00,90525,722,,,,

2024-08-18T05:17:17+09:00,90696,895,,,,

2024-08-18T06:02:42+09:00,90697,896,,,,

タイムアウトがうまくいってないようです。

SNTPでカウンターを0にしたときにlwipのsys_restart_timeouts()を呼ぶ必要がありました。

よくよくログを見たらもう一つバグがありました。

2024-08-18T02:50:19+09:00,90504,700,,,,
2024-08-18T02:50:53+09:00,90505,702,,,,

701がありません。にもかかわらず30秒の2回分のインターバルもありません。これはsntpしてカウンターが戻ったのでmsleepが効かずにアクセスしてThingSpeakから捨てられたものと思われます。
Rubyプログラミング考え方｛その４：N行のデータの入力 (paizaランク D 相当)｝

https://paiza.jp/works/mondai/stdin/stdin_n_line

**ステップ1: 要件の分析**
- 標準入力で最初に整数 `N` が与えられます。
- 続く `N` 行には、文字列がそれぞれ与えられます。
- 与えられた文字列を順にそのまま `N` 行で出力するプログラムを作成します。

**ステップ2: 擬似コードの設計**
1. 標準入力から最初の行で整数 `N` を取得します。
2. ループを使って `N` 回、標準入力から文字列を取得し、その都度出力します。

**ステップ3: コメント付きRubyコードの作成**

```ruby
# 1行目でNを取得
n = gets.to_i

# N行の文字列を順に取得して出力
n.times do
  line = gets.chomp
  puts line
end
```

**ステップ4: コードの解説**
- **`N` の取得:** 最初に `gets.to_i` を使って、入力から整数 `N` を取得します。
- **ループ処理:** `n.times` ループを用いて、`N` 回標準入力から文字列を取得し、`puts` でそのまま出力します。

このコードをRuby環境で実行すると、入力された `N` 行の文字列がそのまま順番に出力されます。
tkinterを使って自己満足のツール作成

## はじめに
皆さんは資格試験に向けた受験勉強はどのように学習していますか？
僕は参考書やPing-tなどを活用することが多いですが、これらの問題が一つのアプリにまとまっていると便利だなと思ったことないですか？
僕は一つのアプリで試験勉強出来たら便利だなと思ったので、pythonの勉強ついでにGUIアプリを作ってみました。

## ツール概要
pythonのtkinterを使って、下図のようなGUI画面を作りました（今回はサンプル）。
問題と選択肢はCSVファイルに記載し、そこから読み出すようにしています。
問題のタイトル（下図では「駅クイズ」）など問題に依存するような文言はなるべく、GUI画面とは切り離すため、CSVファイルから読み出すようにしています。
```python:python画面表示部
        maintitle = self.managedb.getTitle()

        # textbox
        frame1 = Tk.Frame(self, relief=Tk.RIDGE, bg='#30c558')
        self.mainLabel = Tk.Label(frame1, text=maintitle,
                                  font = ('FixedSys', 16),
                                  fg='red', bg='#30c558')
        self.mainLabel.pack()
        frame1.place(x=10, y=10)
```
この例では、以下のようなCSVファイルから「**駅クイズ**」という文字列をgetTitle()で取得してテキストボックスに設定しています。
```text:CSVファイル
駅クイズ,駅クイズ,駅クイズランキング,駅クイズ問題数：
1,"東海道新幹線で新富士の隣の駅はどれ？","熱海","掛川","三島","浜松","",3,"J"
```
```python:pythonタイトル取得部
    def internal_update_db(self, qusname):
        tempList = []
        hIn = open(qusname, 'r', encoding="utf-8")
        reader = csv.reader(hIn)
        counter = 0
        for row in reader:
            if row[0][0] == '#':
                continue
            if counter == 0:
                self.title = row[0]
                counter = 1
                continue
            tempList.append(row)
```
CSVファイル1行目のカンマで区切られた最初のアイテムを取得してタイトルとしています（**self.title**の部分）。
tkinterでは凝ったGUIは作れないと思いますが、自己満足程度のものであれば、下記のように作れますので、勉強ついでに試してみるのも面白いと思います。
![eki.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3750854/f26b9b71-7749-ca2b-5947-08fb71355167.png)
このツールの場合、3回間違えたらゲーム終了となり、履歴を残すようにしています。また、日別の正解率などをopenpyxlを使ってグラフ化しています。
![0824_graph.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3750854/a673e92d-9330-5199-b075-d02579a65685.png)

## 感想
Pythonだとtkinterを使えば、比較的簡単にGUIツールを作ることができるので、楽になったな～という印象です。以前はGUIツールを作成するために、Visual C++などを使っていましたが、その頃に比べると手間が少ないなと感じました。
このツールの場合、問題や選択肢を記載したCSVファイルを別途用意してあげる必要がありますが、その作成に手間がかかってしまうのが少し難点なところ。でもその手間をかけてしまえば、あとはひたすらこのツールを使って受験勉強できるので、僕はその手間をかけてでもCSVファイルを作っています（CSVファイルを作ることで問題や選択肢を何回も見直すことになるので、それで勉強になっているという副作用もありますね）。
今度はtkinterではなくguizeroをつかってみようかなと思っています。