Skip to content

Commit b2fe3b7

Browse files
Add table-ux-improvements spec files and highlight N/A in red for Nodes/Proc/Thread columns [skip-ci]
1 parent bf3925d commit b2fe3b7

5 files changed

Lines changed: 566 additions & 1 deletion

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"specId": "8fd8f522-26af-4da3-89ca-b6a58e89b420", "workflowType": "requirements-first", "specType": "feature"}
Lines changed: 363 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,363 @@
1+
# 設計ドキュメント: テーブルUX改善
2+
3+
## 概要
4+
5+
result_serverのサマリーテーブル(`_results_table.html`)に対するUX改善を実装する。
6+
7+
1. **Compareカラム位置変更**: 左端からFOMカラムの右隣に移動し、ツールチップの見切れ問題を解消
8+
2. **Expフィルタのカスケード連動**: Codeフィルタ選択時にExpの選択肢を連動絞り込み
9+
3. **Proc/nodeカラム追加**: ノード当たりプロセス数をNodesカラムの隣に表示
10+
4. **Thread/procカラム追加**: プロセス当たりスレッド数をProc/nodeカラムの隣に表示。result.shでFOM行から`nthreads:`をパースしResult_JSONに記録
11+
5. **ハードスペック系カラム削除**: CPU Name, GPU Name, CPU/node, GPU/node, CPU Core Countをテーブルから削除(SYSTEMツールチップで既に表示済み)。`_build_row`からも対応フィールド抽出を削除
12+
13+
すべての変更はサーバーサイド処理(Python/Jinja2テンプレート)とCIスクリプト(shell)で完結し、クライアントサイドJavaScriptの変更は最小限に留める。既存のテストが引き続きパスすることを保証する。
14+
15+
変更後のテーブルカラム順序:
16+
`Timestamp | CODE | Exp | FOM | Compare | FOM version | SYSTEM | Nodes | Proc/node | Thread/proc | JSON | PA Data | Mode | Trigger | Pipeline | Detail`
17+
18+
## アーキテクチャ
19+
20+
### 変更対象ファイルと責務
21+
22+
```mermaid
23+
graph TD
24+
A[results.py<br/>ルート] -->|filter_code渡し| B[results_loader.py<br/>データ読み込み]
25+
B -->|rows, columns, filter_options| C[_results_table.html<br/>テーブル描画]
26+
C -->|include| D[_filter_dropdowns.html<br/>フィルタUI]
27+
E[test_results_loader.py<br/>テスト] -->|検証| B
28+
F[result.sh<br/>JSON生成] -->|nthreadsパース追加| B
29+
```
30+
31+
### 変更の流れ
32+
33+
1. `results_loader.py`: カラム順序変更(ハードスペック系削除、Proc/node・Thread/proc追加)、`_build_row`からハードスペック系フィールド抽出削除・`nthreads`/`numproc_node`フィールド追加、`get_filter_options`にカスケードフィルタ対応
34+
2. `results.py`: `get_filter_options`呼び出し時に`filter_code`パラメータを渡す
35+
3. `_results_table.html`: Compareカラムの位置変更、Proc/node・Thread/procカラムの描画追加、ハードスペック系カラムの描画削除
36+
4. `_filter_dropdowns.html`: 変更不要(サーバーサイドで絞り込み済みの選択肢が渡される)
37+
5. `result.sh`: FOM行から`nthreads:`をパースする処理を追加(`numproc_node`と同じパターン)
38+
39+
## コンポーネントとインターフェース
40+
41+
### 1. `results_loader.py` の変更
42+
43+
#### カラムリスト変更
44+
45+
現在の`columns`リスト(`load_results_table`内)を以下の順序に変更する。ハードスペック系カラム(CPU Name, GPU Name, CPU/node, GPU/node, CPU Core Count)を削除し、Proc/nodeとThread/procを追加:
46+
47+
```python
48+
columns = [
49+
("Timestamp", "timestamp"),
50+
("CODE", "code"),
51+
("Exp", "exp"),
52+
("FOM", "fom"),
53+
("FOM version", "fom_version"),
54+
("SYSTEM", "system"),
55+
("Nodes", "nodes"),
56+
("Proc/node", "numproc_node"), # 新規追加
57+
("Thread/proc", "nthreads"), # 新規追加
58+
("JSON", "json_link"),
59+
("PA Data", "data_link"),
60+
("Mode", "execution_mode"),
61+
("Trigger", "ci_trigger"),
62+
("Pipeline", "pipeline_id"),
63+
]
64+
```
65+
66+
注: Compareカラムはテンプレート側で挿入されるため、columnsリストには含まれない。テンプレート側でFOMの後にCompareを挿入することで、最終的なカラム順序は:
67+
`Timestamp | CODE | Exp | FOM | Compare | FOM version | SYSTEM | Nodes | Proc/node | Thread/proc | JSON | PA Data | Mode | Trigger | Pipeline | Detail`
68+
69+
#### `_build_row` の変更
70+
71+
ハードスペック系フィールドの抽出コードを削除し、`numproc_node`および`nthreads`フィールドを追加する。
72+
73+
削除するフィールド抽出:
74+
```python
75+
# 以下を削除
76+
cpu = data.get("cpu_name", "N/A")
77+
gpu = data.get("gpu_name", "N/A")
78+
cpus = data.get("cpus_per_node", "N/A")
79+
gpus = data.get("gpus_per_node", "N/A")
80+
cpu_cores = data.get("cpu_cores", "N/A")
81+
```
82+
83+
追加するフィールド抽出:
84+
```python
85+
numproc_node = data.get("numproc_node", "N/A")
86+
if numproc_node is None or numproc_node == "":
87+
numproc_node = "N/A"
88+
89+
nthreads = data.get("nthreads", "N/A")
90+
if nthreads is None or nthreads == "":
91+
nthreads = "N/A"
92+
```
93+
94+
rowから削除するキー: `"cpu"`, `"gpu"`, `"cpus"`, `"gpus"`, `"cpu_cores"`
95+
96+
rowに追加するキー:
97+
```python
98+
row = {
99+
# ... 既存フィールド
100+
"numproc_node": numproc_node,
101+
"nthreads": nthreads,
102+
# ... 既存フィールド
103+
}
104+
```
105+
106+
#### `get_filter_options` のシグネチャ変更
107+
108+
```python
109+
def get_filter_options(directory, public_only=True, authenticated=False,
110+
affiliations=None, field_map=None, filter_code=None):
111+
```
112+
113+
`filter_code`が指定された場合、Exp抽出時にそのCodeを持つJSONからのみExpを収集する。`filter_code=None`の場合は従来通り全Expを返す。
114+
115+
設計判断: `filter_code`パラメータをキーワード引数として末尾に追加することで、既存の呼び出し元(estimated_results等)に影響を与えない。
116+
117+
### 2. `results.py` の変更
118+
119+
`_render_results_list`内の`get_filter_options`呼び出しに`filter_code`を渡す:
120+
121+
```python
122+
filter_options = get_filter_options(received_dir, filter_code=filter_code, **filter_kwargs)
123+
```
124+
125+
### 3. `_results_table.html` の変更
126+
127+
#### Compareカラムの位置変更
128+
129+
- ヘッダー: `<thead>`内でCompareカラムをFOMの後に配置(`{% if col_name == "FOM" %}`の後にCompare thを挿入)
130+
- ボディ: `<tbody>`内で各行のチェックボックスセルをFOMセルの後に配置
131+
- ツールチップ: `tooltip-left`クラスを削除し、デフォルト方向(中央上)を使用
132+
133+
#### Proc/nodeおよびThread/procカラムの追加
134+
135+
テンプレートの`{% if col_name in [...] %}`リストに`"Proc/node"`, `"Thread/proc"`を追加し、ツールチップ付きヘッダーを描画する。ボディ側の`{% elif key in [...] %}`リストに`"numproc_node"`, `"nthreads"`を追加する。
136+
137+
ツールチップテキスト:
138+
- Proc/node: "Number of processes per node"
139+
- Thread/proc: "Number of threads per process"
140+
141+
#### ハードスペック系カラムの削除
142+
143+
テンプレートの`{% if col_name in [...] %}`リストから`"CPU Name"`, `"GPU Name"`, `"CPU/node"`, `"GPU/node"`, `"CPU Core Count"`を削除する。ボディ側の`{% elif key in [...] %}`リストからも対応するキー(`"cpu"`, `"gpu"`, `"cpus"`, `"gpus"`, `"cpu_cores"`)を削除する。
144+
145+
SYSTEMカラムのツールチップは変更しない(CPU Name, CPU/node, CPU Cores, GPU Name, GPU/node, Memoryの情報を`system_info.py`から取得して引き続き表示)。
146+
147+
### 4. `_filter_dropdowns.html`
148+
149+
変更不要。サーバーサイドで絞り込まれた`filter_options.exps`がそのまま描画される。フィルタ変更時の`applyServerFilter()`は既存のページリロード方式を維持する。
150+
151+
### 5. `result.sh` の変更
152+
153+
#### nthreadsのFOM行パース追加
154+
155+
`numproc_node`と同じパターンで、FOM行から`nthreads:`をパースする処理を追加する。
156+
157+
既存の`numproc_node`パース処理(参考):
158+
```bash
159+
numproc_node_line=$(echo $line | grep -Eo 'numproc_node:[ ]*[0-9]*' | head -n1 | awk -F':' '{print $2}' | sed 's/^ *//')
160+
if [ -n "$numproc_node_line" ]; then
161+
numproc_node=${numproc_node_line}
162+
else
163+
numproc_node=""
164+
fi
165+
```
166+
167+
追加する`nthreads`パース処理:
168+
```bash
169+
nthreads_line=$(echo $line | grep -Eo 'nthreads:[ ]*[0-9]*' | head -n1 | awk -F':' '{print $2}' | sed 's/^ *//')
170+
if [ -n "$nthreads_line" ]; then
171+
nthreads=${nthreads_line}
172+
else
173+
nthreads=""
174+
fi
175+
```
176+
177+
#### nthreadsの初期値
178+
179+
スクリプト冒頭で`numproc_node`と同様に初期値を設定:
180+
```bash
181+
nthreads=""
182+
```
183+
184+
#### Result_JSONへのnthreadsフィールド追加
185+
186+
`write_result_json`関数内のJSON出力テンプレートに`nthreads`フィールドを追加:
187+
188+
```bash
189+
cat <<EOF > results/result${idx}.json
190+
{
191+
"code": "$code",
192+
"system": "$system",
193+
"FOM": "$fom",
194+
"FOM_version": "$fom_version",
195+
"Exp": "$exp",
196+
"node_count": "$node_count",
197+
"numproc_node": "$numproc_node",
198+
"nthreads": "$nthreads",
199+
...
200+
}
201+
EOF
202+
```
203+
204+
## データモデル
205+
206+
### Result_JSON構造(関連フィールド)
207+
208+
```json
209+
{
210+
"code": "qws",
211+
"system": "Fugaku",
212+
"Exp": "CASE0",
213+
"FOM": 1.234,
214+
"node_count": 4,
215+
"numproc_node": "4",
216+
"nthreads": "12"
217+
}
218+
```
219+
220+
`numproc_node`は既存フィールド(FOM行からパース)。`nthreads`は新規フィールド(FOM行からパース、`numproc_node`と同じ方式)。古いJSONファイルには`nthreads`が存在しない場合があり、その場合は`"N/A"`を表示する。
221+
222+
`cpu_name`, `gpu_name`, `cpus_per_node`, `gpus_per_node`, `cpu_cores`はResult_JSONに引き続き記録される(result.shは変更しない)が、テーブルカラムとしては表示しない。`_build_row`からもこれらのフィールド抽出を削除する。
223+
224+
### テーブル行データ(row dict)
225+
226+
`_build_row`が返すdictの変更:
227+
228+
削除するキー: `"cpu"`, `"gpu"`, `"cpus"`, `"gpus"`, `"cpu_cores"`
229+
230+
追加するキー:
231+
```python
232+
row = {
233+
"timestamp": "2025-01-01 12:00:00",
234+
"code": "qws",
235+
"exp": "CASE0",
236+
"fom": 1.234,
237+
"fom_version": "v1",
238+
"system": "Fugaku",
239+
"nodes": 4,
240+
"numproc_node": "4", # 新規追加(値またはN/A)
241+
"nthreads": "12", # 新規追加(値またはN/A)
242+
"json_link": "/results/...",
243+
"data_link": "/results/...",
244+
"has_vector": False,
245+
"detail_link": None,
246+
"filename": "result_...",
247+
"build_time": "-",
248+
"queue_time": "-",
249+
"run_time": "-",
250+
"execution_mode": "-",
251+
"ci_trigger": "-",
252+
"build_job": "-",
253+
"run_job": "-",
254+
"pipeline_id": "-",
255+
}
256+
```
257+
258+
### フィルタオプション(filter_options dict)
259+
260+
```python
261+
{
262+
"systems": ["Fugaku", "RC_GH200"],
263+
"codes": ["qws", "genesis"],
264+
"exps": ["CASE0", "CASE1"] # filter_code指定時は絞り込み済み
265+
}
266+
```
267+
268+
### FOM行フォーマット(result.sh入力)
269+
270+
```
271+
FOM:1.234 node_count:4 numproc_node:4 nthreads:12 Exp:CASE0 FOM_version:v1
272+
```
273+
274+
`nthreads:N``numproc_node:N` と同じパターンでFOM行に記述される。
275+
276+
277+
## 正確性プロパティ (Correctness Properties)
278+
279+
*プロパティとは、システムの全ての有効な実行において成り立つべき特性や振る舞いのことである。人間が読める仕様と機械的に検証可能な正確性保証の橋渡しとなる。*
280+
281+
### Property 1: カスケードフィルタの正確性
282+
283+
*任意の* Result_JSONファイル群と *任意の* `filter_code`値(Noneを含む)に対して、`get_filter_options`が返す`exps`は、`filter_code`が指定されている場合はそのCodeを持つJSONに含まれるExp値の集合と一致し、`filter_code`がNoneの場合は全JSONに含まれるExp値の集合と一致する。
284+
285+
**Validates: Requirements 2.1, 2.2**
286+
287+
### Property 2: numproc_nodeフィールド抽出の正確性
288+
289+
*任意の* Result_JSONデータに対して、`_build_row`が返すrowの`numproc_node`値は、JSONに`numproc_node`フィールドが存在し空でない場合はその値と一致し、存在しないかNullか空文字列の場合は`"N/A"`となる。
290+
291+
**Validates: Requirements 3.2, 3.5**
292+
293+
### Property 3: nthreadsフィールド抽出の正確性
294+
295+
*任意の* Result_JSONデータに対して、`_build_row`が返すrowの`nthreads`値は、JSONに`nthreads`フィールドが存在し空でない場合はその値と一致し、存在しないかNullか空文字列の場合は`"N/A"`となる。
296+
297+
**Validates: Requirements 4.2, 4.5**
298+
299+
### Property 4: ハードスペック系カラムの非表示
300+
301+
*任意の* `load_results_table`呼び出しに対して、返されるカラムリストに`("CPU Name", "cpu")`, `("GPU Name", "gpu")`, `("CPU/node", "cpus")`, `("GPU/node", "gpus")`, `("CPU Core Count", "cpu_cores")`が含まれない。
302+
303+
**Validates: Requirements 5.1**
304+
305+
## エラーハンドリング
306+
307+
### `numproc_node`および`nthreads`フィールドの欠損
308+
309+
古いResult_JSONファイルには`nthreads`フィールドが存在しない場合がある。`_build_row``data.get("nthreads", "N/A")`でフォールバックし、テーブルに`"N/A"`を表示する。`None`値や空文字列の場合も`"N/A"`に変換する。`numproc_node`も同様の処理を行う。
310+
311+
### `get_filter_options``filter_code`パラメータ
312+
313+
- `filter_code=None`: 従来通り全Expを返す(後方互換性維持)
314+
- `filter_code`に存在しないCode値が指定された場合: Expsは空リストとなる(正常動作)
315+
- `filter_code=""`(空文字列): Noneと同様に扱い、全Expを返す
316+
317+
### result.shのnthreadsパース
318+
319+
- FOM行に`nthreads:`が含まれない場合: `nthreads`フィールドは空文字列として記録
320+
- FOM行に`nthreads:12`のように含まれる場合: `nthreads`フィールドに`"12"`を記録
321+
322+
### 既存テストへの影響
323+
324+
カラムリストの変更により、`test_existing_columns_unchanged`テストが失敗する。このテストの期待値を新しいカラムリスト(ハードスペック系削除、Proc/node・Thread/proc追加済み)に更新する必要がある。
325+
326+
## テスト戦略
327+
328+
### テストフレームワーク
329+
330+
- **ユニットテスト**: pytest(既存のテストスイートに追加)
331+
- **プロパティベーステスト**: hypothesis(既存プロジェクトで使用済み)
332+
- 各プロパティテストは最低100イテレーション実行
333+
334+
### ユニットテスト
335+
336+
既存の`test_results_loader.py`に以下のテストを追加:
337+
338+
1. **カラムリスト検証**: `Proc/node`カラムが`Nodes`の直後、`Thread/proc``Proc/node`の直後に存在し、ハードスペック系カラムが含まれないことを確認(Requirements 3.1, 4.1, 5.1)
339+
2. **既存カラムテスト更新**: `test_existing_columns_unchanged`の期待値を新カラムリストに更新
340+
3. **カスケードフィルタの統合テスト**: `get_filter_options``filter_code`を渡した場合のExpフィルタリング動作確認(Requirements 2.3)
341+
4. **numproc_nodeフォールバック**: `numproc_node`なしのJSONで`"N/A"`が返ることを確認(Requirements 3.5のエッジケース)
342+
5. **nthreadsフォールバック**: `nthreads`なしのJSONで`"N/A"`が返ることを確認(Requirements 4.5のエッジケース)
343+
344+
### プロパティベーステスト
345+
346+
各テストはhypothesisの`@given`デコレータを使用し、`@settings(max_examples=100)`で最低100イテレーション実行する。
347+
348+
1. **Feature: table-ux-improvements, Property 1: カスケードフィルタの正確性**
349+
- ランダムなResult_JSONデータセット(複数のcode/exp組み合わせ)を生成
350+
- ランダムなfilter_code値(Noneまたはデータセット内のcode値)を選択
351+
- `get_filter_options`の返すexpsが期待される集合と一致することを検証
352+
353+
2. **Feature: table-ux-improvements, Property 2: numproc_nodeフィールド抽出の正確性**
354+
- ランダムなResult_JSONデータ(numproc_nodeあり/なし/None/空文字列)を生成
355+
- `_build_row`の返すrowのnumproc_node値が期待値と一致することを検証
356+
357+
3. **Feature: table-ux-improvements, Property 3: nthreadsフィールド抽出の正確性**
358+
- ランダムなResult_JSONデータ(nthreadsあり/なし/None/空文字列)を生成
359+
- `_build_row`の返すrowのnthreads値が期待値と一致することを検証
360+
361+
4. **Feature: table-ux-improvements, Property 4: ハードスペック系カラムの非表示**
362+
- ランダムなResult_JSONデータを生成
363+
- `load_results_table`の返すカラムリストにハードスペック系カラムが含まれないことを検証

0 commit comments

Comments
 (0)