import html as html_lib import pandas as pd import re, json from config import ( CUSTOM_CSS, AG_GRID_HEAD, DISPLAY_COLUMNS ) def build_sortable_table(df: pd.DataFrame, table_id: str, title: str) -> str: if df is None or df.empty: return f"""

{html_lib.escape(title)}

No data

""" def format_display_df(df: pd.DataFrame) -> pd.DataFrame: out = df.copy() def truncate_cell(x): if isinstance(x, (list, dict)): x = json.dumps(x, default=str) x = "" if x is None else str(x) return html_lib.escape(x, quote=False) if "evidence" in out.columns: out["evidence"] = out["evidence"].apply( lambda x: json.dumps(x, indent=2, default=str) if isinstance(x, (list, dict)) else x ).apply(truncate_cell) if "a_to_b_mapping" in out.columns and not out["a_to_b_mapping"].astype(str).eq("").all(): out["a_to_b_mapping"] = out["a_to_b_mapping"].astype(str).apply(truncate_cell) return out def _cell_html(value, max_len: int = 120) -> str: if value is None: text = "" elif isinstance(value, float) and pd.isna(value): text = "" elif isinstance(value, (list, dict)): text = json.dumps(value, ensure_ascii=False, default=str) else: text = str(value) escaped_full = html_lib.escape(text) if len(text) <= max_len: return escaped_full escaped_short = html_lib.escape(text[:max_len] + "…") return ( f'' f"{escaped_short}" f"" ) def get_display_df(df: pd.DataFrame, feature_type: str) -> pd.DataFrame: cols = DISPLAY_COLUMNS.get(feature_type, DISPLAY_COLUMNS["default"]) cols = [c for c in cols if c in df.columns] return df[cols].copy() def col_to_class(col: str) -> str: col = str(col).strip().lower() col = re.sub(r"[^a-z0-9]+", "-", col) # replace non-alphanumeric with - return f"col-{col}" def build_sortable_table(df: pd.DataFrame, table_id: str, title: str) -> str: if df is None or df.empty: return f"""

{html_lib.escape(title)}

No data

""" df = df.round(3) headers = [] for idx, col in enumerate(df.columns): col_class = col_to_class(col) headers.append( f'' f'{html_lib.escape(str(col))}' f'' ) body_rows = [] for _, row in df.iterrows(): cells = [] for col in df.columns: cells.append(f"{_cell_html(row[col])}") body_rows.append("" + "".join(cells) + "") return f"""

{html_lib.escape(title)}

{''.join(headers)} {''.join(body_rows)}
""" def render_generated_tables(analysis_df: pd.DataFrame, valid_df: pd.DataFrame, invalid_df: pd.DataFrame) -> tuple[str, str, str]: analysis_html = ( build_sortable_table(format_display_df(analysis_df), "property-relation-table", "Analyze property (all features)") if analysis_df is not None and not analysis_df.empty else "

No Analysis data

" ) valid_html = ( build_sortable_table(format_display_df(valid_df), "generated-table", "Generated Data") if valid_df is not None and not valid_df.empty else "

No generated data

" ) invalid_html = ( build_sortable_table(format_display_df(invalid_df), "invalid-table", "Invalid Data") if invalid_df is not None and not invalid_df.empty else "

No invalid rows

" ) return analysis_html, valid_html, invalid_html, "" def render_tables(dfs): if not isinstance(dfs, dict) or not dfs: return "

No grouped tables to display.

" html_parts = [] for i, (feature_type, df) in enumerate(dfs.items(), start=1): display_df = format_display_df(get_display_df(df, feature_type)) table_id = f"feature_table_{i}" html_parts.append(build_sortable_table(display_df, table_id, str(feature_type))) return "".join(html_parts)