Corin1998 commited on
Commit
f469bad
·
verified ·
1 Parent(s): fc1c283

Upload 11 files

Browse files
Hugging Face/FROM python:3.dockerfile ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ # OS deps for WeasyPrint (Cairo/Pango) + 日本語フォント
4
+ RUN apt-get update && apt-get install -y --no-install-recommends \
5
+ libcairo2 pango1.0-tools libpango-1.0-0 libgdk-pixbuf2.0-0 libffi-dev \
6
+ fonts-noto fonts-noto-cjk \
7
+ && rm -rf /var/lib/apt/lists/*
8
+
9
+ WORKDIR /code
10
+ COPY requirements.txt .
11
+ RUN pip install --no-cache-dir -U pip wheel && pip install --no-cache-dir -r requirements.txt
12
+
13
+ COPY . .
14
+ ENV GRADIO_SERVER_NAME=0.0.0.0
15
+ EXPOSE 7860
16
+ CMD ["python", "app.py"]
Hugging Face/README.md ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # IR/ESG Report Generator (HF Space, LLM+Translation)
2
+
3
+ CSV/YAML をアップロードして IR/ESG レポート(HTML/PDF/DOCX)を生成します。
4
+ LLM 要約+翻訳に **OPENAI_API_KEY2** を使用します。
5
+
6
+ ## 入力例
7
+ - company.yaml
8
+ - financials.csv
9
+ - esg_metrics.csv
10
+
11
+ ## 設定(必須)
12
+ Hugging Face Space → **Settings → Variables and secrets**:
13
+ - Name: `OPENAI_API_KEY2`
14
+ - Value: `<your openai api key>`
15
+
16
+ ## 起動
17
+ このリポジトリを Space にそのままアップロード(Docker Space)。自動ビルド後に起動します。
Hugging Face/app.py ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from pathlib import Path
3
+ import tempfile,datetime, json
4
+ from core import generate_report
5
+ import shutil
6
+
7
+ TITLE = "IR/ESG Report Generator(Hugging Face Space)"
8
+ DESC = ""
9
+ CSV/YAMLをアップロードしてIR/ESGレポート(HTML/PDF/DOCX)を生成します。
10
+ ""
11
+
12
+ def run(company_yaml,financials_csv,esg_csv,use_llm,lang):
13
+ if company_yamal is None or financials_csv is None or esg_csv is None:
14
+ return "すべてのファイルをアップロードしてください。"None,None,None,None
15
+
16
+ with tempfile.TemporaryDirectory() as td:
17
+ cpath = Path(td)/"company_yaml";cpath.write_bytes(company_yaml.read())
18
+ fpath = Path(td)/"financials_csv";fpath.write_bytes(financials_csv.read())
19
+ epath = Path(td)/"esg_csv";epath.write_bytes(esg_csv.read())
20
+
21
+ outdir = Path(td)/"out"
22
+ outdir.mkdir(parents=True, exist_ok=True)
23
+
24
+ llm=None
25
+ if use_llm:
26
+ try:
27
+ from llm import OpenAILLM
28
+ llm = OpenAILLM()
29
+ except Exception as e:
30
+ return f"LLM初期化エラー: {e}",None,None,None
31
+
32
+ html,pdf,docx,meta_json = generate_report(
33
+ company_yamal=str(cpath),
34
+ financials_csv=str(fpath),
35
+ esg_csv=str(epath),
36
+ templates_dir="templates",
37
+ template_name="report.html.j2",
38
+ out_html=str(outdir/"report.html")
39
+ out_pdf=str(outdir/"report.html")
40
+ lang=lang,
41
+ llm=llm
42
+ )
43
+ repo_tmp=Path("./tmp")
44
+ repo_tmp.mkdir(exit_ok=True)
45
+ ts = detatime.datetime.now().strftime("%Ym%d-%H%M%S")
46
+ html_out=repo_tmp/f"report-{ts}.html"
47
+ pdf_out=repo_tmp/f"report-{ts}.pdf"
48
+ docx_out=repo_tmp/f"report-{ts}.docx"
49
+ meta_out=repo_tmp/f"report-{ts}.json"
50
+ shutil.copy(html,html_out)
51
+ shutil.copy(pdf,pdf_out)
52
+ shutil.copy(docx,docx_out)
53
+ Path(meta_out).write_text(json.dumps(meta_json,ensure_ascii=False,indent=2),encoding="utf-8")
54
+
55
+ return"生成が完了しました。",str(html_out),str(pdf_out),str(docx_out),str(meta_out)
56
+
57
+ with gr.Blocks(title=TITLE) as demo:
58
+ gr.Markdown(f"#{TITLE}\n{DESC}")
59
+
60
+ with gr.Row():
61
+ company_yaml=gr.File(label="company.yaml(会社情報・年度等)",file_types=[".yaml",".yml"])
62
+ financials_csv=gr.File(label="financials.csv(財務KPI)",file_types=[".csv"])
63
+ esg_csv=gr.File(label="esg_metrics.csv(ESG指標)",file_types=[".csv"])
64
+
65
+ with gr.Row():
66
+ use_llm=gr.Chheckbox(label="LLMで要約/翻訳を行う(OPENAI_API_KE2 必須)",value=True)
67
+ lang = gr.Dropdown(choices=["ja","en","zh","ko","de","fr"],value="ja",label="出力言語")
68
+
69
+ run_btn=gr.Button("レポート生成")
70
+
71
+ status =gr.Textbox(label="ステータス",interactive=False)
72
+ html_file=gr.File(label="HTMLダウンロード")
73
+ pdf_file=gr.File(label="PDFダウンロード")
74
+ docx_file=gr.File(label="DOCXダウンロード")
75
+ meta_file=gr.File(label="メタ情報(JSON)")
76
+
77
+ run_btn.click(
78
+ fu=run
79
+ inputs=[company_yamal,finanxials_csv,esg_csv,use_llm,lang],
80
+ outputs=[status,html_file,pdf_file,docx_file,meta_file]
81
+ )
82
+
83
+ if__name__=="__main__":
84
+ demo.launch()
Hugging Face/core.py ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import panda as pd
2
+ import yaml,detatime,hashilb,json
3
+ from pathlib import Path
4
+ from templating import get_env,rander
5
+ from models import CompanyMeta,ReportSections,RenderPayload
6
+ from render import hetml_to_pdf,html_to_docx
7
+
8
+ DISPLAY_NAME={
9
+ "co2_emission":"Co2排出量",
10
+ "energy_renewable_ratio":"再生可能エネルギー比率",
11
+ "female_management_ratio":"女性管理職比率"
12
+ }
13
+
14
+ def sha256(p:Path)->str:
15
+ h=hashlib.sha256()
16
+ with p.open("rb") as f:
17
+ for chunk in iter(lambda: f.read(8192), b""):
18
+ h.update(chunk)
19
+ return h.hexdigest()
20
+
21
+ def load_company_meta(path:str)->CompanyMeta:
22
+ data=yaml.safe_load(Path(path).read_text(encoding="utf-8"))
23
+ return CompanyMeta(**data)
24
+
25
+ def load_financials(path:str)->pd.DataFrame:
26
+ return pd.read_csv(path)
27
+
28
+ def compute_kpi(fin_df:pd.DataFrame,fiscal_year:int):
29
+ latest=fin_df[fin_df["year"]==fiscal_year].sort_values("quarter").tail(1)
30
+ prev=fin_df[fin_df["year"]==fiscal_year-1].sort_values("quarter").tail(1)
31
+
32
+ revenue=float(latest["revenue"].iloc[0])
33
+ prev_revenue=float(prev["revenue"].iloc[0]) if not prev.empty else 0
34
+ ebit=float(latest["ebit"].iloc[0]) if not latest.empty else 0
35
+ net_income=float(latest["net_income"].iloc[0]) if not latest.empty else 0
36
+ equity=float(latest["equity"].iloc[0]) if not latest.empty else 0.0
37
+ ebit_margin=ebit /revenue *100 if revenue else 0.0
38
+ revenue_yoy=((revenue/float(prev["revenue"].iloc[0]))-1)*100 if not prev.empty and float(prev["revenue"].iloc[0]) else 0.0
39
+
40
+ return {
41
+ "revenue": revenue,
42
+ "ebit": ebit,
43
+ "ebit_margin": ebit_margin,
44
+ "net_income": net_income,
45
+ "equity": equity,
46
+ "revenue_yoy": revenue_yoy
47
+ }
48
+
49
+ def esg_table (df:pd.DataFrame,fiscal_year:int):
50
+ dfy=df[df["year"]==fiscal_year].copy
51
+ rows=[]
52
+ for _, r in dfy.iterrows():
53
+ display = DISPLAY_NAME.get(r["metric"], r["metric"])
54
+ rows.append({
55
+ "display": display,
56
+ "value": r["value"],
57
+ "unit": r.get("unit", ""),
58
+ "notes": r.get("notes", ""),
59
+ })
60
+
61
+ return rows
62
+ def build_sections(meta:CompanyMeta,kpi:dict,esg_rows:list,llm=None)->ReportSections:
63
+ if llm:
64
+ ceo_message = llm.generate_ceo_message(meta.kpi,esg_rows)
65
+ risk = llm.generate_risk_opportunities(meta.kpi,esg_rows)
66
+ else:
67
+ ceo_message = f"[{meta.fiscal_year}]期は、売上成長と収益性の両立に注力しました。"
68
+ risk = "主要リスクはマクロ環境と規制動向。機会は生成AI活用と脱炭素需要の拡大です。"
69
+ return ReportSections(ceo_message=ceo_message,risk_opportunity=risk)
70
+
71
+ def_translate_payload(payload:dict,lang:str,llm)->dict:
72
+ """payload のうち、テキスト項目をtarget langに翻訳。数値は非対象。"""
73
+    if not llm or lang =="ja":
74
+ return payload
75
+
76
+ texts=[]
77
+
78
+ texts.append(payload["section"]["ceo_message"])
79
+ texts.append(payload["section"]["risk_opportunity"])
80
+
81
+ for row in payload["esg_table"]:
82
+ texts.append(row["display"])
83
+ texts.append(row["notes"] or "")
84
+
85
+ texts.append(payload["meta"]["report_title"])
86
+ for topic in payload["meta"].get("material_topics", []):
87
+ texts.append(topic)
88
+
89
+ translated = llm.translate_text(texts, target_lang=lang)
90
+ it = iter(translated)
91
+
92
+ payload["section"]["ceo_message"] = next(it)
93
+ payload["section"]["risk_opportunity"] = next(it)
94
+
95
+ for row in payload["esg_table"]:
96
+ row["display"] = next(it)
97
+ row["notes"] = next(it)
98
+
99
+ payload["meta"]["report_title"] = next(it)
100
+ mt = payload["meta"].get("material_topics", [])
101
+ for i in range(len(mt)):
102
+ mt[i] = next(it)
103
+
104
+ return payload
105
+
106
+ def generate_report(company_yaml,financials_csv,esg_csv,
107
+ template_dir,template_name="report.html.j2",
108
+ out_html="output/report.html",out_pdf="output/report.pdf",
109
+ out_docx="output/report.docx",lang="ja",llm=None):
110
+ Path(Path(out_html).parent).mkdir(parents=True, exist_ok=True)
111
+ meta= load_company_meta(company_yaml)
112
+ fin= load_financials(financials_csv)
113
+ esg= load_esg(esg_csv)
114
+
115
+ kpi = compute_kpi(fin, meta.fiscal_year)
116
+ esg_rows = esg_table(esg, meta.fiscal_year)
117
+ sections = build_sections(meta, kpi, esg_rows, llm=llm)
118
+
119
+ env = get_env(template_dir)
120
+ payload = RenderPayload(
121
+ meta=meta, kpi=kpi, esg_table=esg_rows,kpi=kpi, sections=sections,
122
+ generated_at=datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),lang=lang
123
+ ).model__dump()
124
+
125
+ payload= _translate_payload_texts(payload, lang=lang, llm=llm)
126
+
127
+ html = render(env, template_name, payload)
128
+ Path(out_html).write_text(html, encoding="utf-8")
129
+ html_to_pdf(html, out_pdf)
130
+ html_to_docx(html, out_docx)
131
+
132
+ meta_json ={
133
+ "inputs":{
134
+ "company_yaml_sha":_sha256(Path(company_yaml)),
135
+ "financials_csv_sha":_sha256(Path(financials_csv)),
136
+ "esg_csv_sha":_sha256(Path(esg_csv)),
137
+ "lang":lang
138
+ },
139
+ "outputs":{"html":out_html,"pdf":out_pdf,"docx":out_docx},
140
+ "template":{"dir":templates_dir,"name":template_name},
141
+ "generated_at":datetime.datetime.now().strftime("timespec=sedconds"),
142
+ }
143
+ return out_html,out_pdf,out_docx,meta_json
Hugging Face/hf.yaml ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ title: IR-ESG-Report-Generator
2
+ emoji: 📈
3
+ colorFrom: gray
4
+ colorTo: indigo
5
+ sdk: docker
6
+ pinned: false
Hugging Face/llm.py ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from typing import List, Dict, Any
3
+ from openai import OpenAI
4
+
5
+ class OpenAILLM:
6
+ def __init__(self,model_chat:str = "gpt-4o",model_translate:str = "gpt-4o", model_summarize:str = "gpt-4o"):
7
+ api_key = os.environ.get("OPENAI_API_KEY2")
8
+ if not api_key:
9
+ raise RuntimeError("OPENAI_API_KEY2 is not set in environment variables.")
10
+ self.client = OpenAI(api_key=api_key)
11
+ self.model_chat = model_chat
12
+ self.model_translate = model_translate
13
+ self.model_summarize = model_summarize
14
+
15
+ def generate_ceo_message(self,meta, kpi:Dict[str,float],esg_row:List[Dict[str,Any]])->str:
16
+ prompt = (
17
+ "以下の企業情報・KPI・ESG指標をもとに、日本語で200字程度のCEOメッセージ草案を出力してください。"
18
+ "投資家に伝わる簡潔なトーンで、過度な形容表現は避けてください。\n\n"
19
+ f"企業情報: {meta.model_dump()}\n"
20
+ f"KPI: {kpi}\n"
21
+ f"ESG指標: {esg_row}\n"
22
+ )
23
+ rsp = self.client.chat.completions.create(
24
+ model=self.model_chat,
25
+ messages=[{"role": "user", "content": prompt}],
26
+ temperature=0.3,
27
+ )
28
+ return rsp.choices[0].message.content.strip()
29
+
30
+ def generate_risk_opportunity(self,meta,kpi:Dict[str,float],esg_row:List[Dict[str,Any]])->str:
31
+ prompt = (
32
+ "以下の情報から、日本語で200字程度のリスクと機会の草案を出力してください。"
33
+ "定量/定性のバランスを取り、具体的な観点を1-2点挙げてください。\n\n"
34
+ f"企業情報: {meta.model_dump()}\n"
35
+ f"KPI: {kpi}\n"
36
+ f"ESG: {esg_row}\n"
37
+ )
38
+ rsp = self.client.chat.completions.create(
39
+ model=self.model_chat,
40
+ messages=[{"role": "user", "content": prompt}],
41
+ temperature=0.3,
42
+ )
43
+ return rsp.choices[0].message.content.strip()
44
+
45
+ def translate_text(self, texts:List[str], target_language: str) -> List[str]:
46
+ if not texts:
47
+ return texts
48
+ system=(
49
+ "You are precise financial/ESG translator.Preserve numbers,units,and proper nouns."
50
+ "Keep tone concise,suitable for investor reports."
51
+ )
52
+ SEP = "\n<<<SEP>>>\n"
53
+ joined = SEP.join(texts)
54
+ prompt = f"Translate the following content to {target_language}.Keep layout minimal.\n\n{joined}"
55
+ rsp = self.client.chat.completions.create(
56
+ model=self.model_translate,
57
+ messages=[
58
+ {"role": "user", "content": system},{"role": "user", "content": prompt}
59
+ ],
60
+ temperature=0.2,
61
+ )
62
+ out = rsp.choices[0].message.content
63
+ parts = [p.strip() for p in out.split("<<<SEP>>>")]
64
+ if len(parts) != len(texts):
65
+ parts = [out]+texts[1:]
66
+ parts = parts[:len(texts)]
67
+ return parts
Hugging Face/models.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel
2
+ from typing import Lisr,Optional,Dict,Any
3
+
4
+ class CompnayMeta(BaseModel):
5
+ company_name:str
6
+ fiscal_year:int
7
+ currency:str ="JPY"
8
+ ticker:Optional[str] = None
9
+ report_title:str ="Integrated Report"
10
+ ceo_name:Optional[str] = None
11
+ material_topics:List[str] = []
12
+ targets:Dict[str, Any] = {}
13
+
14
+ class ReportSection(BaseModel):
15
+ ceo_message:str=""
16
+ risk_opportunity:str=""
17
+
18
+ class ReportPayload(BaseModel):
19
+ meta:CompanyMeta
20
+ esg_table:List[Dict[str, Any]]
21
+ kpi:Dict[str, float]
22
+ sections:ReportSection
23
+ generated_at: str
24
+ lang:str="ja"
Hugging Face/render.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from weasyprint import HTML
2
+ from docx import Document
3
+ from docx.shared import Pt
4
+ from bs4 import BeautifulSoup
5
+
6
+ def html_to_pdf(html_str:str,out_pdf_path:str):
7
+ """
8
+ Convert HTML string to PDF file.
9
+ """
10
+ HTML(string=html_str).write_pdf(out_pdf_path)
11
+
12
+ def html_to_docx(html_str:str,out_docx_path:str):
13
+ doc = Document()
14
+ soup = BeautifulSoup(html_str, 'html.parser')
15
+ for tag in soup.find_all(["h1","h2","h3","p","li"]):
16
+ txt=tag.get_text(strip=True)
17
+ if not txt:
18
+ continue
19
+ if tag.name =="h1":
20
+ p=doc.add_heading(txt,level=0)
21
+ elif tag.name =="h2":
22
+ p = doc.add_heading(txt,level=1)
23
+ elif tag.name =="h3":
24
+ p= doc.add_heading(txt,level=2)
25
+ else:
26
+ p=doc.add_paragraph(txt)
27
+ for run in p.runs:
28
+ run.font.size = Pt(11)
29
+ doc.save(out_docx_path)
Hugging Face/requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ gradio==4.44.0
2
+ pandas==2.2.2
3
+ pydantic==2.7.4
4
+ Jinja2==3.1.4
5
+ python-docx==1.1.2
6
+ WeasyPrint==62.3
7
+ beautifulsoup4==4.12.3
8
+ PyYAML==6.0.2
9
+ openai==1.40.2
Hugging Face/templates:report.html.j2 ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="{{ lang }}">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title>{{ meta.report_title }} - {{ meta.company_name }}</title>
6
+ <style>
7
+ body { font-family: system-ui, -apple-system, "Segoe UI", Helvetica, Arial; line-height: 1.6; }
8
+ h1,h2,h3 { margin: 0.6em 0; }
9
+ .kpi { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
10
+ .card { border: 1px solid #ddd; border-radius: 12px; padding: 12px; }
11
+ .small { color: #666; font-size: 0.9em; }
12
+ table { border-collapse: collapse; width: 100%; }
13
+ th, td { border: 1px solid #eee; padding: 8px; text-align: right; }
14
+ th { background: #fafafa; }
15
+ .left { text-align: left; }
16
+ </style>
17
+ </head>
18
+ <body>
19
+ <h1>{{ meta.report_title }}({{ meta.fiscal_year }})</h1>
20
+ <p class="small">{{ meta.company_name }} / Ticker: {{ meta.ticker }} / 通貨: {{ meta.currency }}</p>
21
+
22
+ <h2>CEOメッセージ</h2>
23
+ <p>{{ sections.ceo_message }}</p>
24
+
25
+ <h2>ハイライトKPI</h2>
26
+ <div class="kpi">
27
+ <div class="card">
28
+ <div class="small">売上高</div>
29
+ <div><strong>{{ kpi.revenue | round(0) | int }} {{ meta.currency }}</strong></div>
30
+ <div class="small">前年比: {{ kpi.revenue_yoy | round(1) }}%</div>
31
+ </div>
32
+ <div class="card">
33
+ <div class="small">EBIT</div>
34
+ <div><strong>{{ kpi.ebit | round(0) | int }} {{ meta.currency }}</strong></div>
35
+ <div class="small">マージン: {{ kpi.ebit_margin | round(1) }}%</div>
36
+ </div>
37
+ <div class="card">
38
+ <div class="small">純利益</div>
39
+ <div><strong>{{ kpi.net_income | round(0) | int }} {{ meta.currency }}</strong></div>
40
+ <div class="small">ROE: {{ kpi.roe | round(1) }}%</div>
41
+ </div>
42
+ </div>
43
+
44
+ <h2>ESGサマリー</h2>
45
+ <table>
46
+ <thead>
47
+ <tr><th class="left">指標</th><th>値</th><th>単位</th><th class="left">備考</th></tr>
48
+ </thead>
49
+ <tbody>
50
+ {% for row in esg_table %}
51
+ <tr>
52
+ <td class="left">{{ row.display }}</td>
53
+ <td>{{ row.value }}</td>
54
+ <td>{{ row.unit }}</td>
55
+ <td class="left">{{ row.notes }}</td>
56
+ </tr>
57
+ {% endfor %}
58
+ </tbody>
59
+ </table>
60
+
61
+ <h2>マテリアリティ & ターゲット</h2>
62
+ <ul>
63
+ {% for topic in meta.material_topics %}
64
+ <li>{{ topic }}</li>
65
+ {% endfor %}
66
+ </ul>
67
+ <p class="small">CO₂削減目標: {{ meta.targets.co2_reduction_pct }}% / 女性管理職比率: {{ meta.targets.female_management_ratio }}% / 再エネ比率: {{ meta.targets.renewable_energy_ratio }}%</p>
68
+
69
+ <h2>リスク & 機会(要約)</h2>
70
+ <p>{{ sections.risk_opportunity }}</p>
71
+
72
+ <footer class="small">Generated on {{ generated_at }}</footer>
73
+ </body>
74
+ </html>
Hugging Face/templating.py ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from jinja2 import Environment, FileSystemLoader, select_autoescape
2
+
3
+ def get_env(templates_dir: str):
4
+ env = Environment(
5
+ loader=FileSystemLoader(templates_dir),
6
+ autoescape=select_autoescape(["html", "xml"])
7
+ )
8
+ return env
9
+
10
+ def render(env, template_name: str, context: dict) -> str:
11
+ template = env.get_template(template_name)
12
+ return template.render(**context)