Spaces:
Sleeping
Sleeping
Download app/ui/confirmation_screen.py from SkillArbitrage/UpworkAutomation: direct link, hf CLI and curl.
- Browser
- Download file 7.76 kB
-
https://huggingface.co/spaces/SkillArbitrage/UpworkAutomation/resolve/main/app/ui/confirmation_screen.py
- Command line
-
hf download hf://spaces/SkillArbitrage/UpworkAutomation/app/ui/confirmation_screen.py
-
curl -L -o confirmation_screen.py https://huggingface.co/spaces/SkillArbitrage/UpworkAutomation/resolve/main/app/ui/confirmation_screen.py
7.76 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 re | |
| import streamlit as st | |
| from app.config import get_settings | |
| from app.services.screenshot_parser import SCREENSHOT_FIELDS, _sanitize_field_value | |
| 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", | |
| "contract_type": "Contract type", | |
| "client_jobs_posted": "Client jobs posted", | |
| "client_hires": "Client hires", | |
| "client_last_active": "Client last active", | |
| "hidden_keyword": "Hidden keyword", | |
| "screening_questions": "Screening questions", | |
| } | |
| # Grouped, user-friendly sections. | |
| FIELD_GROUPS: list[tuple[str, list[str]]] = [ | |
| ( | |
| "Job basics", | |
| [ | |
| "job_title", | |
| "job_description", | |
| "client_need", | |
| "project_type", | |
| "contract_type", | |
| "experience_level", | |
| "project_duration", | |
| ], | |
| ), | |
| ( | |
| "Client details", | |
| [ | |
| "payment_verification", | |
| "client_rating", | |
| "client_total_spend", | |
| "hire_rate", | |
| "client_jobs_posted", | |
| "client_hires", | |
| "client_location", | |
| "client_last_active", | |
| ], | |
| ), | |
| ( | |
| "Budget & competition", | |
| ["budget_or_rate", "proposal_count", "posted_date", "connects_required"], | |
| ), | |
| ( | |
| "Skills & requirements", | |
| [ | |
| "required_skills", | |
| "required_deliverables", | |
| "screening_questions", | |
| "hidden_keyword", | |
| ], | |
| ), | |
| ] | |
| # Fields that benefit from a multi-line box. | |
| LONG_FIELDS = { | |
| "job_description", | |
| "client_need", | |
| "required_deliverables", | |
| "required_skills", | |
| "screening_questions", | |
| } | |
| def _empty_field() -> dict[str, str]: | |
| return {"value": NOT_VISIBLE, "confidence": "low", "source": "not visible"} | |
| def _clean_value(raw: object, key: str) -> str: | |
| """Sanitise a field value exactly as the normal flow's ``confirm_fields`` | |
| does (whitespace, line breaks, length cap); blank reads as "Not visible".""" | |
| value = _sanitize_field_value(str(raw or ""), key=key) | |
| if not value or value.lower() == NOT_VISIBLE.lower(): | |
| return NOT_VISIBLE | |
| return value | |
| 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() | |
| original_value = _clean_value(original.get("value", NOT_VISIBLE), key) | |
| widget_key = f"confirm_field__{key}" | |
| # A field with no box on this page keeps what the screenshot read | |
| # instead of being wiped to "Not visible". | |
| if widget_key in st.session_state: | |
| new_value = _clean_value(st.session_state[widget_key], key) | |
| else: | |
| new_value = original_value | |
| confirmed[key] = { | |
| "value": new_value, | |
| "confidence": original.get("confidence", "low"), | |
| "source": _resolve_source( | |
| original_value=original_value, | |
| 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: | |
| # Keyed per group: the key is what the widget layer styles as a panel, | |
| # and inside a loop it has to differ on every iteration. | |
| slug = re.sub(r"[^a-z0-9]+", "_", title.lower()).strip("_") | |
| with st.container(border=True, key=f"ups_panel_fields_{slug}"): | |
| 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() | |