talas-group/09_MODELE_ECONOMIQUE/Subventions/generate_pdf2_evidence_v2.py
senke 66471934af Initial commit: Talas Group project management & documentation
Knowledge base of ~80+ markdown files across 14 domains (00-13),
Logseq graph, hardware design files (KiCAD), infrastructure configs,
and talas-wiki static site.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 20:10:41 +02:00

336 lines
13 KiB
Python

#!/usr/bin/env python3
"""
Generate PDF 2 (V2): Talas — Delivery Capacity Evidence
For NGI Zero Commons Fund application (25K EUR, personal tone).
No external images required — uses KiCAD screenshots and text metrics.
Optionally add photos/veza_screenshot.png for a Veza platform screenshot.
"""
from fpdf import FPDF
import os
BASE = "/home/senke/Documents/TG__Talas_Group"
PHOTOS = f"{BASE}/09_MODELE_ECONOMIQUE/Subventions/photos"
OUT = f"{BASE}/09_MODELE_ECONOMIQUE/Subventions/talas-project-evidence-v2.pdf"
FONT_DIR = "/usr/share/fonts/liberation-sans-fonts"
class TalasPDF(FPDF):
def __init__(self):
super().__init__()
self.add_font("Sans", "", f"{FONT_DIR}/LiberationSans-Regular.ttf")
self.add_font("Sans", "B", f"{FONT_DIR}/LiberationSans-Bold.ttf")
self.add_font("Sans", "I", f"{FONT_DIR}/LiberationSans-Italic.ttf")
self.add_font("Sans", "BI", f"{FONT_DIR}/LiberationSans-BoldItalic.ttf")
def header(self):
self.set_font("Sans", "B", 9)
self.set_text_color(100, 100, 100)
self.cell(0, 5, "Talas — NGI Zero Commons Fund — Attachment 2: Delivery Evidence",
align="R")
self.ln(8)
def footer(self):
self.set_y(-15)
self.set_font("Sans", "I", 8)
self.set_text_color(150, 150, 150)
self.cell(0, 10, f"Page {self.page_no()}/{{nb}}", align="C")
def section_title(self, title):
self.set_font("Sans", "B", 13)
self.set_text_color(30, 30, 30)
self.cell(0, 10, title, new_x="LMARGIN", new_y="NEXT")
self.set_draw_color(60, 60, 60)
self.line(self.l_margin, self.get_y(), self.w - self.r_margin, self.get_y())
self.ln(4)
def sub_title(self, title):
self.set_font("Sans", "B", 11)
self.set_text_color(50, 50, 50)
self.cell(0, 8, title, new_x="LMARGIN", new_y="NEXT")
self.ln(1)
def body_text(self, text):
self.set_font("Sans", "", 10)
self.set_text_color(40, 40, 40)
self.multi_cell(0, 5, text)
self.ln(2)
def personal_text(self, text):
self.set_font("Sans", "I", 10)
self.set_text_color(60, 60, 60)
self.multi_cell(0, 5, text)
self.ln(2)
def metric_row(self, label, value, detail=""):
self.set_font("Sans", "B", 10)
self.set_text_color(40, 40, 40)
self.cell(55, 6, label)
self.set_font("Sans", "", 10)
self.set_text_color(30, 100, 30)
self.cell(50, 6, value)
self.set_text_color(120, 120, 120)
self.set_font("Sans", "I", 9)
self.cell(0, 6, detail, new_x="LMARGIN", new_y="NEXT")
def label_value(self, label, value):
self.set_font("Sans", "B", 10)
self.set_text_color(40, 40, 40)
self.cell(55, 6, label)
self.set_font("Sans", "", 10)
self.cell(0, 6, value, new_x="LMARGIN", new_y="NEXT")
def bullet(self, text, indent=10):
self.set_font("Sans", "", 9)
self.set_text_color(40, 40, 40)
self.cell(indent, 5, "\u2022")
self.multi_cell(self.w - self.r_margin - self.l_margin - indent, 5, text)
self.ln(1)
def try_image(self, path, max_w=170, max_h=100, caption=None):
if not os.path.exists(path):
return False
from PIL import Image
img = Image.open(path)
w_px, h_px = img.size
ratio = min(max_w / w_px, max_h / h_px)
w_mm = w_px * ratio
h_mm = h_px * ratio
x = (self.w - w_mm) / 2
if self.get_y() + h_mm + 15 > self.h - 20:
self.add_page()
self.image(path, x=x, w=w_mm)
if caption:
self.set_font("Sans", "I", 8)
self.set_text_color(100, 100, 100)
self.cell(0, 5, caption, align="C", new_x="LMARGIN", new_y="NEXT")
self.ln(3)
return True
def main():
pdf = TalasPDF()
pdf.alias_nb_pages()
pdf.set_auto_page_break(auto=True, margin=20)
# =========================================================
# PAGE 1 — Personal intro + Veza delivery proof
# =========================================================
pdf.add_page()
pdf.set_font("Sans", "B", 20)
pdf.set_text_color(20, 20, 20)
pdf.cell(0, 12, "Talas — Why I Can Deliver", align="C",
new_x="LMARGIN", new_y="NEXT")
pdf.set_font("Sans", "", 11)
pdf.set_text_color(80, 80, 80)
pdf.cell(0, 7, "Evidence of delivery capacity — Nikola Milovanovic — March 2026",
align="C", new_x="LMARGIN", new_y="NEXT")
pdf.ln(4)
pdf.personal_text(
"This grant asks you to trust that I can transform an assembled prototype into "
"a verified, measured, documented open commons. Here is the evidence that I "
"finish what I start."
)
# --- Veza ---
pdf.section_title("1. I Built Veza Solo — 38 Releases in 5 Months")
pdf.personal_text(
"Veza is a music platform I have been building on my own — Go + Rust + React, "
"with streaming, real-time chat, e-commerce, and community features. "
"It is currently proprietary and not related to this grant, but it shows "
"that I can ship complex software from scratch. Every line of code is mine."
)
pdf.sub_title("What I Shipped")
pdf.metric_row("Releases:", "38", "v0.1 through v1.0.2, over 5 months")
pdf.metric_row("API endpoints:", "500+", "REST API (Go/Gin)")
pdf.metric_row("Database:", "60+ tables", "115 SQL migrations (PostgreSQL)")
pdf.metric_row("Frontend:", "661 components", "React 18, TypeScript, 52+ routes")
pdf.metric_row("Languages:", "3", "English, French, Spanish")
pdf.metric_row("Streaming:", "HLS adaptive", "Rust/Axum, FFmpeg transcoding")
pdf.metric_row("Real-time:", "WebSocket", "Chat, notifications, co-listening")
pdf.ln(2)
# Veza repo screenshot
pdf.try_image(
f"{PHOTOS}/github_veza.png",
max_w=170, max_h=85,
caption="Veza private repository — 2,210 commits, 36 branches, 51 tags"
)
# Language breakdown
pdf.try_image(
f"{PHOTOS}/github_lang_prog_graph.png",
max_w=100, max_h=40,
caption="Codebase: TypeScript 38%, Go 30%, HTML 20%, Rust 6%"
)
# Activity heatmaps side by side
pdf.try_image(
f"{PHOTOS}/github_activity_2025.png",
max_w=170, max_h=45,
caption="3,415 contributions in 2025"
)
pdf.try_image(
f"{PHOTOS}/github_activity_2026.png",
max_w=170, max_h=45,
caption="2,708 contributions in 2026 (January\u2013March only)"
)
# --- Security ---
pdf.section_title("2. I Take Security Seriously")
pdf.personal_text(
"My academic background is cybersecurity (BSc EPITA, MSc OTERIA). "
"I ran a thorough security audit of Veza using AI-assisted analysis "
"(Claude, Anthropic) to systematically review every attack surface."
)
pdf.sub_title("Security Audit (AI-Assisted, October 2025)")
pdf.metric_row("Findings:", "36", "identified through systematic review")
pdf.metric_row("Remediated:", "36/36", "100% — zero open findings")
pdf.metric_row("Critical/High:", "0", "remaining after remediation")
pdf.ln(2)
pdf.sub_title("Security Stack I Implemented")
features = [
"JWT RS256 authentication (asymmetric key rotation)",
"WebAuthn / Passkeys (passwordless login)",
"Two-factor authentication (TOTP + backup codes)",
"OAuth 2.0 (Google, GitHub, Apple, Facebook)",
"CSRF tokens (Redis-backed), rate limiting (3-tier)",
"ClamAV antivirus on all uploads",
"Coraza WAF with OWASP Core Rule Set",
"GDPR: data export, account deletion, anonymization",
]
for f in features:
pdf.bullet(f)
pdf.ln(2)
# =========================================================
# PAGE 2 — Infrastructure + Investment + What grant funds
# =========================================================
pdf.section_title("3. I Built the Infrastructure Too")
pdf.personal_text(
"The entire stack runs on hardware I own. No AWS, no Cloudflare, no SaaS. "
"Two refurbished Dell servers in my home, managed by Ansible playbooks I wrote. "
"Monthly cost: 135 EUR in electricity — versus 800-1,500 EUR in cloud fees."
)
pdf.sub_title("Hardware I Own")
pdf.metric_row("Servers:", "2x Dell R720", "rack-mounted, purchased used")
pdf.metric_row("RAM:", "768 GB total", "384 GB per server")
pdf.metric_row("CPU:", "64 cores total", "2x Xeon E5-2670 per server")
pdf.metric_row("Network:", "10 GbE", "inter-server + 1 Gbps fiber")
pdf.metric_row("Storage:", "16 HDDs per server", "ZFS mirror pools, ~100 spare HDDs in reserve")
pdf.metric_row("Monthly cost:", "~135 EUR", "electricity only")
pdf.ln(2)
pdf.sub_title("Automation I Wrote")
pdf.metric_row("Ansible:", "Full automation", "infrastructure-as-code")
pdf.metric_row("Deployment:", "Blue-green", "zero-downtime via HAProxy")
pdf.metric_row("Monitoring:", "Prometheus + Grafana", "metrics + dashboards")
pdf.metric_row("Security:", "Coraza WAF", "OWASP rules, WireGuard VPN")
pdf.metric_row("Backup:", "PostgreSQL PITR", "ZFS snapshots, WAL archiving")
pdf.ln(4)
# =========================================================
# Personal investment
# =========================================================
pdf.section_title("4. What I Already Invested (~3,000 EUR)")
pdf.personal_text(
"Before writing this application, I spent approximately 3,000 EUR of my own "
"money on equipment, components, and infrastructure. As a student working "
"part-time, that is a significant personal investment."
)
investments = [
("Measurement lab:", "Rigol oscilloscope, 2 audio interfaces, 2 reference mics, multimeter"),
("Fabrication:", "Soldering station, stereo microscope, RoHS consumables"),
("Components:", "OPA1642, TC4584BF, passives, PCB fabrication (PCBWay), capsules"),
("Servers:", "2x Dell R720 (used), networking, drives"),
("Software dev:", "5 months full-time on Veza (unpaid)"),
]
for label, value in investments:
pdf.label_value(label, value)
pdf.ln(4)
# =========================================================
# Open-core model
# =========================================================
pdf.section_title("5. What Stays Open Forever")
pdf.body_text(
"The hardware funded by this grant is published under CERN-OHL-W-2.0. "
"This license is irrevocable — even I cannot close it. The commons exist "
"permanently, regardless of what happens to the company."
)
pdf.set_font("Sans", "B", 9)
pdf.set_fill_color(240, 240, 240)
pdf.cell(55, 6, "Artifact", border=1, fill=True)
pdf.cell(40, 6, "License", border=1, fill=True)
pdf.cell(55, 6, "Status", border=1, fill=True, new_x="LMARGIN", new_y="NEXT")
pdf.set_font("Sans", "", 9)
licenses = [
("Microphone schematics (KiCAD)", "CERN-OHL-W-2.0", "Designed, publication pending"),
("PCB layouts + Gerber files", "CERN-OHL-W-2.0", "Designed, publication pending"),
("Bill of Materials", "CERN-OHL-W-2.0", "Complete, maintained"),
("Assembly guide", "CC BY-SA 4.0", "Grant Milestone 4"),
("Calibration toolkit (Rust)", "AGPL v3", "Grant Milestone 3"),
("Acoustic measurements", "CC BY-SA 4.0", "Grant Milestone 2"),
("Veza platform", "Proprietary", "Revenue funds open HW"),
]
for comp, lic, status in licenses:
pdf.cell(55, 5, comp, border=1)
pdf.cell(40, 5, lic, border=1)
pdf.cell(55, 5, status, border=1, new_x="LMARGIN", new_y="NEXT")
pdf.ln(2)
pdf.personal_text(
"The model is simple: hardware knowledge is permanently open (commons). "
"Assembled microphones and the Veza platform are commercial products whose "
"revenue funds ongoing maintenance of the commons. This is the Arduino / Prusa "
"model, adapted for audio."
)
# =========================================================
# Summary
# =========================================================
pdf.section_title("6. Summary")
pdf.body_text(
"I am one person. I designed a microphone, built a music platform, set up "
"the infrastructure, wrote the documentation, and invested my own money — "
"all before asking for a single euro of funding."
)
pdf.body_text(
"This grant (25,000 EUR) funds the final step: transforming an assembled prototype "
"into a reproducible open-hardware commons with professional acoustic measurements "
"and complete documentation. If someone builds this from my files and it "
"doesn't work, I have failed."
)
# =========================================================
# Output
# =========================================================
pdf.output(OUT)
print(f"\nPDF generated: {OUT}")
print(f"Pages: {pdf.pages_count}")
# Check optional screenshot
if not os.path.exists(f"{PHOTOS}/veza_screenshot.png"):
print("\nOPTIONAL: Add photos/veza_screenshot.png for a Veza platform screenshot.")
if __name__ == "__main__":
main()