import json
import os
from pathlib import Path
from urllib.parse import urlparse
import requests
from bs4 import BeautifulSoup
from openai import OpenAI
PRESENTON_URL = "https://api.presenton.ai/api/v3/presentation/generate"
PRESENTON_API_KEY = os.environ["PRESENTON_API_KEY"]
openai_client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
def normalize_url(value: str) -> str:
return value if value.startswith(("http://", "https://")) else f"https://{value}"
def fetch_public_company_context(url: str) -> dict:
url = normalize_url(url)
response = requests.get(
url,
headers={"User-Agent": "Presenton pitch-deck research tutorial"},
timeout=20,
)
response.raise_for_status()
soup = BeautifulSoup(response.text, "html.parser")
title = soup.title.get_text(" ", strip=True) if soup.title else ""
description_node = soup.find("meta", attrs={"name": "description"})
description = description_node.get("content", "") if description_node else ""
headings = [node.get_text(" ", strip=True) for node in soup.select("h1, h2")[:12]]
paragraphs = [node.get_text(" ", strip=True) for node in soup.select("p")[:20]]
return {
"url": url,
"domain": urlparse(url).netloc,
"title": title,
"description": description,
"headings": headings,
"public_copy": "\n".join(paragraphs)[:8000],
}
def generate_diligence_questions(company: dict) -> list[str]:
prompt = f"""
You are preparing an investor pitch-deck discovery interview.
Public company context:
{json.dumps(company, indent=2)}
Return JSON with a `questions` array containing exactly six concise questions.
Cover: customer problem, target buyer, evidence of traction, business model,
go-to-market motion, and fundraising objective. Ask for exact figures and time
periods where relevant. Do not assume public marketing claims are verified.
""".strip()
response = openai_client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": prompt}],
response_format={"type": "json_object"},
temperature=0.2,
)
payload = json.loads(response.choices[0].message.content)
questions = payload.get("questions", [])
if len(questions) != 6:
raise ValueError("Question generator did not return exactly six questions")
return questions
def interview_founder(questions: list[str]) -> list[dict]:
answers = []
print("\nAnswer with verified information. Enter 'not available' when unknown.\n")
for number, question in enumerate(questions, start=1):
print(f"{number}. {question}")
answers.append({"question": question, "answer": input("> ").strip()})
return answers
def build_investment_brief(company: dict, interview: list[dict]) -> str:
return f"""
Create an investor pitch-deck draft for {company['title'] or company['domain']}.
Public website context — treat as unverified marketing copy:
{json.dumps(company, indent=2)}
Founder interview — treat these as the supplied source facts:
{json.dumps(interview, indent=2)}
Build an 11-slide narrative:
1. Company, category, and one-sentence value proposition
2. Customer problem and why it is urgent now
3. Product and differentiated workflow
4. Target customer and use cases
5. Market opportunity using only supplied market figures
6. Business model and pricing logic
7. Traction with exact periods and denominators
8. Go-to-market strategy and sales motion
9. Competitive alternatives and defensibility
10. Team and execution advantages
11. Fundraising objective, use of funds, and next milestones
Rules:
- Never invent revenue, growth, customers, market size, margins, runway, or deal terms.
- When a required fact is unavailable, write `Founder input required`.
- Distinguish public claims from founder-verified facts.
- Keep the tone confident, specific, and suitable for an investor meeting.
- Prefer evidence and decisions over generic startup language.
""".strip()
def generate_pitch_deck(brief: str, company_name: str) -> dict:
response = requests.post(
PRESENTON_URL,
headers={
"Authorization": f"Bearer {PRESENTON_API_KEY}",
"Content-Type": "application/json",
},
json={
"content": brief,
"instructions": "Use an investor narrative with clear charts only when verified numeric data exists.",
"n_slides": 11,
"language": "English",
"tone": "sales_pitch",
"verbosity": "concise",
"standard_template": "neo-modern",
"theme": "professional-dark",
"export_as": "pptx",
},
timeout=900,
)
response.raise_for_status()
result = response.json()
output_name = "".join(
character if character.isalnum() or character in "-_" else "_"
for character in company_name
).strip("_")
output_path = Path(f"{output_name or 'company'}_pitch_deck.pptx")
download = requests.get(
result["path"],
headers={"Authorization": f"Bearer {PRESENTON_API_KEY}"},
timeout=120,
)
download.raise_for_status()
output_path.write_bytes(download.content)
print(f"Saved {output_path}")
print(f"Review and edit: {result['edit_path']}")
return result
def main() -> None:
website = input("Company website: ").strip()
company = fetch_public_company_context(website)
questions = generate_diligence_questions(company)
interview = interview_founder(questions)
brief = build_investment_brief(company, interview)
generate_pitch_deck(brief, company["title"] or company["domain"])
if __name__ == "__main__":
main()