移除 equity 文件夹(误放:内容属于 gemdalepi 项目,gemdalepi 已有更新版)

This commit is contained in:
2026-08-07 11:39:00 +08:00
parent 1ad73d7a30
commit 4e7f599274
5 changed files with 0 additions and 3787 deletions
-21
View File
@@ -1,21 +0,0 @@
# 集团股权架构可视化(金地商置境内数据)
## 文件说明
- `金地商置_体系内.drawio` — 仅我司体系内股东关系(578节点/634关系/7层)
- `金地商置_全量.drawio` — 含外部股东全量(1031节点/1172关系/6层)
- `excel2drawio.py` — 通用转换工具(读"母公司|子公司|比例"格式Excel
- `generate_from_master.py` — 直接读主数据表(01法人公司信息+02公司股东信息)生成
## 用法
```bash
# 通用模板方式
python3 excel2drawio.py 表格.xlsx 输出.drawio
# 主数据表方式
python3 generate_from_master.py 主数据.xlsx 输出.drawio [--internal-only]
```
## 生成记录
- 2026-08-06: 基于《金地商置法人主数据信息-境内-2024.xlsx》生成
- 01表: 673家(境内665+BVI 2),去重后667
- 02表: 1171条股东关系
- 体系内股东: 216家
-173
View File
@@ -1,173 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
excel2drawio.py — 从 Excel 股权关系表生成 drawio 股权架构图
=============================================================
用法:
python3 excel2drawio.py 输入.xlsx [输出.drawio]
Excel 格式 (Sheet 名任意, 表头必须为以下列名):
母公司名称 | 子公司名称 | 股权比例(%) | 备注
每行 = 一条股权关系。无母公司的公司 = 根节点(集团/母公司)。
例:
母公司名称 子公司名称 股权比例(%) 备注
集团总公司 子公司A 100 全资
集团总公司 子公司B 60 控股
子公司A 孙公司C 51 控股
输出: drawio 格式 XML, 可用 drawio 桌面版/网页版打开, 或嵌入网页。
"""
import sys, math
import openpyxl
# ---------- 样式 ----------
STYLE_ROOT = "rounded=1;whiteSpace=wrap;html=1;fillColor=#1e6fd9;fontColor=#ffffff;strokeColor=#0d47a1;fontSize=13;fontStyle=1;"
STYLE_NODE = "rounded=1;whiteSpace=wrap;html=1;fillColor=#e8f0fe;fontColor=#000000;strokeColor=#5b9bd5;fontSize=11;"
STYLE_LEAF = "rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;fontColor=#000000;strokeColor=#999999;fontSize=11;"
STYLE_EDGE = "edgeStyle=orthogonalEdgeStyle;rounded=1;html=1;strokeColor=#666666;strokeWidth=1.5;fontSize=10;"
COL_W, COL_H = 180, 44 # 节点宽高
GAP_X, GAP_Y = 30, 80 # 横向/纵向间距
def read_relations(xlsx_path):
"""读取 Excel, 返回 (关系列表, 所有公司集合)"""
wb = openpyxl.load_workbook(xlsx_path, data_only=True)
ws = wb.active
rows = list(ws.iter_rows(values_only=True))
if not rows:
sys.exit("Excel 为空")
header = [str(c).strip() if c else "" for c in rows[0]]
# 兼容列名变体:先精确匹配,再包含匹配(按 names 顺序优先)
def col(*names):
for i, h in enumerate(header):
if h in names:
return i
for n in names:
for i, h in enumerate(header):
if n in h:
return i
return None
i_parent = col("母公司", "父公司", "上级")
i_child = col("子公司", "公司", "被投")
i_ratio = col("股权比例", "比例", "持股")
i_note = col("备注")
if i_parent is None or i_child is None:
sys.exit(f"表头需要包含「母公司名称」「子公司名称」列, 实际: {header}")
relations, companies = [], set()
for r in rows[1:]:
if not r or all(v is None or str(v).strip() == "" for v in r):
continue
parent = str(r[i_parent]).strip() if i_parent is not None and r[i_parent] else ""
child = str(r[i_child]).strip() if r[i_child] else ""
if not child:
continue
ratio = r[i_ratio] if i_ratio is not None and r[i_ratio] is not None else ""
note = str(r[i_note]).strip() if i_note is not None and r[i_note] else ""
relations.append((parent, child, ratio, note))
companies.add(child)
if parent:
companies.add(parent)
return relations, companies
def build_levels(relations, companies):
"""计算每家公司层级 (根=0), 返回 {公司: 层级}"""
children = {}
for p, c, _, _ in relations:
children.setdefault(p, []).append(c)
# 没有作为子节点出现的公司 = 根(集团/母公司)
as_child = {c for _, c, _, _ in relations}
roots = [c for c in companies if c not in as_child]
level = {}
def dfs(node, lv):
if node in level and level[node] <= lv:
return
level[node] = lv
for ch in children.get(node, []):
dfs(ch, lv + 1)
for r in roots:
dfs(r, 0)
# 兜底: 未覆盖的孤儿节点
for c in companies:
if c not in level:
level[c] = 0
return level, roots
def generate_drawio(relations, companies, xlsx_path):
level, roots = build_levels(relations, companies)
# 按层级分组, 保持输入顺序
order = []
seen = set()
def add(n):
if n not in seen:
seen.add(n); order.append(n)
for p, c, _, _ in relations:
add(p); add(c)
for c in companies:
add(c)
by_level = {}
for n in order:
by_level.setdefault(level[n], []).append(n)
max_lv = max(level.values())
# 计算每层宽度 → 画布大小
positions = {}
for lv, nodes in by_level.items():
n = len(nodes)
x0 = (n - 1) * (COL_W + GAP_X) / 2
for i, node in enumerate(nodes):
positions[node] = (x0 - i * (COL_W + GAP_X), lv * (COL_H + GAP_Y))
canvas_w = max((x + COL_W for x, _ in positions.values()), default=800) + 100
canvas_h = (max_lv + 1) * (COL_H + GAP_Y) + 60
# 节点样式: 根=深蓝, 有子公司=浅蓝, 叶子=灰
children = {p for p, _, _, _ in relations}
lines = []
lines.append(f'<mxGraphModel dx="1200" dy="800" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="{int(canvas_w)}" pageHeight="{int(canvas_h)}" math="0" shadow="0">')
lines.append('<root><mxCell id="0"/><mxCell id="1" parent="0"/>')
nid = 1
for node in order:
nid += 1
x, y = positions[node]
if node in roots:
style = STYLE_ROOT
elif node in children:
style = STYLE_NODE
else:
style = STYLE_LEAF
lines.append(f'<mxCell id="{nid}" value="{node}" style="{style}" vertex="1" parent="1"><mxGeometry x="{int(x)}" y="{int(y)}" width="{COL_W}" height="{COL_H}" as="geometry"/></mxCell>')
positions[node] = (nid, x, y) # 记录 id
for p, c, ratio, note in relations:
nid += 1
pid, _, _ = positions[p] if p in positions else (None, None, None)
cid, _, _ = positions[c] if c in positions else (None, None, None)
if pid is None or cid is None:
continue
label = ""
if ratio not in (None, ""):
label = f"{ratio}%"
if note:
label += f" ({note})"
lines.append(f'<mxCell id="{nid}" value="{label}" style="{STYLE_EDGE}" edge="1" parent="1" source="{pid}" target="{cid}"><mxGeometry relative="1" as="geometry"/></mxCell>')
lines.append('</root></mxGraphModel>')
return "\n".join(lines)
def main():
if len(sys.argv) < 2:
sys.exit(__doc__)
xlsx_path = sys.argv[1]
out_path = sys.argv[2] if len(sys.argv) > 2 else xlsx_path.rsplit(".", 1)[0] + ".drawio"
relations, companies = read_relations(xlsx_path)
if not companies:
sys.exit("没有读到任何公司")
xml = generate_drawio(relations, companies, xlsx_path)
with open(out_path, "w", encoding="utf-8") as f:
f.write(xml)
print(f"✅ 生成成功: {out_path}")
print(f" 公司数: {len(companies)}, 关系数: {len(relations)}")
print(" 用 drawio 打开该文件即可查看架构图")
if __name__ == "__main__":
main()
-171
View File
@@ -1,171 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
generate_from_master.py — 从"金地商置法人主数据信息"源表直接生成 drawio 股权架构图
================================================================================
用法:
python3 generate_from_master.py 源表.xlsx [输出.drawio] [--internal-only]
读取:
01法人公司信息 -> 公司主数据(名称/信用代码/二级组织)
02公司股东信息 -> 股东关系(股东企业名称 → 企业名称, 股权出资比例)
--internal-only: 只画"体系内"关系(股东也在01清单里),聚焦集团内部架构
默认: 全量(含外部股东/自然人股东)
"""
import sys, re, math
import openpyxl
STYLE_ROOT = "rounded=1;whiteSpace=wrap;html=1;fillColor=#1e6fd9;fontColor=#ffffff;strokeColor=#0d47a1;fontSize=13;fontStyle=1;"
STYLE_NODE = "rounded=1;whiteSpace=wrap;html=1;fillColor=#e8f0fe;fontColor=#000000;strokeColor=#5b9bd5;fontSize=11;"
STYLE_LEAF = "rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;fontColor=#000000;strokeColor=#999999;fontSize=11;"
STYLE_EXT = "rounded=1;whiteSpace=wrap;html=1;fillColor=#fff8e1;fontColor=#000000;strokeColor=#e6a700;fontSize=10;dashed=1;"
STYLE_EDGE = "edgeStyle=orthogonalEdgeStyle;rounded=1;html=1;strokeColor=#666666;strokeWidth=1.5;fontSize=10;"
COL_W, COL_H = 190, 44
GAP_X, GAP_Y = 40, 90
def parse_ratio(v):
if v is None:
return None
if isinstance(v, (int, float)):
return float(v)
s = str(v).strip()
if not s:
return None
m = re.search(r"([\d.]+)\s*%", s)
if m:
return float(m.group(1)) / 100
m = re.search(r"([\d.]+)", s)
if m:
return float(m.group(1))
return None
def load_data(xlsx_path):
wb = openpyxl.load_workbook(xlsx_path, data_only=True)
# 01 公司主数据
ws1 = wb["01法人公司信息"]
companies = {}
for r in ws1.iter_rows(values_only=True):
if not r or not r[2]:
continue
name = str(r[2]).strip()
if not name:
continue
companies[name] = {
"code": str(r[5]).strip() if r[5] else "",
"org2": str(r[4]).strip() if r[4] else "",
}
# 02 股东关系
ws2 = wb["02公司股东信息"]
rels = []
for r in ws2.iter_rows(values_only=True):
if not r or not r[2] or not r[3]:
continue
company = str(r[2]).strip()
holder = str(r[3]).strip()
if not company or not holder:
continue
ratio = parse_ratio(r[6]) if len(r) > 6 else None
rels.append((holder, company, ratio))
return companies, rels
def build_graph(rels, internal_only, companies):
"""返回 (nodes, edges, roots)。nodes: {name: {ext, org2}}"""
node_info = {}
edges = []
for holder, company, ratio in rels:
if internal_only and holder not in companies:
continue # 只保留体系内股东
node_info.setdefault(holder, {"ext": holder not in companies})
node_info.setdefault(company, {"ext": company not in companies})
edges.append((holder, company, ratio))
as_child = {c for _, c, _ in edges}
roots = [n for n in node_info if n not in as_child]
return node_info, edges, roots
def layout(node_info, edges, roots):
"""BFS 分层布局, 返回 {name: (x, y)} 和层级数"""
children = {}
for h, c, _ in edges:
children.setdefault(h, []).append(c)
level = {}
def dfs(n, lv):
if n in level and level[n] <= lv:
return
level[n] = lv
for ch in children.get(n, []):
dfs(ch, lv + 1)
for r in roots:
dfs(r, 0)
for n in node_info:
level.setdefault(n, 0)
by_level = {}
for n in node_info:
by_level.setdefault(level[n], []).append(n)
max_lv = max(level.values())
pos = {}
for lv, nodes in by_level.items():
n = len(nodes)
x0 = (n - 1) * (COL_W + GAP_X) / 2
for i, node in enumerate(nodes):
pos[node] = (x0 - i * (COL_W + GAP_X), lv * (COL_H + GAP_Y))
return pos, max_lv
def generate_drawio(node_info, edges, roots, out_path):
pos, max_lv = layout(node_info, edges, roots)
canvas_w = max((x + COL_W for x, _ in pos.values()), default=800) + 120
canvas_h = (max_lv + 1) * (COL_H + GAP_Y) + 80
children = {p for p, _, _ in edges}
lines = [f'<mxGraphModel dx="1400" dy="900" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="{int(canvas_w)}" pageHeight="{int(canvas_h)}" math="0" shadow="0">']
lines.append('<root><mxCell id="0"/><mxCell id="1" parent="0"/>')
nid = 1
for node in pos:
nid += 1
x, y = pos[node]
if node in roots:
style = STYLE_ROOT
elif node_info[node]["ext"]:
style = STYLE_EXT
elif node in children:
style = STYLE_NODE
else:
style = STYLE_LEAF
label = node.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace('"', "&quot;")
lines.append(f'<mxCell id="{nid}" value="{label}" style="{style}" vertex="1" parent="1"><mxGeometry x="{int(x)}" y="{int(y)}" width="{COL_W}" height="{COL_H}" as="geometry"/></mxCell>')
pos[node] = (nid, x, y)
for h, c, ratio in edges:
nid += 1
if h not in pos or c not in pos:
continue
hid, _, _ = pos[h]
cid, _, _ = pos[c]
label = f"{ratio*100:.0f}%" if ratio is not None else ""
lines.append(f'<mxCell id="{nid}" value="{label}" style="{STYLE_EDGE}" edge="1" parent="1" source="{hid}" target="{cid}"><mxGeometry relative="1" as="geometry"/></mxCell>')
lines.append('</root></mxGraphModel>')
with open(out_path, "w", encoding="utf-8") as f:
f.write("\n".join(lines))
return len(pos), len(edges), max_lv
def main():
if len(sys.argv) < 2:
sys.exit(__doc__)
xlsx_path = sys.argv[1]
out_path = sys.argv[2] if len(sys.argv) > 2 and not sys.argv[2].startswith("--") else xlsx_path.rsplit(".", 1)[0] + ".drawio"
internal_only = "--internal-only" in sys.argv
companies, rels = load_data(xlsx_path)
node_info, edges, roots = build_graph(rels, internal_only, companies)
n_nodes, n_edges, max_lv = generate_drawio(node_info, edges, roots, out_path)
mode = "体系内(仅我司股东)" if internal_only else "全量(含外部股东)"
print(f"{out_path}")
print(f" 模式: {mode} | 节点数: {n_nodes} | 关系数: {n_edges} | 最大层级: {max_lv+1}")
print(f" 根节点: {len(roots)}")
if __name__ == "__main__":
main()
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff