from reportlab.platypus import *
from reportlab.lib import colors
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT
from reportlab.platypus.tables import Table, TableStyle
from reportlab.graphics.shapes import Drawing
from reportlab.graphics.charts.barcharts import VerticalBarChart
from reportlab.graphics.charts.linecharts import HorizontalLineChart
from reportlab.lib.units import mm
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
pdf_path = "/mnt/data/premium_commercial_real_estate_checklist.pdf"
doc = SimpleDocTemplate(
pdf_path,
pagesize=A4,
rightMargin=18*mm,
leftMargin=18*mm,
topMargin=18*mm,
bottomMargin=18*mm
)
styles = getSampleStyleSheet()
BLUE = colors.HexColor("#0B1F3A")
GOLD = colors.HexColor("#C8A45D")
LIGHT = colors.HexColor("#F4F7FB")
TEXT = colors.HexColor("#222222")
title_style = ParagraphStyle(
"title",
parent=styles["Heading1"],
fontSize=28,
leading=34,
textColor=colors.white,
alignment=TA_CENTER,
spaceAfter=20
)
subtitle_style = ParagraphStyle(
"subtitle",
parent=styles["BodyText"],
fontSize=12,
leading=18,
textColor=colors.white,
alignment=TA_CENTER,
)
h1 = ParagraphStyle(
"h1",
parent=styles["Heading1"],
fontSize=20,
leading=24,
textColor=BLUE,
spaceBefore=18,
spaceAfter=12
)
body = ParagraphStyle(
"body",
parent=styles["BodyText"],
fontSize=10.5,
leading=16,
textColor=TEXT
)
small = ParagraphStyle(
"small",
parent=styles["BodyText"],
fontSize=9,
leading=13,
textColor=TEXT
)
elements = []
# COVER
cover = Table(
[[Paragraph("
АВТОРСКАЯ МЕТОДИКА
ЭКСПРЕСС-ОЦЕНКИ
КОММЕРЧЕСКОЙ НЕДВИЖИМОСТИ", title_style)]],
colWidths=[170*mm],
rowHeights=[90*mm]
)
cover.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), BLUE),
("BOX", (0,0), (-1,-1), 2, GOLD),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
elements.append(Spacer(1, 35*mm))
elements.append(cover)
sub = Table(
[[Paragraph(
"Как за 15 минут понять, где в объекте есть реальная доходность,
а где только маркетинговая упаковка",
subtitle_style
)]],
colWidths=[170*mm]
)
sub.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), GOLD),
("BOTTOMPADDING", (0,0), (-1,-1), 14),
("TOPPADDING", (0,0), (-1,-1), 14),
]))
elements.append(sub)
elements.append(PageBreak())
# INTRO
elements.append(Paragraph("Что оценивает профессиональный инвестор", h1))
intro = """
Большинство объектов на рынке коммерческой недвижимости продаются через маркетинг, визуализацию и обещания высокой доходности.
Но профессиональный инвестор оценивает не презентацию, а устойчивость денежного потока, ликвидность актива,
качество арендаторов и потенциал роста NOI.
Этот чек-лист позволяет за 15 минут определить:
- есть ли у объекта реальная экономика
- подтверждена ли доходность цифрами
- насколько объект устойчив к рыночным циклам
- можно ли увеличить стоимость актива через управление
"""
elements.append(Paragraph(intro, body))
# TABLE 1
elements.append(Paragraph("Экспресс-оценка инвестиционного объекта", h1))
data = [
["Критерий", "Сильный объект", "Слабый объект"],
["Источник спроса", "Стабильный поток", "Искусственный трафик"],
["Доходность", "Подтверждена NOI", "Построена на прогнозах"],
["Ликвидность", "Высокий спрос", "Узкий рынок"],
["Управляемость", "Можно увеличить NOI", "Доходность фиксирована"],
["Фаза рынка", "Рост / недооценка", "Перегрев сегмента"],
]
tbl = Table(data, colWidths=[55*mm, 55*mm, 55*mm])
tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), BLUE),
("TEXTCOLOR", (0,0), (-1,0), colors.white),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("GRID", (0,0), (-1,-1), 0.8, colors.HexColor("#D7DCE5")),
("ROWBACKGROUNDS", (0,1), (-1,-1), [colors.white, LIGHT]),
("BOTTOMPADDING", (0,0), (-1,-1), 10),
("TOPPADDING", (0,0), (-1,-1), 10),
]))
elements.append(tbl)
elements.append(Spacer(1, 12))
# CHART
elements.append(Paragraph("Пример расхождения заявленной и реальной доходности", h1))
drawing = Drawing(420, 220)
bc = VerticalBarChart()
bc.x = 40
bc.y = 40
bc.height = 130
bc.width = 280
bc.data = [[14, 8.9]]
bc.categoryAxis.categoryNames = ['Заявленная', 'Реальная']
bc.valueAxis.valueMin = 0
bc.valueAxis.valueMax = 16
bc.valueAxis.valueStep = 2
bc.bars[0].fillColor = GOLD
drawing.add(bc)
elements.append(drawing)
explain = """
В большинстве инвестиционных презентаций показывается «грязная доходность» — без учёта вакантности,
CAPEX, налогов, ротации арендаторов и эксплуатационных расходов.
После профессионального расчёта реальная доходность часто оказывается ниже на 25–40%.
"""
elements.append(Paragraph(explain, body))
# NOI
elements.append(Paragraph("Пример профессионального расчёта NOI", h1))
noi_data = [
["Показатель", "Значение"],
["Стоимость объекта", "120 млн ₽"],
["Валовый арендный поток", "16,8 млн ₽"],
["Вакантность", "7%"],
["OPEX", "2,4 млн ₽"],
["Налоги", "1,1 млн ₽"],
["CAPEX", "800 тыс ₽"],
["Управление", "600 тыс ₽"],
]
noi_tbl = Table(noi_data, colWidths=[90*mm, 60*mm])
noi_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), GOLD),
("TEXTCOLOR", (0,0), (-1,0), colors.white),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("GRID", (0,0), (-1,-1), 0.8, colors.HexColor("#D7DCE5")),
("ROWBACKGROUNDS", (0,1), (-1,-1), [colors.white, LIGHT]),
("BOTTOMPADDING", (0,0), (-1,-1), 10),
("TOPPADDING", (0,0), (-1,-1), 10),
]))
elements.append(noi_tbl)
calc_text = """
Формула:
NOI = арендный поток − вакантность − OPEX − CAPEX − налоги − управление
Расчёт:
16,8 − 1,176 − 2,4 − 1,1 − 0,8 − 0,6 =
10,724 млн ₽ Реальная доходность:
10,724 / 120 × 100 =
8,93% годовых
"""