Spaces:
Sleeping
Sleeping
| from pydantic import BaseModel | |
| from typing import Optional, List, Dict, Any | |
| from enum import StrEnum | |
| # ββ Authorized Representatives lookup map ββββββββββββββββββββββββββββββββββββββ | |
| # Keys: full name (displayed in dropdown) | |
| # Values: dict of data_key β value for the other rep fields | |
| REPRESENTATIVES: Dict[str, Dict[str, str]] = { | |
| "Ahmad Royhan": { | |
| "rep_id": "3174050905990008", | |
| "rep_address": "Jl. Kramat 1 No 15, Kwitang", | |
| "rep_city": "Jakarta Selatan", | |
| }, | |
| # Add more representatives here: | |
| # "Full Name": { | |
| # "rep_id": "...", | |
| # "rep_address": "...", | |
| # "rep_city": "...", | |
| # }, | |
| } | |
| class DocumentType(StrEnum): | |
| COVER_LETTER_SCHENGEN = "cover_letter_schengen" | |
| COVER_LETTER_UK = "cover_letter_uk" | |
| LETTER_OF_AUTHORIZATION = "letter_of_authorization" | |
| LETTER_OF_AUTHORIZATION_MINOR = "letter_of_authorization_minor" | |
| # INVITATION_LETTER = "invitation_letter" | |
| # BANK_REFERENCE = "bank_reference" | |
| class FieldSchema(BaseModel): | |
| """Describes one editable field for a document type.""" | |
| widget_key: str # Streamlit session-state key | |
| data_key: str # Key inside the data dict | |
| label: str | |
| placeholder: str = "" | |
| description: Optional[str] = None # Sent to /prefill-document; falls back to placeholder | |
| field_type: str = "text_input" # "text_input" | "text_area" | "select" | |
| options: Optional[List[str]] = None # For field_type="select" | |
| section: str = "general" # Maps to a column/section in the form | |
| nested_under: Optional[str] = None # e.g. "personal_details" | |
| class DocumentSchema(BaseModel): | |
| """Full metadata for a document type. | |
| Drives form rendering, session-state key management, and API calls. | |
| The backend uses `document_name` to ask an LLM to generate the right SQL. | |
| """ | |
| document_type: DocumentType | |
| title: str | |
| icon: str = "π" | |
| # section_key -> display header, order is preserved | |
| sections: Dict[str, str] | |
| fields: List[FieldSchema] | |
| has_trip_type: bool = False | |
| has_group_members: bool = False | |
| def widget_keys(self) -> List[str]: | |
| """All Streamlit widget keys that must be cleared on undo/redo.""" | |
| keys: List[str] = [] | |
| if self.has_trip_type: | |
| keys.append(f"{self.document_type}_trip_type") | |
| keys.extend(f.widget_key for f in self.fields) | |
| if self.has_group_members: | |
| keys.append(f"{self.document_type}_group_members_editor") | |
| return keys | |
| def fields_in_section(self, section: str) -> List[FieldSchema]: | |
| return [f for f in self.fields if f.section == section] | |
| def nested_fields(self, nested_under: str) -> List[FieldSchema]: | |
| return [f for f in self.fields if f.nested_under == nested_under] | |
| def top_level_fields(self, section: str) -> List[FieldSchema]: | |
| return [f for f in self.fields if f.section == section and f.nested_under is None] | |
| def generate_person_fields(person_type: str, section: str, role_label: str) -> List[FieldSchema]: | |
| """Helper to keep the schema DRY by generating common personal fields for Minor LOA.""" | |
| prefix = f"loa_minor_{person_type}" | |
| return [ | |
| FieldSchema(widget_key=f"{prefix}_name", data_key=f"{person_type}_name", label=f"{role_label} Name", placeholder="e.g. John Doe", section=section), | |
| FieldSchema(widget_key=f"{prefix}_id", data_key=f"{person_type}_id", label=f"{role_label} National ID", placeholder="e.g. 3174000000000000", section=section), | |
| FieldSchema(widget_key=f"{prefix}_address", data_key=f"{person_type}_address", label=f"{role_label} Address", placeholder="e.g. Jl. Bangka XI...", section=section), | |
| FieldSchema(widget_key=f"{prefix}_city", data_key=f"{person_type}_city", label=f"{role_label} City/Province", placeholder="e.g. Jakarta Selatan", section=section), | |
| ] | |
| DOCUMENT_REGISTRY: Dict[str, DocumentSchema] = { | |
| DocumentType.COVER_LETTER_UK: DocumentSchema( | |
| document_type=DocumentType.COVER_LETTER_UK, | |
| title="UK Visa Cover Letter", | |
| icon="π¬π§", | |
| sections={ | |
| "trip": "π Trip & Embassy Details", | |
| "applicant": "π€ Main Applicant", | |
| "employment": "πΌ Employment & Ties", | |
| "financials": "π° Financial Clarifications", | |
| "contact": "π Contact & Documents", | |
| }, | |
| fields=[ | |
| # ββ Trip & Embassy Section ββββββββββββββββββββββββββββββββ | |
| FieldSchema(widget_key="uk_cl_city", data_key="city", label="City of Application", placeholder="e.g. Jakarta", section="trip"), | |
| FieldSchema(widget_key="uk_cl_emb_city", data_key="embassy_city", label="Embassy City", placeholder="e.g. Jakarta", section="trip"), | |
| FieldSchema(widget_key="uk_cl_emb_country", data_key="embassy_country", label="Embassy Country", placeholder="e.g. Indonesia", section="trip"), | |
| FieldSchema(widget_key="uk_cl_visa_type", data_key="visa_type", label="Visa Type", placeholder="e.g. Standard Visitor", section="trip"), | |
| FieldSchema(widget_key="uk_cl_purpose", data_key="visit_purpose", label="Visit Purpose", placeholder="e.g. tourism and cultural exploration", section="trip"), | |
| FieldSchema(widget_key="uk_cl_start", data_key="start_date", label="Travel Start Date", placeholder="e.g. 17 May 2026", section="trip"), | |
| FieldSchema(widget_key="uk_cl_end", data_key="end_date", label="Travel End Date", placeholder="e.g. 23 May 2026", section="trip"), | |
| FieldSchema(widget_key="uk_cl_duration", data_key="duration", label="Duration", placeholder="e.g. seven days and six nights", section="trip"), | |
| FieldSchema(widget_key="uk_cl_locations", data_key="locations_to_visit", label="Locations to Visit", placeholder="e.g. London and Edinburgh", section="trip"), | |
| FieldSchema(widget_key="uk_cl_highlight", data_key="trip_highlight", label="Trip Highlight", placeholder="e.g. attend an Orchestra Concert", field_type="text_area", section="trip"), | |
| # ββ Applicant Section (nested under personal_details) βββββ | |
| FieldSchema(widget_key="uk_cl_pd_name", data_key="name", label="Full Name", placeholder="e.g. John Doe", section="applicant", nested_under="personal_details"), | |
| FieldSchema(widget_key="uk_cl_pd_dob", data_key="dob", label="Date of Birth", placeholder="e.g. 3 October 2000", section="applicant", nested_under="personal_details"), | |
| FieldSchema(widget_key="uk_cl_pd_nationality", data_key="nationality", label="Nationality", placeholder="e.g. Indonesian", section="applicant", nested_under="personal_details"), | |
| FieldSchema(widget_key="uk_cl_pd_passport", data_key="passport_number", label="Passport Number", placeholder="e.g. X3236019", section="applicant", nested_under="personal_details"), | |
| FieldSchema(widget_key="uk_cl_home_country", data_key="home_country", label="Home Country", placeholder="e.g. Indonesia", section="applicant"), | |
| # ββ Employment Section ββββββββββββββββββββββββββββββββββββ | |
| FieldSchema(widget_key="uk_cl_job_title", data_key="job_title", label="Job Title", placeholder="e.g. Front End Developer", section="employment"), | |
| FieldSchema(widget_key="uk_cl_company", data_key="company_name", label="Company Name", placeholder="e.g. PT Radya Anugrah", section="employment"), | |
| FieldSchema(widget_key="uk_cl_job_resp", data_key="job_responsibilities", label="Job Responsibilities", placeholder="e.g. designing and building web interfaces...", field_type="text_area", section="employment"), | |
| FieldSchema(widget_key="uk_cl_projects", data_key="project_details", label="Project Details (Ties)", placeholder="e.g. pharmaceutical tracking systems...", field_type="text_area", section="employment"), | |
| FieldSchema(widget_key="uk_cl_add_income", data_key="additional_income_source", label="Additional Income Source", placeholder="e.g. project-based freelance income", section="employment"), | |
| FieldSchema(widget_key="uk_cl_family", data_key="family_members", label="Family Members (Ties)", placeholder="e.g. parents", section="employment"), | |
| # ββ Financials Section ββββββββββββββββββββββββββββββββββββ | |
| FieldSchema(widget_key="uk_cl_bank_names", data_key="bank_names", label="Bank Names", placeholder="e.g. BNI & Jago", section="financials"), | |
| FieldSchema(widget_key="uk_cl_acc_usage", data_key="account_usage_explanation", label="Account Usage Explanation", placeholder="e.g. BNI for payroll, Jago for daily operations", field_type="text_area", section="financials"), | |
| FieldSchema(widget_key="uk_cl_other_funds_src", data_key="other_funds_source", label="Other Funds Source", placeholder="e.g. Freelance Inflows", section="financials"), | |
| FieldSchema(widget_key="uk_cl_other_funds_exp", data_key="other_funds_explanation", label="Other Funds Explanation", placeholder="Explain freelance invoices...", field_type="text_area", section="financials"), | |
| FieldSchema(widget_key="uk_cl_turnover_rsn", data_key="turnover_reason", label="High Turnover Reason", placeholder="e.g. Investments", section="financials"), | |
| FieldSchema(widget_key="uk_cl_turnover_exp", data_key="turnover_explanation", label="High Turnover Explanation", placeholder="Explain high turnover...", field_type="text_area", section="financials"), | |
| FieldSchema(widget_key="uk_cl_outflow_rsn", data_key="outflow_reason", label="Large Outflow Reason", placeholder="e.g. One-off Asset", section="financials"), | |
| FieldSchema(widget_key="uk_cl_outflow_exp", data_key="outflow_explanation", label="Large Outflow Explanation", placeholder="Explain large outflow...", field_type="text_area", section="financials"), | |
| FieldSchema(widget_key="uk_cl_monthly_exp", data_key="monthly_expenditure_amount", label="Monthly Expenditure", placeholder="e.g. IDR 9,000,000", section="financials"), | |
| FieldSchema(widget_key="uk_cl_liquid", data_key="liquid_funds_amount", label="Liquid Funds Available", placeholder="e.g. IDR 75,000,000", section="financials"), | |
| # ββ Contact & Documents Section βββββββββββββββββββββββββββ | |
| FieldSchema(widget_key="uk_cl_email", data_key="email", label="Email", placeholder="e.g. m.irfan@gmail.com", section="contact"), | |
| FieldSchema(widget_key="uk_cl_phone", data_key="phone_number", label="Phone Number", placeholder="e.g. +6283829851734", section="contact"), | |
| FieldSchema(widget_key="uk_cl_add_docs", data_key="list_of_documents", label="List of Documents", placeholder="List any docs like 'β’ Payroll slip'...", field_type="text_area", section="contact"), | |
| ], | |
| has_trip_type=True, | |
| has_group_members=True, | |
| ), | |
| DocumentType.COVER_LETTER_SCHENGEN: DocumentSchema( | |
| document_type=DocumentType.COVER_LETTER_SCHENGEN, | |
| title="Schengen Visa Cover Letter", | |
| icon="π", | |
| sections={ | |
| "trip": "π Trip Details", | |
| "applicant": "π€ Main Applicant Details", | |
| "contact": "π Contact & Financials", | |
| }, | |
| fields=[ | |
| # ββ Trip section βββββββββββββββββββββββββββββββ | |
| # data_key matches the prefill response keys returned by /prefill-document | |
| FieldSchema(widget_key="cl_country", data_key="country", label="Country of Embassy", placeholder="e.g. Germany", section="trip"), | |
| FieldSchema(widget_key="cl_city", data_key="city", label="City of Application", placeholder="e.g. Jakarta", section="trip"), | |
| FieldSchema(widget_key="cl_purpose", data_key="purpose", label="Purpose of Trip", placeholder="e.g. tourism", section="trip"), | |
| FieldSchema(widget_key="cl_main_dest", data_key="main_dest", label="Main Destination", placeholder="e.g. Germany", section="trip"), | |
| FieldSchema(widget_key="cl_event", data_key="event", label="Event", placeholder="e.g. personal vacation", section="trip"), | |
| FieldSchema(widget_key="cl_other_dest", data_key="other_dest", label="Other Destinations", placeholder="e.g. France and Italy", section="trip"), | |
| FieldSchema(widget_key="cl_start", data_key="start", label="Travel Start Date", placeholder="e.g. 2026-05-01", section="trip"), | |
| FieldSchema(widget_key="cl_end", data_key="end", label="Travel End Date", placeholder="e.g. 2026-05-14", section="trip"), | |
| FieldSchema(widget_key="cl_duration", data_key="duration", label="Duration", placeholder="e.g. 14 days", section="trip"), | |
| # ββ Applicant section (nested under personal_details) ββ | |
| FieldSchema(widget_key="cl_pd_name", data_key="name", label="Full Name", placeholder="e.g. John Doe", section="applicant", nested_under="personal_details"), | |
| FieldSchema(widget_key="cl_pd_dob", data_key="dob", label="Date of Birth", placeholder="e.g. 1st Jan 1990", section="applicant", nested_under="personal_details"), | |
| FieldSchema(widget_key="cl_pd_nationality", data_key="nationality", label="Nationality", placeholder="e.g. Indonesian", section="applicant", nested_under="personal_details"), | |
| FieldSchema(widget_key="cl_pd_occupation", data_key="occupation", label="Occupation", placeholder="e.g. Engineer", section="applicant", nested_under="personal_details"), | |
| FieldSchema(widget_key="cl_pd_passport_number", data_key="passport_number", label="Passport Number", placeholder="e.g. A1234567", section="applicant", nested_under="personal_details"), | |
| # ββ Contact section ββββββββββββββββββββββββββββ | |
| FieldSchema(widget_key="cl_trip_highlight", data_key="trip_highlight", label="Trip Highlight", placeholder="Key highlights of your trip...", field_type="text_area", section="contact"), | |
| FieldSchema(widget_key="cl_contact_email", data_key="contact_email", label="Contact Email", placeholder="e.g. john@email.com", section="contact"), | |
| FieldSchema(widget_key="cl_contact_phone", data_key="contact_phone", label="Contact Phone", placeholder="e.g. +62 812 345 6789", section="contact"), | |
| FieldSchema(widget_key="cl_job_commitment", data_key="job_commitment", label="Job Commitment", placeholder="e.g. returning to work on...", section="contact"), | |
| FieldSchema(widget_key="cl_financial_status", data_key="financial_status", label="Financial Status", placeholder="e.g. sufficient funds", section="contact"), | |
| FieldSchema(widget_key="cl_list_of_documents", data_key="list_of_documents", label="List of Documents", placeholder="e.g. passport, bank statement, hotel booking...", field_type="text_area", section="contact"), | |
| ], | |
| has_trip_type=True, | |
| has_group_members=True, | |
| ), | |
| DocumentType.LETTER_OF_AUTHORIZATION: DocumentSchema( | |
| document_type=DocumentType.LETTER_OF_AUTHORIZATION, | |
| title="Letter of Authorization for Visa", | |
| icon="βοΈ", | |
| sections={ | |
| "grantor": "π€ First Party (Grantor)", | |
| "representative": "π€ Authorized Representative", | |
| "authorization": "π Authorization Details", | |
| }, | |
| fields=[ | |
| # ββ Grantor section βββββββββββββββββββββββββββββββ | |
| FieldSchema(widget_key="loa_grantor_name", data_key="grantor_name", label="Full Name based on Passport", placeholder="e.g. Neni Diankrisna Putri", description="Full name of the applicant as it appears on their passport (maps to applicant_name or full_name from the profile). This person is granting authorization.", section="grantor"), | |
| FieldSchema(widget_key="loa_grantor_address", data_key="grantor_address", label="Address", placeholder="e.g. Jl. Mustika Jaya I...", description="Home address of the applicant (grantor).", section="grantor"), | |
| FieldSchema(widget_key="loa_grantor_id", data_key="grantor_id", label="National ID Number (Ktp)", placeholder="e.g. 3175025510900006", description="Indonesian national ID number (NIK/KTP) of the applicant (grantor).", section="grantor"), | |
| FieldSchema(widget_key="loa_grantor_city", data_key="grantor_city", label="City/Province", placeholder="e.g. Jakarta Timur", description="City and/or province where the applicant (grantor) resides.", section="grantor"), | |
| # ββ Representative section ββββββββββββββββββββββββ | |
| FieldSchema(widget_key="loa_rep_name", data_key="rep_name", label="Full Name", placeholder="Select a representative", section="representative", field_type="select", options=list(REPRESENTATIVES.keys())), | |
| FieldSchema(widget_key="loa_rep_id", data_key="rep_id", label="National ID Number", placeholder="e.g. 3174050905990008", section="representative"), | |
| FieldSchema(widget_key="loa_rep_address", data_key="rep_address", label="Address", placeholder="e.g. Jl. Kramat 1 No 15...", section="representative"), | |
| FieldSchema(widget_key="loa_rep_city", data_key="rep_city", label="City/Province", placeholder="e.g. Jakarta Selatan", section="representative"), | |
| # ββ Authorization section βββββββββββββββββββββββββ | |
| FieldSchema(widget_key="loa_passport_num", data_key="passport_number", label="Passport Number", placeholder="e.g. X1373768", section="authorization"), | |
| FieldSchema(widget_key="loa_passport_name", data_key="passport_name", label="Passport Name", placeholder="e.g. Neni Diankrisna Putri", section="authorization"), | |
| FieldSchema(widget_key="loa_place", data_key="city", label="Signing City", placeholder="e.g. Jakarta", section="authorization"), | |
| FieldSchema(widget_key="loa_date", data_key="date", label="Signing Date", placeholder="e.g. 13 February 2026", section="authorization"), | |
| ], | |
| has_trip_type=False, | |
| has_group_members=False, | |
| ), | |
| DocumentType.LETTER_OF_AUTHORIZATION_MINOR: DocumentSchema( | |
| document_type=DocumentType.LETTER_OF_AUTHORIZATION_MINOR, | |
| title="Letter of Authorization for Minor's Visa", | |
| icon="π§", | |
| sections={ | |
| "parents": "πͺ Parents (First Party)", | |
| "representative": "π€ Authorized Representative", | |
| "authorization": "π Authorization Details", | |
| }, | |
| fields=( | |
| # ββ Parents (Grantors) section ββββββββββββββββββββββββ | |
| generate_person_fields("father", "parents", "Father's") + | |
| generate_person_fields("mother", "parents", "Mother's") + | |
| # ββ Representative section (name is a dropdown, rest auto-filled) ββ | |
| [FieldSchema(widget_key="loa_minor_rep_name", data_key="rep_name", label="Representative's Name", | |
| section="representative", field_type="select", options=list(REPRESENTATIVES.keys()))] + | |
| generate_person_fields("rep", "representative", "Representative's")[1:] + | |
| # ββ Authorization & Minor section βββββββββββββββββββββ | |
| [ | |
| FieldSchema(widget_key="loa_minor_passport", data_key="passport_number", label="Child's Passport Number", placeholder="e.g. X2588429", section="authorization"), | |
| FieldSchema(widget_key="loa_minor_pass_name", data_key="passport_name", label="Child's Passport Name", placeholder="e.g. Mohamad Noah...", section="authorization"), | |
| FieldSchema(widget_key="loa_minor_place", data_key="city", label="Signing City", placeholder="e.g. Jakarta", section="authorization"), | |
| FieldSchema(widget_key="loa_minor_date", data_key="date", label="Signing Date", placeholder="e.g. 24 November 2025", section="authorization"), | |
| ] | |
| ), | |
| has_trip_type=False, | |
| has_group_members=False, | |
| ), | |
| } | |
| # ββ API request models βββββββββββββββββββββββββββββββββββββ | |
| class PrefillDocumentRequest(BaseModel): | |
| """ | |
| Sent to /prefill-document (structure mode). | |
| `structure` describes the fields the LLM should map from DB data. | |
| Returns a flat dict of { field_key: value_or_null, ..., "_missing_required": [...] }. | |
| """ | |
| application_id: int | |
| structure: List[Dict[str, Any]] | |
| class GenerateDocumentRequest(BaseModel): | |
| """ | |
| Sent to /generate-document. | |
| `doc_data` carries all form fields as-is; the backend merges them on top | |
| of the DB data and generates the document. | |
| Adding a new document type requires no change to this model. | |
| """ | |
| application_id: int | |
| document_name: str # value from DocumentType | |
| doc_data: Optional[Dict[str, Any]] = None | |
| class DocChatRequest(BaseModel): | |
| """Sent to /generate-document/chat (new stateless draft-revision flow).""" | |
| query: str | |
| history: List[Dict[str, str]] | |
| session_uuid: Optional[str] = None | |
| current_document_content: str = "" | |
| structure: Optional[List[Dict[str, Any]]] = None | |
| class GenerateDraftRequest(BaseModel): | |
| """ | |
| Sent to /generate-document/draft. | |
| Generates a Markdown draft without creating a Google Doc. | |
| `doc_type` and `structure` are stored on the frontend for later handover to the core API. | |
| """ | |
| doc_type: str | |
| data: Dict[str, Any] | |
| structure: Optional[List[Dict[str, Any]]] = None # field definitions for core API handover | |
| session_uuid: Optional[str] = None | |
| class ExportDocumentRequest(BaseModel): | |
| """Sent to /export-document once the user is satisfied with the draft.""" | |
| document_content: str | |
| title: Optional[str] = None | |
| export_format: str = "gdocs" | |
| # ββ Shared sub-models (used for type hints / validation internally) ββ | |
| class PersonalDetails(BaseModel): | |
| name: Optional[str] = None | |
| dob: Optional[str] = None | |
| nationality: Optional[str] = None | |
| occupation: Optional[str] = None | |
| passport_number: Optional[str] = None | |
| class GroupMember(BaseModel): | |
| relationship: Optional[str] = None | |
| name: Optional[str] = None | |
| dob: Optional[str] = None | |
| occupation: Optional[str] = None | |
| nationality: Optional[str] = None | |
| passport_number: Optional[str] = None | |