File size: 6,425 Bytes
6303ae6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
"""Screen 4: Confirm details (developer/admin only).

This screen is gated behind ``SHOW_DEBUG_PANEL=true``. Normal users never
see it — their job details are confirmed automatically in the backend
right after extraction (see :mod:`app.ui.screenshot_screen`). In debug
mode it lets a developer review/edit the extracted job fields before the
analysis runs. Every value is editable; unknown values stay as
"Not visible" so the tool never scores against a guess. Fields are
grouped into four simple sections to make review quick.
"""

from __future__ import annotations

import streamlit as st

from app.config import get_settings
from app.services.screenshot_parser import SCREENSHOT_FIELDS
from app.ui import theme


NOT_VISIBLE = "Not visible"

FIELD_LABELS: dict[str, str] = {
    "job_title": "Job title",
    "job_description": "Job description",
    "client_need": "Client need",
    "required_deliverables": "Required deliverables",
    "required_skills": "Required skills",
    "budget_or_rate": "Budget or rate",
    "project_type": "Project type",
    "experience_level": "Experience level",
    "project_duration": "Project duration",
    "posted_date": "Posted",
    "proposal_count": "Proposals submitted",
    "payment_verification": "Payment verified",
    "client_rating": "Client rating",
    "client_total_spend": "Client total spend",
    "hire_rate": "Hire rate",
    "client_location": "Client location",
    "connects_required": "Connects required",
}

# Grouped, user-friendly sections.
FIELD_GROUPS: list[tuple[str, list[str]]] = [
    (
        "Job basics",
        [
            "job_title",
            "job_description",
            "client_need",
            "project_type",
            "experience_level",
            "project_duration",
        ],
    ),
    (
        "Client details",
        [
            "payment_verification",
            "client_rating",
            "client_total_spend",
            "hire_rate",
            "client_location",
        ],
    ),
    (
        "Budget & competition",
        ["budget_or_rate", "proposal_count", "posted_date", "connects_required"],
    ),
    (
        "Skills & requirements",
        ["required_skills", "required_deliverables"],
    ),
]

# Fields that benefit from a multi-line box.
LONG_FIELDS = {
    "job_description",
    "client_need",
    "required_deliverables",
    "required_skills",
}


def _empty_field() -> dict[str, str]:
    return {"value": NOT_VISIBLE, "confidence": "low", "source": "not visible"}


def _resolve_source(original_value: str, original_source: str, new_value: str) -> str:
    if new_value == NOT_VISIBLE:
        return "not visible"
    if original_value == NOT_VISIBLE:
        return "manually entered"
    if new_value != original_value:
        return "user corrected"
    return original_source


def _render_field(key: str, extracted: dict, debug: bool) -> None:
    label = FIELD_LABELS.get(key, key)
    field = extracted.get(key) or _empty_field()
    value = field.get("value", NOT_VISIBLE)
    widget_key = f"confirm_field__{key}"
    if key in LONG_FIELDS:
        st.text_area(label, value=value, key=widget_key, height=90)
    else:
        st.text_input(label, value=value, key=widget_key)
    if debug:
        st.caption(
            f"confidence `{field.get('confidence', 'low')}` • "
            f"source `{field.get('source', 'not visible')}`"
        )


def _commit(extracted: dict) -> None:
    confirmed: dict[str, dict[str, str]] = {}
    for key in SCREENSHOT_FIELDS:
        original = extracted.get(key) or _empty_field()
        raw_value = st.session_state.get(f"confirm_field__{key}", "")
        new_value = (raw_value or "").strip()
        if not new_value or new_value.lower() == NOT_VISIBLE.lower():
            new_value = NOT_VISIBLE
        confirmed[key] = {
            "value": new_value,
            "confidence": original.get("confidence", "low"),
            "source": _resolve_source(
                original_value=original.get("value", NOT_VISIBLE),
                original_source=original.get("source", "not visible"),
                new_value=new_value,
            ),
        }

    st.session_state.confirmed_job_fields = confirmed
    st.session_state.fields_confirmed = True
    # Force the analysis to recompute from the confirmed values. The
    # confirmed fields themselves were just set above, so we clear only
    # the derived per-opportunity outputs (and the fingerprint, so the
    # analysis screen treats this as a new opportunity).
    for k in (
        "current_job_fingerprint",
        "match_data",
        "scoring_result",
        "recommendation_result",
        "generated_proposal",
        "verified_proposal",
        "proposal_context",
        "selected_evidence_for_proposal",
    ):
        st.session_state[k] = None
    st.session_state.current_step = "analysis"


def render() -> None:
    settings = get_settings()
    debug = bool(getattr(settings, "show_debug_panel", False))

    if not debug:
        # Confirm Details is a developer/admin-only screen. Normal users never
        # reach it — job details are confirmed automatically in the backend
        # after extraction. If reached directly, bounce to the right step.
        st.session_state.current_step = (
            "analysis" if st.session_state.get("fields_confirmed") else "screenshot"
        )
        return

    extracted = st.session_state.get("extracted_job_fields")
    if not extracted:
        st.error(
            "This step is locked. Upload a screenshot and extract job details first."
        )
        if st.button("Back to Job Screenshot", key="back_to_screenshot_from_confirm"):
            st.session_state.current_step = "screenshot"
            st.rerun()
        return

    st.subheader("Confirm Details")
    st.caption("Developer/admin mode — normal users skip this step.")
    st.write(
        "Review the extracted job details and correct anything that's off. "
        "Leave unknown values as “Not visible” — analysis runs once you confirm."
    )

    for title, keys in FIELD_GROUPS:
        with st.container(border=True):
            theme.section_label(title)
            for key in keys:
                _render_field(key, extracted, debug)

    st.write("")
    if st.button("Confirm Details & Analyze", type="primary", key="confirm_fields_btn"):
        _commit(extracted)
        st.rerun()