Spaces:
Sleeping
Sleeping
| # ============================================ | |
| # app.py β Gradio interface for HuggingFace Spaces | |
| # Provides both a web UI and automatic API endpoints | |
| # ============================================ | |
| import gradio as gr | |
| from bias_detector import BiasDetector | |
| # Load models once at startup | |
| bd = BiasDetector() | |
| # Session-level sustainability log (accumulated across all requests in one session) | |
| _session_log: list[dict] = [] | |
| def _format_sustainability(s: dict) -> str: | |
| """Format a sustainability dict into a human-readable string.""" | |
| return ( | |
| f"β‘ Energy: {s['energy_kwh']:.6f} kWh\n" | |
| f"π¨ COβeq: {s['co2_grams']:.4f} gCOβe\n" | |
| f"π§ Water: {s['water_liters']:.6f} L" | |
| ) | |
| def _session_totals() -> dict: | |
| """Sum all sustainability metrics recorded so far this session.""" | |
| return { | |
| "energy_kwh": sum(r["energy_kwh"] for r in _session_log), | |
| "co2_grams": sum(r["co2_grams"] for r in _session_log), | |
| "water_liters": sum(r["water_liters"] for r in _session_log), | |
| } | |
| def _build_history_table() -> str: | |
| """Return a plain-text table of all requests so far.""" | |
| if not _session_log: | |
| return "No requests yet this session." | |
| lines = [ | |
| f"{'#':<4} {'Type':<20} {'Energy (kWh)':<16} {'COβ (gCOβe)':<16} {'Water (L)':<12}", | |
| "-" * 72, | |
| ] | |
| for i, r in enumerate(_session_log, 1): | |
| lines.append( | |
| f"{i:<4} {r['type']:<20} " | |
| f"{r['energy_kwh']:<16.6f} " | |
| f"{r['co2_grams']:<16.4f} " | |
| f"{r['water_liters']:<12.6f}" | |
| ) | |
| totals = _session_totals() | |
| lines.append("-" * 72) | |
| lines.append( | |
| f"{'TOTAL':<24} " | |
| f"{totals['energy_kwh']:<16.6f} " | |
| f"{totals['co2_grams']:<16.4f} " | |
| f"{totals['water_liters']:<12.6f}" | |
| ) | |
| return "\n".join(lines) | |
| # ---- Endpoint functions ---- | |
| def bias_detection(text): | |
| """Endpoint: analyze + rewrite a job posting.""" | |
| rewrite = bd.rewrite_job_posting(text) | |
| result = rewrite["analysis"] | |
| summary = ( | |
| f"Overall: {result['overall_label']}\n" | |
| f"Bias Score: {result['bias_score']}\n" | |
| f"ML Model: {result['ml_model_result']['label']} " | |
| f"({round(result['ml_model_result']['score'], 3)})\n" | |
| f"Masculine-coded words: {result['masculine_count']}\n" | |
| f"Feminine-coded words: {result['feminine_count']}" | |
| ) | |
| flagged_summary = "" | |
| for f in result["flagged_words"]: | |
| flagged_summary += f"β’ \"{f['word']}\" β {f['category']}\n" | |
| if not flagged_summary: | |
| flagged_summary = "No gendered words detected by lexicon." | |
| # Log sustainability | |
| s = rewrite["sustainability"] | |
| _session_log.append({"type": "Job posting rewrite", **s}) | |
| totals = _session_totals() | |
| return ( | |
| summary, | |
| flagged_summary, | |
| rewrite["fully_rewritten"], | |
| _format_sustainability(s), | |
| _build_history_table(), | |
| _format_sustainability(totals), | |
| ) | |
| def anonymize(text): | |
| """Endpoint: anonymize a CV/letter.""" | |
| result = bd.anonymize_document(text) | |
| # Log sustainability | |
| s = result["sustainability"] | |
| _session_log.append({"type": "CV anonymization", **s}) | |
| totals = _session_totals() | |
| return ( | |
| result["surface_anonymized"], | |
| result["fully_anonymized"], | |
| _format_sustainability(s), | |
| _build_history_table(), | |
| _format_sustainability(totals), | |
| ) | |
| def refresh_sustainability(): | |
| """Refresh the sustainability tab manually.""" | |
| totals = _session_totals() | |
| return _build_history_table(), _format_sustainability(totals) | |
| # ---- Build Gradio UI ---- | |
| with gr.Blocks(title="Nubias: Gender Bias Detector & CV Anonymizer") as demo: | |
| gr.Markdown("# π Nubias: Gender Bias Detector & CV Anonymizer") | |
| gr.Markdown( | |
| "Detecting gender bias in job postings and anonymizing CVs/letters " | |
| "to promote fair hiring practices β aligned with **SDG 5: Gender Equality**." | |
| ) | |
| # Shared sustainability components (declared here, rendered inside the tab below) | |
| history_box = gr.Textbox(label="Request History", lines=12, interactive=False, visible=False) | |
| totals_box = gr.Textbox(label="Session Totals", lines=4, interactive=False, visible=False) | |
| with gr.Tab("Job Posting Analyzer"): | |
| gr.Markdown("### Analyze and rewrite a job posting for gendered language") | |
| input_posting = gr.Textbox( | |
| label="Paste job posting here", | |
| lines=8, | |
| placeholder="e.g. We are looking for an aggressive self-starter who can dominate the market...", | |
| ) | |
| btn_analyze = gr.Button("Analyze & Rewrite", variant="primary") | |
| output_summary = gr.Textbox(label="Bias Analysis", lines=5) | |
| output_flagged = gr.Textbox(label="Flagged Words", lines=5) | |
| output_rewrite = gr.Textbox(label="Gender-Neutral Rewrite", lines=8) | |
| output_sustain_j = gr.Textbox(label="β»οΈ This Request β Sustainability", lines=4) | |
| with gr.Tab("CV / Letter Anonymizer"): | |
| gr.Markdown("### Anonymize a CV, cover letter, or recommendation letter") | |
| input_cv = gr.Textbox( | |
| label="Paste document text here", | |
| lines=8, | |
| placeholder="e.g. Sarah Johnson (sarah@email.com) is an exceptionally warm leader...", | |
| ) | |
| btn_anonymize = gr.Button("Anonymize", variant="primary") | |
| output_surface = gr.Textbox( | |
| label="Surface Anonymized (names, emails, pronouns, titles removed)", lines=6 | |
| ) | |
| output_full = gr.Textbox( | |
| label="Fully Anonymized (gendered style words neutralized)", lines=6 | |
| ) | |
| output_sustain_c = gr.Textbox(label="β»οΈ This Request β Sustainability", lines=4) | |
| with gr.Tab("β»οΈ Sustainability"): | |
| gr.Markdown( | |
| "### Environmental impact of Nubias this session\n" | |
| "Estimates are based on measured CPU energy draw (via **CodeCarbon**) " | |
| "and a data-centre Water Usage Effectiveness (WUE) of **1.6 L / kWh**.\n\n" | |
| "Carbon intensity uses CodeCarbon's location-aware grid data. " | |
| "All figures are per-request and cumulative for the current session." | |
| ) | |
| tab_history = gr.Textbox(label="Request History", lines=12, interactive=False) | |
| tab_totals = gr.Textbox(label="Session Totals", lines=4, interactive=False) | |
| btn_refresh = gr.Button("π Refresh", variant="secondary") | |
| btn_refresh.click( | |
| refresh_sustainability, | |
| inputs=[], | |
| outputs=[tab_history, tab_totals], | |
| ) | |
| # Wire action buttons β both update the sustainability tab directly | |
| btn_analyze.click( | |
| bias_detection, | |
| inputs=input_posting, | |
| outputs=[output_summary, output_flagged, output_rewrite, | |
| output_sustain_j, tab_history, tab_totals], | |
| ) | |
| btn_anonymize.click( | |
| anonymize, | |
| inputs=input_cv, | |
| outputs=[output_surface, output_full, output_sustain_c, | |
| tab_history, tab_totals], | |
| ) | |
| demo.launch(server_name="0.0.0.0", server_port=7860) | |