Files
fmd26/build_rose_card.py

346 lines
13 KiB
Python
Raw Permalink 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.
# -*- coding: utf-8 -*-
from docx import Document
from docx.shared import Pt, Cm, RGBColor
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.enum.table import WD_TABLE_ALIGNMENT
from docx.oxml.ns import qn
from docx.oxml import OxmlElement
OUT = 'Метод_Дегтярева_роза.docx'
GREEN = RGBColor(0x2E, 0x7D, 0x5B)
ROSE = RGBColor(0xB0, 0x3A, 0x55)
DARK = RGBColor(0x1A, 0x1A, 0x1A)
GRAY = RGBColor(0x5F, 0x66, 0x6B)
LIGHT = RGBColor(0x8A, 0x90, 0x95)
def set_run(run, name='Arial', size=9, bold=False, italic=False, color=DARK):
run.font.name = name
run.font.size = Pt(size)
run.font.bold = bold
run.font.italic = italic
run.font.color.rgb = color
rPr = run._element.get_or_add_rPr()
rFonts = rPr.find(qn('w:rFonts'))
if rFonts is None:
rFonts = OxmlElement('w:rFonts')
rPr.append(rFonts)
for attr in ('w:ascii', 'w:hAnsi', 'w:eastAsia', 'w:cs'):
rFonts.set(qn(attr), name)
def shade(cell, hex_color):
tcPr = cell._tc.get_or_add_tcPr()
shd = OxmlElement('w:shd')
shd.set(qn('w:val'), 'clear')
shd.set(qn('w:color'), 'auto')
shd.set(qn('w:fill'), hex_color)
tcPr.append(shd)
def cell_margins(table, top=60, bottom=60, left=100, right=100):
tblPr = table._tbl.tblPr
mar = OxmlElement('w:tblCellMar')
for tag, val in (('top', top), ('left', left), ('bottom', bottom), ('right', right)):
el = OxmlElement('w:' + tag)
el.set(qn('w:w'), str(val))
el.set(qn('w:type'), 'dxa')
mar.append(el)
tblPr.append(mar)
def table_borders(table, color='C9D6CF', size=6):
tblPr = table._tbl.tblPr
borders = OxmlElement('w:tblBorders')
for edge in ('top', 'left', 'bottom', 'right', 'insideH', 'insideV'):
el = OxmlElement('w:' + edge)
el.set(qn('w:val'), 'single')
el.set(qn('w:sz'), str(size))
el.set(qn('w:space'), '0')
el.set(qn('w:color'), color)
borders.append(el)
tblPr.append(borders)
def add_line_table(cell, lines=2, width_cm=8.0, height_cm=0.5):
tbl = cell.add_table(rows=lines, cols=1)
tbl.autofit = False
tblPr = tbl._tbl.tblPr
borders = OxmlElement('w:tblBorders')
for edge in ('top', 'left', 'bottom', 'right', 'insideH', 'insideV'):
el = OxmlElement('w:' + edge)
el.set(qn('w:val'), 'none')
borders.append(el)
tblPr.append(borders)
for row in tbl.rows:
row.height = Cm(height_cm)
c = row.cells[0]
c.width = Cm(width_cm)
tcPr = c._tc.get_or_add_tcPr()
tcBorders = OxmlElement('w:tcBorders')
bottom = OxmlElement('w:bottom')
bottom.set(qn('w:val'), 'dotted')
bottom.set(qn('w:sz'), '4')
bottom.set(qn('w:space'), '0')
bottom.set(qn('w:color'), 'AFC4B9')
tcBorders.append(bottom)
tcPr.append(tcBorders)
p = c.paragraphs[0]
p.paragraph_format.space_before = Pt(0)
p.paragraph_format.space_after = Pt(0)
p.paragraph_format.line_spacing = 1.0
def add_block(cell, num, title, prompt, color=GREEN, fill='F3F9F6', lines=2, width_cm=8.0):
cell.text = ''
shade(cell, fill)
p = cell.paragraphs[0]
p.paragraph_format.space_before = Pt(0)
p.paragraph_format.space_after = Pt(1)
r = p.add_run(num + ' ')
set_run(r, size=7.5, bold=True, color=color)
r = p.add_run(title)
set_run(r, size=9.5, bold=True, color=color)
p = cell.add_paragraph()
p.paragraph_format.space_before = Pt(0)
p.paragraph_format.space_after = Pt(3)
p.paragraph_format.line_spacing = 1.0
r = p.add_run(prompt)
set_run(r, size=7.5, color=GRAY)
add_line_table(cell, lines=lines, width_cm=width_cm, height_cm=0.5)
def nested_grid(cell, rows, cols, widths, header_fill='EAF4EF'):
tbl = cell.add_table(rows=rows, cols=cols)
tbl.autofit = False
tblPr = tbl._tbl.tblPr
borders = OxmlElement('w:tblBorders')
for edge in ('top', 'left', 'bottom', 'right', 'insideH', 'insideV'):
el = OxmlElement('w:' + edge)
el.set(qn('w:val'), 'single')
el.set(qn('w:sz'), '4')
el.set(qn('w:space'), '0')
el.set(qn('w:color'), 'D9E2DD')
borders.append(el)
tblPr.append(borders)
for row in tbl.rows:
for i, c in enumerate(row.cells):
c.width = Cm(widths[i])
return tbl
def build():
doc = Document()
section = doc.sections[0]
section.page_width = Cm(21.0)
section.page_height = Cm(29.7)
section.top_margin = Cm(1.3)
section.bottom_margin = Cm(1.1)
section.left_margin = Cm(1.4)
section.right_margin = Cm(1.4)
normal = doc.styles['Normal']
normal.font.name = 'Arial'
normal.font.size = Pt(9)
normal.font.color.rgb = DARK
# Header
p = doc.add_paragraph()
p.paragraph_format.space_before = Pt(0)
p.paragraph_format.space_after = Pt(0)
r = p.add_run('РОЗА')
set_run(r, name='Georgia', size=20, bold=True, color=ROSE)
p = doc.add_paragraph()
p.paragraph_format.space_before = Pt(1)
p.paragraph_format.space_after = Pt(1)
r = p.add_run('Шаг 4. «Роза» • Метод Дегтярева')
set_run(r, size=9, bold=True, color=GRAY)
p = doc.add_paragraph()
p.paragraph_format.space_before = Pt(0)
p.paragraph_format.space_after = Pt(1)
r = p.add_run('Сделайте результат видимым и желанным. Не функция, а то, что человек почувствует.')
set_run(r, size=8, italic=True, color=ROSE)
p = doc.add_paragraph()
p.paragraph_format.space_before = Pt(0)
p.paragraph_format.space_after = Pt(5)
r = p.add_run('Команда: ______________________________ Продукт: ______________________________________')
set_run(r, size=8.5, color=GRAY)
# Main table
table = doc.add_table(rows=6, cols=2)
table.alignment = WD_TABLE_ALIGNMENT.CENTER
table.autofit = False
table_borders(table)
cell_margins(table)
heights = [3.0, 3.6, 2.6, 3.2, 3.0, 2.6]
for i, row in enumerate(table.rows):
row.height = Cm(heights[i])
for cell in row.cells:
cell.width = Cm(9.0)
# 01 Value (merged)
c = table.cell(0, 0).merge(table.cell(0, 1))
add_block(c, '01', 'ЦЕННОСТЬ В ОДНОМ ПРЕДЛОЖЕНИИ',
'«[Продукт] помогает [группа] [решить проблему] за счёт [механизм]. В отличие от [альтернатива], мы [отличие]».',
lines=3, width_cm=17.2)
# 02 Scenario (merged)
c = table.cell(1, 0).merge(table.cell(1, 1))
c.text = ''
shade(c, 'F3F9F6')
p = c.paragraphs[0]
p.paragraph_format.space_before = Pt(0)
p.paragraph_format.space_after = Pt(1)
r = p.add_run('02 ')
set_run(r, size=7.5, bold=True, color=GREEN)
r = p.add_run('СЦЕНАРИЙ ИСПОЛЬЗОВАНИЯ')
set_run(r, size=9.5, bold=True, color=GREEN)
p = c.add_paragraph()
p.paragraph_format.space_before = Pt(0)
p.paragraph_format.space_after = Pt(3)
r = p.add_run('Путь человека от боли до результата. Пять шагов.')
set_run(r, size=7.5, color=GRAY)
scenario = nested_grid(c, 2, 5, [3.4, 3.4, 3.4, 3.4, 3.4])
scenario.rows[0].height = Cm(0.45)
scenario.rows[1].height = Cm(1.55)
headers = ['1. ТРИГГЕР', '2. ВХОД', '3. ДЕЙСТВИЕ', '4. РЕЗУЛЬТАТ', '5. ВОЗВРАТ']
prompts = ['Что происходит в момент боли?', 'Как человек попадает в продукт?', 'Что он делает?', 'Что получает?', 'Почему возвращается?']
for i, h in enumerate(headers):
cell = scenario.cell(0, i)
cell.text = ''
shade(cell, 'EAF4EF')
p = cell.paragraphs[0]
p.paragraph_format.space_before = Pt(0)
p.paragraph_format.space_after = Pt(0)
r = p.add_run(h)
set_run(r, size=6.8, bold=True, color=GREEN)
cell2 = scenario.cell(1, i)
cell2.text = ''
p = cell2.paragraphs[0]
p.paragraph_format.space_before = Pt(0)
p.paragraph_format.space_after = Pt(0)
r = p.add_run(prompts[i])
set_run(r, size=6.5, color=GRAY)
# 03 Before / 04 After
add_block(table.cell(2, 0), '03', 'ДО',
'Что человек чувствует сейчас: тревога, злость, усталость, стыд?',
color=GRAY, fill='F7F8F8', lines=2, width_cm=8.2)
add_block(table.cell(2, 1), '04', 'ПОСЛЕ',
'Что он почувствует с продуктом: спокойствие, контроль, гордость?',
color=ROSE, fill='FDF4F6', lines=2, width_cm=8.2)
# 05 MVP (merged)
c = table.cell(3, 0).merge(table.cell(3, 1))
c.text = ''
shade(c, 'F3F9F6')
p = c.paragraphs[0]
p.paragraph_format.space_before = Pt(0)
p.paragraph_format.space_after = Pt(1)
r = p.add_run('05 ')
set_run(r, size=7.5, bold=True, color=GREEN)
r = p.add_run('MVP — МИНИМАЛЬНАЯ ВЕРСИЯ, КОТОРАЯ ПРОВЕРЯЕТ ЦЕННОСТЬ')
set_run(r, size=9.5, bold=True, color=GREEN)
p = c.add_paragraph()
p.paragraph_format.space_before = Pt(0)
p.paragraph_format.space_after = Pt(3)
r = p.add_run('Что обязательно входит, что делаем вручную, что не входит в первую версию.')
set_run(r, size=7.5, color=GRAY)
mvp = nested_grid(c, 2, 3, [5.7, 5.7, 5.7])
mvp.rows[0].height = Cm(0.45)
mvp.rows[1].height = Cm(1.35)
mvp_headers = ['ВХОДИТ В MVP', 'ДЕЛАЕМ ВРУЧНУЮ', 'НЕ ВХОДИТ']
for i, h in enumerate(mvp_headers):
cell = mvp.cell(0, i)
cell.text = ''
shade(cell, 'EAF4EF')
p = cell.paragraphs[0]
p.paragraph_format.space_before = Pt(0)
p.paragraph_format.space_after = Pt(0)
r = p.add_run(h)
set_run(r, size=7, bold=True, color=GREEN)
cell2 = mvp.cell(1, i)
cell2.text = ''
p = cell2.paragraphs[0]
p.paragraph_format.space_before = Pt(0)
p.paragraph_format.space_after = Pt(0)
r = p.add_run(' ')
set_run(r, size=7, color=RGBColor(0xFF, 0xFF, 0xFF))
# 06 Difference (merged)
c = table.cell(4, 0).merge(table.cell(4, 1))
c.text = ''
shade(c, 'FDF4F6')
p = c.paragraphs[0]
p.paragraph_format.space_before = Pt(0)
p.paragraph_format.space_after = Pt(1)
r = p.add_run('06 ')
set_run(r, size=7.5, bold=True, color=ROSE)
r = p.add_run('ОТЛИЧИЕ ОТ АЛЬТЕРНАТИВ')
set_run(r, size=9.5, bold=True, color=ROSE)
p = c.add_paragraph()
p.paragraph_format.space_before = Pt(0)
p.paragraph_format.space_after = Pt(3)
r = p.add_run('Чем вы лучше по трём параметрам: скорость, цена, доверие?')
set_run(r, size=7.5, color=GRAY)
diff = nested_grid(c, 2, 3, [5.7, 5.7, 5.7])
diff.rows[0].height = Cm(0.45)
diff.rows[1].height = Cm(1.25)
diff_headers = ['МЫ', 'АЛЬТЕРНАТИВА 1', 'АЛЬТЕРНАТИВА 2']
for i, h in enumerate(diff_headers):
cell = diff.cell(0, i)
cell.text = ''
shade(cell, 'FDF4F6')
p = cell.paragraphs[0]
p.paragraph_format.space_before = Pt(0)
p.paragraph_format.space_after = Pt(0)
r = p.add_run(h)
set_run(r, size=7, bold=True, color=ROSE)
cell2 = diff.cell(1, i)
cell2.text = ''
p = cell2.paragraphs[0]
p.paragraph_format.space_before = Pt(0)
p.paragraph_format.space_after = Pt(0)
r = p.add_run(' ')
set_run(r, size=7, color=RGBColor(0xFF, 0xFF, 0xFF))
# 07 Pitch (merged)
c = table.cell(5, 0).merge(table.cell(5, 1))
c.text = ''
shade(c, 'F3F9F6')
p = c.paragraphs[0]
p.paragraph_format.space_before = Pt(0)
p.paragraph_format.space_after = Pt(1)
r = p.add_run('07 ')
set_run(r, size=7.5, bold=True, color=GREEN)
r = p.add_run('ПИТЧ-ФОРМУЛА')
set_run(r, size=9.5, bold=True, color=GREEN)
p = c.add_paragraph()
p.paragraph_format.space_before = Pt(0)
p.paragraph_format.space_after = Pt(2)
r = p.add_run('Проблема → Решение → Ценность → Доказательство → Призыв.')
set_run(r, size=8, italic=True, color=GRAY)
add_line_table(c, lines=2, width_cm=17.2, height_cm=0.5)
# Footer
p = doc.add_paragraph()
p.paragraph_format.space_before = Pt(5)
p.paragraph_format.space_after = Pt(0)
r = p.add_run('Сделайте результат видимым. • Метод Дегтярева • Форум молодёжи Донбасса, 2026')
set_run(r, size=7.5, color=LIGHT)
doc.save(OUT)
print('saved', OUT)
if __name__ == '__main__':
build()