Spaces:
Sleeping
Sleeping
File size: 24,105 Bytes
fef5f29 2b326e3 fef5f29 ec196c2 a9f36a6 e01e2f8 ec196c2 fef5f29 2b326e3 fef5f29 e01e2f8 fef5f29 a9f36a6 e01e2f8 a9f36a6 ec196c2 fef5f29 ec196c2 fef5f29 ec196c2 e01e2f8 ec196c2 fef5f29 ec196c2 fef5f29 a9f36a6 fef5f29 e01e2f8 2b326e3 e01e2f8 2b326e3 e01e2f8 2b326e3 e01e2f8 fef5f29 ec196c2 fef5f29 ec196c2 fef5f29 ec196c2 fef5f29 ec196c2 fef5f29 | 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 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 | 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
|