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