UpworkAutomation / app /ui /confirmation_screen.py
ananyajoshi's picture
Deploy app: Docker Space, session-only API keys, cloud file-upload dossier
6303ae6 verified
Raw
History Blame Contribute Delete
6.43 kB
"""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()