Spaces:
Sleeping
Sleeping
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()
|