File size: 12,950 Bytes
3a303f8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import gradio as gr
import pandas as pd
import plotly.graph_objects as go

from ui.welcome_page import build_welcome_page
from ui.multi_report_page import build_multi_report_page

from ui.filters import (
    sdg_images,
    gri_images,
    filter_sdg,
    filter_gri,
    toggle_filters,
    quality_task_1_gallery,
    quality_task_2_gallery,
    quality_task_3_gallery,
    filter_quality_1,
    filter_quality_2,
    filter_quality_3,
    toggle_quality_filters,
    climate_images,
    gri_climate_images,
    sdg13_images,
    gri_sdg13_images,
    filter_climate,
    filter_gri_climate,
    filter_sdg13,
    filter_gri_sdg13,
    toggle_climate
)

from ui.gradio_functions import (
    load_pdf,
    change_page,
    load_pdf_wrapper,
    highlight,
    upload_csv1,
    upload_csv2,
    run_sa_wrapper,
    set_task,
    run_task,
    run_selected_tasks
)


def _empty_plot():
    """
    Returns a blank Plotly figure to use as the initial value for every
    gr.Plot(). Gradio (6.0.2+, including 6.14) has a confirmed bug where a
    gr.Plot left at its default value=None fails to render on the FIRST
    update that sets a real value -- it only shows up correctly starting
    from the second update onward. See:
    https://github.com/gradio-app/gradio/issues/12511
    https://github.com/gradio-app/gradio/issues/10162
    Giving the component a non-None placeholder value at construction time
    avoids that first-update failure.
    """
    return go.Figure()


def _empty_df():
    """
    Returns an empty DataFrame to use as the initial value for gr.Dataframe
    components that get populated later via .select()/.click() events.
    Same first-update rendering bug as gr.Plot affects gr.Dataframe when
    left at value=None (see issue links above) -- giving it a non-None
    placeholder avoids the "works on the second click, not the first" bug.
    """
    return pd.DataFrame()


def build_single_report_page():
    """
    Builds the original Single Report Analysis workflow (Steps 1-5),
    unchanged in content. This is placed inside a gr.Tab by build_interface(),
    rather than toggled via Column visibility -- this avoids a known Gradio
    rendering bug where gr.Plot / gr.Gallery / gr.Dataframe children can
    break (render as a blank page) after their parent Column's visibility
    is toggled off and back on.
    """
    back_btn = gr.Button("⬅ Back to Home", size="sm")

    gr.Markdown("# UniTor ESG Insights: An Interactive Human-in-the-Loop System for Traceable Sustainability Report Analysis")

    gr.Image(
        value="assets/workflow.png",
        label="Human-in-the-Loop Interactive Workflow Overview",
        interactive=False,
    )

    # =========================
    # STAGE 1: PDF -> CSV
    # =========================
    gr.Markdown("## Step 1: Upload PDF and Extract Data")

    file = gr.File(label="Upload PDF")
    load_btn = gr.Button("Load PDF", variant="primary")

    extracted_download = gr.File(label="Download Extracted CSV")

    upload_csv_1 = gr.File(label="(Optional) Upload Custom CSV")

    with gr.Row():
        prev = gr.Button("⬅", variant="primary")
        next = gr.Button("➡", variant="primary")

    with gr.Row():
        img = gr.Image()
        table = gr.Dataframe(value=_empty_df(), interactive=True)

    text = gr.Markdown()

    # =========================
    # STAGE 2: SA
    # =========================
    gr.Markdown("## Step 2: Sustainability Frameworks (GRI & SDG) Alignment (SFA) for Relevant/Irrelevant Classification Task")

    sa_btn = gr.Button("Run SFA", variant="primary")

    sa_download = gr.File(label="Download SFA Results")

    chart1 = gr.Plot(value=_empty_plot(), label="Distribution")

    upload_csv_2 = gr.File(label="(Optional) Upload Modified SFA CSV")

    # =========================
    # CONNECTIONS
    # =========================
    load_btn.click(load_pdf_wrapper, file, [img, table, extracted_download])

    next.click(lambda: change_page(1), outputs=[img, table])
    prev.click(lambda: change_page(-1), outputs=[img, table])

    table.select(highlight, outputs=text)

    upload_csv_1.upload(upload_csv1, upload_csv_1, table)

    sa_btn.click(
        run_sa_wrapper,
        outputs=[sa_download, table, chart1]
    )

    upload_csv_2.upload(upload_csv2, upload_csv_2, table)

    # =========================
    # STAGE 3: SDG / GRI
    # =========================
    gr.Markdown("## Step 3: SDG/GRI Classification (Topic Alignment)")

    task_selector_1 = gr.Dropdown(
        choices=[
            "17 SDG Alignment",
            "GRI Topic Alignment"
        ],
        multiselect=True,
        label="Select Tasks"
    )

    run_tasks_btn_1 = gr.Button("Run", variant="primary")

    chart_tasks_1 = gr.Plot(value=_empty_plot())
    download_1 = gr.File(label="Download Results")

    with gr.Column(visible=True) as sdg_container:
        gr.Markdown("### SDG Filter")
        sdg_gallery = gr.Gallery(value=sdg_images, columns=6, allow_preview=False)

    sdg_result_table = gr.Dataframe(value=_empty_df(), interactive=True)
    sdg_gallery.select(fn=filter_sdg, outputs=[sdg_result_table, text])

    with gr.Column(visible=True) as gri_container:
        gr.Markdown("### GRI Topic Filter")
        gri_gallery = gr.Gallery(value=gri_images, columns=4, allow_preview=False)

    gri_result_table = gr.Dataframe(value=_empty_df(), interactive=True)
    gri_gallery.select(fn=filter_gri, outputs=[gri_result_table, text])

    task_selector_1.change(
        fn=toggle_filters,
        inputs=task_selector_1,
        outputs=[sdg_container, gri_container]
    )

    run_tasks_btn_1.click(
        run_selected_tasks,
        inputs=task_selector_1,
        outputs=[download_1, table, chart_tasks_1]
    )

    # =========================
    # STAGE 4: DISCLOSURE QUALITY
    # =========================
    gr.Markdown("## Step 4: Disclosure Quality Analysis")

    task_selector_2 = gr.Dropdown(
        choices=[
            "Informative & Non-Informative/Vague Sustainability Text Identification",
            "Qualitative & Quantitative Sustainability Text Identification",
            "High Potential Greenwashing Detection"
        ],
        multiselect=True,
        label="Select Tasks"
    )

    run_tasks_btn_2 = gr.Button("Run", variant="primary")

    chart_tasks_2 = gr.Plot(value=_empty_plot())
    download_2 = gr.File(label="Download Results")

    with gr.Row():
        with gr.Column(visible=True) as quality1_container:
            gr.Markdown("### Informative vs Non-Informative/Vague")
            quality1_gallery = gr.Gallery(value=quality_task_1_gallery, columns=2, allow_preview=False)

        with gr.Column(visible=True) as quality2_container:
            gr.Markdown("### Qualitative vs Quantitative")
            quality2_gallery = gr.Gallery(value=quality_task_2_gallery, columns=2, allow_preview=False)

        with gr.Column(visible=True) as quality3_container:
            gr.Markdown("### Greenwashing Potential")
            quality3_gallery = gr.Gallery(value=quality_task_3_gallery, columns=2, allow_preview=False)

    quality_result_table = gr.Dataframe(value=_empty_df(), interactive=True)

    quality1_gallery.select(fn=filter_quality_1, outputs=[quality_result_table, text])
    quality2_gallery.select(fn=filter_quality_2, outputs=[quality_result_table, text])
    quality3_gallery.select(fn=filter_quality_3, outputs=[quality_result_table, text])

    task_selector_2.change(
        fn=toggle_quality_filters,
        inputs=task_selector_2,
        outputs=[quality1_container, quality2_container, quality3_container]
    )

    run_tasks_btn_2.click(
        run_selected_tasks,
        inputs=task_selector_2,
        outputs=[download_2, table, chart_tasks_2]
    )

    # =========================
    # STAGE 5: CLIMATE ANALYSIS
    # =========================
    gr.Markdown("## Step 5: More Specific Climate Analysis")

    task_selector_3 = gr.Dropdown(
        choices=[
            "Climate Alignment",
            "GRI Climate Alignment",
            "GRI Climate Action (SDG13) Alignment",
            "Climate Action (SDG13) Alignment"
        ],
        multiselect=True,
        label="Select Tasks"
    )

    run_tasks_btn_3 = gr.Button("Run", variant="primary")

    chart_tasks_3 = gr.Plot(value=_empty_plot())
    download_3 = gr.File(label="Download Results")

    with gr.Row():
        with gr.Column(visible=True) as climate1_container:
            gr.Markdown("### Climate Alignment")
            climate1_gallery = gr.Gallery(value=climate_images, columns=2, allow_preview=False)

        with gr.Column(visible=True) as climate2_container:
            gr.Markdown("### GRI Climate Alignment")
            climate2_gallery = gr.Gallery(value=gri_climate_images, columns=4, allow_preview=False)

    climate_table_1 = gr.Dataframe(value=_empty_df(), interactive=True)

    with gr.Row():
        with gr.Column(visible=True) as climate3_container:
            gr.Markdown("### Climate Action (SDG13)")
            climate3_gallery = gr.Gallery(value=sdg13_images, columns=2, allow_preview=False)

        with gr.Column(visible=True) as climate4_container:
            gr.Markdown("### GRI Climate Action (SDG13)")
            climate4_gallery = gr.Gallery(value=gri_sdg13_images, columns=3, allow_preview=False)

    climate_table_2 = gr.Dataframe(value=_empty_df(), interactive=True)

    climate1_gallery.select(fn=filter_climate, outputs=[climate_table_1, text])
    climate2_gallery.select(fn=filter_gri_climate, outputs=[climate_table_1, text])
    climate3_gallery.select(fn=filter_sdg13, outputs=[climate_table_2, text])
    climate4_gallery.select(fn=filter_gri_sdg13, outputs=[climate_table_2, text])

    task_selector_3.change(
        fn=toggle_climate,
        inputs=task_selector_3,
        outputs=[climate1_container, climate2_container, climate3_container, climate4_container]
    )

    run_tasks_btn_3.click(
        run_selected_tasks,
        inputs=task_selector_3,
        outputs=[download_3, table, chart_tasks_3]
    )

    toggle_containers = [
        sdg_container, gri_container,
        quality1_container, quality2_container, quality3_container,
        climate1_container, climate2_container, climate3_container, climate4_container
    ]

    return back_btn, toggle_containers


def build_interface():
    with gr.Blocks() as demo:

        with gr.Tabs(selected="welcome") as tabs:

            with gr.Tab("Home", id="welcome"):
                btn_single, btn_multi = build_welcome_page()

            with gr.Tab("Single Report Analysis", id="single"):
                single_back_btn, toggle_containers = build_single_report_page()

            with gr.Tab("Multi-Report Comparative Analysis", id="multi"):
                multi_back_btn = build_multi_report_page()

        # =========================
        # ICON-FILTER VISIBILITY WORKAROUND
        # =========================
        # The SDG / GRI / quality / climate icon-filter Columns are created
        # with visible=True (see build_single_report_page) instead of
        # visible=False, then immediately hidden here via demo.load().
        # This works around a confirmed Gradio bug (6.0+) where a Column's
        # FIRST-EVER visibility change can fail to render correctly --
        # manifesting as "select task N, see task N-1's icons" or needing
        # to reselect the last option to see it. By giving every toggle
        # Column one real visibility transition (True -> False) immediately
        # on page load, the dropdown's later True/False toggles are no
        # longer anyone's first transition, and update correctly every time.
        # See: https://github.com/gradio-app/gradio/issues/13521
        #      https://github.com/gradio-app/gradio/issues/12511
        def _hide_all_toggle_containers():
            return [gr.update(visible=False) for _ in toggle_containers]

        demo.load(_hide_all_toggle_containers, outputs=toggle_containers)

        # =========================
        # PAGE NAVIGATION
        # =========================
        # Using gr.Tabs(selected=...) to switch "pages" is the robust,
        # well-supported Gradio pattern for this. The earlier approach
        # (toggling gr.Column visibility for whole pages) triggered a
        # known Gradio bug where complex children -- gr.Plot, gr.Gallery,
        # gr.Dataframe -- can break and render a blank page once their
        # parent Column's visibility is toggled off and back on. Tabs
        # don't have this issue since each Tab's content stays mounted.
        btn_single.click(lambda: gr.Tabs(selected="single"), outputs=tabs)
        btn_multi.click(lambda: gr.Tabs(selected="multi"), outputs=tabs)
        single_back_btn.click(lambda: gr.Tabs(selected="welcome"), outputs=tabs)
        multi_back_btn.click(lambda: gr.Tabs(selected="welcome"), outputs=tabs)

        return demo