CoolFace
Apppublic

rt0209/S4HANA_Migration_Intelligence_Agent

sourceHugging Facemitupdated 3mo agoView on Hugging Face
0likes
app.py451 linesDownload Raw Back to root
1"""2S/4HANA Migration Intelligence Agent — HuggingFace Spaces (Gradio)3===================================================================4Entry point: app.py5SDK: gradio6 7Set in HF Space Secrets:8  ZAI_API_KEY  →  your key from https://z.ai (or Together AI / OpenRouter)9  ZAI_BASE_URL →  https://api.z.ai/v1  (optional, this is the default)10  GLM_MODEL    →  glm-5.2              (optional, this is the default)11"""12 13import sys, os14#sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))15os.environ["NO_PROXY"] = "localhost,127.0.0.1,0.0.0.0"16os.environ["no_proxy"] = "localhost,127.0.0.1,0.0.0.0"17os.environ["GRADIO_ANALYTICS_ENABLED"] = "False"18import gradio as gr19import asyncio20import os21from pathlib import Path22from dotenv import load_dotenv23 24load_dotenv()25 26from abap_analyzer import ABAPAnalyzer27from report_generator import ReportGenerator28 29# ── Sample ABAP code ──────────────────────────────────────────────────30 31SAMPLES = {32    "Vendor Aging Report": {33        "name": "ZVENDOR_AGING_REPORT",34        "type": "ABAP Report",35        "code": """\36REPORT ZVENDOR_AGING_REPORT.37INCLUDE: <ICON>.38 39* Deprecated: Logical database removed in S/4HANA40NODES: LFB1.41TABLES: BSEG, LFA1, LFB1.42 43SELECT-OPTIONS: S_BUKRS FOR BSEG-BUKRS.44SELECT-OPTIONS: S_GJAHR FOR BSEG-GJAHR.45 46CONSTANTS: gc_bukrs TYPE BUKRS VALUE '1000'.   " Hard-coded company code47 48START-OF-SELECTION.49  " CRITICAL: BSEG removed in S/4HANA — replaced by ACDOCA50  SELECT * FROM BSEG51    WHERE BUKRS IN S_BUKRS52    AND   GJAHR IN S_GJAHR53    AND   KOART = 'K'54    INTO TABLE @DATA(lt_bseg).55 56  LOOP AT lt_bseg INTO DATA(ls_bseg).57    " Performance: SELECT SINGLE inside LOOP58    SELECT SINGLE NAME1 FROM LFA159      WHERE LIFNR = ls_bseg-LIFNR60      INTO @DATA(lv_name).61  ENDLOOP.62 63  CALL FUNCTION 'REUSE_ALV_GRID_DISPLAY'64    EXPORTING i_structure_name = 'BSEG'65    TABLES    t_outtab          = lt_bseg.\66""",67    },68    "Material Stock Check": {69        "name": "ZMAT_STOCK_CHECK",70        "type": "ABAP Report",71        "code": """\72REPORT ZMAT_STOCK_CHECK.73 74DATA: lv_matnr TYPE MATNR.    " 18-char field — S/4HANA extends to 4075 76" CRITICAL: MARD table retired in S/4HANA77SELECT MATNR WERKS LABST EINME78  FROM MARD79  WHERE WERKS = '0001'80  AND MATNR = @lv_matnr81  INTO TABLE @DATA(lt_stock).82 83" Integration risk: RFC to external BW system84CALL FUNCTION 'ZBAPI_GET_FORECAST'85  DESTINATION 'BW_SYSTEM'86  EXPORTING matnr = lv_matnr87  IMPORTING forecast = DATA(ls_forecast).88 89" Old user exit style — must migrate to BAdI90CALL CUSTOMER-FUNCTION '001'91  EXPORTING matnr = lv_matnr.92 93CONSTANTS: gc_werks TYPE WERKS_D VALUE '0001'.   " Hard-coded plant\94""",95    },96    "GL Posting Function": {97        "name": "ZFI_POST_GL_DOCUMENT",98        "type": "Function Module",99        "code": """\100FUNCTION ZFI_POST_GL_DOCUMENT.101*"  Importing: BUKRS TYPE BUKRS, BLDAT TYPE BLDAT102*"  Exporting: BELNR TYPE BELNR_D103 104  DATA: ls_header TYPE BAPIACHE09,105        lt_gl     TYPE TABLE OF BAPIACGL09,106        lt_curr   TYPE TABLE OF BAPIACCR09.107 108  " Deprecated BAPI — use BAPI_ACCFI_POST in S/4HANA109  CALL FUNCTION 'BAPI_ACC_DOCUMENT_POST'110    EXPORTING documentheader = ls_header111    TABLES    accountgl      = lt_gl112              currencyamount = lt_curr.113 114  " CRITICAL: Direct DB modify — data corruption risk in S/4HANA115  MODIFY BKPF FROM ls_bkpf.116  MODIFY BSEG FROM ls_bseg.117 118  " Hard-coded chart of accounts119  DATA: lv_ktopl TYPE KTOPL VALUE 'INT'.120 121ENDFUNCTION.\122""",123    },124}125 126OBJECT_TYPES = [127    "ABAP Report", "Function Module", "Function Group",128    "ABAP Class", "ABAP Interface", "BAdI Implementation",129    "SmartForm", "SAP Script", "Transformation",130    "User Exit", "Custom Table", "Other",131]132 133# ── Severity palette ──────────────────────────────────────────────────134 135_SEV = {136    "Critical": ("#FCEBEB", "#A32D2D", "#F09595"),137    "High":     ("#FAEEDA", "#854F0B", "#EF9F27"),138    "Medium":   ("#E6F1FB", "#0C447C", "#85B7EB"),139    "Low":      ("#EAF3DE", "#27500A", "#97C459"),140}141 142def _badge(severity: str) -> str:143    bg, fg, bd = _SEV.get(severity, ("#f5f5f5", "#666", "#ccc"))144    return (145        f'<span style="background:{bg};color:{fg};border:0.5px solid {bd};'146        f'padding:2px 10px;border-radius:20px;font-size:11px;font-weight:600">'147        f'{severity}</span>'148    )149 150def _metric(label: str, value: str) -> str:151    return (152        f'<div style="background:#f8f9fa;border-radius:8px;padding:12px 14px">'153        f'<div style="font-size:10px;font-weight:600;color:#888;text-transform:uppercase;'154        f'letter-spacing:0.05em;margin-bottom:4px">{label}</div>'155        f'<div style="font-size:20px;font-weight:600;color:#1a1a1a">{value}</div>'156        f'</div>'157    )158 159# ── HTML formatter ────────────────────────────────────────────────────160 161def format_results(result) -> str:162    """Convert an AnalysisResult into styled HTML for gr.HTML display."""163 164    r = result.model_dump() if hasattr(result, "model_dump") else result165 166    sev = r.get("overall_risk", "Medium")167    bg, fg, bd = _SEV.get(sev, ("#f5f5f5", "#666", "#ccc"))168 169    findings = r.get("findings", [])170    counts = {s: 0 for s in _SEV}171    for f in findings:172        counts[f.get("severity", "Medium")] = counts.get(f.get("severity", "Medium"), 0) + 1173 174    severity_order = ["Critical", "High", "Medium", "Low"]175    sorted_findings = sorted(176        findings,177        key=lambda f: severity_order.index(f.get("severity", "Low"))178        if f.get("severity") in severity_order else 99,179    )180 181    sev_pills = " ".join(182        _badge(f"{c} {s}")183        for s, c in counts.items() if c > 0184    )185 186    finding_html = ""187    for f in sorted_findings:188        s = f.get("severity", "Medium")189        fbg, ffg, fbd = _SEV.get(s, ("#f5f5f5", "#666", "#ccc"))190        finding_html += f"""191<details style="margin-bottom:8px;border-left:3px solid {ffg};background:#fafafa;192                border-radius:0 6px 6px 0;overflow:hidden">193  <summary style="padding:11px 14px;cursor:pointer;display:flex;align-items:center;194                  gap:8px;list-style:none;-webkit-appearance:none">195    {_badge(s)}196    <span style="font-weight:500;color:#1a1a1a;font-size:13px">{f.get('title','')}</span>197    <span style="color:#aaa;font-size:11px;margin-left:auto;white-space:nowrap">198      {f.get('category','')} &nbsp;·&nbsp; {f.get('effort_days', 0)}d199    </span>200  </summary>201  <div style="padding:0 14px 14px;border-top:0.5px solid #eee">202    <table style="width:100%;border-collapse:collapse;margin-top:12px;font-size:13px">203      <tr>204        <td style="width:130px;color:#888;padding:4px 0;vertical-align:top">Location</td>205        <td style="color:#333;padding:4px 0;font-family:monospace">{f.get('line_reference','')}</td>206      </tr>207      <tr>208        <td style="color:#888;padding:4px 0;vertical-align:top">Issue</td>209        <td style="color:#333;padding:4px 0">{f.get('issue','')}</td>210      </tr>211      <tr>212        <td style="color:#888;padding:4px 0;vertical-align:top">S/4 impact</td>213        <td style="color:#333;padding:4px 0">{f.get('impact','')}</td>214      </tr>215      <tr>216        <td style="color:#888;padding:4px 0;vertical-align:top">Remediation</td>217        <td style="color:#0C447C;padding:4px 0;font-weight:500">{f.get('remediation','')}</td>218      </tr>219    </table>220  </div>221</details>222"""223 224    recs = r.get("recommendations", [])225    rec_html = "".join(226        f'<li style="margin-bottom:8px;color:#444;line-height:1.5">{rec}</li>'227        for rec in recs228    )229 230    return f"""231<div style="font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;232            line-height:1.5;max-width:100%;padding-bottom:8px">233 234  <!-- Header -->235  <div style="display:flex;align-items:center;gap:10px;margin-bottom:16px;flex-wrap:wrap">236    <h2 style="margin:0;font-size:17px;color:#1a1a1a">{r.get('object_name','')}</h2>237    <span style="background:{bg};color:{fg};border:0.5px solid {bd};238                 padding:3px 12px;border-radius:20px;font-size:12px;font-weight:600">239      {sev} Risk240    </span>241    <span style="color:#888;font-size:12px">{r.get('object_type','')}</span>242  </div>243 244  <!-- Metrics -->245  <div style="display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin-bottom:16px">246    {_metric("Readiness score", f"{r.get('s4_readiness_score', 0)}/100")}247    {_metric("Effort estimate", f"{r.get('estimated_effort_days', 0)}d")}248    {_metric("Findings", str(len(findings)))}249    {_metric("Complexity", r.get('migration_complexity', '-'))}250  </div>251 252  <!-- Severity pills -->253  <div style="display:flex;gap:8px;margin-bottom:14px;flex-wrap:wrap">{sev_pills}</div>254 255  <!-- Executive summary -->256  <div style="background:#f8f9fa;border-radius:8px;padding:14px;margin-bottom:18px;257              color:#555;font-size:13px;line-height:1.65">258    {r.get('executive_summary', '')}259  </div>260 261  <!-- Findings -->262  <h3 style="font-size:14px;font-weight:600;color:#1a1a1a;margin:0 0 10px">263    Findings ({len(findings)})264  </h3>265  {finding_html if finding_html else '<p style="color:#888;font-size:13px">No findings.</p>'}266 267  <!-- Recommendations -->268  <h3 style="font-size:14px;font-weight:600;color:#1a1a1a;margin:18px 0 10px">269    Recommendations270  </h3>271  <ol style="padding-left:18px;margin:0">{rec_html}</ol>272 273</div>274"""275 276# ── Analysis handler ──────────────────────────────────────────────────277 278async def run_analysis(279    code: str,280    object_name: str,281    object_type: str,282    client_name: str,283    progress=gr.Progress(),284) -> tuple[str, str | None]:285    """286    Called by Gradio on button click.287    Returns (html_string, docx_path_or_None).288    """289    if not code or not code.strip():290        return "<p style='color:#c00'>Please paste ABAP code or load a sample first.</p>", None291 292    api_key = os.getenv("ZAI_API_KEY", "")293    if not api_key:294        return (295            "<p style='color:#c00'>"296            "<strong>ZAI_API_KEY not set.</strong><br>"297            "Add it in Space Settings → Secrets → New Secret: <code>ZAI_API_KEY</code>. "298            "Get a key at <a href='https://z.ai' target='_blank'>z.ai</a>."299            "</p>",300            None,301        )302 303    try:304        progress(0.05, desc="Initialising GLM-5.2 client...")305        analyzer = ABAPAnalyzer()306 307        def _cb(pct: int, msg: str):308            progress(min(pct / 100, 0.92), desc=msg)309 310        progress(0.10, desc="Sending to GLM-5.2 (1M context)...")311        result = await analyzer.analyze(312            source_code=code,313            object_name=object_name or "ZCUSTOM_OBJECT",314            object_type=object_type or "ABAP Object",315            progress_callback=_cb,316        )317 318        progress(0.94, desc="Generating DOCX report...")319        gen = ReportGenerator()320        report_path = gen.generate_docx(321            results=[result],322            job_id=object_name.replace("/", "_"),323            client_name=client_name or "Client",324            project_name="S/4HANA Migration Assessment",325        )326 327        progress(1.0, desc="Done!")328        html = format_results(result)329        return html, str(report_path)330 331    except Exception as exc:332        return (333            f"<p style='color:#c00'><strong>Analysis failed:</strong> {exc}</p>",334            None,335        )336 337 338# ── Sample loader ─────────────────────────────────────────────────────339 340def load_sample(name: str):341    s = SAMPLES.get(name, {})342    return s.get("code", ""), s.get("name", "ZCUSTOM_OBJECT"), s.get("type", "ABAP Report")343 344 345# ── Gradio UI ─────────────────────────────────────────────────────────346 347DESCRIPTION = """348### 🏭 S/4HANA Migration Intelligence Agent349Powered by **GLM-5.2** (1M token context · open-weight MIT license)350 351Paste ABAP source code, click **Analyse**, and get a structured risk assessment:352deprecated API detection, removed table flags, effort estimate, and a downloadable DOCX report.353 354> Built by **Rohan_Tripathi** as a migration accelerator for SAP clients.355"""356 357with gr.Blocks(358    theme=gr.themes.Soft(primary_hue="blue", neutral_hue="slate"),359    title="S/4HANA Migration Intelligence Agent",360) as demo:361 362    gr.Markdown(DESCRIPTION)363 364    with gr.Row():365        # ── Left column: inputs ──────────────────────────────────────366        with gr.Column(scale=1, min_width=320):367            gr.Markdown("#### Input")368 369            sample_radio = gr.Radio(370                choices=list(SAMPLES.keys()),371                label="Load a sample",372                value=None,373            )374 375            with gr.Row():376                object_name = gr.Textbox(377                    value="ZCUSTOM_OBJECT",378                    label="Object name",379                    placeholder="ZREPORT_CUSTOM",380                    scale=2,381                )382                object_type = gr.Dropdown(383                    choices=OBJECT_TYPES,384                    value="ABAP Report",385                    label="Type",386                    scale=1,387                )388 389            client_name = gr.Textbox(390                value="Client",391                label="Client name (for DOCX report header)",392        393            )394 395            code_input = gr.Code(396                label="ABAP source code",397                language=None,398                lines=24,399            )400 401            analyze_btn = gr.Button(402                "🔍  Analyse for S/4HANA migration",403                variant="primary",404                size="lg",405            )406 407        # ── Right column: results ─────────────────────────────────────408        with gr.Column(scale=1, min_width=360):409            gr.Markdown("#### Analysis Results")410 411            results_display = gr.HTML(412                value=(413                    "<div style='padding:40px;text-align:center;color:#aaa;font-size:14px'>"414                    "Analysis results will appear here.</div>"415                )416            )417 418            report_file = gr.File(419                label="📄 Download Migration Report (DOCX)",420                visible=True,421                file_types=[".docx"],422            )423 424    # ── Event wiring ──────────────────────────────────────────────────425 426    sample_radio.change(427        fn=load_sample,428        inputs=[sample_radio],429        outputs=[code_input, object_name, object_type],430    )431 432    analyze_btn.click(433        fn=run_analysis,434        inputs=[code_input, object_name, object_type, client_name],435        outputs=[results_display, report_file],436    )437 438    gr.Markdown(439        "<div style='text-align:center;color:#aaa;font-size:12px;margin-top:12px'>"440        "Set <code>ZAI_API_KEY</code> in Space Secrets → "441        "<a href='https://z.ai' target='_blank'>get a key at z.ai</a>"442        "</div>"443    )444 445 446if __name__ == "__main__":447    demo.queue().launch(448        server_name = "0.0.0.0",449        server_port = 7860,450        share = True,451    )