Download export.py from build-small-hackathon/OSINTAgentTool: direct link, hf CLI and curl.
- Browser
- Download file 18.4 kB
-
https://huggingface.co/spaces/build-small-hackathon/OSINTAgentTool/resolve/main/export.py
- Command line
-
hf download hf://spaces/build-small-hackathon/OSINTAgentTool/export.py
-
curl -L -o export.py https://huggingface.co/spaces/build-small-hackathon/OSINTAgentTool/resolve/main/export.py
18.4 kB
| """ | |
| export.py - Traveler-friendly PDF brief. | |
| Compact 2-page layout: at-a-glance safety info, key risks, news, contacts. | |
| """ | |
| import os | |
| import tempfile | |
| from datetime import datetime | |
| from typing import Optional, Tuple | |
| from fpdf import FPDF | |
| from brief import ThreatBrief | |
| from map_utils import generate_country_map | |
| # --------------------------------------------------------------------------- | |
| # Colors | |
| # --------------------------------------------------------------------------- | |
| _BLACK = (20, 20, 20) | |
| _DARK = (10, 10, 10) | |
| _MID = (55, 55, 55) | |
| _MUTED = (110, 110, 110) | |
| _LIGHT_GREY = (246, 246, 246) | |
| _RULE = (210, 210, 210) | |
| _LINK = (30, 80, 200) | |
| _WHITE = (255, 255, 255) | |
| _SEV = { | |
| "Critical": (180, 20, 20), | |
| "High": (210, 70, 10), | |
| "Medium": (190, 100, 0), | |
| "Low": (20, 140, 60), | |
| "Minimal": (100, 110, 120), | |
| } | |
| _CON = { | |
| "High": (20, 140, 60), | |
| "Medium": (190, 100, 0), | |
| "Low": (180, 20, 20), | |
| } | |
| _ADV = { | |
| "1": ((20, 140, 60), "Level 1 - Normal Precautions"), | |
| "2": ((190, 100, 0), "Level 2 - Increased Caution"), | |
| "3": ((210, 70, 10), "Level 3 - Reconsider Travel"), | |
| "4": ((180, 20, 20), "Level 4 - Do Not Travel"), | |
| } | |
| _AIR = { | |
| "Open": (20, 140, 60), | |
| "Restricted": (210, 70, 10), | |
| "Partially Restricted": (190, 100, 0), | |
| "Closed": (180, 20, 20), | |
| "Unknown": (110, 110, 110), | |
| } | |
| # --------------------------------------------------------------------------- | |
| # Unicode safety | |
| # --------------------------------------------------------------------------- | |
| _SUBS = str.maketrans({ | |
| "—": "--", "–": "-", "‘": "'", "’": "'", | |
| "“": '"', "”": '"', "…": "...", "·": "*", | |
| "•": "-", " ": " ", "→": "->", "←": "<-", | |
| "\xe9": "e", "\xe8": "e", "\xea": "e", "\xe0": "a", "\xe2": "a", | |
| "\xf4": "o", "\xfb": "u", "\xfc": "u", "\xe4": "a", "\xf6": "o", | |
| "\xdf": "ss", "\xe7": "c", "\xf1": "n", "\xed": "i", "\xf3": "o", | |
| "\xfa": "u", "\xe1": "a", "\xe6": "ae", | |
| }) | |
| def _s(text) -> str: | |
| if not isinstance(text, str): | |
| text = str(text) | |
| return text.translate(_SUBS).encode("latin-1", errors="replace").decode("latin-1") | |
| # --------------------------------------------------------------------------- | |
| # PDF class | |
| # --------------------------------------------------------------------------- | |
| class _PDF(FPDF): | |
| def __init__(self, country="", date=""): | |
| super().__init__() | |
| self._country = country | |
| self._date = date | |
| def header(self): | |
| self.set_fill_color(255, 255, 255) | |
| self.rect(0, 0, self.w, self.h, "F") | |
| self.set_fill_color(*_LIGHT_GREY) | |
| self.rect(0, 0, self.w, 6, "F") | |
| self.set_font("Helvetica", "", 5.5) | |
| self.set_text_color(*_MUTED) | |
| self.set_xy(0, 0.8) | |
| self.cell(self.w, 4.5, | |
| "UNCLASSIFIED // FOR OFFICIAL USE ONLY // AI-GENERATED FROM OPEN SOURCES", | |
| align="C") | |
| self.ln(6) | |
| def footer(self): | |
| self.set_y(-14) | |
| self.set_fill_color(*_LIGHT_GREY) | |
| self.rect(0, self.h - 14, self.w, 14, "F") | |
| # Disclaimer | |
| self.set_font("Helvetica", "I", 5.5) | |
| self.set_text_color(*_MUTED) | |
| self.set_x(self.l_margin) | |
| self.cell(self.epw, 4, | |
| "AI-generated from open sources. Not for operational use without independent verification. " | |
| "Verify all embassy/consular contacts before travel.", | |
| align="L") | |
| self.ln(4) | |
| # Page line | |
| self.set_font("Helvetica", "", 6.5) | |
| self.set_x(self.l_margin) | |
| self.cell(self.epw / 2, 5, | |
| _s(f"{self._country} | {self._date} | OSINT Intelligence Platform"), | |
| align="L") | |
| self.cell(self.epw / 2, 5, | |
| f"Page {self.page_no()}", align="R") | |
| # --------------------------------------------------------------------------- | |
| # Helpers | |
| # --------------------------------------------------------------------------- | |
| def _rx(pdf): pdf.set_x(pdf.l_margin) | |
| def _lm(pdf): return pdf.l_margin | |
| def _epw(pdf): return pdf.epw | |
| def _rule(pdf, color=_RULE): | |
| _rx(pdf) | |
| pdf.set_draw_color(*color) | |
| pdf.line(_lm(pdf), pdf.get_y(), _lm(pdf) + _epw(pdf), pdf.get_y()) | |
| pdf.ln(2.5) | |
| def _section(pdf, title, accent=(50, 80, 160)): | |
| pdf.ln(3) | |
| _rx(pdf) | |
| y = pdf.get_y() | |
| pdf.set_fill_color(*_LIGHT_GREY) | |
| pdf.rect(_lm(pdf), y, _epw(pdf), 6.5, "F") | |
| pdf.set_fill_color(*accent) | |
| pdf.rect(_lm(pdf), y, 2.5, 6.5, "F") | |
| pdf.set_xy(_lm(pdf) + 5, y + 0.5) | |
| pdf.set_font("Helvetica", "B", 7.5) | |
| pdf.set_text_color(*_DARK) | |
| pdf.cell(_epw(pdf) - 5, 5.5, _s(title.upper()), ln=True) | |
| pdf.ln(2.5) | |
| def _label(pdf, text): | |
| _rx(pdf) | |
| pdf.set_font("Helvetica", "B", 6.5) | |
| pdf.set_text_color(*_MUTED) | |
| pdf.cell(_epw(pdf), 3.5, _s(text.upper()), ln=True) | |
| pdf.ln(0.5) | |
| def _body(pdf, text, size=8.5, color=None): | |
| _rx(pdf) | |
| pdf.set_font("Helvetica", "", size) | |
| pdf.set_text_color(*(color or _BLACK)) | |
| pdf.multi_cell(_epw(pdf), 4.5, _s(text)) | |
| def _bullets(pdf, items, size=8.5): | |
| if not items: | |
| return | |
| pdf.set_font("Helvetica", "", size) | |
| pdf.set_text_color(*_BLACK) | |
| for item in items: | |
| s = str(item).strip() | |
| if s and s.lower() not in ("n/a", "none", ""): | |
| _rx(pdf) | |
| pdf.multi_cell(_epw(pdf), 4.5, _s(f" • {s}")) | |
| def _badge(pdf, x, y, w, h, label, rgb): | |
| pdf.set_fill_color(*rgb) | |
| pdf.set_text_color(255, 255, 255) | |
| pdf.set_font("Helvetica", "B", 7.5) | |
| pdf.set_xy(x, y) | |
| pdf.cell(w, h, _s(f" {label}"), fill=True, border=0, ln=0) | |
| def _kv(pdf, label, value, label_w=28): | |
| if not value: | |
| return | |
| _rx(pdf) | |
| pdf.set_font("Helvetica", "B", 8) | |
| pdf.set_text_color(*_MID) | |
| pdf.cell(label_w, 4.5, _s(f"{label}:"), ln=False) | |
| pdf.set_font("Helvetica", "", 8) | |
| pdf.set_text_color(*_BLACK) | |
| pdf.multi_cell(_epw(pdf) - label_w, 4.5, _s(value)) | |
| # --------------------------------------------------------------------------- | |
| # Main | |
| # --------------------------------------------------------------------------- | |
| def generate_pdf(brief: ThreatBrief) -> str: | |
| date_str = brief.assessment_date or datetime.utcnow().strftime("%Y-%m-%d") | |
| pdf = _PDF(country=brief.country or "", date=date_str) | |
| pdf.set_auto_page_break(auto=True, margin=12) | |
| pdf.add_page() | |
| lm = pdf.l_margin | |
| epw = pdf.epw | |
| # ========================================================= | |
| # TITLE BLOCK | |
| # ========================================================= | |
| pdf.set_font("Helvetica", "B", 20) | |
| pdf.set_text_color(*_DARK) | |
| _rx(pdf) | |
| pdf.cell(epw, 9, _s(brief.country or "Unknown Country"), ln=True) | |
| pdf.set_font("Helvetica", "", 7.5) | |
| pdf.set_text_color(*_MUTED) | |
| _rx(pdf) | |
| pdf.cell(epw, 4, _s( | |
| f"{brief.region or 'Unknown Region'} | " | |
| f"Assessment: {date_str} | " | |
| f"Generated: {datetime.utcnow().strftime('%Y-%m-%d %H:%M UTC')}" | |
| ), ln=True) | |
| pdf.ln(3) | |
| # 4 status badges | |
| level = (brief.travel_advisory_level or "").strip() | |
| adv_rgb, adv_label = _ADV.get(level, ((110,110,110), f"Level {level}" if level else "Advisory N/A")) | |
| sev_rgb = _SEV.get(brief.severity, (110,110,110)) | |
| as_rgb = _AIR.get(brief.airspace_status, (110,110,110)) | |
| gap = 2 | |
| badge_w = (epw - gap * 3) / 4 | |
| by = pdf.get_y() | |
| pdf.set_font("Helvetica", "B", 7.5) | |
| _badge(pdf, lm, by, badge_w, 7, f"Severity: {brief.severity or 'N/A'}", sev_rgb) | |
| _badge(pdf, lm + (badge_w+gap), by, badge_w, 7, f"Confidence: {brief.confidence or 'N/A'}", _CON.get(brief.confidence, (110,110,110))) | |
| _badge(pdf, lm + (badge_w+gap)*2, by, badge_w, 7, f"Advisory: L{level or '?'}", adv_rgb) | |
| _badge(pdf, lm + (badge_w+gap)*3, by, badge_w, 7, f"Airspace: {brief.airspace_status or 'N/A'}", as_rgb) | |
| pdf.ln(9) | |
| pdf.ln(3) | |
| _rule(pdf) | |
| # ========================================================= | |
| # MAP + ADVISORY SUMMARY (side by side) | |
| # ========================================================= | |
| map_col_w = epw * 0.52 | |
| info_col_w = epw - map_col_w - 4 | |
| row_start = pdf.get_y() | |
| # --- Map (left) --- | |
| map_path = map_err = None | |
| try: | |
| result = generate_country_map(brief.country or "") | |
| map_path, map_err = result if isinstance(result, tuple) else (result, None) | |
| except Exception as e: | |
| map_err = str(e) | |
| map_h = 0 | |
| if map_path: | |
| try: | |
| pdf.image(map_path, x=lm, y=row_start, w=map_col_w) | |
| map_h = map_col_w * (540 / 900) # native aspect ratio | |
| except Exception as e: | |
| map_err = str(e) | |
| finally: | |
| try: os.unlink(map_path) | |
| except: pass | |
| if not map_path or map_err: | |
| pdf.set_xy(lm, row_start) | |
| pdf.set_fill_color(*_LIGHT_GREY) | |
| pdf.rect(lm, row_start, map_col_w, 38, "F") | |
| pdf.set_xy(lm, row_start + 15) | |
| pdf.set_font("Helvetica", "I", 8) | |
| pdf.set_text_color(*_MUTED) | |
| pdf.cell(map_col_w, 5, _s(f"Map unavailable"), align="C", ln=True) | |
| map_h = 38 | |
| # --- Advisory info (right) --- | |
| info_x = lm + map_col_w + 4 | |
| pdf.set_xy(info_x, row_start) | |
| # Advisory banner | |
| pdf.set_fill_color(*adv_rgb) | |
| pdf.set_text_color(255,255,255) | |
| pdf.set_font("Helvetica", "B", 8) | |
| pdf.cell(info_col_w, 7, _s(f" {adv_label}"), fill=True, ln=True) | |
| pdf.ln(2) | |
| if brief.travel_advisory_indicators: | |
| pdf.set_x(info_x) | |
| pdf.set_font("Helvetica", "B", 7) | |
| pdf.set_text_color(*_MUTED) | |
| pdf.cell(info_col_w, 3.5, "RISK CATEGORIES", ln=True) | |
| pdf.set_x(info_x) | |
| pdf.set_font("Helvetica", "", 8) | |
| pdf.set_text_color(*_BLACK) | |
| pdf.multi_cell(info_col_w, 4.5, _s(", ".join(brief.travel_advisory_indicators))) | |
| pdf.ln(2) | |
| if brief.narrative_summary: | |
| pdf.set_x(info_x) | |
| pdf.set_font("Helvetica", "B", 7) | |
| pdf.set_text_color(*_MUTED) | |
| pdf.cell(info_col_w, 3.5, "SITUATION", ln=True) | |
| pdf.set_x(info_x) | |
| pdf.set_font("Helvetica", "", 8) | |
| pdf.set_text_color(*_BLACK) | |
| # Limit to ~3 lines in this column | |
| summary = brief.narrative_summary[:260] | |
| if len(brief.narrative_summary) > 260: | |
| summary = summary.rsplit(" ", 1)[0] + "..." | |
| pdf.multi_cell(info_col_w, 4.5, _s(summary)) | |
| # Advance Y past the taller of the two columns | |
| pdf.set_y(row_start + max(map_h, pdf.get_y() - row_start) + 3) | |
| _rule(pdf) | |
| # ========================================================= | |
| # KEY RISKS (2 columns: findings | watch items) | |
| # ========================================================= | |
| _section(pdf, "Key Risks & Watch Items", accent=(180, 20, 20)) | |
| col_w = epw / 2 - 2 | |
| row_y = pdf.get_y() | |
| row_pg = pdf.page | |
| # Left: risk analysis snippet + key findings | |
| _rx(pdf) | |
| if brief.risk_analysis: | |
| pdf.set_font("Helvetica", "", 8.5) | |
| pdf.set_text_color(*_BLACK) | |
| ra = brief.risk_analysis[:300] | |
| if len(brief.risk_analysis) > 300: | |
| ra = ra.rsplit(" ", 1)[0] + "..." | |
| pdf.multi_cell(col_w, 4.5, _s(ra)) | |
| pdf.ln(1.5) | |
| all_findings = (brief.key_findings or []) + (brief.indicators_of_escalation or []) | |
| if all_findings: | |
| _rx(pdf) | |
| pdf.set_font("Helvetica", "B", 6.5) | |
| pdf.set_text_color(*_MUTED) | |
| pdf.cell(col_w, 3.5, "KEY FINDINGS", ln=True) | |
| pdf.set_font("Helvetica", "", 8.5) | |
| pdf.set_text_color(*_BLACK) | |
| for item in all_findings[:5]: | |
| s = str(item).strip() | |
| if s and s.lower() not in ("n/a","none",""): | |
| _rx(pdf) | |
| pdf.multi_cell(col_w, 4.5, _s(f" - {s}")) | |
| left_end_y = pdf.get_y() | |
| left_end_pg = pdf.page | |
| # Right: actors + watch items | |
| if left_end_pg == row_pg: | |
| rx = lm + col_w + 4 | |
| pdf.set_xy(rx, row_y) | |
| all_actors = (brief.primary_actors or [])[:4] | |
| if all_actors: | |
| pdf.set_font("Helvetica", "B", 6.5) | |
| pdf.set_text_color(*_MUTED) | |
| pdf.cell(col_w, 3.5, "PRIMARY ACTORS", ln=True) | |
| pdf.set_font("Helvetica", "", 8.5) | |
| pdf.set_text_color(*_BLACK) | |
| for a in all_actors: | |
| pdf.set_x(rx) | |
| pdf.multi_cell(col_w, 4.5, _s(f" - {a}")) | |
| pdf.set_x(rx); pdf.ln(2) | |
| watch = (brief.recommended_watch_items or [])[:4] | |
| if watch: | |
| pdf.set_x(rx) | |
| pdf.set_font("Helvetica", "B", 6.5) | |
| pdf.set_text_color(*_MUTED) | |
| pdf.cell(col_w, 3.5, "MONITOR", ln=True) | |
| pdf.set_font("Helvetica", "", 8.5) | |
| pdf.set_text_color(*_BLACK) | |
| for w in watch: | |
| pdf.set_x(rx) | |
| pdf.multi_cell(col_w, 4.5, _s(f" - {w}")) | |
| pdf.set_y(max(left_end_y, pdf.get_y()) + 2) | |
| else: | |
| pdf.set_y(left_end_y + 2) | |
| _rule(pdf) | |
| # ========================================================= | |
| # AIRSPACE + TRAVEL CONTACTS (side by side) | |
| # ========================================================= | |
| _section(pdf, "Airspace & Travel", accent=(210, 70, 10)) | |
| col_w = epw / 2 - 2 | |
| row_y = pdf.get_y() | |
| row_pg = pdf.page | |
| # Left: airspace | |
| _rx(pdf) | |
| pdf.set_fill_color(*as_rgb) | |
| pdf.set_text_color(255,255,255) | |
| pdf.set_font("Helvetica", "B", 8) | |
| pdf.cell(col_w, 6.5, _s(f" {brief.airspace_status or 'Unknown'}"), fill=True, ln=True) | |
| pdf.ln(1.5) | |
| if brief.aviation_notes: | |
| _rx(pdf) | |
| pdf.set_font("Helvetica", "", 8) | |
| pdf.set_text_color(*_MID) | |
| note = brief.aviation_notes[:200] | |
| pdf.multi_cell(col_w, 4.5, _s(note)) | |
| pdf.ln(1) | |
| combined_air = (brief.no_fly_zones or []) + (brief.airspace_restrictions or []) | |
| if combined_air: | |
| _rx(pdf) | |
| pdf.set_font("Helvetica", "B", 6.5) | |
| pdf.set_text_color(*_MUTED) | |
| pdf.cell(col_w, 3.5, "RESTRICTIONS / NO-FLY ZONES", ln=True) | |
| pdf.set_font("Helvetica", "", 8) | |
| pdf.set_text_color(*_BLACK) | |
| for item in combined_air[:4]: | |
| if str(item).strip().lower() not in ("none identified", "n/a", "none", ""): | |
| _rx(pdf) | |
| pdf.multi_cell(col_w, 4.5, _s(f" - {item}")) | |
| air_end_y = pdf.get_y() | |
| air_end_pg = pdf.page | |
| # Right: travel advisory + embassy | |
| if air_end_pg == row_pg: | |
| rx = lm + col_w + 4 | |
| pdf.set_xy(rx, row_y) | |
| pdf.set_fill_color(*adv_rgb) | |
| pdf.set_text_color(255,255,255) | |
| pdf.set_font("Helvetica", "B", 8) | |
| pdf.cell(col_w, 6.5, _s(f" {adv_label}"), fill=True, ln=True) | |
| pdf.ln(1.5) | |
| if brief.travel_advisory_date: | |
| pdf.set_x(rx) | |
| pdf.set_font("Helvetica", "I", 7.5) | |
| pdf.set_text_color(*_MUTED) | |
| pdf.cell(col_w, 4, _s(f"Updated: {brief.travel_advisory_date}"), ln=True) | |
| if brief.passport_country and brief.embassy_name: | |
| pdf.set_x(rx); pdf.ln(1.5) | |
| pdf.set_font("Helvetica", "B", 7.5) | |
| pdf.set_text_color(*_DARK) | |
| pdf.set_x(rx) | |
| pdf.cell(col_w, 4.5, _s(f"{brief.passport_country} Embassy"), ln=True) | |
| pdf.set_x(rx) | |
| pdf.set_font("Helvetica", "", 8) | |
| pdf.set_text_color(*_BLACK) | |
| pdf.multi_cell(col_w, 4.5, _s(brief.embassy_name)) | |
| if brief.embassy_address: | |
| pdf.set_x(rx) | |
| pdf.multi_cell(col_w, 4.5, _s(brief.embassy_address)) | |
| if brief.embassy_emergency_phone: | |
| pdf.set_x(rx) | |
| pdf.set_font("Helvetica", "B", 8) | |
| pdf.set_text_color((180,20,20)) | |
| pdf.cell(col_w, 4.5, _s(f"Emergency: {brief.embassy_emergency_phone}"), ln=True) | |
| if brief.embassy_phone: | |
| pdf.set_x(rx) | |
| pdf.set_font("Helvetica", "", 8) | |
| pdf.set_text_color(*_BLACK) | |
| pdf.cell(col_w, 4.5, _s(f"Main: {brief.embassy_phone}"), ln=True) | |
| if brief.embassy_website: | |
| pdf.set_x(rx) | |
| pdf.set_font("Helvetica", "I", 7.5) | |
| pdf.set_text_color(*_LINK) | |
| pdf.cell(col_w, 4.5, _s(brief.embassy_website), ln=True) | |
| pdf.set_y(max(air_end_y, pdf.get_y()) + 2) | |
| else: | |
| pdf.set_y(air_end_y + 2) | |
| _rule(pdf) | |
| # ========================================================= | |
| # NEWS HEADLINES (compact — title + source + date only) | |
| # ========================================================= | |
| news = brief.notable_news or [] | |
| if news: | |
| _section(pdf, f"Latest News ({len(news)} articles)", accent=(37, 99, 200)) | |
| for article in news: | |
| # Title bold | |
| _rx(pdf) | |
| pdf.set_font("Helvetica", "B", 8.5) | |
| pdf.set_text_color(*_BLACK) | |
| pdf.multi_cell(epw, 4.5, _s(article.title or "Untitled")) | |
| # Source | date | [NOTABLE] on same line | |
| _rx(pdf) | |
| pdf.set_font("Helvetica", "I", 7) | |
| pdf.set_text_color(*_MUTED) | |
| date_s = (article.published or "")[:16] | |
| notable = " [NOTABLE]" if article.notable else "" | |
| pdf.cell(epw, 3.5, _s(f"{article.source} | {date_s}{notable}"), ln=True) | |
| # One-line summary (capped at 140 chars) | |
| if article.summary: | |
| _rx(pdf) | |
| pdf.set_font("Helvetica", "", 8) | |
| pdf.set_text_color(*_MID) | |
| summ = article.summary[:140] | |
| if len(article.summary) > 140: | |
| summ = summ.rsplit(" ", 1)[0] + "..." | |
| pdf.multi_cell(epw, 4, _s(summ)) | |
| # URL | |
| if article.url: | |
| _rx(pdf) | |
| pdf.set_font("Helvetica", "I", 7) | |
| pdf.set_text_color(*_LINK) | |
| url_d = article.url[:90] + "..." if len(article.url) > 90 else article.url | |
| pdf.cell(epw, 3.5, _s(url_d), ln=True) | |
| pdf.ln(1.5) | |
| _rx(pdf) | |
| pdf.set_draw_color(*_RULE) | |
| pdf.line(lm, pdf.get_y(), lm + epw, pdf.get_y()) | |
| pdf.ln(2) | |
| path = tempfile.mktemp(suffix=".pdf") | |
| pdf.output(path) | |
| return path | |