Spaces:
Build error
Build error
Commit ·
24beb7e
1
Parent(s): 78a797a
feat: japan letter pdf
Browse files- app/util/JapanMultiEntryvisaLetterGenerator.py +80 -0
- app/util/PDFDocumentGenerator.py +62 -0
- docker-compose.yaml +11 -0
- requirements.txt +2 -1
- server.py +47 -3
app/util/JapanMultiEntryvisaLetterGenerator.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import datetime
|
| 2 |
+
from fpdf import FPDF
|
| 3 |
+
from .PDFDocumentGenerator import PDFDocumentGenerator # The fpdf2-based one
|
| 4 |
+
|
| 5 |
+
class JapanMultiEntryVisaLetterGenerator(PDFDocumentGenerator):
|
| 6 |
+
"""
|
| 7 |
+
Generates the specific PDF for the Japan Multiple Entry Visa application
|
| 8 |
+
using the pure-Python fpdf2 library.
|
| 9 |
+
"""
|
| 10 |
+
def __init__(self, data: dict):
|
| 11 |
+
super().__init__(data)
|
| 12 |
+
|
| 13 |
+
def build_document(self, pdf: FPDF):
|
| 14 |
+
"""
|
| 15 |
+
Creates the full PDF document for the Japan visa letter
|
| 16 |
+
by adding content to the FPDF object.
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
name = self.data.get("name", "YOUR NAME")
|
| 20 |
+
address = self.data.get("address", "YOUR ADDRESS")
|
| 21 |
+
city_postal = self.data.get("city_postal", "CITY, POSTAL CODE")
|
| 22 |
+
email = self.data.get("email", "your.email@example.com")
|
| 23 |
+
phone = self.data.get("phone", "+123456789")
|
| 24 |
+
|
| 25 |
+
today = datetime.date.today()
|
| 26 |
+
formatted_date = today.strftime("%d, %B, %Y")
|
| 27 |
+
|
| 28 |
+
pdf.set_font('Arial', '', 11)
|
| 29 |
+
pdf.set_margins(25, 25)
|
| 30 |
+
|
| 31 |
+
sender_info = (
|
| 32 |
+
f"{name}\n"
|
| 33 |
+
f"{address}\n"
|
| 34 |
+
f"{city_postal}\n"
|
| 35 |
+
f"{email}\n"
|
| 36 |
+
f"{phone}"
|
| 37 |
+
)
|
| 38 |
+
pdf.multi_cell(0, 5, sender_info)
|
| 39 |
+
pdf.ln(10)
|
| 40 |
+
|
| 41 |
+
pdf.cell(0, 5, f"Jakarta, {formatted_date}")
|
| 42 |
+
|
| 43 |
+
pdf.ln(10)
|
| 44 |
+
recipient_info = (
|
| 45 |
+
"To:\n"
|
| 46 |
+
"Embassy of Japan in Indonesia\n"
|
| 47 |
+
"Jl. M.H. Thamrin No.24, Gondangdia, Menteng, Jakarta Pusat\n"
|
| 48 |
+
"Jakarta 10350"
|
| 49 |
+
)
|
| 50 |
+
pdf.multi_cell(0, 5, recipient_info)
|
| 51 |
+
|
| 52 |
+
pdf.ln(10)
|
| 53 |
+
pdf.set_font('Arial', 'B', 11)
|
| 54 |
+
pdf.cell(0, 5, "Subject: Reason for Applying for a Japan Multiple Entry Tourist Visa")
|
| 55 |
+
pdf.set_font('Arial', '', 11) #
|
| 56 |
+
pdf.ln(5)
|
| 57 |
+
pdf.multi_cell(0, 5, "Dear Sir/Madam,")
|
| 58 |
+
pdf.ln(5)
|
| 59 |
+
|
| 60 |
+
body_text = (
|
| 61 |
+
"I am writing to apply for a Japan multiple-entry tourist visa. The purpose "
|
| 62 |
+
"of my application is to facilitate easier travel to Japan for tourism "
|
| 63 |
+
"and personal reasons. I plan to visit Japan multiple times over the "
|
| 64 |
+
"next few years, and a multiple-entry visa would offer the flexibility "
|
| 65 |
+
"to do so without needing to reapply for a new visa each time.\n\n"
|
| 66 |
+
"This visa will enable me to explore various regions, experience "
|
| 67 |
+
"cultural events, and take part in Japan's tourism offerings.\n\n"
|
| 68 |
+
"I have attached all necessary documents to support my application. "
|
| 69 |
+
"Should you require any further information or additional "
|
| 70 |
+
"documentation, please feel free to contact me.\n\n"
|
| 71 |
+
"Thank you for considering my application. I look forward to your response."
|
| 72 |
+
)
|
| 73 |
+
pdf.multi_cell(0, 5, body_text)
|
| 74 |
+
|
| 75 |
+
pdf.ln(10)
|
| 76 |
+
pdf.cell(0, 5, "Sincerely,")
|
| 77 |
+
|
| 78 |
+
pdf.ln(25)
|
| 79 |
+
|
| 80 |
+
pdf.cell(0, 5, name)
|
app/util/PDFDocumentGenerator.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import tempfile
|
| 3 |
+
import datetime
|
| 4 |
+
from abc import ABC, abstractmethod
|
| 5 |
+
from fpdf import FPDF # <-- Import FPDF
|
| 6 |
+
|
| 7 |
+
class PDFDocumentGenerator(ABC):
|
| 8 |
+
"""
|
| 9 |
+
Abstract Base Class for generating a PDF document from data
|
| 10 |
+
using a pure-Python library (fpdf2).
|
| 11 |
+
"""
|
| 12 |
+
def __init__(self, data: dict):
|
| 13 |
+
"""
|
| 14 |
+
Initializes the generator with user-provided data.
|
| 15 |
+
|
| 16 |
+
:param data: A dictionary containing the data for the document.
|
| 17 |
+
"""
|
| 18 |
+
self.data = data
|
| 19 |
+
|
| 20 |
+
@abstractmethod
|
| 21 |
+
def build_document(self, pdf: FPDF):
|
| 22 |
+
"""
|
| 23 |
+
Subclasses must implement this method to build the PDF
|
| 24 |
+
by calling methods on the provided fpdf.FPDF object.
|
| 25 |
+
|
| 26 |
+
:param pdf: An FPDF object to build upon.
|
| 27 |
+
"""
|
| 28 |
+
pass
|
| 29 |
+
|
| 30 |
+
def compile_pdf(self, tempdir: str) -> (str, str):
|
| 31 |
+
"""
|
| 32 |
+
Generates the PDF document using fpdf2 and saves it in tempdir.
|
| 33 |
+
|
| 34 |
+
:param tempdir: The temporary directory to use for compilation.
|
| 35 |
+
:return: A tuple of (pdf_filepath, error_message).
|
| 36 |
+
On success, (pdf_filepath, None).
|
| 37 |
+
On failure, (None, error_message).
|
| 38 |
+
"""
|
| 39 |
+
try:
|
| 40 |
+
# 1. Initialize the FPDF object
|
| 41 |
+
pdf = FPDF()
|
| 42 |
+
pdf.add_page()
|
| 43 |
+
|
| 44 |
+
# 2. Let the subclass build the document
|
| 45 |
+
self.build_document(pdf)
|
| 46 |
+
|
| 47 |
+
# 3. Define file path and save the document
|
| 48 |
+
pdf_filename = "document.pdf"
|
| 49 |
+
pdf_filepath = os.path.join(tempdir, pdf_filename)
|
| 50 |
+
|
| 51 |
+
pdf.output(pdf_filepath)
|
| 52 |
+
|
| 53 |
+
# 4. Check if PDF was successfully created
|
| 54 |
+
if not os.path.exists(pdf_filepath):
|
| 55 |
+
return None, "PDF file was not created by fpdf2."
|
| 56 |
+
|
| 57 |
+
# Success
|
| 58 |
+
return pdf_filepath, None
|
| 59 |
+
|
| 60 |
+
except Exception as e:
|
| 61 |
+
print(f"Error during PDF generation: {e}")
|
| 62 |
+
return None, str(e)
|
docker-compose.yaml
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: '3.8'
|
| 2 |
+
|
| 3 |
+
services:
|
| 4 |
+
app:
|
| 5 |
+
build: .
|
| 6 |
+
ports:
|
| 7 |
+
- "7860:7860"
|
| 8 |
+
environment:
|
| 9 |
+
- PORT=7860
|
| 10 |
+
- PLAYWRIGHT_BROWSERS_PATH=/home/user/.cache/ms-playwright
|
| 11 |
+
restart: always
|
requirements.txt
CHANGED
|
@@ -10,4 +10,5 @@ google-genai
|
|
| 10 |
beautifulsoup4
|
| 11 |
pymupdf
|
| 12 |
flask[async]
|
| 13 |
-
pandas
|
|
|
|
|
|
| 10 |
beautifulsoup4
|
| 11 |
pymupdf
|
| 12 |
flask[async]
|
| 13 |
+
pandas
|
| 14 |
+
fpdf
|
server.py
CHANGED
|
@@ -1,16 +1,19 @@
|
|
| 1 |
import os
|
|
|
|
| 2 |
os.environ["PLAYWRIGHT_BROWSERS_PATH"] = "/home/user/.cache/ms-playwright"
|
| 3 |
|
| 4 |
import logging
|
| 5 |
-
from flask import Flask, request, jsonify
|
| 6 |
from dotenv import load_dotenv
|
| 7 |
import json
|
| 8 |
import requests
|
| 9 |
import uuid
|
|
|
|
|
|
|
| 10 |
|
| 11 |
from app.util.gen_ai_base import GenAIBaseClient
|
| 12 |
from app.util.browser_agent import BrowserAgent
|
| 13 |
-
|
| 14 |
import sys
|
| 15 |
sys.stdout.reconfigure(line_buffering=True)
|
| 16 |
API = "https://api-dev.spun.global"
|
|
@@ -109,7 +112,48 @@ def create_app() -> Flask:
|
|
| 109 |
# else:
|
| 110 |
# return jsonify({"error": "Failed to retrieve visa information"}), 404
|
| 111 |
# return jsonify({"error": "Unexpected error"}), 500
|
| 112 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
@app.route('/', methods=['GET'])
|
| 114 |
def hello_world():
|
| 115 |
return "Flask server is running.", 200
|
|
|
|
| 1 |
import os
|
| 2 |
+
import tempfile
|
| 3 |
os.environ["PLAYWRIGHT_BROWSERS_PATH"] = "/home/user/.cache/ms-playwright"
|
| 4 |
|
| 5 |
import logging
|
| 6 |
+
from flask import Flask, request, jsonify, send_file
|
| 7 |
from dotenv import load_dotenv
|
| 8 |
import json
|
| 9 |
import requests
|
| 10 |
import uuid
|
| 11 |
+
import asyncio
|
| 12 |
+
import io
|
| 13 |
|
| 14 |
from app.util.gen_ai_base import GenAIBaseClient
|
| 15 |
from app.util.browser_agent import BrowserAgent
|
| 16 |
+
from app.util.JapanMultiEntryvisaLetterGenerator import JapanMultiEntryVisaLetterGenerator
|
| 17 |
import sys
|
| 18 |
sys.stdout.reconfigure(line_buffering=True)
|
| 19 |
API = "https://api-dev.spun.global"
|
|
|
|
| 112 |
# else:
|
| 113 |
# return jsonify({"error": "Failed to retrieve visa information"}), 404
|
| 114 |
# return jsonify({"error": "Unexpected error"}), 500
|
| 115 |
+
@app.route('/generate/japan-multientry-tourist', methods=['POST'])
|
| 116 |
+
def generate_japan_multientry_tourist():
|
| 117 |
+
"""
|
| 118 |
+
Generates a Japan Visa Letter PDF and returns it as a downloadable file.
|
| 119 |
+
Expects JSON payload:
|
| 120 |
+
{
|
| 121 |
+
"name": "Your Name",
|
| 122 |
+
"address": "Your Address Line 1",
|
| 123 |
+
"city_postal": "City, Postal Code",
|
| 124 |
+
"email": "your.email@example.com",
|
| 125 |
+
"phone": "+123456789"
|
| 126 |
+
}
|
| 127 |
+
"""
|
| 128 |
+
try:
|
| 129 |
+
data = request.get_json()
|
| 130 |
+
if not data:
|
| 131 |
+
return jsonify({"error": "No JSON payload provided"}), 400
|
| 132 |
+
|
| 133 |
+
letter_generator = JapanMultiEntryVisaLetterGenerator(data)
|
| 134 |
+
|
| 135 |
+
with tempfile.TemporaryDirectory() as tempdir:
|
| 136 |
+
pdf_filepath, error_msg = letter_generator.compile_pdf(tempdir)
|
| 137 |
+
|
| 138 |
+
if error_msg:
|
| 139 |
+
return jsonify({"error": error_msg}), 500
|
| 140 |
+
|
| 141 |
+
with open(pdf_filepath, "rb") as f:
|
| 142 |
+
pdf_bytes = io.BytesIO(f.read())
|
| 143 |
+
|
| 144 |
+
pdf_bytes.seek(0)
|
| 145 |
+
|
| 146 |
+
return send_file(
|
| 147 |
+
pdf_bytes,
|
| 148 |
+
mimetype="application/pdf",
|
| 149 |
+
as_attachment=True,
|
| 150 |
+
download_name="Japan_Visa_Application_Letter.pdf"
|
| 151 |
+
)
|
| 152 |
+
|
| 153 |
+
except Exception as e:
|
| 154 |
+
print(f"Error in route: {e}")
|
| 155 |
+
return jsonify({"error": str(e)}), 500
|
| 156 |
+
|
| 157 |
@app.route('/', methods=['GET'])
|
| 158 |
def hello_world():
|
| 159 |
return "Flask server is running.", 200
|