04/22 Add support old document *.doc
This commit is contained in:
@@ -37,16 +37,16 @@ logger = logging.getLogger("ai_summary_view")
|
||||
SUPPORTED_EXT = {
|
||||
".txt", ".md", ".csv",
|
||||
".pdf",
|
||||
".docx",
|
||||
".doc", ".docx",
|
||||
".xlsx", ".xls",
|
||||
}
|
||||
|
||||
FILE_DIALOG_TYPES = [
|
||||
("Supported documents",
|
||||
"*.txt *.md *.csv *.pdf *.docx *.xlsx *.xls"),
|
||||
"*.txt *.md *.csv *.pdf *.docx *.doc *.xlsx *.xls"),
|
||||
("Text files", "*.txt *.md *.csv"),
|
||||
("PDF files", "*.pdf"),
|
||||
("Word documents", "*.docx"),
|
||||
("Word documents", "*.docx *.doc"),
|
||||
("Excel files", "*.xlsx *.xls"),
|
||||
("All files", "*.*"),
|
||||
]
|
||||
@@ -65,11 +65,18 @@ _CFG_KEY_KEY = "api_key"
|
||||
_CFG_KEY_MODEL = "model"
|
||||
|
||||
# -- Focused extraction prompt (procurement / solicitation) --------------------
|
||||
_OFFICE_ADDRESS = "2815 Hartland Road, Falls Church, VA 22043, USA"
|
||||
|
||||
_EXTRACTION_PROMPT = """\
|
||||
You are an expert government procurement analyst.
|
||||
The user has provided {n} document(s). Your job is to extract specific \
|
||||
information from each document and present it in a clean, structured format.
|
||||
|
||||
IMPORTANT — Our office is located at:
|
||||
{office}
|
||||
Use this as the ORIGIN address for all driving distance and travel time \
|
||||
calculations in field #9 below.
|
||||
|
||||
For EACH document, extract and clearly label the following fields \
|
||||
(write "N/A" if a field is not found):
|
||||
|
||||
@@ -78,10 +85,16 @@ For EACH document, extract and clearly label the following fields \
|
||||
3. Set-Aside (e.g. Small Business, 8(a), N/A)
|
||||
4. Description / Scope of Work
|
||||
5. Work Site / Location(s)
|
||||
6. Pre-Proposal Conference / Site-Visit (date, time, location — mandatory or optional)
|
||||
6. Pre-Proposal Conference / Site-Visit (date, time, full address — mandatory or optional)
|
||||
7. Point of Contact (POC) (name, phone, email)
|
||||
8. Total Square Footage (if applicable)
|
||||
9. Driving Distance / Travel Time to Pre-Proposal Conference
|
||||
9. Driving Distance & Travel Time
|
||||
- Origin: {office}
|
||||
- Destination: the Pre-Proposal Conference or Site-Visit address from field #6 \
|
||||
(if no conference, use the primary Work Site address from field #5)
|
||||
- Provide your best estimate of driving distance (miles) and typical \
|
||||
driving time using major highways
|
||||
- Note that these are AI estimates; actual times may vary with traffic
|
||||
10. Last Day to Submit Questions
|
||||
11. Due Date & Time
|
||||
12. Any other notable requirements or deadlines
|
||||
@@ -626,7 +639,7 @@ class AiSummaryView(ttk.Frame):
|
||||
# 2. Build prompt
|
||||
n = len(file_paths)
|
||||
prompt = _EXTRACTION_PROMPT.format(
|
||||
n=n, documents=combined_text)
|
||||
n=n, office=_OFFICE_ADDRESS, documents=combined_text)
|
||||
|
||||
# 3. Call Groq API
|
||||
self.after(0, lambda: self._set_status(
|
||||
@@ -715,6 +728,8 @@ def _extract_text(file_path: str) -> str:
|
||||
return _read_pdf(file_path)
|
||||
if ext == ".docx":
|
||||
return _read_docx(file_path)
|
||||
if ext == ".doc":
|
||||
return _read_doc(file_path)
|
||||
if ext in (".xlsx", ".xls"):
|
||||
return _read_excel(file_path)
|
||||
raise ValueError(f"Unsupported file type: {ext}")
|
||||
@@ -750,6 +765,7 @@ def _read_pdf(path: str) -> str:
|
||||
|
||||
|
||||
def _read_docx(path: str) -> str:
|
||||
"""Read modern .docx (Office Open XML) via python-docx."""
|
||||
try:
|
||||
import docx
|
||||
doc = docx.Document(path)
|
||||
@@ -761,6 +777,76 @@ def _read_docx(path: str) -> str:
|
||||
)
|
||||
|
||||
|
||||
def _read_doc(path: str) -> str:
|
||||
"""Read legacy .doc (binary OLE) files.
|
||||
|
||||
Strategy (Windows):
|
||||
1. win32com — automates Word to save as temp .docx, then reads it.
|
||||
Requires Microsoft Word to be installed; most reliable.
|
||||
2. docx2txt — pure-Python fallback that can sometimes handle simple .doc
|
||||
files by treating them as ZIP-like structures (works for some .doc).
|
||||
3. Raw text scrape — last resort ASCII extraction from the binary.
|
||||
"""
|
||||
# --- Strategy 1: Word COM automation (Windows + Word installed) -----------
|
||||
try:
|
||||
import win32com.client
|
||||
import tempfile
|
||||
import pythoncom
|
||||
pythoncom.CoInitialize()
|
||||
word = win32com.client.Dispatch("Word.Application")
|
||||
word.Visible = False
|
||||
try:
|
||||
abs_path = os.path.abspath(path)
|
||||
doc = word.Documents.Open(abs_path)
|
||||
# Save as a temp .docx so python-docx can read it cleanly
|
||||
with tempfile.NamedTemporaryFile(suffix=".docx", delete=False) as tf:
|
||||
tmp_path = tf.name
|
||||
doc.SaveAs2(tmp_path, FileFormat=16) # 16 = wdFormatXMLDocument
|
||||
doc.Close(False)
|
||||
text = _read_docx(tmp_path)
|
||||
return text
|
||||
finally:
|
||||
try:
|
||||
word.Quit()
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
os.unlink(tmp_path)
|
||||
except Exception:
|
||||
pass
|
||||
pythoncom.CoUninitialize()
|
||||
except Exception:
|
||||
pass # Word not installed or COM error — try next strategy
|
||||
|
||||
# --- Strategy 2: docx2txt (pure Python, works on some .doc files) --------
|
||||
try:
|
||||
import docx2txt
|
||||
text = docx2txt.process(path)
|
||||
if text and text.strip():
|
||||
return text
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# --- Strategy 3: raw ASCII scrape from binary ----------------------------
|
||||
try:
|
||||
with open(path, "rb") as f:
|
||||
raw = f.read()
|
||||
# Extract printable ASCII runs of 4+ chars
|
||||
import re
|
||||
chunks = re.findall(rb"[ -~]{4,}", raw)
|
||||
text = "\n".join(c.decode("ascii", errors="ignore") for c in chunks)
|
||||
if text.strip():
|
||||
return text
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
raise ValueError(
|
||||
f"Could not extract text from '{os.path.basename(path)}'.\n"
|
||||
"For best results with .doc files, install Microsoft Word "
|
||||
"or convert the file to .docx before uploading."
|
||||
)
|
||||
|
||||
|
||||
def _read_excel(path: str) -> str:
|
||||
try:
|
||||
import openpyxl
|
||||
@@ -787,6 +873,7 @@ def _file_icon(path: str) -> str:
|
||||
ext = os.path.splitext(path)[1].lower()
|
||||
return {
|
||||
".pdf": "📄",
|
||||
".doc": "📝",
|
||||
".docx": "📝",
|
||||
".xlsx": "📊",
|
||||
".xls": "📊",
|
||||
|
||||
Reference in New Issue
Block a user