""" 昆明市大学生创业园数据导入: 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())