CCockrum commited on
Commit
3c67c1b
·
verified ·
1 Parent(s): 6080190

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +277 -158
app.py CHANGED
@@ -1,37 +1,69 @@
 
1
  import requests
2
  import pandas as pd
 
3
  import streamlit as st
4
  import matplotlib
5
  import plotly.express as px
6
  from sklearn.feature_extraction.text import TfidfVectorizer
7
  from sklearn.metrics.pairwise import cosine_similarity
8
 
9
- # ------------------- Custom CSS -------------------
10
  st.markdown("""
11
  <style>
12
- html, body, [data-testid="stApp"] {
13
- background-color: #1A1A1A !important;
14
- }
15
  .main {
16
  background-color: #D3D3D3 !important;
17
  color: #1A1A1A!important;
 
18
  }
19
  .block-container {
20
  background-color: gray !important;
21
- color: #1A1A1A !important;
22
- padding-left: 2rem !important;
23
- padding-right: 2rem !important;
24
  }
25
- header[data-testid="stHeader"] {
26
- background-color: #1A1A1A !important;
 
 
 
 
 
 
27
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  section[data-testid="stSidebar"] > div:first-child {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  background-color: #1A1A1A !important;
30
- color: #FFFFFF !important;
31
- padding: 2rem 1.5rem 1.5rem 1.5rem !important;
32
- border-radius: 12px;
33
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
34
- font-size: 0.95rem;
35
  }
36
  .custom-table {
37
  background-color: #D3D3D3;
@@ -42,44 +74,35 @@ st.markdown("""
42
  overflow-x: auto;
43
  white-space: pre;
44
  border: 1px solid #ccc;
 
45
  }
46
  .sidebar-stats {
47
  color: lightgray !important;
48
  font-size: 1.1rem !important;
 
49
  font-weight: 600;
50
  }
51
  .sidebar-contrast-block {
52
- background-color: #2b2b2b !important;
53
  padding: 1.25rem;
54
  border-radius: 10px;
55
  margin-top: 1.5rem;
56
  }
57
- .sidebar-section h3 {
58
- color: lightgray !important;
59
- font-size: 1.1rem !important;
60
- margin-top: 1.5rem;
61
- }
62
- .sidebar-links a {
63
- color: lightgray !important;
64
- text-decoration: none !important;
65
- }
66
- .sidebar-links a:hover {
67
- text-decoration: underline !important;
68
- }
69
- </style>
70
  """, unsafe_allow_html=True)
71
 
72
- # ------------------- Banner Image -------------------
73
  st.image("https://cdn-uploads.huggingface.co/production/uploads/67351c643fe51cb1aa28f2e5/7ThcAOjbuM8ajrP85bGs4.jpeg", use_container_width=True)
74
 
75
- # ------------------- App Title & Description -------------------
76
  st.title("MetaDiscovery Agent for Library of Congress Collections")
77
  st.markdown("""
78
  This tool connects to the LOC API, retrieves metadata from a selected collection, and performs
79
  an analysis of metadata completeness, suggests enhancements, and identifies authority gaps.
80
  """)
81
 
82
- # ------------------- Collection Selection -------------------
83
  collections = {
84
  "American Revolutionary War Maps": "american+revolutionary+war+maps",
85
  "Civil War Maps": "civil+war+maps",
@@ -87,144 +110,240 @@ collections = {
87
  "World War I Posters": "world+war+posters"
88
  }
89
 
 
 
 
 
 
 
 
90
  selected = st.sidebar.selectbox("Select a collection", list(collections.keys()), key="collection_selector")
91
  search_query = collections[selected]
 
 
92
  collection_url = f"https://www.loc.gov/search/?q={search_query}&fo=json"
93
 
94
- # ------------------- Placeholders -------------------
95
  stats_placeholder = st.sidebar.empty()
 
 
96
  completeness_placeholder = st.sidebar.empty()
97
 
98
- # ------------------- Helpful Resources -------------------
 
 
 
99
  st.sidebar.markdown("""
100
- <div class="sidebar-section">
101
- <h3>🔗 Helpful Resources</h3>
102
- <div class="sidebar-links">
103
- <ul style='padding-left: 1em'>
104
- <li><a href="https://www.loc.gov/apis/" target="_blank">LOC API Info</a></li>
105
- <li><a href="https://www.loc.gov/" target="_blank">Library of Congress Homepage</a></li>
106
- <li><a href="https://www.loc.gov/collections/" target="_blank">LOC Digital Collections</a></li>
107
- <li><a href="https://www.loc.gov/marc/" target="_blank">MARC Metadata Standards</a></li>
108
- <li><a href="https://labs.loc.gov/about-labs/digital-strategy/" target="_blank">LOC Digital Strategy</a></li>
109
- </ul>
110
- </div>
111
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  """, unsafe_allow_html=True)
113
 
114
- # ------------------- Fetch Data -------------------
115
- with st.spinner(f"Fetching data for {selected}..."):
116
- headers = {"User-Agent": "Mozilla/5.0"}
117
- try:
118
- response = requests.get(collection_url, headers=headers)
119
- response.raise_for_status()
120
- data = response.json()
121
- records = data.get("results") or data.get("items") or []
122
- except:
123
- records = []
124
- st.error("Failed to load data from LOC API")
125
-
126
- # ------------------- Data Preparation -------------------
127
- items = []
128
- for record in records:
129
- description = record.get("description", "")
130
- if isinstance(description, list):
131
- description = " ".join([str(d) for d in description])
132
- item = {
133
- "id": record.get("id", ""),
134
- "title": record.get("title", ""),
135
- "date": record.get("date", ""),
136
- "subject": ", ".join(record.get("subject", [])) if isinstance(record.get("subject"), list) else record.get("subject", ""),
137
- "creator": record.get("creator", ""),
138
- "description": description
139
- }
140
- items.append(item)
141
-
142
- metadata_df = pd.DataFrame(items)
143
-
144
- # ------------------- Completeness Logic -------------------
145
- def is_incomplete(value):
146
- return pd.isna(value) or value in ["", "N/A", "null", None]
147
-
148
- if not metadata_df.empty:
149
- incomplete_mask = metadata_df.map(is_incomplete).any(axis=1)
150
- incomplete_count = incomplete_mask.sum()
151
- total_fields = metadata_df.size
152
- filled_fields = (~metadata_df.map(is_incomplete)).sum().sum()
153
- overall_percent = (filled_fields / total_fields) * 100
154
- completeness = (~metadata_df.map(is_incomplete)).mean() * 100
155
- completeness_df = pd.DataFrame({"Field": completeness.index, "Completeness (%)": completeness.values})
156
- completeness_table = completeness_df.set_index("Field")
157
-
158
- # ------------------- Quick Stats -------------------
159
- stats_html = f"""
160
- <div class="sidebar-stats">
161
- <h3 style="color: lightgray;">📊 Quick Stats</h3>
162
- <p style="color:lightgray;">Total Records: <b>{len(metadata_df)}</b></p>
163
- <p style="color:lightgray;">Incomplete Records: <b>{incomplete_count}</b></p>
164
- <p style="color:lightgray;">Overall Metadata Completeness: <b>{overall_percent:.1f}%</b></p>
165
- </div>
166
- """
167
- stats_placeholder.markdown(stats_html, unsafe_allow_html=True)
168
-
169
- # ------------------- Field Completeness Table -------------------
170
- with completeness_placeholder:
171
- st.markdown("""
172
- <div style='
173
- background-color: #2e2e2e;
174
- padding: 1.2rem;
175
- border-radius: 10px;
176
- margin-top: 1.5rem;
177
- color: lightgray;
178
- '>
179
- <h4 style='margin-bottom: 1rem;'>Field Completeness Breakdown</h4>
180
- """, unsafe_allow_html=True)
181
- st.dataframe(
182
- completeness_table.style.background_gradient(cmap="Greens").format("{:.1f}%"),
183
- use_container_width=True,
184
- height=240
185
- )
186
- st.markdown("</div>", unsafe_allow_html=True)
187
-
188
- # ------------------- Main Panel -------------------
189
- # Metadata completeness analysis (enhanced)
190
- st.subheader("📊 Metadata Completeness Analysis")
191
 
192
- fig = px.bar(
193
- completeness_df.reset_index(),
194
- x="Field",
195
- y="Completeness (%)",
196
- title="Metadata Completeness by Field",
197
- labels={"Field": "Metadata Field", "Completeness (%)": "Completeness (%)"}
198
- )
199
- st.plotly_chart(fig, use_container_width=True)
200
-
201
-
202
- # ------------------- Metadata Suggestions -------------------
203
- st.subheader("✨ Suggested Metadata Enhancements")
204
- incomplete_with_desc = metadata_df[incomplete_mask & metadata_df['description'].notnull()]
205
- reference_df = metadata_df[metadata_df['subject'].notnull() & metadata_df['description'].notnull()]
206
-
207
- if len(incomplete_with_desc) > 1 and len(reference_df) > 1:
208
  try:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
209
  tfidf = TfidfVectorizer(stop_words='english')
210
- tfidf_matrix = tfidf.fit_transform(reference_df['description'])
211
- suggestions = []
212
- for _, row in incomplete_with_desc.iterrows():
213
- if pd.isna(row['subject']) and pd.notna(row['description']):
214
- desc_vec = tfidf.transform([str(row['description'])])
215
- sims = cosine_similarity(desc_vec, tfidf_matrix).flatten()
216
- top_idx = sims.argmax()
217
- suggested_subject = reference_df.iloc[top_idx]['subject']
218
- if pd.notna(suggested_subject):
219
- suggestions.append((row['title'], suggested_subject))
220
- if suggestions:
221
- suggestions_df = pd.DataFrame(suggestions, columns=["Title", "Suggested Subject"])
222
- st.markdown("<div class='custom-table'>" + suggestions_df.to_markdown(index=False) + "</div>", unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
223
  else:
224
- st.info("No metadata enhancement suggestions available.")
225
- except Exception as e:
226
- st.error(f"Error generating suggestions: {e}")
227
- else:
228
- st.info("Not enough descriptive data to generate metadata suggestions.")
229
- else:
230
- st.warning("⚠️ No metadata records found for this collection.")
 
 
1
+ # MetaDiscovery Agent - LOC API with Enhanced Completeness and Quality Analysis
2
  import requests
3
  import pandas as pd
4
+ import numpy as np
5
  import streamlit as st
6
  import matplotlib
7
  import plotly.express as px
8
  from sklearn.feature_extraction.text import TfidfVectorizer
9
  from sklearn.metrics.pairwise import cosine_similarity
10
 
11
+ # Custom CSS for white background, styled sidebar, banner, and dark grey font
12
  st.markdown("""
13
  <style>
14
+
 
 
15
  .main {
16
  background-color: #D3D3D3 !important;
17
  color: #1A1A1A!important;
18
+
19
  }
20
  .block-container {
21
  background-color: gray !important;
22
+ color: #808080!important;
 
 
23
  }
24
+ section[data-testid="stSidebar"] > div:first-child {
25
+ background-color: #808080 !important;
26
+ padding: 1rem;
27
+ border-radius: 0.5rem;
28
+ color: #808080 !important;
29
+ }
30
+ .stMarkdown, .stTextInput, .stDataFrame {
31
+ color: #1A1A1A!important;
32
  }
33
+ img.banner {
34
+ width: 100%;
35
+ border-radius: 12px;
36
+ margin-bottom: 1rem;
37
+ }
38
+ .stAlert {
39
+ background-color: #f0f0f5 !important;
40
+ color: #333333 !important;
41
+ padding: 1.25rem !important;
42
+ font-size: 1rem !important;
43
+ border-radius: 0.5rem !important;
44
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05) !important;
45
+ }
46
+ header[data-testid="stHeader"] {
47
+ background-color: gray !important;
48
+ }
49
  section[data-testid="stSidebar"] > div:first-child {
50
+ background-color: #1A1A1A !important;
51
+ color: #FFFFFF !important;
52
+ padding: 2rem 1.5rem 1.5rem 1.5rem !important;
53
+ border-radius: 12px;
54
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
55
+ font-size: 0.95rem;
56
+ line-height: 1.5;
57
+ }
58
+ .block-container {
59
+ background-color: gray !important;
60
+ color: #1A1A1A !important;
61
+ padding-left: 2rem !important;
62
+ padding-right: 2rem !important;
63
+ box-shadow: none !important;
64
+ }
65
+ html, body, [data-testid="stApp"] {
66
  background-color: #1A1A1A !important;
 
 
 
 
 
67
  }
68
  .custom-table {
69
  background-color: #D3D3D3;
 
74
  overflow-x: auto;
75
  white-space: pre;
76
  border: 1px solid #ccc;
77
+
78
  }
79
  .sidebar-stats {
80
  color: lightgray !important;
81
  font-size: 1.1rem !important;
82
+ margin-top: 1.5rem;
83
  font-weight: 600;
84
  }
85
  .sidebar-contrast-block {
86
+ background-color: #2b2b2b !important; /* Slightly lighter than #1A1A1A */
87
  padding: 1.25rem;
88
  border-radius: 10px;
89
  margin-top: 1.5rem;
90
  }
91
+
92
+ </style>
 
 
 
 
 
 
 
 
 
 
 
93
  """, unsafe_allow_html=True)
94
 
95
+ # OPTION 1: Use an image from a URL for the banner
96
  st.image("https://cdn-uploads.huggingface.co/production/uploads/67351c643fe51cb1aa28f2e5/7ThcAOjbuM8ajrP85bGs4.jpeg", use_container_width=True)
97
 
98
+ # Streamlit app header
99
  st.title("MetaDiscovery Agent for Library of Congress Collections")
100
  st.markdown("""
101
  This tool connects to the LOC API, retrieves metadata from a selected collection, and performs
102
  an analysis of metadata completeness, suggests enhancements, and identifies authority gaps.
103
  """)
104
 
105
+ # Updated collection URLs using the correct LOC API format
106
  collections = {
107
  "American Revolutionary War Maps": "american+revolutionary+war+maps",
108
  "Civil War Maps": "civil+war+maps",
 
110
  "World War I Posters": "world+war+posters"
111
  }
112
 
113
+ # Sidebar for selecting collection
114
+ #st.sidebar.markdown("## Settings")
115
+
116
+ # Create empty metadata_df variable to ensure it exists before checking
117
+ metadata_df = pd.DataFrame()
118
+
119
+ # Add a key to the selectbox to ensure it refreshes properly
120
  selected = st.sidebar.selectbox("Select a collection", list(collections.keys()), key="collection_selector")
121
  search_query = collections[selected]
122
+
123
+ # Define the collection URL
124
  collection_url = f"https://www.loc.gov/search/?q={search_query}&fo=json"
125
 
126
+ # Create an empty placeholder for Quick Stats
127
  stats_placeholder = st.sidebar.empty()
128
+
129
+ # Create placeholder for Field Completeness Breakdown
130
  completeness_placeholder = st.sidebar.empty()
131
 
132
+ # Helpful Resources (styled and moved below dropdown)
133
+ st.sidebar.markdown("### Helpful Resources", unsafe_allow_html=True)
134
+ # Helpful Resources styled section
135
+ # 3. Helpful Resources Section (Fixed, under Completeness)
136
  st.sidebar.markdown("""
137
+ <style>
138
+ .sidebar-section h3 {
139
+ color: lightgray !important;
140
+ font-size: 1.1rem !important;
141
+ margin-top: 1.5rem;
142
+ }
143
+ .sidebar-links a {
144
+ color: lightgray !important;
145
+ text-decoration: none !important;
146
+ }
147
+ .sidebar-links a:hover {
148
+ text-decoration: underline !important;
149
+ }
150
+ </style>
151
+ <div class="sidebar-section">
152
+ <h3>🔗 Helpful Resources</h3>
153
+ <div class="sidebar-links">
154
+ <ul style='padding-left: 1em'>
155
+ <li><a href="https://www.loc.gov/apis/" target="_blank">LOC API Info</a></li>
156
+ <li><a href="https://www.loc.gov/" target="_blank">Library of Congress Homepage</a></li>
157
+ <li><a href="https://www.loc.gov/collections/" target="_blank">LOC Digital Collections</a></li>
158
+ <li><a href="https://www.loc.gov/marc/" target="_blank">MARC Metadata Standards</a></li>
159
+ <li><a href="https://labs.loc.gov/about-labs/digital-strategy/" target="_blank">LOC Digital Strategy</a></li>
160
+ </ul>
161
+ </div>
162
+ </div>
163
  """, unsafe_allow_html=True)
164
 
165
+
166
+ # Add a fetch button to make the action explicit
167
+ fetch_data = True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
 
169
+ if fetch_data:
170
+ # Display a loading spinner while fetching data
171
+ with st.spinner(f"Fetching data for {selected}..."):
172
+ # Fetch data from LOC API with spoofed User-Agent header
173
+ headers = {
174
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/110.0.0.0 Safari/537.36"
175
+ }
176
+
 
 
 
 
 
 
 
 
177
  try:
178
+ response = requests.get(collection_url, headers=headers)
179
+ response.raise_for_status()
180
+ data = response.json()
181
+
182
+ if "results" in data:
183
+ records = data.get("results", [])
184
+ elif "items" in data:
185
+ records = data.get("items", [])
186
+ else:
187
+ records = []
188
+ st.error("Unexpected API response structure. No records found.")
189
+ st.write(f"Retrieved {len(records)} records")
190
+
191
+ except requests.exceptions.RequestException as e:
192
+ st.error(f"API Connection Error: {e}")
193
+ records = []
194
+ except ValueError:
195
+ st.error("Failed to parse API response as JSON")
196
+ records = []
197
+
198
+ # Extract selected metadata fields
199
+ items = []
200
+ for record in records:
201
+ if isinstance(record, dict):
202
+ description = record.get("description", "")
203
+ if isinstance(description, list):
204
+ description = " ".join([str(d) for d in description])
205
+ item = {
206
+ "id": record.get("id", ""),
207
+ "title": record.get("title", ""),
208
+ "date": record.get("date", ""),
209
+ "subject": ", ".join(record.get("subject", [])) if isinstance(record.get("subject"), list) else record.get("subject", ""),
210
+ "creator": record.get("creator", ""),
211
+ "description": description
212
+ }
213
+ if not item["title"] and "item" in record:
214
+ item["title"] = record.get("item", {}).get("title", "")
215
+ if not item["date"] and "item" in record:
216
+ item["date"] = record.get("item", {}).get("date", "")
217
+ items.append(item)
218
+
219
+ metadata_df = pd.DataFrame(items)
220
+
221
+ # Define custom completeness check
222
+ def is_incomplete(value):
223
+ return pd.isna(value) or value in ["", "N/A", "null", None]
224
+
225
+ if not metadata_df.empty:
226
+ # Incomplete record detection
227
+ incomplete_mask = metadata_df.apply(lambda row: row.map(is_incomplete), axis=1).any(axis=1)
228
+ incomplete_count = incomplete_mask.sum()
229
+
230
+ # Overall completeness
231
+ total_fields = metadata_df.size
232
+ filled_fields = metadata_df.apply(lambda row: row.map(lambda x: not is_incomplete(x)), axis=1).sum().sum()
233
+ overall_percent = (filled_fields / total_fields) * 100
234
+
235
+ # Field-by-field completeness
236
+ completeness = metadata_df.map(lambda x: not is_incomplete(x)).mean() * 100
237
+ completeness_table = completeness.round(1).to_frame(name="Completeness (%)")
238
+
239
+ # Render stats summary in sidebar
240
+ stats_html = f"""
241
+ <div class="sidebar-stats">
242
+ <h3 style="color: lightgray;">Quick Stats</h3>
243
+ <p style="color:lightgray;">Total Records: <b>{len(metadata_df)}</b></p>
244
+ <p style="color:lightgray;">Incomplete Records: <b>{incomplete_count}</b></p>
245
+ <p style="color:lightgray;">Overall Metadata Completeness: <b>{overall_percent:.1f}%</b></p>
246
+ </div>
247
+ """
248
+ stats_placeholder.markdown(stats_html, unsafe_allow_html=True)
249
+
250
+
251
+ # Utility functions for deeper metadata quality analysis
252
+ def is_incomplete(value):
253
+ return pd.isna(value) or value in ["", "N/A", "null", None]
254
+
255
+ def is_valid_date(value):
256
+ try:
257
+ pd.to_datetime(value)
258
+ return True
259
+ except:
260
+ return False
261
+
262
+ if not metadata_df.empty:
263
+ st.subheader("Retrieved Metadata Sample")
264
+ st.dataframe(metadata_df.head())
265
+
266
+ # Metadata completeness analysis (enhanced)
267
+ st.subheader("Metadata Completeness Analysis")
268
+ # Create the completeness table
269
+ completeness = metadata_df.map(lambda x: not is_incomplete(x)).mean() * 100
270
+ completeness_df = pd.DataFrame({
271
+ "Field": completeness.index,
272
+ "Completeness (%)": completeness.values
273
+ })
274
+ completeness_table = completeness_df.set_index("Field")
275
+
276
+ # FILL THE PLACEHOLDER created earlier
277
+
278
+ with completeness_placeholder:
279
+ st.markdown("""
280
+ <div style='
281
+ background-color: #2e2e2e;
282
+ padding: 1.2rem;
283
+ border-radius: 10px;
284
+ margin-top: 1.5rem;
285
+ color: lightgray;
286
+ '>
287
+ <h4 style='margin-bottom: 1rem;'>Field Completeness Breakdown</h4>
288
+ """, unsafe_allow_html=True)
289
+
290
+ st.dataframe(
291
+ completeness_table.style.background_gradient(cmap="Greens").format("{:.1f}%"),
292
+ use_container_width=True,
293
+ height=240
294
+ )
295
+
296
+ st.markdown("</div>", unsafe_allow_html=True)
297
+
298
+
299
+ # Then continue plotting in main panel
300
+ fig = px.bar(completeness_df, x="Field", y="Completeness (%)", title="Metadata Completeness by Field")
301
+ st.plotly_chart(fig)
302
+
303
+
304
+
305
+ # Identify incomplete records
306
+ incomplete_mask = metadata_df.map(is_incomplete).any(axis=1)
307
+ incomplete_records = metadata_df[incomplete_mask]
308
+
309
+ st.subheader("✨ Suggested Metadata Enhancements")
310
+
311
+ incomplete_with_desc = incomplete_records[incomplete_records['description'].notnull()]
312
+ reference_df = metadata_df[metadata_df['subject'].notnull() & metadata_df['description'].notnull()]
313
  tfidf = TfidfVectorizer(stop_words='english')
314
+
315
+ if len(incomplete_with_desc) > 1 and len(reference_df) > 1:
316
+ try:
317
+ suggestions = []
318
+ tfidf_matrix = tfidf.fit_transform(reference_df['description'])
319
+
320
+ for idx, row in incomplete_with_desc.iterrows():
321
+ if pd.isna(row['subject']) and pd.notna(row['description']):
322
+ desc_vec = tfidf.transform([str(row['description'])])
323
+ sims = cosine_similarity(desc_vec, tfidf_matrix).flatten()
324
+ top_idx = sims.argmax()
325
+ suggested_subject = reference_df.iloc[top_idx]['subject']
326
+ if pd.notna(suggested_subject) and suggested_subject:
327
+ suggestions.append((row['title'], suggested_subject))
328
+
329
+ if suggestions:
330
+ suggestions_df = pd.DataFrame(suggestions, columns=["Title", "Suggested Subject"])
331
+ st.markdown("<div class='custom-table'>" + suggestions_df.to_markdown(index=False) + "</div>", unsafe_allow_html=True)
332
+ else:
333
+ st.markdown("""
334
+ <div class='custom-table'>
335
+ <b>No metadata enhancement suggestions available.</b>
336
+ </div>
337
+ """, unsafe_allow_html=True)
338
+
339
+ except Exception as e:
340
+ st.error(f"Error generating metadata suggestions: {e}")
341
  else:
342
+ st.markdown("""
343
+ <div class='custom-table'>
344
+ <b>Not enough descriptive data to generate metadata suggestions.</b>
345
+ </div>
346
+ """, unsafe_allow_html=True)
347
+ else:
348
+ st.warning("⚠️ No metadata records found for this collection. Try selecting another one.")
349
+