File size: 5,324 Bytes
61e2cc7
 
 
661eb14
 
61e2cc7
 
 
 
b384020
61e2cc7
b384020
 
 
 
 
61e2cc7
 
b384020
 
61e2cc7
661eb14
b384020
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61e2cc7
b384020
 
 
 
 
 
 
61e2cc7
b384020
 
 
 
 
 
 
 
 
61e2cc7
b384020
61e2cc7
b384020
61e2cc7
 
 
b384020
 
 
61e2cc7
b384020
61e2cc7
b384020
 
 
 
 
 
8a64f5c
b384020
 
 
 
 
 
 
 
 
 
 
 
8a64f5c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import tempfile
from pathlib import Path

import streamlit as st

from core import criteria_extractor
from core.config import DATA_DIR

_MOCK_TENDER = DATA_DIR / "tender" / "crpf_construction_tender.pdf"
_REAL_DIR    = DATA_DIR / "tender" / "real_tenders"

_REAL_LABELS = {
    "crpf_bhopal_water_tanks_2025.pdf":
        "CRPF GC Bhopal — Water Storage Tanks (NIT-71, Aug 2025, Est. ₹62.9L)",
    "crpf_jammu_office_repair_2026.pdf":
        "CRPF J&K Zone HQ Jammu — Office Building Repair (Jan 2026, Est. ₹24.3L)",
}

_CAT_ICONS = {"financial": "🔵", "technical": "🟢", "compliance": "🟠"}


def render() -> None:
    st.markdown(
        '<h2 style="font-family:Inter,sans-serif;font-weight:800;font-size:1.5rem;'
        'color:#0D1B2A;margin-bottom:4px;">Tender Analysis</h2>'
        '<p style="color:#64748B;font-size:0.875rem;margin-bottom:1rem;">'
        'Extract eligibility criteria from any tender PDF using DeepSeek.</p>',
        unsafe_allow_html=True,
    )

    # ── Tender source selector ────────────────────────────────────────────────
    real_files = sorted(_REAL_DIR.glob("*.pdf")) if _REAL_DIR.exists() else []

    preset_options = {"Mock tender (CRPF Construction, 5 criteria)": _MOCK_TENDER}
    for f in real_files:
        preset_options[_REAL_LABELS.get(f.name, f.name)] = f

    tab_preset, tab_upload = st.tabs(["📂 Pre-loaded Tenders", "⬆️ Upload Your Own"])

    with tab_preset:
        chosen_label = st.selectbox("Select tender", options=list(preset_options.keys()))
        tender_path  = preset_options[chosen_label]
        tender_name  = tender_path.name

        if real_files and chosen_label != "Mock tender (CRPF Construction, 5 criteria)":
            st.markdown(
                '<div style="background:#F0FDF4;border:1px solid #BBF7D0;border-radius:8px;'
                'padding:10px 14px;font-size:0.83rem;color:#166534;margin-top:6px;">'
                '✅ <strong>Real government tender</strong> — downloaded from crpf.gov.in</div>',
                unsafe_allow_html=True,
            )

    with tab_upload:
        uploaded = st.file_uploader("Upload a tender PDF", type=["pdf"],
                                    label_visibility="collapsed")
        if uploaded:
            tender_bytes = uploaded.read()
            tender_name  = uploaded.name
            with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as tmp:
                tmp.write(tender_bytes)
                tender_path = Path(tmp.name)

    st.caption(f"Active: **{tender_name}**")

    # ── Extract button ────────────────────────────────────────────────────────
    if st.button("Extract Criteria (Live LLM)", type="primary"):
        with st.spinner("Calling DeepSeek to extract eligibility criteria…"):
            criteria = criteria_extractor.extract_criteria(tender_path)
        st.session_state["criteria"]     = [c.model_dump() for c in criteria]
        st.session_state["tender_path"]  = str(tender_path)
        st.success(f"Extracted {len(criteria)} criteria.")

    # ── Display results ───────────────────────────────────────────────────────
    criteria_data = st.session_state.get("criteria")
    if not criteria_data:
        return

    if st.session_state.get("fallback_active"):
        st.warning("⚠ Live API unavailable — showing pre-computed criteria.")

    st.caption(f"{len(criteria_data)} criteria — click × to remove any you don't want evaluated.")

    for c in criteria_data:
        icon     = _CAT_ICONS.get(c["category"], "⚪")
        mand_lbl = "🔴 Mandatory" if c["mandatory"] else "🟡 Optional"
        rule     = c["rule"]
        rule_str = f'`{rule["type"]}` · `{rule["field"]} {rule["operator"]}'
        if rule.get("value") is not None:
            rule_str += f' {rule["value"]}'
        if rule.get("unit"):
            rule_str += f' {rule["unit"]}'
        rule_str += "`"

        btn_col, exp_col = st.columns([0.04, 0.96])
        with btn_col:
            if st.button("×", key=f"rm_{c['id']}", help=f"Remove {c['id']}"):
                st.session_state["criteria"] = [
                    x for x in criteria_data if x["id"] != c["id"]
                ]
                st.rerun()
        with exp_col:
            with st.expander(
                f'{icon} **{c["id"]}** — {c["title"]}  ·  {mand_lbl}',
                expanded=False,
            ):
                col1, col2 = st.columns([2, 1])
                with col1:
                    st.markdown(f"**Description:** {c['description']}")
                    st.markdown(f"**Rule:** {rule_str}")
                    if c.get("query_hints"):
                        hints = "  ·  ".join(f"`{h}`" for h in c["query_hints"])
                        st.markdown(f"**Query hints:** {hints}")
                with col2:
                    st.markdown(f"**Category:** {c['category'].capitalize()}")
                    st.markdown(f"**Source:** Page {c['source_page']}, Clause `{c['source_clause']}`")