Files
server-core/scripts/import_kunming_park.py
T

372 lines
17 KiB
Python
Raw Normal View History

"""
昆明市大学生创业园数据导入:
1. 入驻企业信息表 → 补充 park_companies 的 zone/founder/bio
2. 月报目录 → 解析所有企业运营情况表,写入 incubator_monthly_reports
"""
import asyncio
import json
import os
import re
import secrets
from pathlib import Path
import asyncmy
import openpyxl
import xlrd
DB = dict(host="47.108.226.213", port=8091, user="opc",
password="jjjsgysyujkwjwgb", database="opc")
TENANT_ID = "T001"
BASE = Path("/Volumes/Pine/mycode/opc/昆明市大学生创业园资料")
ENTERPRISE_XLSX = BASE / "昆明市大学生创业园入驻企业信息表.xlsx"
MONTHLY_DIR = BASE / "月报"
# ── 工具 ──────────────────────────────────────────────────────────────
def norm(s):
if s is None:
return ""
return str(s).strip().replace("\n", "").replace("\r", "").replace(" ", "")
def norm_name(s):
"""企业名归一化:去掉所有标点、括号、空格,用于模糊匹配"""
if s is None:
return ""
s = str(s)
for ch in "()()【】[]「」『』、,,。.·•—-—_~~!!??\"\"''\\/|":
s = s.replace(ch, "")
return s.strip().replace(" ", "")
def to_float(v):
if v is None or v == "" or v == "/":
return None
try:
return float(str(v).replace(",", "").replace("", "").strip())
except (ValueError, TypeError):
return None
def to_int(v):
f = to_float(v)
return int(f) if f is not None else None
def read_sheet_rows(path):
"""读取第一个 sheet 的所有行,返回 list[list]"""
ext = path.suffix.lower()
rows = []
if ext == ".xlsx":
wb = openpyxl.load_workbook(str(path), data_only=True)
ws = wb[wb.sheetnames[0]]
rows = [list(r) for r in ws.iter_rows(values_only=True)]
elif ext in (".xls", ".et"):
book = xlrd.open_workbook(str(path))
sh = book.sheet_by_index(0)
for r in range(sh.nrows):
rows.append(sh.row_values(r))
return rows
def find_header_row(rows):
"""找到包含'孵化区域''单元号'的列头行(排除大标题行)"""
for i, row in enumerate(rows):
vals = [norm(c) for c in row]
if "孵化区域" in vals and any("单元号" in v for v in vals):
return i
# 降级:找包含"孵化区域"的行
for i, row in enumerate(rows):
vals = [norm(c) for c in row]
if "孵化区域" in vals:
return i
return None
def extract_monthly_data(rows, filename, month_hint):
"""从月报行数据中提取字段 dict"""
hdr_idx = find_header_row(rows)
if hdr_idx is None:
return None
headers = [norm(c) for c in rows[hdr_idx]]
# 数据行:表头之后跳过空行,找第3列(企业名)非空的行
data_row = None
name_col = 2 # 默认企业名在第3列(索引2
for j, h in enumerate(headers):
if "创业企业" in h or "企业名称" in h or "项目名称" in h:
name_col = j
break
for i in range(hdr_idx + 1, min(hdr_idx + 6, len(rows))):
vals = [norm(c) for c in rows[i]]
if len(vals) > name_col and vals[name_col] and vals[name_col] not in ("创业企业(项目)名称",):
# 排除表头重复和"创业团队主要成员"行
if "创业团队主要成员" not in "".join(vals):
data_row = rows[i]
break
def col(name_keywords):
for j, h in enumerate(headers):
for kw in name_keywords:
if kw in h:
return data_row[j] if j < len(data_row) else None
return None
d = {}
d["zone"] = norm(col(["孵化区域"]))
d["unit_no"] = norm(col(["单元号"]))
d["company_name"] = norm(col(["创业企业", "企业名称", "项目名称"]))
d["park_entry_date"] = norm(col(["入园时间"]))
d["leader_name"] = norm(col(["负责人姓名"]))
d["registered_at"] = norm(col(["注册时间"]))
d["registered_capital_wan"] = to_float(col(["注册资金"]))
d["credit_code"] = norm(col(["统一信用代码", "统一社会信用代码"]))
d["rd_invest_wan"] = to_float(col(["研发", "研发投入"]))
d["month_revenue_wan"] = to_float(col(["当月营业额"]))
d["avg_year_revenue_wan"] = to_float(col(["年平均营业额", "年均营业额"]))
d["month_gross_profit_wan"] = to_float(col(["当月毛利润", "毛利润"]))
d["month_tax_wan"] = to_float(col(["当月缴税"]))
d["avg_year_tax_wan"] = to_float(col(["年平均缴税", "年均缴税"]))
d["loan_startup_wan"] = to_float(col(["创业担保贷款"]))
d["loan_yunling_wan"] = to_float(col(["云岭创业贷款"]))
d["contest_award_wan"] = to_float(col(["创业大赛扶持"]))
d["patents"] = to_int(col(["获得专利"]))
d["jobs_created"] = to_int(col(["带动就业"]))
# 团队成员:在数据行之后找"创业团队主要成员"行
team = []
for i in range(hdr_idx + 1, len(rows)):
vals = [norm(c) for c in rows[i]]
if "创业团队主要成员" in "".join(vals):
# 下一行是子表头(姓名/毕业院校/毕业时间/职务),再下一行开始是数据
for j in range(i + 2, min(i + 8, len(rows))):
row = rows[j]
name = norm(row[0]) if len(row) > 0 else ""
if not name or name in ("姓名",):
continue
school = norm(row[2]) if len(row) > 2 else ""
grad = norm(row[3]) if len(row) > 3 else ""
title = norm(row[5]) if len(row) > 5 else ""
if name:
team.append({"name": name, "school": school, "grad": grad, "title": title})
# 第二组(第9列开始)
name2 = norm(row[9]) if len(row) > 9 else ""
if name2:
school2 = norm(row[12]) if len(row) > 12 else ""
grad2 = norm(row[15]) if len(row) > 15 else ""
title2 = norm(row[17]) if len(row) > 17 else ""
team.append({"name": name2, "school": school2, "grad": grad2, "title": title2})
break
d["team_json"] = json.dumps(team, ensure_ascii=False) if team else ""
# 公司经营状况 / 建议
for i in range(hdr_idx + 1, len(rows)):
vals = [norm(c) for c in rows[i]]
joined = "".join(vals)
if "公司经营状况" in joined or "经营状况" in joined:
# 经营状况文本通常在第3列或整行
d["business_note"] = norm(rows[i][3]) if len(rows[i]) > 3 and rows[i][3] else (vals[1] if len(vals) > 1 else "")
if not d["business_note"]:
d["business_note"] = joined.replace("公司经营状况", "").strip()
if "建议" in joined and ("创业园" in joined or "管理服务" in joined):
d["suggestion"] = norm(rows[i][3]) if len(rows[i]) > 3 and rows[i][3] else ""
if not d["suggestion"]:
d["suggestion"] = joined.replace("对创业园管理服务的建议和意见:", "").strip()
# 填表人/邮箱/填表时间
for i in range(len(rows) - 1, max(len(rows) - 6, 0), -1):
vals = [str(c) for c in rows[i] if c]
joined = "".join(vals)
if "填表人" in joined:
m = re.search(r"填表人[:]\s*(\S+)", joined)
if m:
d["filler_name"] = m.group(1)
m = re.search(r"邮箱[:]\s*(\S+)", joined)
if m:
d["filler_email"] = m.group(1)
m = re.search(r"填表时间[:]\s*(\S+)", joined)
if m:
d["submitted_at"] = m.group(1)
break
# report_month:优先从 submitted_at 推断,其次用 month_hint
if d.get("submitted_at"):
m = re.search(r"(20\d{2})[年\-/](\d{1,2})", d["submitted_at"])
if m:
d["report_month"] = f"{m.group(1)}-{int(m.group(2)):02d}"
if not d.get("report_month"):
d["report_month"] = month_hint
d["status"] = "submitted"
return d
# ── 主流程 ────────────────────────────────────────────────────────────
async def main():
conn = await asyncmy.connect(**DB)
async with conn.cursor() as cur:
# ── 1. 入驻企业信息表 → 补充 park_companies ──
wb = openpyxl.load_workbook(str(ENTERPRISE_XLSX), data_only=True)
ws = wb[wb.sheetnames[0]]
enterprises = []
for i, row in enumerate(ws.iter_rows(values_only=True)):
if i < 2:
continue
seq = row[0]
if not seq or not str(seq).strip().isdigit():
continue
name = norm(row[1])
founder = str(row[2]).strip() if row[2] else ""
bio = str(row[3]).strip() if row[3] else ""
zone = norm(row[4])
if name:
enterprises.append((name, founder, bio, zone))
print(f"入驻企业信息表: {len(enterprises)}")
updated = 0
for name, founder, bio, zone in enterprises:
# 模糊匹配:去掉空格和括号差异
await cur.execute(
"SELECT id, name FROM park_companies WHERE tenant_id=%s",
(TENANT_ID,))
all_rows = await cur.fetchall()
matched = None
nname = norm_name(name)
for cid, cname in all_rows:
cn = norm_name(cname)
if cn == nname or nname in cn or cn in nname:
matched = cid
break
if matched:
await cur.execute(
"""UPDATE park_companies SET zone=%s, founder=%s, bio=%s
WHERE id=%s""",
(zone, founder, bio, matched))
updated += 1
else:
print(f" ⚠ 未匹配到企业: {name}")
await conn.commit()
print(f"park_companies 补充/创建: {updated}")
# ── 2. 月报导入 ──
month_map = {"4月": "2026-04", "5月": "2026-05",
"6、7月": None, "8月": "2026-08"}
# 预加载所有 T001 企业用于匹配
await cur.execute("SELECT id, name, founder FROM park_companies WHERE tenant_id=%s", (TENANT_ID,))
all_companies = list(await cur.fetchall())
async def match_company(name, leader):
"""按企业名→负责人姓名匹配,匹配不到则自动创建"""
nname = norm_name(name)
# 1. 企业名匹配
for cid, cname, founder in all_companies:
cn = norm_name(cname)
if cn == nname or nname in cn or cn in nname:
return cid
# 2. 负责人姓名匹配(founder 里通常含负责人名)
if leader:
nleader = norm_name(leader)
for cid, cname, founder in all_companies:
if founder and nleader in norm_name(founder):
return cid
# 3. 自动创建企业
cid = "PC" + secrets.token_hex(4)
await cur.execute(
"""INSERT INTO park_companies
(id, tenant_id, company_kind, name, zone, room, industry,
bio, founder, status, employees, legal_person, legal_phone,
registered_capital, company_type, honors, address, contact_phone,
founded_at, compute_discount, compute_quota, compute_quota_used,
engine_group, owner_user_id, created_at, compute_balance, compute_balance_used)
VALUES (%s,%s,'project',%s,'','','','',%s,'active',0,'','','','','','','',0,0,0,0,'','','',0,0)""",
(cid, TENANT_ID, name, leader or ""))
all_companies.append((cid, name, leader or ""))
print(f" + 自动创建企业: {name}")
return cid
total = 0
skipped = []
for month_dir, month_hint in month_map.items():
mdir = MONTHLY_DIR / month_dir
if not mdir.exists():
continue
for fname in sorted(os.listdir(mdir)):
if fname.startswith(".") or fname.endswith(".DS_Store"):
continue
fpath = mdir / fname
if fpath.suffix.lower() not in (".xls", ".xlsx", ".et"):
continue
# 跳过模板/统计表(非企业月报)
if "模版" in fname or "模板" in fname or "运行情况统计表" in fname:
continue
try:
rows = read_sheet_rows(fpath)
if not rows:
skipped.append((fname, "空文件"))
continue
data = extract_monthly_data(rows, fname, month_hint)
if not data or not data.get("company_name"):
skipped.append((fname, "无法解析企业名"))
continue
# 6、7月:从文件名或内容推断
if month_dir == "6、7月":
if "6月" in fname or "06" in fname:
data["report_month"] = "2026-06"
elif "7月" in fname or "07" in fname or "7" in fname:
data["report_month"] = "2026-07"
elif data.get("submitted_at"):
pass # 已从 submitted_at 推断
else:
data["report_month"] = "2026-07"
# 兜底确保 report_month 不为空
if not data.get("report_month"):
data["report_month"] = month_hint or "2026-07"
# 匹配 company_id(企业名→负责人→自动创建)
company_id = await match_company(data["company_name"], data.get("leader_name", ""))
# 幂等:同一企业同一月份只保留一条
await cur.execute(
"""DELETE FROM incubator_monthly_reports
WHERE tenant_id=%s AND company_id=%s AND report_month=%s""",
(TENANT_ID, company_id, data["report_month"]))
mid = "mr_" + secrets.token_hex(8)
now = "2026-09-08"
await cur.execute(
"""INSERT INTO incubator_monthly_reports
(id, tenant_id, company_id, company_name, report_month, status,
zone, unit_no, park_entry_date, leader_name, registered_at,
registered_capital_wan, credit_code, rd_invest_wan, month_revenue_wan,
avg_year_revenue_wan, month_gross_profit_wan, month_tax_wan,
avg_year_tax_wan, loan_startup_wan, loan_yunling_wan,
contest_award_wan, patents, jobs_created, team_json,
business_note, suggestion, filler_name, filler_email,
submitted_at, created_at, updated_at)
VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)""",
(mid, TENANT_ID, company_id, data["company_name"],
data["report_month"], data["status"],
data.get("zone", ""), data.get("unit_no", ""),
data.get("park_entry_date", ""), data.get("leader_name", ""),
data.get("registered_at", ""), data.get("registered_capital_wan"),
data.get("credit_code", ""), data.get("rd_invest_wan"),
data.get("month_revenue_wan"), data.get("avg_year_revenue_wan"),
data.get("month_gross_profit_wan"), data.get("month_tax_wan"),
data.get("avg_year_tax_wan"), data.get("loan_startup_wan"),
data.get("loan_yunling_wan"), data.get("contest_award_wan"),
data.get("patents"), data.get("jobs_created"),
data.get("team_json", ""), data.get("business_note", ""),
data.get("suggestion", ""), data.get("filler_name", ""),
data.get("filler_email", ""), data.get("submitted_at", ""),
now, now))
total += 1
print(f"{data['report_month']} {data['company_name'][:20]}")
except Exception as e:
skipped.append((fname, str(e)[:80]))
print(f"{fname}: {e}")
await conn.commit()
print(f"\n月报导入完成: {total}")
if skipped:
print(f"跳过 {len(skipped)} 个文件:")
for f, r in skipped[:10]:
print(f" - {f}: {r}")
await conn.ensure_closed()
asyncio.run(main())