File size: 39,251 Bytes
94e6baf
90e21a2
fa09659
 
94e6baf
fa09659
29ed877
 
 
 
 
 
 
 
 
 
 
90e21a2
 
 
29ed877
 
90e21a2
 
 
29ed877
 
 
 
 
 
90e21a2
 
 
29ed877
 
90e21a2
 
 
29ed877
 
 
 
 
 
90e21a2
 
 
29ed877
 
90e21a2
 
 
29ed877
 
 
90e21a2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29ed877
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90e21a2
 
 
 
 
 
 
 
 
29ed877
 
 
 
 
90e21a2
29ed877
 
90e21a2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29ed877
 
 
 
 
90e21a2
 
29ed877
 
 
 
 
 
 
 
 
 
 
90e21a2
29ed877
 
 
 
 
 
90e21a2
29ed877
90e21a2
29ed877
 
 
 
 
 
 
 
 
 
90e21a2
 
 
 
 
 
 
29ed877
90e21a2
 
 
 
 
 
 
 
 
29ed877
 
 
 
 
 
 
 
 
90e21a2
29ed877
 
 
 
 
 
 
 
 
 
90e21a2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29ed877
 
 
 
 
 
90e21a2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29ed877
 
 
 
 
 
 
 
40efb1e
 
 
 
90e21a2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40efb1e
29ed877
 
 
 
 
 
 
 
 
 
 
90e21a2
 
 
 
 
 
 
 
29ed877
 
90e21a2
fa09659
90e21a2
29ed877
 
90e21a2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40efb1e
90e21a2
 
 
 
 
40efb1e
 
 
 
 
 
 
90e21a2
40efb1e
 
 
90e21a2
40efb1e
 
 
 
 
90e21a2
40efb1e
90e21a2
 
 
 
 
 
 
fa09659
40efb1e
 
90e21a2
 
29ed877
 
 
 
 
 
 
 
 
90e21a2
 
29ed877
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40efb1e
90e21a2
 
 
 
 
 
 
 
 
 
 
 
40efb1e
 
90e21a2
 
 
40efb1e
 
 
 
 
 
 
 
 
 
 
fa09659
 
29ed877
 
 
 
 
 
 
 
 
fa09659
 
 
29ed877
 
90e21a2
 
29ed877
 
 
 
90e21a2
29ed877
 
90e21a2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29ed877
 
90e21a2
 
 
 
 
 
 
29ed877
 
 
 
90e21a2
29ed877
90e21a2
29ed877
fa09659
 
 
29ed877
 
 
 
 
 
 
fa09659
464e6d8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
from pathlib import Path
from typing import Any, ClassVar, Dict, List, Optional, Tuple, Union

import gradio as gr
from dotenv import load_dotenv

load_dotenv()


class Document_structureUI:
    # サンプルデータ (議案一覧)
    sample_data: List[Dict[str, str]] = [
        {
            "議案番号": "1",
            "タイトル": "役員様より各修繕提案の件",
            "meeting_no": 4,
            "物件名": "経堂パーク・マンション",
            "文書種別": "議案書",
            "年度": "2021",
            "会議日": "2022-05-18",
            "議案タイプ": "審議事項",
            "議案内容": "前回理事会に引き続きご検討お願いいたします。\n<添付資料>\n·株式会社ホゼン 見積書\n·ルシア株式会社 提案書\n【参考:前回の審議結果】\n<審議経過>\n1非常階段の長尺シートの全面貼り替えの件\n→専門業者から見積りと仕様書の提示を受け検討しました。本件について、他メーカーのカタログを取り寄せ、詳細検討を進めることとしました。\n2正面玄関階段の手すり設置工事の件\n→専門業者からイメージ図を取得し検討をしました。本件について、シンプルな仕様で詳細検討を進めることとし、同社へ次回理事会にてプレゼンテーションを依頼することとしました。",
            "AI提案": "設備修繕",
            "status": "理事会議論中",
            "memorandum": "非常階段の長尺シート貼り替えと正面玄関の手すり設置工事について、業者選定と見積もり取得を進め、来期の修繕計画および予算案に計上する必要がある。",
        },
        {
            "議案番号": "2",
            "タイトル": "マンション隣地(東側) ブロック塀及び垣根復旧対応報告",
            "meeting_no": 4,
            "物件名": "経堂パーク・マンション",
            "文書種別": "議案書",
            "年度": "2021",
            "会議日": "2022-05-18",
            "議案タイプ": "報告事項'",
            "議案内容": "マンション隣地(東側) ブロック塀および垣根復旧作業が完了しました。 (2022年5月11日完了)\n管理会社にブロック塀に関して当初のコーキング案と今回のステンレスアングル案の実質的な違いに関する質問をし、結論として最終的にはステンレスアングル案の方が当管理組合にとって有利であったという管理会社としてのご見解をいただきました。\n<審議経過>\n大東建託より昨年11月に是正工事の説明を受け、年明けに設計図と完成イメージ図の提出を受け確認しました。 本件について、敷地境界線のコーキングが経年劣化した場合、補修の費用負担は隣地(長島 邸) であると大東建託担当者から説明があったことから、後のことを考えその旨の書面作成を条件に工事許可をすることとしましたが、後日、大東建託担当より書面取り付けが大変厳しい状況である旨報告を受けました。\nその後、代替え案として、劣化の少ないステンレスのアングルを設置し、隙間を埋めて植栽を復旧する工法の提案を受けました。\n本図面を管理会社技術部にて確認した結果、隙間を埋めるのはあくまで美観状の問題のため、耐候性のあるステンレス部材であれば問題ない旨確認しました。 ただし、今後の劣化を考え、アングルを留めるボルトも耐候性、耐食性等のあるステンレス材を使用した方が良い旨、先方に申し送りしアングル留付用ボルトもステンレス製固定となりました。以上から本仕様にて工事承認としました。",
            "AI提案": "設備修繕",
            "status": "終了済み",
            "memorandum": "なし",
        },
        {
            "議案番号": "3",
            "タイトル": "管理計画認定制度およびマンション管理適正評価制度への登録の件",
            "meeting_no": 4,
            "物件名": "経堂パーク・マンション",
            "文書種別": "議案書",
            "年度": "2021",
            "会議日": "2022-05-18",
            "議案タイプ": "審議事項",
            "議案内容": "標題の件、2022年4月より、マンションにおける建物維持管理の新たな指針として、マンション管理適正化法に基づく「管理計画認定制度」および一般社団法人マンション管理業協会による「マンション管理適正評価制度」の二つの制度が運用開始となります。\n「管理計画認定制度」は、マンションの管理計画が一定の基準を満たす場合に、適切な管理計画を持つマンションとして認定を受けることができる制度であり、認定を受けることができたマンションは市場において評価されることが期待されます。\nまた、「マンション管理適正評価制度」は、同様にマンションの管理状況をチェックし、その情報がポータルサイト等で開示されることで、管理の行き届いているマンションの管理評価が市場価値·流通価格に反映されることが期待されます。一方、登録しない=非開示となっているマンションは市場での評価が困難となることも想定されます。\n本件に関して、管理会社にて両制度の事前評価を実施しましたので、以下の結果をご確認いただくとともに、本結果をご参考のうえ、両制度への登録および情報開示に関して今年度通常総会へ議案上程することについて、ご審議をお願いいたします。\nまた、総会への議案上程に際して、より評価を向上させるための対応策もご提案させていただきますので合わせてご審議ください。\nなお、今後のインセンティブ制度等確立のため、今回ご提出した「マンション管理適正評価制度」の『等級評価結果』および、配管の材質や漏水事故の発生率等の情報をまとめた『維持管理情報』の提供をマンション管理業協会より求められる可能性があります。\nその場合、両制度への登録の有無に関わらず、マンション名付でマンション管理業協会へ提出することについて、ご承諾をお願いいたします。\n※情報提供は、将来の損害保険インセンティブ制度確立のための“参考資料”として取り扱いされますが、個々の提供情報について審査されるものではありません。また、管理会社とマンション管理業協会にて、損害保険インセンティブ制度の検討以外に使用されないこと、秘密情報の取り扱い等を規定した秘密保持契約を締結しております。\n<事前評価結果のご報告>\n事前の評価結果は以下のとおりです。\n管理計画認定制度:不適合\nマンション管理適正評価制度:★4つ (76ポイント)\n※評価結果の詳細は添付の「等級評価シート」をご参照ください。\n<添付資料>\n·管理計画認定制度·マンション管理適正評価制度~登録のご提案~\n·等級評価シート",
            "AI提案": "管理計画認定制度",
            "status": "総会での報告承認待ち",
            "memorandum": "両制度への登録費用、および評価向上策にかかる費用を来期予算に計上することを検討する。総会での承認が必要。",
        },
    ]

    THEME_KEYS: ClassVar[list[str]]=[
        "照明設備",
        "エレベーター",
        "エントランス自動ドア",
        "通信・IT設備",
        "受水槽",
        "宅配ボックス",
        "固定電話・IP網",
        "消火器",
        "防犯カメラ",
        "大規模修繕",
        "建物調査診断",
        "設備修繕",
        "植栽",
        "機械式駐車場",
        "自転車置場",
        "理事会・総会",
        "管理委託契約",
        "管理組合運営",
        "管理規約・使用細則",
        "管理計画認定制度",
        "リフォーム工事細則",
        "年度予算・検討課題",
        "未収金",
        "長期修繕計画",
        "ごみ",
        "防災",
        "管理会社対応",
        "電子化・DX",
        "会計・収支",
        "その他",
    ]

    # 物件名の候補リスト
    BUKKEN_CHOICES: ClassVar[list[str]] = [
        "経堂パーク・マンション",
        "祖師ヶ谷大蔵パーク・ホームズ",
        "パークシティ弦巻",
        "パークホームズ学芸大学グレーススクエア",
        "パーク・ノヴァ猿江恩賜公園",
    ]

    def __init__(self) -> None:
        """初期化"""
        self.approval_status: Dict[int, bool] = dict.fromkeys(range(len(self.sample_data)), False)

    def _get_custom_css(self) -> str:
        """カスタムCSSを取得"""
        return """
        .status-approved {
            color: #059669;
            background-color: #d1fae5;
            padding: 4px 12px;
            border-radius: 9999px;
            font-size: 0.875rem;
            font-weight: 600;
            display: inline-block;
        }
        .status-pending {
            color: #d97706;
            background-color: #fef3c7;
            padding: 4px 12px;
            border-radius: 9999px;
            font-size: 0.875rem;
            font-weight: 600;
            display: inline-block;
        }
        .status-saved {
            color: #059669;
            font-size: 0.875rem;
            font-weight: 600;
        }

        /* highlight panels */
        .theme-highlight {
            background: #eff6ff; /* blue-50 */
            border-left: 4px solid #60a5fa; /* blue-400 */
            padding: 12px;
            border-radius: 6px;
        }
        .panel-gray {
            background: #eff6ff; /* blue-50 */
            border-left: 4px solid #60a5fa; /* blue-400 */
            padding: 12px;
            border-radius: 6px;
        }
        .panel-white {
            background: #ffffff;
            border-left: 4px solid #60a5fa; /* blue-400 */
            padding: 12px;
            border-radius: 6px;
        }
        .small-hint {
            font-size: 12px;
            color: #1e40af; /* blue-800 */
        }

        /* blue buttons */
        .btn-blue {
            background: linear-gradient(135deg, #4F7FFF 0%, #1D4ED8 100%) !important;
            color: #ffffff !important;
            border: none !important;
            box-shadow: 0 2px 8px rgba(29, 78, 216, 0.25) !important;
        }
        .btn-blue:hover {
            filter: brightness(1.05);
            transform: translateY(-1px);
        }

        /* tabs - active/hover color (force underline to blue) */
        button[role="tab"] {
            position: relative !important;
            border-bottom: 2px solid transparent !important;
            box-shadow: inset 0 -2px transparent !important;
        }
        button[role="tab"]::after {
            content: "";
            position: absolute;
            left: 0;
            right: 0;
            bottom: 0;
            height: 2px;
            background: transparent;
        }
        button[role="tab"][aria-selected="true"] {
            background: #eff6ff !important; /* blue-50 */
            color: #1d4ed8 !important;      /* blue-700 */
        }
        button[role="tab"][aria-selected="true"]::after {
            background: #1d4ed8 !important;
        }
        button[role="tab"]:hover {
            color: #1d4ed8 !important;
        }
        button[role="tab"]:hover::after {
            background: #60a5fa !important; /* lighter on hover */
        }

        /* accordion labels as headings */
        details > summary {
            font-weight: 700 !important;
            font-size: 1.05rem !important;
            color: #111827 !important; /* gray-900 */
        }

        /* approval message textbox */
        .msg-approved textarea {
            background-color: #d1fae5 !important; /* green-100 */
            color: #059669 !important;            /* green-600 */
            border: 1px solid #059669 !important;
            border-left: 4px solid #059669 !important;
        }
        .bg-white {
            background: #fff !important;
            border-radius: 6px;
            padding: 12px;
        }
        .btn-water {
            background-color: #5bc0eb !important;
            color: #ffffff !important;
            border-color: #5bc0eb !important;
        }
        .btn-water:hover, .btn-water:focus {
            background-color: #48b3de !important;
            border-color: #48b3de !important;
        }
        """

    def process_file(self, file: Optional[Union[str, Path]]) -> Tuple[Any, Any]:
        """ファイルアップロード処理"""
        if file is None:
            return gr.update(value="⚠️ ファイルが選択されていません", visible=True), gr.update(), gr.update()

        file_name = Path(file).name if isinstance(file, (str, Path)) else "uploaded_file"
        return (
            gr.update(value=f"{file_name}がアップロードされました", visible=True),
            gr.update(visible=True),
            gr.update(value="アップロード済み", variant="secondary"),
        )

    def switch_tab(self,selected:int) -> Any:
        return gr.update(selected=selected)

    def edit_info(self) -> Any:
        return (
            gr.update(visible=False),
            gr.update(visible=True),
            gr.update(visible=False),
            gr.update(interactive=True),
            gr.update(interactive=True),
            gr.update(interactive=True),
            gr.update(interactive=True),
        )

    def save_info(self) -> Any:
        return (
            gr.update(visible=True),
            gr.update(visible=False),
            gr.update(visible=True),
            gr.update(interactive=False),
            gr.update(interactive=False),
            gr.update(interactive=False),
            gr.update(interactive=False),
        )

    def approve_item(self, index: int) -> str:
        """個別の議案を承認"""
        self.approval_status[index] = True
        return f"議案 {self.sample_data[index]['議案番号']}: {self.sample_data[index]['タイトル']} を承認しました。"

    def unapprove_item(self, index: int) -> str:
        """個別の議案を承認"""
        self.approval_status[index] = False
        return f"議案 {self.sample_data[index]['議案番号']}: {self.sample_data[index]['タイトル']} を承認しました。"

    def _get_approval_progress_html(self) -> str:
        """承認進捗のHTML (バッジ) を返す"""
        approved_count = sum(1 for v in self.approval_status.values() if v is True)
        total = len(self.sample_data)
        clazz = "status-approved" if approved_count == total else "status-pending"
        return "<span class='" + clazz + "'>" + f"{approved_count}/{total} 承認済み" + "</span>"

    def on_approve_and_close(self, idx: int) -> Tuple[str, Any, Any, Any]:
        if self.approval_status.get(idx) is False:
            """承認メッセージ、メッセージ更新、進捗更新、必要ならタブ遷移を返す"""
            msg = self.approve_item(idx)
            progress_html = self._get_approval_progress_html()
            all_done = sum(1 for v in self.approval_status.values() if v is True) == len(self.sample_data)
            register_btn_update = gr.update(visible=all_done)
            btn_update = gr.update(value="✅ 承認済み", variant="secondary", interactive=True)
            if all_done:
                approve_all_btn_update = gr.update(value="✅すべて承認済み", variant="secondary")
            else:
                approve_all_btn_update = gr.update(value="すべて承認", variant="primary")
            return (
                msg,
                gr.update(value=progress_html),
                gr.update(open=False),
                btn_update,
                register_btn_update,
                approve_all_btn_update,
            )
        msg = self.unapprove_item(idx)
        progress_html = self._get_approval_progress_html()
        all_done = sum(1 for v in self.approval_status.values() if v is True) == len(self.sample_data)
        register_btn_update = gr.update(visible=all_done)
        btn_update = gr.update(value="承認", variant="primary", interactive=True)
        if all_done:
            approve_all_btn_update = gr.update(value="✅すべて承認済み", variant="secondary")
        else:
            approve_all_btn_update = gr.update(value="すべて承認", variant="primary")
        return (
            msg,
            gr.update(value=progress_html),
            gr.update(open=True),
            btn_update,
            register_btn_update,
            approve_all_btn_update,
        )

    def approve_all_and_go_back(self) -> Any:
        all_done = sum(1 for v in self.approval_status.values() if v is True) == len(self.sample_data)
        if all_done:
            for i in range(len(self.sample_data)):
                self.approval_status[i] = False
            progress_html = self._get_approval_progress_html()
            register_btn_update = gr.update(visible=False)
            per_button_updates = [
                gr.update(value="承認", variant="primary", interactive=True) for _ in range(len(self.sample_data))
            ]
            acc_updates = [gr.update(open=True) for _ in range(len(self.sample_data))]
            return (
                gr.update(value=progress_html),
                register_btn_update,
                gr.update(value="すべて承認", variant="primary"),
                *per_button_updates,
                *acc_updates,
            )

        """すべ議案を承認してStep1に戻る。メッセージは表示しない。"""
        for i in range(len(self.sample_data)):
            self.approval_status[i] = True
        progress_html = self._get_approval_progress_html()
        register_btn_update = gr.update(visible=True)
        per_button_updates = [
            gr.update(value="✅ 承認済み", variant="secondary", interactive=True) for _ in range(len(self.sample_data))
        ]
        acc_updates = [gr.update(open=False) for _ in range(len(self.sample_data))]
        return (
            gr.update(value=progress_html),
            register_btn_update,
            gr.update(value="✅すべて承認済み", variant="secondary"),
            *per_button_updates,
            *acc_updates,
        )

    def on_register_documents(self):
        # TODO: 実際の資料登録処理をここに実装する
        # 例: DB保存やAPI呼び出しなど

        # 成功メッセージを表示
        return gr.update(visible=True),gr.update(visible=True)

    def _search_bukken(self, search_text: str) -> tuple[gr.Radio, str, str]:
        """物件名を検索して候補を更新"""
        print(f"DEBUG: _search_bukken called with: '{search_text}'")
        print(f"DEBUG: Available choices: {self.BUKKEN_CHOICES}")

        if not search_text or len(search_text.strip()) == 0:
            # 検索テキストが空の場合は候補を非表示
            print("DEBUG: Empty search text, returning empty choices")
            return gr.Radio(choices=[], visible=False, value=None), "", ""

        # 検索テキストで候補を絞り込み(大文字小文字を区別しない)
        search_text_lower = search_text.lower()
        filtered_choices = [choice for choice in self.BUKKEN_CHOICES if search_text_lower in choice.lower()]
        print(f"DEBUG: Filtered choices: {filtered_choices}")

        if len(filtered_choices) == 0:
            # マッチする候補がない場合
            print("DEBUG: No matches found")
            return gr.Radio(choices=[], visible=False, value=None), "", ""

        # 候補が1件の場合は自動的に選択して値を設定
        if len(filtered_choices) == 1:
            selected = filtered_choices[0]
            keiyaku_name = f"{selected}管理組合"
            print(f"DEBUG: Single match found: {selected}")
            return (
                gr.Radio(choices=filtered_choices, value=selected, visible=True),
                selected,
                keiyaku_name,
            )

        # 複数候補がある場合は表示
        print(f"DEBUG: Multiple matches found: {filtered_choices}")
        return gr.Radio(choices=filtered_choices, value=None, visible=True), "", ""

    def _select_bukken(self, selected: str) -> tuple[str, str, str]:
        """物件を選択"""
        print(f"DEBUG: _select_bukken called with: '{selected}'")
        if not selected:
            print("DEBUG: No selection, returning empty values")
            return "", "", ""
        # 選択された値を各フィールドに反映
        keiyaku_name = f"{selected}管理組合"
        print(f"DEBUG: Setting selected_bukken='{selected}', keiyaku_name='{keiyaku_name}'")
        return selected, selected
    



    def create_interface(self):
        """document_structure_uiの内容を生成する関数"""
        components = {}

        with gr.Column():
            gr.Markdown("# 資料登録システム")

            with gr.Tabs() as tabs:
                components["tabs"] = tabs

                with gr.Tab("Step1: ファイルアップロード", id=0):
                    gr.Markdown("## 基本情報登録")
                    with gr.Row():
                        with gr.Column(scale=0.3, min_width=1):
                            gr.Markdown("")
                        with gr.Column(scale=4, min_width=15, variant="panel"):
                            gr.Markdown("**物件名**")
                        with gr.Column(scale=16, min_width=300):
                            with gr.Column():
                                self.bukken_search = gr.Textbox(
                                    placeholder="🔍物件名を入力してください (例: 経堂、パーク、など) ",
                                    show_label=False,
                                    interactive=True,
                                    container=False,
                                )
                                self.bukken_select = gr.Radio(
                                    choices=[],
                                    show_label=False,
                                    interactive=True,
                                    visible=False,
                                )
                                # 選択された物件名を保持する隠しフィールド
                                self.selected_bukken = gr.Textbox(value="", visible=False)

                                # Step 1: 物件検索機能
                                self.bukken_search.change(
                                    fn=self._search_bukken,
                                    inputs=[self.bukken_search],
                                    outputs=[self.bukken_select, self.selected_bukken],
                                )

                                # Step 1: 物件選択時の処理
                                self.bukken_select.change(
                                    fn=self._select_bukken,
                                    inputs=[self.bukken_select],
                                    outputs=[self.selected_bukken, self.bukken_search],
                                )
                    with gr.Row():
                        with gr.Column(scale=0.3, min_width=1):
                            gr.Markdown("")
                        with gr.Column(scale=4, min_width=15, variant="panel"):
                            gr.Markdown("**資料タイプ**")
                        with gr.Column(scale=16, min_width=300):
                            self.meeting_type = gr.Dropdown(
                                choices=["理事会", "総会"],
                                value="理事会",
                                show_label=False,
                                interactive=True,
                                container=False,
                            )

                    gr.Markdown("## ファイルアップロード")

                    with gr.Row():
                        with gr.Column(scale=2):
                            file_input = gr.File(
                                label="ファイルを選択(PDF, DOCX)",
                                file_types=[".pdf", ".docx"],
                                file_count="single",
                                type="filepath",
                            )
                            components["file_input"] = file_input

                            upload_btn = gr.Button("アップロード", variant="primary", size="lg", scale=1)
                            structure_btn = gr.Button(
                                "AIによるデータ整理",
                                variant="primary",
                                size="lg",
                                scale=1,
                                visible=False,
                                elem_classes=["btn-blue"],
                            )
                            components["upload_btn"] = upload_btn
                            components["structure_btn"] = structure_btn

                            upload_status = gr.Markdown(value="", visible=False)
                            components["upload_status"] = upload_status

                with gr.Tab("Step2: AI出力の確認", id=1):
                    gr.Markdown("# 共通データの確認")
                    with gr.Row():
                        gr.Markdown("")
                        with gr.Column():
                            save_info_message = gr.Textbox(
                                value="共通データを修正しました。",
                                label="",
                                show_label=False,
                                interactive=False,
                                visible=False,
                                elem_classes=["msg-approved"],
                                container=False
                            )
                            components["save_info_message"] = save_info_message
                            with gr.Column(scale=3):
                                edit_info_btn = gr.Button(value="共通データの修正", variant="primary")
                            components["edit_info_btn"] = edit_info_btn
                            save_info_btn = gr.Button(
                                "修正内容の確定",
                                variant="primary",  # 見た目は必要に応じて
                                visible=False,  # 任意
                            )
                            components["save_info_btn"] = save_info_btn
                    with gr.Row():
                        with gr.Column(scale=3.6, min_width=10, variant="panel"):
                            gr.Markdown("**開催時期**")
                        with gr.Column(scale=0.2, min_width=1):
                            gr.Markdown("第")
                        with gr.Column(scale=3, min_width=120):
                            meeting_no = gr.Textbox(
                                value=self.sample_data[0]["meeting_no"],
                                placeholder="例) 3",
                                show_label=False,
                                interactive=False,
                                container=False,
                            )  # 期数をMarkdownで表示
                        with gr.Column(scale=0.2, min_width=1):
                            gr.Markdown("回")
                        with gr.Column(scale=0.3, min_width=1):
                            gr.Markdown("")
                        with gr.Column(scale=3, min_width=120):
                            year = gr.Textbox(
                                value=self.sample_data[0]["年度"],
                                placeholder="例) 2023",
                                show_label=False,
                                interactive=False,
                                container=False,
                            )  # 期数をMarkdownで表示
                        with gr.Column(scale=0.5, min_width=3):
                            gr.Markdown("年度")
                        with gr.Column(scale=0.3, min_width=1):
                            gr.Markdown("")
                        with gr.Column(scale=0.9, min_width=1):
                            gr.Markdown("開催日時")
                        with gr.Column(scale=4, min_width=120):
                            meeting_date = gr.DateTime(
                                value=self.sample_data[0]["会議日"],
                                include_time=False,  # 時間ピッカーを有効
                                type="string",  # 文字列として返す(扱いやすい)
                                interactive=False,
                                show_label=False,
                            )
                    with gr.Row():
                        with gr.Column(scale=3, min_width=10, variant="panel"):
                            gr.Markdown("**文書種別**")
                        with gr.Column(scale=6, min_width=150):
                            doc_type = gr.Dropdown(
                                choices=["議案書", "議事録"],
                                value=self.sample_data[0]["文書種別"],
                                show_label=False,
                                interactive=False,
                                container=False,
                            )
                        with gr.Column(scale=6, min_width=1):
                            gr.Markdown("")
                    gr.Markdown("")
                    gr.Markdown("")
                    gr.Markdown("# 各議案データの確認")
                    with gr.Row():
                        global_message = gr.Textbox(label="", show_label=False, interactive=False, visible=False)
                        components["global_message"] = global_message

                        approval_progress = gr.HTML(value=self._get_approval_progress_html())
                        components["approval_progress"] = approval_progress
                        with gr.Column():
                            approve_all_btn = gr.Button(value="すべて承認", variant="primary", size="lg")
                            components["approve_all_btn"] = approve_all_btn
                            register_doc_btn = gr.Button(
                                "このテーマキーで資料登録",
                                variant="primary",  # 見た目は必要に応じて
                                size="lg",
                                visible=False,  # 任意
                            )
                            components["register_doc_btn"] = register_doc_btn
                            register_doc_message = gr.Textbox(
                                value="資料登録完了しました",
                                label="",
                                show_label=False,
                                interactive=False,
                                visible=False,
                                elem_classes=["msg-approved"],
                                container=False
                            )
                            with gr.Row():
                                gr.Markdown("")
                                return_step1_btn = gr.Button(
                                    "ファイルアップロード画面に戻る",
                                    variant="secondary",
                                    visible=False,
                                )

                    agenda_components = []
                    approve_btn_list = []
                    acc_list = []
                    gr.Markdown("")
                    for i, data in enumerate(self.sample_data):
                        with gr.Blocks():
                            with gr.Row(elem_classes=["agenda-header-row"]):
                                with gr.Column(scale=9):
                                    gr.Markdown(
                                        f"### 議案 {data['議案番号']}: {data['タイトル']}",
                                        elem_classes=["agenda-title"],
                                    )
                                with gr.Column(scale=3):
                                    approve_btn = gr.Button("承認", variant="primary")
                                    approve_btn.elem_classes=["btn-water"]
                            acc = gr.Accordion(label="", open=True, elem_classes=["agenda-accordion-body"])
                            with acc:
                                with gr.Row():
                                    with gr.Column(scale=2):
                                        gr.Dropdown(
                                            choices=["審議事項", "報告事項", "その他"],
                                            value=data["議案タイプ"],
                                            label="議案タイプ",
                                            interactive=True,  # 編集可能に
                                        )
                                        gr.Textbox(
                                            value=data["議案内容"],
                                            label="議案内容",
                                            show_label=True,
                                            lines=2,
                                            interactive=False,
                                        )

                                    with gr.Column(scale=1):
                                        gr.Dropdown(
                                            label="ステータス",
                                            choices=["理事会議論中","総会での報告承認待ち","終了済み"],
                                            value=data["status"],
                                            interactive=True,
                                            )
                                        gr.Textbox(
                                            value=data["memorandum"],
                                            label="備忘",
                                            show_label=True,
                                            interactive=True,
                                        )
                                        with gr.Group(elem_classes=["panel-gray"]):
                                            gr.Markdown("### 議案のテーマ付与")
                                            gr.Dropdown(
                                                label="テーマキー",
                                                choices=self.THEME_KEYS,
                                                value=data["AI提案"],
                                                interactive=True,
                                            )

                                        message = gr.Textbox(
                                            label="",
                                            show_label=False,
                                            interactive=False,
                                            visible=False,
                                            elem_classes=["msg-approved"],
                                        )

                            approve_btn.click(
                                fn=lambda idx=i: self.on_approve_and_close(idx),
                                outputs=[
                                    message,
                                    approval_progress,
                                    acc,
                                    approve_btn,
                                    register_doc_btn,
                                    approve_all_btn,
                                ],
                            )

                            agenda_components.append({"approve_btn": approve_btn, "message": message})
                            approve_btn_list.append(approve_btn)  # ← 追加
                            acc_list.append(acc)
                        gr.Markdown("")
                        gr.Markdown("")

            components["upload_btn"].click(
                fn=self.process_file,
                inputs=[components["file_input"]],
                outputs=[components["upload_status"], components["structure_btn"], components["upload_btn"]],
            )

            components["structure_btn"].click(
                fn=lambda idx=1: self.switch_tab(idx),
                outputs=[components["tabs"]],
            )
            components["edit_info_btn"].click(
                fn=self.edit_info,
                outputs=[
                    components["edit_info_btn"],
                    components["save_info_btn"],
                    components["save_info_message"],
                    meeting_no,
                    year,
                    meeting_date,
                    doc_type,
                ],
            )
            components["save_info_btn"].click(
                fn=self.save_info,
                outputs=[components["edit_info_btn"],
                        components["save_info_btn"], 
                        components["save_info_message"],
                        meeting_no,
                        year,
                        meeting_date,
                        doc_type,
                ],
            )
            components["approve_all_btn"].click(
                fn=self.approve_all_and_go_back,
                outputs=[
                    approval_progress,
                    components["register_doc_btn"],
                    components["approve_all_btn"],
                    *approve_btn_list,
                    *acc_list,
                ],
            )

            components["register_doc_btn"].click(
                fn=self.on_register_documents,
                outputs=[register_doc_message,return_step1_btn],
            )
            return_step1_btn.click(fn=lambda idx=0: self.switch_tab(idx),outputs=[components["tabs"]])
        return components


# アプリケーション起動
def main():
    """メイン関数"""
    app = Document_structureUI()
    demo = app.create_interface()
    demo.launch(server_name="0.0.0.0", server_port=7860, share=False)


if __name__ == "__main__":
    main()