Files
server-core/scripts/import_kunming_park.py
T

372 lines
17 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
昆明市大学生创业园数据导入:
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())