#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""生成超星知识图谱导入文件 importTopicTemplate 同构 xlsx。
依据：知识库(110 md) + 题目全集(1299题) + 课程目录(目录.txt) + 教学大纲。
零依赖：zipfile 手写 OOXML。"""
import os, re, json, zipfile, html

BASE = "/home/zyw/Downloads/dl-hub/01-AI课程设计项目/知识库问答库"
KB = os.path.join(BASE, "知识库")
OUT = "/home/zyw/Downloads/dl-hub/01-AI课程设计项目/应用报告/知识图谱导入-人工智能基础.xlsx"

ALLQ = json.load(open(os.path.join(BASE, "题目全集.json"), encoding='utf-8'))

def esc(s): return html.escape(str(s), quote=False)

# 章节顺序（用于前置/后置推导）
CHAPTERS = ["第1章 人工智能概述","第2章 Python程序设计基础","第3章 NumPy数值分析库",
            "第4章 pandas数据分析库","第5章 计算机视觉技术与应用","第6章 智能语音处理与应用",
            "第7章 自然语言处理与应用","第8章 生成式大模型应用","实验篇"]

# 章→一级知识点名（与知识库分类一致，≤8字）
CAT1 = {
 "第1章 人工智能概述":"人工智能概述","第2章 Python程序设计基础":"Python基础",
 "第3章 NumPy数值分析库":"NumPy分析","第4章 pandas数据分析库":"pandas分析",
 "第5章 计算机视觉技术与应用":"计算机视觉","第6章 智能语音处理与应用":"智能语音",
 "第7章 自然语言处理与应用":"自然语言处理","第8章 生成式大模型应用":"生成式大模型",
 "实验篇":"上机实验"
}

# 二级：章内节（如 1.1/1.2/...）；三级：小节（如 1.1.1/1.4.3）
def parse_items():
    rows = []  # (cat1, cat2, cat3, nid_full, title, kb_text, kp_type)
    for ch in CHAPTERS:
        d = os.path.join(KB, ch)
        if not os.path.isdir(d): continue
        cat1 = CAT1[ch]
        for fn in sorted(os.listdir(d)):
            if not fn.endswith(".md") or fn == "生成日志.md": continue
            m = re.match(r'^(.+?)-\s*(.+)\.md$', fn)
            if not m: continue
            nid, title = m.group(1), m.group(2)
            text = open(os.path.join(d, fn), encoding='utf-8').read()
            # 实验篇 nid 形如 实验1；理论章 形如 1.4.3
            if nid.startswith("实验"):
                cat2 = "实验"; cat3 = nid + " " + title
            else:
                parts = nid.split(".")
                if len(parts) >= 2:
                    cat2 = parts[0] + "." + parts[1]
                else:
                    cat2 = nid
                cat3 = nid + " " + title
            # 知识点类型：含代码块→程序性；含历史/发展/应用→事实性；否则概念性
            if "```python" in text or "代码" in fn or "函数" in fn or "命令" in fn:
                kp_type = "程序性"
            elif re.search(r'发展历程|概念|概述|应用|简介|风险|挑战', title + text[:800]):
                kp_type = "事实性"
            else:
                kp_type = "概念性"
            rows.append({"cat1":cat1,"cat2":cat2,"cat3":cat3,"nid":nid,"title":title,
                         "text":text,"type":kp_type,"ch":ch})
    return rows

def chapter_idx(ch):
    for i,c in enumerate(CHAPTERS):
        if c==ch: return i
    return -1

def nid_lt(a,b):
    # 同章内按节号排序
    try:
        va=[int(x) for x in a.split(".") if x.isdigit()]
        vb=[int(x) for x in b.split(".") if x.isdigit()]
        return va<vb
    except: return a<b

def cognitive_dim(nid, title, text, qs):
    """按题型 + 题干关键词映射认知维度（简答/代码题升维）"""
    types = [q["type"] for q in qs]
    n_judge = types.count("判断题"); n_choice = types.count("单选题")+types.count("多选题")
    n_short = types.count("简答题")
    # 简答题题干关键词
    short_questions = [q.get("q","") for q in qs if q["type"]=="简答题"]
    joined = "".join(short_questions)
    eval_kw = ["评价","论证","立场","你认为","裁决","利弊","方案"]
    ana_kw = ["分析","比较","为什么","原因","区别","流程","步骤"]
    app_kw = ["编写","实现","运行","代码","命令","创建","使用"]
    if n_short>=1 and any(k in joined for k in eval_kw):
        return "评价"
    if n_short>=2 and any(k in joined for k in ana_kw):
        return "分析"
    if any(k in joined for k in app_kw) or "代码" in title or "程序" in title or nid.startswith("实验"):
        return "应用"
    # 选择/判断为主
    if n_short==0:
        if n_judge>=1:
            return "理解"
        return "记忆"
    return "理解"

def labels_for(nid,title,text):
    labs = ["考点"]
    if "代码" in title or "程序" in title or nid.startswith("实验") or "函数" in title:
        labs.append("难点")
    # 思政章节：第1章(发展/自立自强)、第7章(NLP舆情/争议)、第8章(大模型伦理)、第5章(人脸识别)
    if nid.startswith("1.") or nid.startswith("7.") or nid.startswith("8.") \
       or "人脸" in title or "风险" in title or "挑战" in title or "伦理" in text[:200]:
        labs.append("课程思政")
    return ";".join(labs)

def objective_from(text,nid,title):
    """从知识库首段+关键要点提炼可测量目标"""
    m = re.search(r'## 关键要点\n(.*?)(?=\n##|\Z)', text, re.S)
    points = []
    if m:
        points = [p.lstrip("- ").strip() for p in m.group(1).splitlines() if p.strip()]
    if points:
        return "；".join(points[:3])[:300]
    first = re.sub(r'\s+',' ', text[:300]).strip()
    return first[:200]

def desc_from(text):
    # 说明：去掉标题行后取正文首段（600字内）
    body = re.sub(r'^#.*$','',text,flags=re.M)
    body = re.sub(r'## 关键要点.*$','',body,flags=re.S)
    first = re.sub(r'\s+',' ', body).strip()
    return first[:600]

rows = parse_items()
print("知识小节数:", len(rows))

# 按 (章顺序, 节号) 排序，建立全局顺序用于前置/后置
rows.sort(key=lambda r:(chapter_idx(r["ch"]), r["nid"]))
by_ch = {}
for r in rows:
    by_ch.setdefault(r["ch"], []).append(r)

# H/I/J：前置/后置/关联
def fill_relations():
    rel = {r["cat3"]: {"pre":[], "post":[], "asso":[]} for r in rows}
    # 章间前置：第2章为第3/4章前置；第3章为第4章前置；第1章为基础
    pre_chain = {
        "第3章 NumPy数值分析库": ["Python基础"],
        "第4章 pandas数据分析库": ["Python基础", "NumPy分析"],
        "第5章 计算机视觉技术与应用": ["人工智能概述"],
        "第6章 智能语音处理与应用": ["人工智能概述"],
        "第7章 自然语言处理与应用": ["人工智能概述"],
        "第8章 生成式大模型应用": ["人工智能概述", "自然语言处理"],
        "实验篇": ["Python基础"],
    }
    asso_map = {
        "第7章 自然语言处理与应用": ["生成式大模型", "计算机视觉"],
        "第8章 生成式大模型应用": ["自然语言处理", "智能语音"],
        "第5章 计算机视觉技术与应用": ["自然语言处理", "pandas分析"],
    }
    # 章间关联用章的一级名
    for ch, pres in pre_chain.items():
        for r in by_ch.get(ch, []):
            for p in pres:
                if p not in rel[r["cat3"]]["pre"]:
                    rel[r["cat3"]]["pre"].append(p)
    for ch, asso in asso_map.items():
        for r in by_ch.get(ch, []):
            for a in asso:
                if a not in rel[r["cat3"]]["asso"]:
                    rel[r["cat3"]]["asso"].append(a)
    # 章内：小节按序相邻前置（同一二级节内的前一节；跨二级节则取整章首个）
    for ch, items in by_ch.items():
        for i, r in enumerate(items):
            # 章内前一节（若同二级）
            for j in range(i-1, -1, -1):
                prev = items[j]
                if prev["cat2"] == r["cat2"] and prev["cat3"] != r["cat3"]:
                    rel[r["cat3"]]["pre"].append(prev["cat3"])
                    break
            # 章内后一节（若同二级）
            for j in range(i+1, len(items)):
                nxt = items[j]
                if nxt["cat2"] == r["cat2"] and nxt["cat3"] != r["cat3"]:
                    rel[r["cat3"]]["post"].append(nxt["cat3"])
                    break
    # 章内二级节之间：同章上一节的第一小节为前置（如 1.1 → 1.2.x 前置 1.1.1）
    for ch, items in by_ch.items():
        secs = {}
        for r in items:
            secs.setdefault(r["cat2"], []).append(r)
        sec_keys = sorted(secs)
        for si, sk in enumerate(sec_keys):
            if si == 0: continue
            prev_sec = sec_keys[si-1]
            first_of_prev = secs[prev_sec][0]["cat3"]
            for r in secs[sk]:
                if first_of_prev not in rel[r["cat3"]]["pre"]:
                    rel[r["cat3"]]["pre"].append(first_of_prev)
    return rel

rel = fill_relations()

# 题目按小节索引（用于认知维度）
qs_by_cat3 = {}
for q in ALLQ:
    cat = q.get("cat","")
    # cat 形如 "人工智能概述/1.1.1" 或 "实验篇/实验1"
    seg = cat.split("/")[-1] if "/" in cat else cat
    qs_by_cat3.setdefault(seg, []).append(q)
def questions_for(nid, title):
    """按节号精确匹配；匹配不到则回退空"""
    return qs_by_cat3.get(nid, [])

# 组装行（15列）
hdr = ["一级知识点","二级知识点","三级知识点","四级知识点","五级知识点","六级知识点","七级知识点",
       "前置知识点","后置知识点","关联知识点","标签","认知维度","分类","教学目标","知识点说明"]
body_rows = []
for r in rows:
    pre = ";".join(rel[r["cat3"]]["pre"]) if rel[r["cat3"]]["pre"] else ""
    post = ";".join(rel[r["cat3"]]["post"]) if rel[r["cat3"]]["post"] else ""
    asso = ";".join(rel[r["cat3"]]["asso"]) if rel[r["cat3"]]["asso"] else ""
    dim = cognitive_dim(r["nid"], r["title"], r["text"], questions_for(r["nid"], r["title"]))
    labs = labels_for(r["nid"], r["title"], r["text"])
    obj = objective_from(r["text"], r["nid"], r["title"])
    desc = desc_from(r["text"])
    body_rows.append([r["cat1"], r["cat2"], r["cat3"], "", "", "", "",
                      pre, post, asso, labs, dim, r["type"], obj, desc])

def xcell(col, row, v):
    return f'<c r="{col}{row}" t="inlineStr"><is><t xml:space="preserve">{esc(v)}</t></is></c>'

COLS = "ABCDEFGHIJKLMNO"
rows_xml = []
hdr_cells = "".join(xcell(COLS[i], 2, h) for i, h in enumerate(hdr))
rows_xml.append(f'<row r="2">{hdr_cells}</row>')
for i, rr in enumerate(body_rows, start=3):
    cells = "".join(xcell(COLS[j], i, v) for j, v in enumerate(rr))
    rows_xml.append(f'<row r="{i}">{cells}</row>')

sheet = ('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
         '<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">'
         '<sheetData>' + "".join(rows_xml) + '</sheetData></worksheet>')
CT = ('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
      '<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">'
      '<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>'
      '<Default Extension="xml" ContentType="application/xml"/>'
      '<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>'
      '<Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>'
      '</Types>')
RELS = ('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
        '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'
        '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>'
        '</Relationships>')
WB = ('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
      '<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" '
      'xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">'
      '<sheets><sheet name="知识图谱" sheetId="1" r:id="rId1"/></sheets></workbook>')
WB_RELS = ('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
           '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'
           '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/>'
           '</Relationships>')
with zipfile.ZipFile(OUT,'w',zipfile.ZIP_DEFLATED) as z:
    z.writestr('[Content_Types].xml', CT)
    z.writestr('_rels/.rels', RELS)
    z.writestr('xl/workbook.xml', WB)
    z.writestr('xl/_rels/workbook.xml.rels', WB_RELS)
    z.writestr('xl/worksheets/sheet1.xml', sheet)

print("已生成:", OUT, os.path.getsize(OUT), "字节")
print("知识行数:", len(body_rows))
# 统计
from collections import Counter
print("标签含课程思政:", sum(1 for r in body_rows if '课程思政' in r[10]))
print("认知维度:", Counter(r[11] for r in body_rows))
print("分类:", Counter(r[12] for r in body_rows))
print("有前置的行:", sum(1 for r in body_rows if r[7]))
print("有后置的行:", sum(1 for r in body_rows if r[8]))
print("有关联的行:", sum(1 for r in body_rows if r[9]))