simbiot-harness/scripts/spa_fill.py

168 lines
9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
# ═══════════════════════════════════════════════════════════
# SPA filler — Studio_Participation_Agreement_2026-09 → per-founder
# Задачи t-261008143050 (Siarhei Zhukovich, когорта с 16-го) и
# t-261008144131 (7 участников 42 Labs за 2026-09).
# Паттерн подстановки — как _contract_docx_bytes в /root/coop/app.py.
# ═══════════════════════════════════════════════════════════
import os, sys, copy
import docx
TEMPLATE = "/root/coop/legal/Studio_Participation_Agreement_2026-09.docx"
OUT = "/root/coop/legal"
DRAFT_LINE = "DRAFT TEMPLATE — for review by U.S. counsel before first use. Not legal advice."
# Перепривязка платежного графика на 16-е число (когорты с 16-го, t-261008143050)
SIXTEENTH = [
("no later than the 1st day of the first month of participation",
"no later than the 16th day of the first month of participation"),
("each subsequent Monthly Fee no later than the 1st day of the corresponding month of participation",
"each subsequent Monthly Fee no later than the 16th day of the corresponding month of participation"),
("If the Agreement is signed after the 1st, the Program Start is the 1st day of the next calendar month",
"If the Agreement is signed after the 16th, the Program Start is the 16th day of the next calendar month"),
("for Monthly Fee tranches, the 1st day of the participation month",
"for Monthly Fee tranches, the 16th day of the participation month"),
("takes effect on the 1st day of the next participation month",
"takes effect on the 16th day of the next participation month"),
("payable in advance by the 1st of each participation month",
"payable in advance by the 16th of each participation month"),
("the 1st day of the month of signature if signed on the 1st, otherwise the 1st day of the next calendar month",
"the 16th day of the month of signature if signed on the 16th, otherwise the 16th day of the next calendar month"),
]
def founder_replacements(f):
"""f: dict(name, passport, pid, address, email, project, one_line, rev_share,
effective, cohort, program_start, sixteenth: bool)"""
r = {
"[DATE]": f["effective"],
"[FULL LEGAL NAME]": f["name"],
"[NAME]": f["name"],
"[EMAIL]": f.get("email") or "[EMAIL]",
"cohort [MONTH YEAR]": f"cohort {f['cohort']}",
"1 [MONTH] [YEAR]": f["program_start"],
"[MONTH YEAR]": f["cohort"],
}
if f.get("rev_share"):
r["[X]"] = str(f["rev_share"])
if f.get("project"):
r["[PROJECT NAME]"] = f["project"]
if f.get("one_line"):
r["[ONE LINE]"] = f["one_line"]
# Строка Founder: имя + статус + паспорт + адрес одной подстановкой
line_old = "[FULL LEGAL NAME], an individual / [ENTITY TYPE], residing at [ADDRESS], [COUNTRY]"
line_new = (f"{f['name']}, an individual, passport of the Republic of Belarus "
f"{f.get('passport') or '[PASSPORT NO.]'} (personal identification No. "
f"{f.get('pid') or '[PERSONAL ID NO.]'}), residing at "
f"{f.get('address') or '[ADDRESS]'}, Republic of Belarus")
r[line_old] = line_new
# фолбэк, если строка разбита рант differently
r["an individual / [ENTITY TYPE]"] = "an individual"
# паспорт/ид.номер вставляются после схлопывания individual-строки
# (порядок ключей dict важен: этот ключ применяется после предыдущего)
pass_line = (f"passport of the Republic of Belarus {f.get('passport') or '[PASSPORT NO.]'}"
f" (personal identification No. {f.get('pid') or '[PERSONAL ID NO.]'}), residing at")
r["an individual, residing at"] = f"an individual, {pass_line}"
r["[ADDRESS], [COUNTRY]"] = f"{f.get('address') or '[ADDRESS]'}, Republic of Belarus"
r["[ADDRESS]"] = f.get("address") or "[ADDRESS]"
r["[COUNTRY]"] = "Republic of Belarus"
return r
def fill(f, out_docx):
doc = docx.Document(TEMPLATE)
reps = founder_replacements(f)
if f.get("sixteenth"):
reps = dict(SIXTEENTH) | reps
reps[DRAFT_LINE] = ""
for p in doc.paragraphs:
if not p.runs:
continue
full = "".join(r.text for r in p.runs)
new = full
for k, v in reps.items():
if k in new:
new = new.replace(k, v)
if new != full:
p.runs[0].text = new
for r in p.runs[1:]:
r.text = ""
doc.save(out_docx)
return out_docx
def to_pdf(docx_path):
import subprocess, tempfile
with tempfile.TemporaryDirectory() as td:
r = subprocess.run(["soffice", "--headless", "--convert-to", "pdf", "--outdir", td, docx_path],
capture_output=True, timeout=90)
pdf = os.path.join(td, os.path.basename(docx_path).replace(".docx", ".pdf"))
if os.path.exists(pdf):
dst = docx_path.replace(".docx", ".pdf")
open(dst, "wb").write(open(pdf, "rb").read())
return dst
return None
def leftover_placeholders(docx_path):
import re
d = docx.Document(docx_path)
found = set()
for p in d.paragraphs:
found.update(re.findall(r"\[[A-Z][A-Z0-9 /.,%()\[\]-]*\]", p.text))
return sorted(found)
FOUNDERS = [
# ── t-261008143050: Siarhei Zhukovich, когорта с 16-го ──
dict(file="SPA_Siarhei_Zhukovich", name="Siarhei Zhukovich",
passport="AB3393306", pid=None, # ид. номер — нет в базе, плейсхолдер
address="28 July str., 33B-42, Brest", email=None,
project="podrabotki.by (rebranding to StaffGo)",
one_line="a platform for hiring line staff: employment/contract placement and turnkey contractor paperwork (documents and payments)",
rev_share=50, effective="October 16, 2026", cohort="October 2026",
program_start="16 October 2026", sixteenth=True),
# ── t-261008144131: участники 42 Labs за 2026-09 (lab_debts) ──
dict(file="SPA_Alex_Shkor", name="Alex Shkor", passport=None, pid=None,
address=None, email="alex@shkor.com", project=None, one_line=None,
rev_share=None, effective="September 1, 2026", cohort="September 2026",
program_start="1 September 2026", sixteenth=False),
dict(file="SPA_Vlad", name="Vlad", passport=None, pid=None,
address=None, email="vla2music.com@gmail.com", project=None, one_line=None,
rev_share=None, effective="September 1, 2026", cohort="September 2026",
program_start="1 September 2026", sixteenth=False),
dict(file="SPA_Yura", name="Yura", passport=None, pid=None,
address=None, email="yura28440@gmail.com", project=None, one_line=None,
rev_share=None, effective="September 1, 2026", cohort="September 2026",
program_start="1 September 2026", sixteenth=False),
dict(file="SPA_Stas_Dashchinskiy", name="Stas Dashchinskiy", passport=None, pid=None,
address=None, email="dashchinskiystas@gmail.com", project=None, one_line=None,
rev_share=None, effective="September 1, 2026", cohort="September 2026",
program_start="1 September 2026", sixteenth=False),
dict(file="SPA_Alexander", name="Александр", passport=None, pid=None,
address=None, email="Justgbox@gmail.com", project=None, one_line=None,
rev_share=None, effective="September 1, 2026", cohort="September 2026",
program_start="1 September 2026", sixteenth=False),
dict(file="SPA_Melnikau", name="Andrei Melnikau", passport="MP2848482",
pid="3120386A064PB1", address=None, email="ex3.mac@gmail.com",
project="tYESter",
one_line="a family of software tools for testing computer hardware components without the need to purchase additional equipment",
rev_share=None, effective="September 1, 2026", cohort="September 2026",
program_start="1 September 2026", sixteenth=False),
dict(file="SPA_Dmitry_Siasitski", name="Dmitry Siasitski", passport=None, pid=None,
address=None, email="sesitski.dmitry@gmail.com", project=None, one_line=None,
rev_share=None, effective="September 1, 2026", cohort="September 2026",
program_start="1 September 2026", sixteenth=False),
]
if __name__ == "__main__":
only = sys.argv[1:] or None
for f in FOUNDERS:
if only and f["file"] not in only:
continue
docx_path = os.path.join(OUT, f["file"] + ".docx")
fill(f, docx_path)
pdf = to_pdf(docx_path)
print(f"{f['file']}: docx={'OK' if os.path.exists(docx_path) else 'FAIL'} "
f"pdf={'OK' if pdf else 'FAIL'} leftover={leftover_placeholders(docx_path)}")