3 / IR:ESG Reporter.py
Corin1998's picture
Upload 2 files
674d706 verified
# Write the code from cell 3wKDeFDDoQZU into the file models.py
models_code = """
from pydantic import BaseModel,Field
from typing import List,Optional,Dict,Any
class CompanyMeta(BaseModel):
company_name:str
fiscal_year:int
currency:str="JPY"
ticker:Optional[str] = None
report_title:str = "Integrated Report"
ceo_name:Optional[str] = None
material_topics:List[str]=[]
targets:Dict[str,Any]={}
class ESGRow(BaseModel):
year:int
metric:str
value:float
unit:str
scope:Optional[str]=None
notes:Optional[str]=None
class ReportSections(BaseModel):
ceo_message:str = ""
risl_opportunity:str = ""
class RenderPayload(BaseModel):
meta:CompanyMeta
esg_table:List[Dict[str,Any]]
kpi:Dict[str,float]
sections:ReportSections
generated_at:str
"""
with open("models.py", "w", encoding="utf-8") as f:
f.write(models_code)
# Write the code from cell b7VKsopm8D2F into the file templating.py
templating_code = """
from jinja2 import Environment,FileSystemLoader,select_autoescape
from pathlib import Path
def get_env(templates_dir:str):
env = Environment(
loader = FileSystemLoader(templates_dir),
autoescape=select_autoescape(["html","xml"])
)
return env
def render(env,template_name: str,context :dict)->str:
template = env.get_template(template_name)
return template.render(**context)
"""
with open("templating.py", "w", encoding="utf-8") as f:
f.write(templating_code)
# Write the code from cell zncVxyX69kXW into the file render.py
render_code = """
# from weasyprint import HTML # Commenting out due to potential dependency issues
from docx import Document
from docx.shared import Pt
from bs4 import BeautifulSoup
def html_to_pdf(html_str:str,out_pdf_path:str):
# HTML(string=html_str).write_pdf(out_pdf_path) # Commenting out due to potential dependency issues
print("PDF generation skipped due to weasyprint dependency")
def html_to_docx(html_str:str,out_docx_path:str):
doc = Document()
soup = BeautifulSoup(html_str,"html.parser")
for tag in soup.find_all(["h1","h2","h3","p","li"]):
text = tag.get_text(strip=True)
if not text:
continue
if tag.name =="h1":
p = doc.add_heading(text,level=0)
elif tag.name=="h2":
p=doc.add_heading(text,level=1)
elif tag.name=="h3":
p=doc.add_heading(text,level=2)
else:
p= doc.add_paragraph(text)
for run in p.runs:
run.font.size = Pt(11)
doc.save(out_docx_path)
"""
with open("render.py", "w", encoding="utf-8") as f:
f.write(render_code)
# Write the code from cell Gc-m0Xv-CM6a into the file core.py
core_code = """
import pandas as pd
import yaml, datetime
from pathlib import Path
from templating import get_env,render
from models import CompanyMeta, ReportSections,RenderPayload
from render import html_to_pdf,html_to_docx
DISPLAY_NAME={
"co2_emissions":"CO2排出量",
"energy_renewable_ratio":"再生可能エネルギー比率",
"female_management_ratio":"女性管理職比率"
}
def load_company_meta(path:str)->CompanyMeta:
with open(path, 'r', encoding='utf-8') as f:
data = yaml.safe_load(f)
return CompanyMeta(**data)
def load_financials(path:str)->pd.DataFrame:
df = pd.read_csv(path)
return df
def load_esg(path:str)->pd.DataFrame:
df = pd.read_csv(path)
return df
def compute_kpi(fin_df:pd.DataFrame, fiscal_year:int):
latest = fin_df[fin_df["year"] == fiscal_year].sort_values("quarter").tail(1)
prev = fin_df[fin_df["year"] == fiscal_year - 1].sort_values("quarter").tail(1)
revenue = float(latest["revenue"].iloc[0]) if not latest.empty and "revenue" in latest.columns else 0.0
operating_income = float(latest["operating_income"].iloc[0]) if not latest.empty and "operating_income" in latest.columns else 0.0
ebit = float(latest["ebit"].iloc[0]) if not latest.empty and "ebit" in latest.columns else 0.0
net_income = float(latest["net_income"].iloc[0]) if not latest.empty and "net_income" in latest.columns else 0.0
equity = float(latest["equity"].iloc[0]) if not latest.empty and "equity" in latest.columns else 0.0 # Assuming 'equity' is a column in financials
ebit_margin = ebit / revenue * 100 if revenue else 0.0
roe = net_income / equity * 100 if equity else 0.0
revenue_yoy = ((revenue / float(prev["revenue"].iloc[0])) - 1) * 100 if not prev.empty and "revenue" in prev.columns else 0.0
return {
"revenue": revenue,
"operating_income": operating_income,
"ebit": ebit,
"net_income": net_income,
"ebit_margin": ebit_margin,
"roe": roe,
"revenue_yoy": revenue_yoy,
}
def esg_table(df:pd.DataFrame, fiscal_year:int):
dfy = df[df["year"]==fiscal_year].copy()
rows = []
for _,r in dfy.iterrows():
display = DISPLAY_NAME.get(r["metric"],r["metric"])
rows.append({
"display":display,
"value":r["value"],
"unit":r.get("unit",""),
"scope":r.get("scope",""),
"notes":r.get("notes","")
})
return rows
def build_sections(meta:CompanyMeta,kpi:dict,esg_rows:list,llm=None)->ReportSections:
if llm:
ceo_message = llm.generate_ceo_message(meta,kpi,esg_rows)
risk=llm.generate_risk_opportunity(meta,kpi,esg_rows)
else:
ceo_message =f"{meta.fiscal_year}期は、売り上げ成長と収益性の両立に注力しました。今後も中長期的な企業価値向上を目指します。"
risk="主要リスクはマクロ環境と規制動向。機会は生成AI活用と脱炭素需要の拡大です。"
return ReportSections(ceo_message=ceo_message,risk_opportunity=risk)
def generate_report(
company_yaml:str,financials_csv:str,esg_csv:str,
template_dir:str,template_name:str ="report.html.j2",
out_html="output/report.html",out_pdf="output/report.pdf",out_docx="output/report.docx",
llm=None,
):
Path("output").mkdir(exist_ok=True)
meta = load_company_meta(company_yaml)
fin =load_financials(financials_csv)
esg= load_esg(esg_csv)
kpi = compute_kpi(fin,meta.fiscal_year)
esg_rows = esg_table(esg,meta.fiscal_year)
sections = build_sections(meta,kpi,esg_rows,llm=llm)
env=get_env(template_dir)
payload = RenderPayload(
meta=meta,esg_table=esg_rows,kpi=kpi,sections=sections,generated_at=datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
).model_dump()
html = render(env,template_name,payload)
Path(out_html).write_text(html,encoding="utf-8")
# html_to_pdf(html,out_pdf) # Commenting out due to potential weasyprint dependency issues
html_to_docx(html,out_docx)
return out_html,out_pdf,out_docx
"""
with open("core.py", "w", encoding="utf-8") as f:
f.write(core_code)
# Write the code from cell M9d5MAI4iiOK into the file app/main.py
main_code = """
from fastapi import FastAPI, UploadFile, Form
from fastapi.responses import FileResponse
from pathlib import Path
import tempfile, shutil
from core import generate_report
app = FastAPI()
@app.post("/generate")
async def generate(company: UploadFile, financials: UploadFile, esg: UploadFile):
with tempfile.TemporaryDirectory() as td:
cpath = Path(td)/"company.yaml"; cpath.write_bytes(await company.read())
fpath = Path(td)/"financials.csv"; fpath.write_bytes(await financials.read())
epath = Path(td)/"esg.csv"; epath.write_bytes(await esg.read())
html, pdf, docx = generate_report(
str(cpath), str(fpath), str(epath), templates_dir="app/templates"
)
return {"html": html, "pdf": pdf, "docx": docx}
# uvicorn app.main:app --reload
"""
with open("app/main.py", "w", encoding="utf-8") as f:
f.write(main_code)
# Write the code from cell QnYd9YY5i_X_ into the file app/templates/report.html.j2
html_template_content = """
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8" />
<title>{{ meta.report_title }} - {{ meta.company_name }}</title>
<style>
body { font-family: system-ui, -apple-system, "Segoe UI", Helvetica, Arial; line-height: 1.6; }
h1,h2,h3 { margin: 0.6em 0; }
.kpi { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
.card { border: 1px solid #ddd; border-radius: 12px; padding: 12px; }
.small { color: #666; font-size: 0.9em; }
table { border-collapse: collapse; width: 100%; }
th, td { border: 1px solid #eee; padding: 8px; text-align: right; }
th { background: #fafafa; }
.left { text-align: left; }
</style>
</head>
<body>
<h1>{{ meta.report_title }}({{ meta.fiscal_year }})</h1>
<p class="small">{{ meta.company_name }} / Ticker: {{ meta.ticker }} / 通貨: {{ meta.currency }}</p>
<h2>CEOメッセージ</h2>
<p>{{ sections.ceo_message }}</p>
<h2>ハイライトKPI</h2>
<div class="kpi">
<div class="card">
<div class="small">売上高</div>
<div><strong>{{ kpi.revenue | round(0) | int }} {{ meta.currency }}</strong></div>
<div class="small">前年比: {{ kpi.revenue_yoy | round(1) }}%</div>
</div>
<div class="card">
<div class="small">EBIT</div>
<div><strong>{{ kpi.ebit | round(0) | int }} {{ meta.currency }}</strong></div>
<div class="small">マージン: {{ kpi.ebit_margin | round(1) }}%</div>
</div>
<div class="card">
<div class="small">純利益</div>
<div><strong>{{ kpi.net_income | round(0) | int }} {{ meta.currency }}</strong></div>
<div class="small">ROE: {{ kpi.roe | round(1) }}%</div>
</div>
</div>
<h2>ESGサマリー</h2>
<table>
<thead>
<tr><th class="left">指標</th><th>値</th><th>単位</th><th class="left">備考</th></tr>
</thead>
<tbody>
{% for row in esg_table %}
<tr>
<td class="left">{{ row.display }}</td>
<td>{{ row.value }}</td>
<td>{{ row.unit }}</td>
<td class="left">{{ row.notes }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<h2>マテリアリティ & ターゲット</h2>
<ul>
{% for topic in meta.material_topics %}
<li>{{ topic }}</li>
{% endfor %}
</ul>
<p class="small">CO₂削減目標: {{ meta.targets.co2_reduction_pct }}% / 女性管理職比率: {{ meta.targets.female_management_ratio }}% / 再エネ比率: {{ meta.targets.renewable_energy_ratio }}%</p>
<h2>リスク & 機会(要約)</h2>
<p>{{ sections.risk_opportunity }}</p>
<footer class="small">Generated on {{ generated_at }}</footer>
</body>
</html>
"""
with open("app/templates/report.html.j2", "w", encoding="utf-8") as f:
f.write(html_template_content)