From 38deb6dc7ff5c855271792dacc82d5af2245b445 Mon Sep 17 00:00:00 2001 From: IGUCHI Masato <67450288+iguchi-lab@users.noreply.github.com> Date: Fri, 14 Aug 2026 12:13:54 +0900 Subject: [PATCH] Reorganize input UI and align BRI labels --- CHANGELOG.md | 7 + README.md | 1 + apps/gradio/src/verification_app/form_app.py | 36 +- .../gradio/src/verification_app/form_model.py | 46 +- .../src/verification_app/form_presentation.py | 396 ++++++++++++++++++ docs/input_guide.md | 106 +++++ docs/underfloor_ac_input_guide.md | 28 +- tests/test_form_model.py | 98 +++-- tests/test_gradio_app.py | 66 +-- 9 files changed, 691 insertions(+), 93 deletions(-) create mode 100644 apps/gradio/src/verification_app/form_presentation.py create mode 100644 docs/input_guide.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b2461f..b5b864b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ ## Unreleased +### Changed + +- 入力画面を目的別の8セクションへ再編し、暖房・冷房は選択方式の項目を同じ + セクション内に表示するようにした。建研Web由来項目の表示名を建研Webの文言へ + 揃え、各セクション・グループ・主要入力に用途と計算への影響を説明する文を追加した。 +- 入力画面ガイドを追加し、床下関連設定の入力ガイドを新しい画面構成へ更新した。 + ## [1.2.0] - 2026-08-13 ### Added diff --git a/README.md b/README.md index 87b2747..cc45ed8 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,7 @@ docker run --rm -p 8080:8080 verification-platform-next - [アーキテクチャ](docs/ARCHITECTURE.md) - [リリースと成果物の版管理](docs/RELEASING.md) - [変更履歴](CHANGELOG.md) +- [入力画面ガイド](docs/input_guide.md) - [床下関連設定の入力ガイド](docs/underfloor_ac_input_guide.md) - [床下空調計算の7つの変更点](docs/underfloor_ac_seven_changes.md) - [Excelとの床下計算整合](docs/underfloor_ac_excel_alignment.md) diff --git a/apps/gradio/src/verification_app/form_app.py b/apps/gradio/src/verification_app/form_app.py index 3699481..db11ed2 100644 --- a/apps/gradio/src/verification_app/form_app.py +++ b/apps/gradio/src/verification_app/form_app.py @@ -81,7 +81,19 @@ font-size: 1rem !important; font-weight: 600 !important; line-height: 1.45 !important; - margin: 0.65rem 0 0.35rem !important; + margin: 0.65rem 0 0.2rem !important; +} +.input-section-description p, +.input-group-heading p { + color: var(--body-text-color-subdued); + font-size: 0.88rem !important; + line-height: 1.55 !important; +} +.input-section-description p { + margin: 0 0 0.75rem !important; +} +.input-group-heading p { + margin: 0 0 0.45rem !important; } .annual-summary { margin: 0.85rem 0 1rem; @@ -222,7 +234,12 @@ def build_app( ) as section_container: section_containers[section.name] = section_container section_dom_ids[section.name] = section_dom_id - if section.name == "⑥ その他": + if section.description: + gr.Markdown( + section.description, + elem_classes=["input-section-description"], + ) + if section.name == "④ 床下・空気搬送": gr.Markdown( "📘 床下関係の方式選択、推奨値、入力例は" f"[床下関連設定の入力ガイド]({_UNDERFLOOR_INPUT_GUIDE_URL})" @@ -234,7 +251,8 @@ def build_app( form_field.definition for form_field in group.fields ) gr.Markdown( - f"## {group.name}", + f"## {group.name}" + + (f"\n\n{group.description}" if group.description else ""), elem_id=group_dom_id, elem_classes=["input-group-heading"], ) @@ -245,6 +263,9 @@ def build_app( field_dom_id = f"input-field-container-{len(components)}" component = _input_component( form_field.definition, + label=form_field.label, + description=form_field.description, + choices=form_field.choices, visible=form_field.visible, min_width=280, elem_id=field_dom_id, @@ -537,15 +558,18 @@ def _section_is_visible( def _input_component( field: FieldDefinition, *, + label: str | None = None, + description: str | None = None, + choices: tuple[Any, ...] | None = None, visible: bool = True, min_width: int | None = None, elem_id: str | None = None, elem_classes: list[str] | None = None, ) -> Any: common = { - "label": field.label, + "label": label or field.label, "value": field.default, - "info": field.description or None, + "info": description if description is not None else field.description or None, "key": f"field:{field.key}", "visible": visible, "min_width": min_width, @@ -560,7 +584,7 @@ def _input_component( return gr.Checkbox(**common) if field.kind is FieldKind.SELECT: return gr.Dropdown( - choices=list(field.choices), + choices=list(choices if choices is not None else field.choices), allow_custom_value=False, **common, ) diff --git a/apps/gradio/src/verification_app/form_model.py b/apps/gradio/src/verification_app/form_model.py index f539a8c..550a48c 100644 --- a/apps/gradio/src/verification_app/form_model.py +++ b/apps/gradio/src/verification_app/form_model.py @@ -5,11 +5,21 @@ from verification_core import FieldDefinition, FieldKind, InputSchema, load_input_schema +from .form_presentation import ( + SECTION_DESCRIPTIONS, + SECTION_ORDER, + group_description, + present_field, +) + @dataclass(frozen=True, slots=True) class FormField: definition: FieldDefinition visible: bool + label: str + description: str + choices: tuple[Any, ...] @property def key(self) -> str: @@ -20,12 +30,14 @@ def key(self) -> str: class FormGroup: name: str fields: tuple[FormField, ...] + description: str = "" @dataclass(frozen=True, slots=True) class FormSection: name: str groups: tuple[FormGroup, ...] + description: str = "" @dataclass(frozen=True, slots=True) @@ -72,9 +84,19 @@ def load_form_model( resolved_values.update(values) visibility = _visibility(schema, resolved_values) + presented_fields = tuple( + (field, present_field(field)) + for field in schema.fields + ) sections: list[FormSection] = [] - for section_name in dict.fromkeys(field.section for field in schema.fields): - section_fields = tuple(field for field in schema.fields if field.section == section_name) + for section_name in SECTION_ORDER: + section_fields = tuple( + (field, presentation) + for field, presentation in presented_fields + if presentation.section == section_name + ) + if not section_fields: + continue groups = tuple( FormGroup( name=group_name, @@ -82,14 +104,26 @@ def load_form_model( FormField( definition=field, visible=visibility[field.key], + label=presentation.label, + description=presentation.description, + choices=presentation.choices, ) - for field in section_fields - if field.group == group_name + for field, presentation in section_fields + if presentation.group == group_name ), + description=group_description(group_name), + ) + for group_name in dict.fromkeys( + presentation.group for _, presentation in section_fields + ) + ) + sections.append( + FormSection( + name=section_name, + groups=groups, + description=SECTION_DESCRIPTIONS.get(section_name, ""), ) - for group_name in dict.fromkeys(field.group for field in section_fields) ) - sections.append(FormSection(name=section_name, groups=groups)) return FormModel(schema=schema, sections=tuple(sections)) diff --git a/apps/gradio/src/verification_app/form_presentation.py b/apps/gradio/src/verification_app/form_presentation.py new file mode 100644 index 0000000..7b8c262 --- /dev/null +++ b/apps/gradio/src/verification_app/form_presentation.py @@ -0,0 +1,396 @@ +from __future__ import annotations + +import re +from dataclasses import dataclass +from typing import Any + +from verification_core import FieldDefinition + + +@dataclass(frozen=True, slots=True) +class FieldPresentation: + section: str + group: str + label: str + description: str + choices: tuple[Any, ...] + + +SECTION_ORDER = ( + "① 計算条件・外部データ", + "② 住宅基本情報", + "③ 外皮性能", + "④ 床下・空気搬送", + "⑤ 暖房設備", + "⑥ 冷房設備", + "⑦ 熱交換型換気設備", + "⑧ 詳細設定・比較検証", +) + +SECTION_DESCRIPTIONS = { + "① 計算条件・外部データ": ( + "計算条件名を付け、必要に応じて標準データを置き換える外部ファイルを指定します。" + "外部ファイルを指定しない場合は、プログラムに同梱した標準データを使用します。" + ), + "② 住宅基本情報": ( + "建研Webプログラムの「基本情報」に対応する項目です。" + "床面積と地域の区分は、設備容量、負荷、気象条件などの算定に使用します。" + ), + "③ 外皮性能": ( + "建研Webプログラムの「外皮」に対応する項目です。" + "外皮面積、熱貫流率、日射熱取得率を入力します。" + ), + "④ 床下・空気搬送": ( + "建研Webの床下換気と、Verification Platformで追加した床下空調・風量制御を設定します。" + "床下換気と床下空調は目的と計算経路が異なるため、説明を確認して選択してください。" + ), + "⑤ 暖房設備": ( + "暖房方式を選択すると、その方式で必要な項目だけが表示されます。" + "青色の項目名は建研Webプログラムの文言を引き継いでいます。" + ), + "⑥ 冷房設備": ( + "冷房方式を選択すると、その方式で必要な項目だけが表示されます。" + "青色の項目名は建研Webプログラムの文言を引き継いでいます。" + ), + "⑦ 熱交換型換気設備": ( + "建研Webプログラムの「熱交換型換気設備」に対応する項目です。" + "設置する場合だけ温度交換効率を入力します。" + ), + "⑧ 詳細設定・比較検証": ( + "計算式の比較、研究用の補助定数、機器モデルの詳細条件を変更する上級者向け設定です。" + "通常の計算では既定値のまま使用してください。" + ), +} + +GROUP_DESCRIPTIONS = { + "計算条件": "計算結果や出力ファイルを識別するための名称です。", + "外部データ(任意)": ( + "気象データまたは暖冷房負荷データを差し替える場合だけ指定します。" + "「-」のままなら標準データを使用します。" + ), + "床面積": ( + "建研Webの「床面積」に対応します。主たる居室、その他の居室、合計の関係を確認して入力してください。" + ), + "地域": "建設地に対応する省エネルギー基準の地域区分を選択します。", + "外皮性能": ( + "建研Webで外皮性能を直接入力する場合の項目です。記号と単位は各項目の説明に示します。" + ), + "床下換気(建研Web)": ( + "外気を床下経由で導入する建研Webの換気方式です。床下を空調給気経路として使う設定ではありません。" + ), + "全館空調の風量制御": ( + "ダクト式セントラル空調機の全体風量を制御します。床下空調の有無にかかわらず影響します。" + ), + "床下空調": ( + "床下を一体の給気空間として扱うVerification Platform独自方式です。" + "Excel床下14に合わせたPass 1・Pass 2と地盤逐時計算を使用します。" + ), + "床下空調|補助定数": ( + "手動設定を有効にした場合だけ使用します。通常は既定値または自動算定値を使用してください。" + ), + "建研式との比較・補正": ( + "OFFは建研式との比較用、ONは確認済みの不整合を補正する計算です。" + "比較結果を追跡できるよう、補正は個別に切り替えられます。" + ), + "蓄熱・過剰熱量の持ち越し": ( + "前時刻の過剰熱量を建物の熱容量へ持ち越すVerification Platform独自計算です。" + "床下空調とは同時に使用できません。" + ), + "暖房方式": "暖房の評価方法を選択します。選択した方式の入力欄だけが表示されます。", + "冷房方式": "冷房の評価方法を選択します。選択した方式の入力欄だけが表示されます。", + "ダクト・換気方式": ( + "ダクトの断熱区画、VAV方式、全般換気機能の有無を設定します。" + "暖房と冷房はそれぞれ独立した入力です。" + ), + "風量・送風機電力": ( + "設計風量、サーモOFF時などの最低風量、最低送風機電力を設定します。" + "入力しない場合は計算モデルの既定式で算定します。" + ), + "熱交換型換気設備": "設置の有無と、設置する場合の温度交換効率を設定します。", + "熱源機出口温度の制限": "暖房・冷房時の熱源機出口空気温度に適用する上下限です。", + "ダクト式セントラル空調機のデフロスト": ( + "低外気温・高湿度時の暖房能力低下を表すダクト式セントラル空調機用の条件です。" + ), + "ダクト・送風機の補助定数": "ダクト熱損失と設計風量算定に用いる補助定数です。", + "ルームエアコンディショナーのデフロスト": ( + "低外気温・高湿度時の暖房能力低下を表すルームエアコンディショナー用の条件です。" + ), + "冷房能力・湿度補正": "冷房出力の湿度補正と定格冷房能力の上限を設定します。", + "計算方法の比較切替": ( + "建研由来式と検討中の代替式を比較するためのスイッチです。結果への影響を確認して使用してください。" + ), +} + + +_SECTION_MAP = { + "①・② 計算条件・外部ファイル": "① 計算条件・外部データ", + "④ 基本情報": "② 住宅基本情報", + "⑤ 外皮条件": "③ 外皮性能", + "⑥ その他": "④ 床下・空気搬送", + "⑦ 暖房全般": "⑤ 暖房設備", + "⑧ 冷房全般": "⑥ 冷房設備", + "⑨ 熱交換型換気設備": "⑦ 熱交換型換気設備", + "③ 計算時定数等": "⑧ 詳細設定・比較検証", +} + +_METHOD_NAMES = { + "⑦-1": "ダクト式セントラル空調", + "⑦-2": "RAC活用型(省エネ法モデル)", + "⑦-3": "RAC活用型(潜熱評価モデル)", + "⑦-4": "RAC活用型(電中研モデル)", + "⑧-1": "ダクト式セントラル空調", + "⑧-2": "RAC活用型(省エネ法モデル)", + "⑧-3": "RAC活用型(潜熱評価モデル)", + "⑧-4": "RAC活用型(電中研モデル)", +} + +_GROUP_RENAMES = { + "外部ファイル(任意)": "外部データ(任意)", + "基本項目": "床面積", + "床下換気(建研方式)": "床下換気(建研Web)", + "床下空調(Verification Platform)": "床下空調", + "手動設定する補助定数": "床下空調|補助定数", + "建研本家との比較設定": "建研式との比較・補正", + "過剰熱量持越し": "蓄熱・過剰熱量の持ち越し", + "暖房機器の種類": "暖房方式", + "冷房機器の種類": "冷房方式", + "設計風量": "風量・送風機電力", + "最低風量": "風量・送風機電力", + "最低電力": "風量・送風機電力", + "ダクト・送風機": "ダクト・送風機の補助定数", + "計算方法の切替": "計算方法の比較切替", +} + +_BRI_LABELS = { + "A_A__0": "合計", + "A_MR__0": "主たる居室", + "A_OR__0": "その他の居室", + "region__0": "地域の区分", + "A_env__0": "外皮面積の合計", + "U_A__0": "外皮平均熱貫流率(U_A)", + "eta_A_C__0": "冷房期の平均日射熱取得率(η_AC)", + "eta_A_H__0": "暖房期の平均日射熱取得率(η_AH)", + "underfloor_ventilation__0": "床下空間を経由して外気を導入する換気方式の利用", + "r_A_ufvnt__0": "外気が経由する床下の面積の割合", + "underfloor_insulation__0": "床下空間の断熱", + "H_A_duct_insulation__0": "ダクトが通過する空間", + "H_A_VAV__0": "VAV方式", + "H_A_general_ventilation__0": "全般換気機能", + "H_A_input_V_hs_dsgn_H__0": "設計風量の入力", + "H_A_V_hs_dsgn_H__0": "設計風量", + "C_A_duct_insulation__0": "ダクトが通過する空間", + "C_A_VAV__0": "VAV方式", + "C_A_general_ventilation__0": "全般換気機能", + "C_A_input_V_hs_dsgn_C__0": "設計風量の入力", + "C_A_V_hs_dsgn_C__0": "設計風量", + "H_A_input__0": "機器の仕様の入力", + "C_A_input__0": "機器の仕様の入力", + "H_A_input_mode__0": "エネルギー消費効率の入力", + "H_A_mode__0": "エネルギー消費効率の区分", + "H_A_dualcompressor__0": "小能力時高効率型コンプレッサー", + "C_A_input_mode__0": "エネルギー消費効率の入力", + "C_A_mode__0": "エネルギー消費効率の区分", + "C_A_dualcompressor__0": "小能力時高効率型コンプレッサー", + "HEX_install__0": "熱交換型換気設備", + "etr_t__0": "温度交換効率", +} + +_PLATFORM_LABELS = { + "case_name__0": "計算条件名", + "climateFile__0": "気象データ(任意)", + "loadFile__0": "暖冷房負荷データ(任意)", + "change_supply_volume_before_vav_adjust__0": "VAV調整前の風量配分を時刻別負荷比へ変更する", + "change_heat_source_outlet_required_temperature__0": "熱源機出口温度を区画別要求温度から決定する", + "change_V_supply_d_t_i_max__0": "区画別吹き出し風量の上限方式", + "hs_CAV__0": "熱源機の全体風量を設計風量に固定する", + "change_underfloor_temperature__0": "床下空調を使用する", + "input_ufac_consts__0": "床下空調の補助定数を手動設定する", + "R_g__0": "地盤表面熱伝達抵抗 R_g", + "phi__0": "基礎側面の線熱貫流率 φ", + "correct_cooling_partition_heat_transfer__0": "冷房時の間仕切壁熱移動を物理的な符号へ補正する", + "correct_no_general_ventilation_airflow__0": "全般換気なし時の給気風量下限を補正する", + "carry_over_heat__0": "過剰熱量の持ち越し計算を行う", + "H_A_input_V_hs_min__0": "最低風量の入力", + "H_A_V_hs_min__0": "最低風量", + "H_A_input_E_E_fan_min__0": "最低送風機電力の入力", + "H_A_E_E_fan_logic__0": "最低送風機電力の計算方法", + "H_A_E_E_fan_min__0": "最低送風機電力", + "C_A_input_V_hs_min__0": "最低風量の入力", + "C_A_V_hs_min__0": "最低風量", + "C_A_input_E_E_fan_min__0": "最低送風機電力の入力", + "C_A_E_E_fan_logic__0": "最低送風機電力の計算方法", + "C_A_E_E_fan_min__0": "最低送風機電力", + "H_A_type__0": "暖房方式", + "C_A_type__0": "冷房方式", +} + +_DESCRIPTIONS = { + "case_name__0": "出力ファイル名と計算結果の識別に使用します。計算式には影響しません。", + "climateFile__0": "「-」の場合は地域区分に対応する標準気象データを使用します。", + "loadFile__0": "「-」の場合は同梱した標準暖冷房負荷を使用します。", + "A_A__0": "住戸の床面積の合計です。単位は m² です。", + "A_MR__0": "主たる居室の床面積です。単位は m² です。", + "A_OR__0": "その他の居室の床面積です。単位は m² です。", + "region__0": "建設地に対応する1~7地域を選択します。", + "A_env__0": "当該住戸の外皮面積の合計です。単位は m² です。", + "U_A__0": "外皮平均熱貫流率です。単位は W/(m²・K) です。", + "eta_A_C__0": "冷房期の平均日射熱取得率です。単位は % です。", + "eta_A_H__0": "暖房期の平均日射熱取得率です。単位は % です。", + "H_A_input_V_hs_dsgn_H__0": "入力しない場合は建研式で暖房時の設計風量を算定します。", + "H_A_V_hs_dsgn_H__0": "暖房時の設計風量です。単位は m³/h です。", + "C_A_input_V_hs_dsgn_C__0": "入力しない場合は建研式で冷房時の設計風量を算定します。", + "C_A_V_hs_dsgn_C__0": "冷房時の設計風量です。単位は m³/h です。", + "H_A_input__0": "入力しない場合は建研Webと同じ既定の機器特性を使用します。", + "C_A_input__0": "入力しない場合は建研Webと同じ既定の機器特性を使用します。", + "H_A_input_mode__0": "入力しない場合は暖房の既定区分で評価します。", + "C_A_input_mode__0": "入力しない場合は冷房の既定区分で評価します。", + "H_A_dualcompressor__0": "暖房能力の直接入力とは独立した設定です。", + "C_A_dualcompressor__0": "冷房能力の直接入力とは独立した設定です。", + "HEX_install__0": "設置する場合だけ温度交換効率の入力欄が表示されます。", + "etr_t__0": "熱交換型換気設備の温度交換効率です。単位は % です。", +} + +_THERMAL_CAPACITY_LABELS = { + "c1_BR_R_1__0": "主たる居室(区画1)の熱容量", + "c1_BR_R_2__0": "その他の居室(区画2)の熱容量", + "c1_BR_R_3__0": "その他の居室(区画3)の熱容量", + "c1_BR_R_4__0": "その他の居室(区画4)の熱容量", + "c1_BR_R_5__0": "その他の居室(区画5)の熱容量", + "c1_NR_R__0": "非居室の熱容量", +} + + +def present_field(field: FieldDefinition) -> FieldPresentation: + section = _section_name(field.section) + group = _group_name(field) + label = _field_label(field) + description = _field_description(field) + return FieldPresentation( + section=section, + group=group, + label=label, + description=description, + choices=_field_choices(field), + ) + + +def _section_name(source_section: str) -> str: + for prefix in _METHOD_NAMES: + if source_section.startswith(prefix): + return "⑤ 暖房設備" if prefix.startswith("⑦") else "⑥ 冷房設備" + try: + return _SECTION_MAP[source_section] + except KeyError as error: + raise ValueError(f"Unknown input section: {source_section}") from error + + +def _group_name(field: FieldDefinition) -> str: + for prefix, method_name in _METHOD_NAMES.items(): + if field.section.startswith(prefix): + return f"{method_name}|{field.group}" + if field.section == "④ 基本情報": + return "地域" if field.key == "region__0" else "床面積" + if field.section == "⑤ 外皮条件": + return "外皮性能" + if field.section == "⑨ 熱交換型換気設備": + return "熱交換型換気設備" + return _GROUP_RENAMES.get(field.group, field.group) + + +def _field_label(field: FieldDefinition) -> str: + if field.key in _BRI_LABELS: + return _BRI_LABELS[field.key] + if field.key in _PLATFORM_LABELS: + return _PLATFORM_LABELS[field.key] + if field.key in _THERMAL_CAPACITY_LABELS: + return _THERMAL_CAPACITY_LABELS[field.key] + test_value_label = _test_value_label(field.key) + if test_value_label is not None: + return test_value_label + coefficient = _coefficient_label(field) + if coefficient is not None: + return coefficient + return _clean_label(field.label) + + +def _test_value_label(field_key: str) -> str | None: + if not any( + token in field_key + for token in ("_hs_rtd_", "_hs_mid_", "_fan_rtd_", "_fan_mid_") + ): + return None + if "_q_hs_" in field_key: + return "能力" + if "_P_hs_" in field_key: + return "消費電力" + if "_V_fan_" in field_key: + return "風量" + if "_P_fan_" in field_key: + return "室内側送風機の消費電力" + return None + + +def _clean_label(label: str) -> str: + label = re.sub(r"([^)]* or [^)]*)", "", label) + label = label.replace("(入力する場合のみ)", "") + return label.strip() + + +def _field_description(field: FieldDefinition) -> str: + description = _DESCRIPTIONS.get(field.key, field.description) + if description: + return description + if "[m3/h]" in field.label: + return "単位は m³/h です。" + if "[W/(m2・K)]" in field.label: + return "単位は W/(m²・K) です。" + if "[W]" in field.label: + return "単位は W です。" + if "[m2]" in field.label: + return "単位は m² です。" + return "" + + +def _field_choices(field: FieldDefinition) -> tuple[Any, ...]: + return field.choices + + +def _coefficient_label(field: FieldDefinition) -> str | None: + if not field.key.startswith(("a4__", "a3__", "a2__", "a1__", "a0__")): + return None + degree = field.key[1] + return f"{degree}次係数 a{degree}" + + +def group_description(group_name: str) -> str: + if group_name in GROUP_DESCRIPTIONS: + return GROUP_DESCRIPTIONS[group_name] + if "|" not in group_name: + return "" + method, detail = group_name.split("|", 1) + detail_descriptions = { + "設置方法の入力": ( + "熱源機と送風機の組み合わせ方を設定します。補正係数を直接入力する場合だけ係数欄が表示されます。" + ), + "機器仕様の入力": ( + "機器仕様を入力する範囲を選択します。選択後、必要な試験値だけが表示されます。" + ), + "定格暖房能力試験": "定格暖房能力試験の能力、消費電力、風量、送風機電力を入力します。", + "中間暖房能力試験": "中間暖房能力試験の能力、消費電力、風量、送風機電力を入力します。", + "定格冷房能力試験": "定格冷房能力試験の能力、消費電力、風量、送風機電力を入力します。", + "中間冷房能力試験": "中間冷房能力試験の能力、消費電力、風量、送風機電力を入力します。", + "エネルギー消費効率の入力": "エネルギー消費効率を直接指定するか選択します。暖房・冷房で個別に設定します。", + "暖房能力の入力": "暖房能力を床面積から算定するか、機器値を直接入力するか選択します。", + "冷房能力の入力": "冷房能力を床面積から算定するか、機器値を直接入力するか選択します。", + "小能力時高効率型コンプレッサー": "能力の入力方法とは独立して、コンプレッサーの特性を設定します。", + "ファンの消費電力": "送風機の比消費電力を自動算定するか、直接入力するか選択します。", + "コイル特性": "熱交換器の小容量・大容量時の表面積を設定します。", + "コンプレッサ効率特性": "コンプレッサー効率を表す多項式係数です。式はこのグループ全体で共通です。", + "熱伝達特性": "熱伝達特性を表す多項式係数です。式はこのグループ全体で共通です。", + "風量特性": "最小・最大風量と、風量特性を表す多項式係数を設定します。", + "ファン消費電力": "送風機消費電力を表す多項式係数です。式はこのグループ全体で共通です。", + } + detail_description = detail_descriptions.get(detail, "") + if detail_description: + return f"{method}の設定です。{detail_description}" + return f"{method}の「{detail}」に関する入力です。" diff --git a/docs/input_guide.md b/docs/input_guide.md new file mode 100644 index 0000000..ced6790 --- /dev/null +++ b/docs/input_guide.md @@ -0,0 +1,106 @@ +# 入力画面ガイド + +## 1. この画面の考え方 + +Verification Platform Nextの入力画面は、建築研究所の住宅のエネルギー消費性能 +計算プログラム(以下「建研Web」)にある項目と、検証のために追加した項目を同じ +画面で扱います。 + +- 青色: 建研Webにある入力。項目名は建研Webの表示文言を引き継ぎます。 +- オレンジ色: Verification Platformで追加・拡張した入力です。 +- 緑色: 現在の入力がVerification Platformのデフォルト値から変更されています。 + +「入力をデフォルトに戻す」を押すと、全項目がデフォルト値へ戻り、選択した方式に +不要な項目も再び非表示になります。 + +## 2. 入力する順番 + +| 順番 | セクション | 主な内容 | +|---:|---|---| +| 1 | 計算条件・外部データ | 計算条件名、任意の気象・負荷データ | +| 2 | 住宅基本情報 | 床面積、地域の区分 | +| 3 | 外皮性能 | 外皮面積、外皮平均熱貫流率、日射熱取得率 | +| 4 | 床下・空気搬送 | 建研Webの床下換気、床下空調、全館空調の風量制御 | +| 5 | 暖房設備 | 暖房方式、ダクト・換気、風量、方式別の機器仕様 | +| 6 | 冷房設備 | 冷房方式、ダクト・換気、風量、方式別の機器仕様 | +| 7 | 熱交換型換気設備 | 設置の有無、温度交換効率 | +| 8 | 詳細設定・比較検証 | 補助定数、計算式の比較切替、研究用の詳細条件 | + +通常の計算では1~7を確認し、8はデフォルト値のまま使用します。暖房方式または +冷房方式を変更すると、選択した方式で必要なグループだけが表示されます。 + +## 3. 建研Webに対応する項目 + +建研Web由来の項目は、次の各画面の文言に合わせています。Verification Platformで +単位や計算への影響を補足する場合は、項目名を変えず、入力欄の説明へ記載します。 + +- [基本情報](https://house.app.lowenergy.jp/#/basic) +- [外皮](https://house.app.lowenergy.jp/#/envelope) +- [暖房](https://house.app.lowenergy.jp/#/heating) +- [冷房](https://house.app.lowenergy.jp/#/cooling) +- [換気](https://house.app.lowenergy.jp/#/ventilation) +- [熱交換型換気設備](https://house.app.lowenergy.jp/#/heat-exchanger) + +建研Webにない全館空調方式や床下空調の項目はオレンジ色で表示し、建研Webの項目と +誤認しないようにしています。保存されるJSONキー、選択肢の保存値、デフォルト値は、 +表示名の整理前後で変わりません。 + +## 4. セクション別の注意点 + +### ① 計算条件・外部データ + +計算条件名は出力ファイルの識別に使い、計算式には影響しません。気象データと +暖冷房負荷データが「-」の場合は、プログラムに同梱した標準データを使用します。 + +### ② 住宅基本情報 + +建研Webの「床面積」に対応し、「主たる居室」「その他の居室」「合計」を入力します。 +単位はm²です。「地域の区分」は標準気象データや設備容量などの算定に影響します。 + +### ③ 外皮性能 + +「外皮面積の合計」「外皮平均熱貫流率(U_A)」「冷房期の平均日射熱取得率 +(η_AC)」「暖房期の平均日射熱取得率(η_AH)」を入力します。外皮平均熱貫流率の +単位はW/(m²・K)、日射熱取得率の単位は%です。 + +### ④ 床下・空気搬送 + +「床下空間を経由して外気を導入する換気方式」は建研Webの床下換気であり、床下を +空調給気経路として使う「床下空調」とは別の計算です。床下空調の方式選択、推奨値、 +面積と補助定数については[床下関連設定の入力ガイド](underfloor_ac_input_guide.md)を +参照してください。 + +「建研式との比較・補正」は、建研式との照合用にOFFを残し、確認済みの不整合を +補正する計算をONで選べるようにした項目です。「蓄熱・過剰熱量の持ち越し」は +床下空調と同時には使用できません。 + +### ⑤・⑥ 暖房設備・冷房設備 + +最初に「暖房方式」または「冷房方式」を選択します。続いてダクトが通過する空間、 +VAV方式、全般換気機能、設計風量を確認します。設計風量や機器仕様で +「入力しない」を選んだ場合は、建研Webと同じ算定方法または既定の機器特性を +使用します。 + +Verification Platformで追加した最低風量・最低送風機電力は、入力を有効にした +場合だけ数値欄を表示します。RAC活用型は、次の3モデルを区別して表示します。 + +- RAC活用型(省エネ法モデル) +- RAC活用型(潜熱評価モデル) +- RAC活用型(電中研モデル) + +### ⑦ 熱交換型換気設備 + +建研Webの「熱交換型換気設備」に対応します。「設置する」を選んだ場合だけ +「温度交換効率」を入力します。 + +### ⑧ 詳細設定・比較検証 + +熱源機出口温度、デフロスト条件、ダクト・送風機の補助定数、冷房能力・湿度補正、 +計算方法の比較切替をまとめています。これらは年間結果や時刻別系列へ直接影響する +ため、検証目的と変更理由を記録できる場合だけ変更してください。 + +## 5. 表示名と計算データの関係 + +画面のセクション名・グループ名・項目名と説明を整理しても、選択肢の保存値は変更 +しません。保存されるJSONキー、デフォルト値、表示条件、計算エンジンへ渡す値は、 +今回の画面整理前後で同じです。このため、既存JSONと計算結果には影響しません。 diff --git a/docs/underfloor_ac_input_guide.md b/docs/underfloor_ac_input_guide.md index b313e62..2963ea6 100644 --- a/docs/underfloor_ac_input_guide.md +++ b/docs/underfloor_ac_input_guide.md @@ -2,7 +2,7 @@ ## 1. この文書の目的 -この文書は、Verification Platform Next の「⑥ その他」にある床下関連項目について、 +この文書は、Verification Platform Next の「④ 床下・空気搬送」にある床下関連項目について、 どの設定がどの計算経路と数値に影響するかを説明する。 詳細な計算式とExcel床下14との整合内容は、次の文書を参照する。 @@ -15,13 +15,13 @@ 床下に関係する計算方式は次の3通りである。 -| 計算したい内容 | 「床下換気を評価する」 | 「床下空調を使用する」 | 計算経路 | +| 計算したい内容 | 「床下空間を経由して外気を導入する換気方式の利用」 | 「床下空調を使用する」 | 計算経路 | |---|---:|---:|---| | 床下を利用しない通常計算 | OFF | OFF | 床下関連の補正を行わない | | 建研方式の床下換気 | ON | OFF | 外気を床下経由で導入することによる負荷補正 | | 床下を空調給気経路として使用 | OFF | ON | Excel床下14に対応するPass 1・Pass 2・地盤逐時計算 | -「床下換気(建研方式)」と「床下空調(Verification Platform)」は別の計算である。 +「床下換気(建研Web)」と「床下空調」は別の計算である。 - 床下換気は、外気を床下経由で導入する換気方式を評価する。 - 床下空調は、熱源機からの空調空気を1階床下へ送り、床下から居室・非居室へ @@ -34,9 +34,9 @@ ## 3. 現在の画面構成 ```text -⑥ その他 -├─ 床下換気(建研方式) -│ ├─ 床下空間を経由して外気を導入する換気方式を評価する +④ 床下・空気搬送 +├─ 床下換気(建研Web) +│ ├─ 床下空間を経由して外気を導入する換気方式の利用 │ └─ ONのときだけ表示 │ ├─ 外気が経由する床下の面積の割合 │ └─ 床下空間の断熱 @@ -44,26 +44,26 @@ ├─ 全館空調の風量制御 │ └─ 熱源機の全体風量を設計風量に固定する │ -├─ 床下空調(Verification Platform) +├─ 床下空調 │ ├─ 床下空調を使用する │ └─ ONのときだけ表示 │ ├─ 床下空調の補助定数を手動設定する │ └─ 手動設定ONのときだけ表示 -│ ├─ 地盤またはそれを覆う基礎の表面熱伝達抵抗 R_g +│ ├─ 地盤表面熱伝達抵抗 R_g │ └─ 基礎側面の線熱貫流率 φ │ (チェック項目の説明文に、不易層温度と床板熱貫流率の │ 自動算定・固定採用方法を表示) │ -└─ 建研本家との比較設定 - ├─ ① 冷房時の間仕切熱移動を物理的な符号に補正する - └─ ② 全般換気なし時の給気風量下限を補正する +└─ 建研式との比較・補正 + ├─ 冷房時の間仕切壁熱移動を物理的な符号へ補正する + └─ 全般換気なし時の給気風量下限を補正する ``` 親となるチェックがOFFのとき、その配下の入力欄は表示されない。 -## 4. 床下換気(建研方式) +## 4. 床下換気(建研Web) -### 4.1 床下空間を経由して外気を導入する換気方式を評価する +### 4.1 床下空間を経由して外気を導入する換気方式の利用 建研付録D系の床下換気による暖冷房負荷補正を有効にする。 @@ -92,7 +92,7 @@ 床下空調と同時に使用できるが、比較条件として固定する場合は、床下以外の 全館空調計算にも影響することに注意する。 -## 6. 床下空調(Verification Platform) +## 6. 床下空調 ### 6.1 床下空調を使用する diff --git a/tests/test_form_model.py b/tests/test_form_model.py index f43739d..594f8ba 100644 --- a/tests/test_form_model.py +++ b/tests/test_form_model.py @@ -8,7 +8,16 @@ def test_form_model_preserves_schema_order_and_groups() -> None: model = load_form_model() assert len(model.fields) == 223 - assert len(model.sections) == 16 + assert tuple(section.name for section in model.sections) == ( + "① 計算条件・外部データ", + "② 住宅基本情報", + "③ 外皮性能", + "④ 床下・空気搬送", + "⑤ 暖房設備", + "⑥ 冷房設備", + "⑦ 熱交換型換気設備", + "⑧ 詳細設定・比較検証", + ) assert model.keys == tuple(field.key for field in model.schema.fields) assert all(section.groups for section in model.sections) assert all(group.fields for section in model.sections for group in section.groups) @@ -28,30 +37,60 @@ def test_form_model_preserves_input_origin() -> None: def test_general_input_sections_are_grouped_by_calculation_role() -> None: model = load_form_model() - constants = next(section for section in model.sections if section.name.startswith("③")) + constants = next(section for section in model.sections if section.name.startswith("⑧")) assert tuple(group.name for group in constants.groups) == ( "熱源機出口温度の制限", "ダクト式セントラル空調機のデフロスト", - "ダクト・送風機", + "ダクト・送風機の補助定数", "ルームエアコンディショナーのデフロスト", "冷房能力・湿度補正", - "計算方法の切替", + "計算方法の比較切替", ) for section_name, equipment_group in ( - ("⑦ 暖房全般", "暖房機器の種類"), - ("⑧ 冷房全般", "冷房機器の種類"), + ("⑤ 暖房設備", "暖房方式"), + ("⑥ 冷房設備", "冷房方式"), ): section = next(item for item in model.sections if item.name == section_name) - assert tuple(group.name for group in section.groups) == ( + assert tuple(group.name for group in section.groups[:3]) == ( equipment_group, "ダクト・換気方式", - "設計風量", - "最低風量", - "最低電力", + "風量・送風機電力", ) +def test_bri_web_fields_use_bri_web_wording_in_the_presentation() -> None: + model = load_form_model() + fields = {field.key: field for field in model.fields} + + assert fields["A_A__0"].label == "合計" + assert fields["A_MR__0"].label == "主たる居室" + assert fields["A_OR__0"].label == "その他の居室" + assert fields["region__0"].label == "地域の区分" + assert fields["A_env__0"].label == "外皮面積の合計" + assert fields["H_A_input__0"].label == "機器の仕様の入力" + assert fields["H_A_q_hs_rtd_H1__0"].label == "能力" + assert fields["H_A_P_hs_rtd_H1__0"].label == "消費電力" + assert fields["H_A_V_fan_rtd_H1__0"].label == "風量" + assert ( + fields["H_A_P_fan_rtd_H1__0"].label + == "室内側送風機の消費電力" + ) + assert fields["HEX_install__0"].label == "熱交換型換気設備" + + +def test_presentation_changes_do_not_modify_schema_values_or_keys() -> None: + model = load_form_model() + fields = {field.key: field for field in model.fields} + + assert model.keys == tuple(field.key for field in model.schema.fields) + assert fields["H_A_type__0"].definition.choices[0] == "ダクト式セントラル空調機" + assert fields["H_A_type__0"].choices == fields["H_A_type__0"].definition.choices + assert model.values_from_sequence( + tuple(field.definition.default for field in model.fields) + ) == model.schema.defaults() + + def test_calculation_switches_explain_their_formula_changes() -> None: model = load_form_model() fields = {field.key: field for field in model.schema.fields} @@ -73,14 +112,16 @@ def test_calculation_switches_explain_their_formula_changes() -> None: def test_underfloor_manual_constants_follow_their_control() -> None: model = load_form_model() - underfloor = next(section for section in model.sections if section.name == "⑥ その他") + underfloor = next( + section for section in model.sections if section.name == "④ 床下・空気搬送" + ) main_group = next( group for group in underfloor.groups - if group.name == "床下空調(Verification Platform)" + if group.name == "床下空調" ) constants_group = next( - group for group in underfloor.groups if group.name == "手動設定する補助定数" + group for group in underfloor.groups if group.name == "床下空調|補助定数" ) assert tuple(field.key for field in main_group.fields) == ( @@ -107,11 +148,12 @@ def test_underfloor_manual_constants_follow_their_control() -> None: def test_rac_efficiency_classes_are_grouped_by_season_like_bri_web() -> None: model = load_form_model() - heating = next(section for section in model.sections if section.name.startswith("⑦-2")) + heating = next(section for section in model.sections if section.name == "⑤ 暖房設備") heating_group = next( group for group in heating.groups - if group.name == "エネルギー消費効率の入力" + if group.name + == "RAC活用型(省エネ法モデル)|エネルギー消費効率の入力" ) assert tuple(field.key for field in heating_group.fields) == ( @@ -120,11 +162,12 @@ def test_rac_efficiency_classes_are_grouped_by_season_like_bri_web() -> None: ) assert not any(field.visible for field in heating_group.fields) - cooling = next(section for section in model.sections if section.name.startswith("⑧-2")) + cooling = next(section for section in model.sections if section.name == "⑥ 冷房設備") cooling_group = next( group for group in cooling.groups - if group.name == "エネルギー消費効率の入力" + if group.name + == "RAC活用型(省エネ法モデル)|エネルギー消費効率の入力" ) assert tuple(field.key for field in cooling_group.fields) == ( "C_A_input_mode__0", @@ -155,20 +198,27 @@ def test_rac_efficiency_classes_are_grouped_by_season_like_bri_web() -> None: def test_rac_dual_compressor_is_grouped_separately_from_capacity_inputs() -> None: model = load_form_model() - for section_prefix, compressor_key, capacity_group_name in ( - ("⑦-2", "H_A_dualcompressor__0", "暖房能力の入力"), - ("⑧-2", "C_A_dualcompressor__0", "冷房能力の入力"), + for section_name, compressor_key, capacity_group_name in ( + ( + "⑤ 暖房設備", + "H_A_dualcompressor__0", + "RAC活用型(省エネ法モデル)|暖房能力の入力", + ), + ( + "⑥ 冷房設備", + "C_A_dualcompressor__0", + "RAC活用型(省エネ法モデル)|冷房能力の入力", + ), ): - section = next( - section for section in model.sections if section.name.startswith(section_prefix) - ) + section = next(section for section in model.sections if section.name == section_name) capacity_group = next( group for group in section.groups if group.name == capacity_group_name ) compressor_group = next( group for group in section.groups - if group.name == "小能力時高効率型コンプレッサー" + if group.name + == "RAC活用型(省エネ法モデル)|小能力時高効率型コンプレッサー" ) assert compressor_key not in tuple( diff --git a/tests/test_gradio_app.py b/tests/test_gradio_app.py index 1127be4..07c89f8 100644 --- a/tests/test_gradio_app.py +++ b/tests/test_gradio_app.py @@ -58,16 +58,9 @@ def test_gradio_app_builds_all_schema_inputs_and_events() -> None: if isinstance(component["props"].get("key"), str) } - def equipment_section(prefix: str) -> dict[str, object]: - return next( - component - for label, component in accordions.items() - if label.startswith(prefix) - ) - assert gradio.__version__.startswith("6.") assert config["title"] == "Verification Platform Next ver.1.2.0" - assert component_types["accordion"] == 16 + assert component_types["accordion"] == 8 assert component_types["number"] == 156 assert component_types["dropdown"] == 54 assert component_types["checkbox"] == 10 @@ -80,7 +73,11 @@ def equipment_section(prefix: str) -> dict[str, object]: and "docs/underfloor_ac_input_guide.md" in value for value in markdown_values ) - assert "## 計算条件" in markdown_values + assert any(value.startswith("## 計算条件") for value in markdown_values) + assert any( + "建研Webプログラムの「基本情報」に対応" in value + for value in markdown_values + ) assert not any(value.startswith("### ") for value in markdown_values) assert group_headings assert all( @@ -120,22 +117,29 @@ def equipment_section(prefix: str) -> dict[str, object]: "input-origin-bri-web": 47, "input-origin-verification-platform": 176, } - assert equipment_section("⑦-1")["props"]["visible"] is True - assert equipment_section("⑧-1")["props"]["visible"] is True - assert "⑨ 熱交換型換気設備" in accordions - assert "⑨ 熱交換型換気設備)" not in accordions + assert tuple(accordions) == ( + "① 計算条件・外部データ", + "② 住宅基本情報", + "③ 外皮性能", + "④ 床下・空気搬送", + "⑤ 暖房設備", + "⑥ 冷房設備", + "⑦ 熱交換型換気設備", + "⑧ 詳細設定・比較検証", + ) assert all( "input-section" in component["props"].get("elem_classes", ()) for component in accordions.values() ) assert all(component["props"]["open"] is False for component in accordions.values()) - for prefix in ("⑦-2", "⑦-3", "⑦-4", "⑧-2", "⑧-3", "⑧-4"): - assert equipment_section(prefix)["props"]["visible"] is False + assert all(component["props"]["visible"] is True for component in accordions.values()) dependencies = config["dependencies"] calculation_started, calculation, graph_generation, csv_export = dependencies[:4] reset_inputs = next( - dependency for dependency in dependencies if len(dependency["outputs"]) == 231 + dependency + for dependency in dependencies + if len(dependency["outputs"]) == 223 and dependency.get("js") ) install_default_highlights = next( dependency @@ -159,10 +163,10 @@ def equipment_section(prefix: str) -> dict[str, object]: assert len(visibility_dependencies) >= 14 assert 2 in {len(dependency["outputs"]) for dependency in visibility_dependencies} assert 3 in {len(dependency["outputs"]) for dependency in visibility_dependencies} - assert 78 in {len(dependency["outputs"]) for dependency in visibility_dependencies} - assert 83 in {len(dependency["outputs"]) for dependency in visibility_dependencies} + assert 74 in {len(dependency["outputs"]) for dependency in visibility_dependencies} + assert 79 in {len(dependency["outputs"]) for dependency in visibility_dependencies} assert len(reset_inputs["inputs"]) == 0 - assert len(reset_inputs["outputs"]) == 231 + assert len(reset_inputs["outputs"]) == 223 assert len(install_default_highlights["inputs"]) == 0 assert len(install_default_highlights["outputs"]) == 0 assert install_default_highlights["queue"] is False @@ -180,30 +184,6 @@ def equipment_section(prefix: str) -> dict[str, object]: assert tuple(update["visible"] for update in reset_outputs[:223]) == tuple( field.visible for field in model.fields ) - assert [update["visible"] for update in reset_outputs[-8:]] == [ - True, - False, - False, - False, - True, - False, - False, - False, - ] - heating_section_ids = { - equipment_section(prefix)["id"] for prefix in ("⑦-1", "⑦-2", "⑦-3", "⑦-4") - } - cooling_section_ids = { - equipment_section(prefix)["id"] for prefix in ("⑧-1", "⑧-2", "⑧-3", "⑧-4") - } - assert any( - heating_section_ids <= set(dependency["outputs"]) - for dependency in visibility_dependencies - ) - assert any( - cooling_section_ids <= set(dependency["outputs"]) - for dependency in visibility_dependencies - ) assert "先行計算がある場合は、順番に実行します" in ( form_app._calculation_started_outputs()[0] )