v1.0.0 高考志愿门户完整修复

变更摘要:
- [fix] 清理根目录 index.html 遗留代码(questionnaire-error + retryLoad 移除)
- [fix] 副标题同步更新为自估591分 + 候选志愿546条数据校准
- [fix] 页脚注入版本号 v1.0.0
- [fix] 部署版 index.html: 取消家长标签,统一 filler-tianheng
- [fix] Dashboard 文案修正(4份→2份线上问卷)
- [fix] 清理冗余部署脚本(v1-v4归档至 archived_scripts/)
- [fix] 等效位次工具纳入 deploy 目录
- [chore] .gitignore 初始化
- [init] Git 仓库初始化
This commit is contained in:
2026-06-24 13:11:34 +08:00
commit b6df028839
71 changed files with 22898 additions and 0 deletions
+28
View File
@@ -0,0 +1,28 @@
# ============================================================
# Flask 后端 Dockerfile
# 基于 Python 3.12 Alpine(精简镜像,约 50MB
# ============================================================
FROM python:3.12-alpine
# 设置工作目录
WORKDIR /app
# 先复制依赖文件(利用 Docker 缓存层,代码改动时不用重装依赖)
COPY requirements.txt .
# 安装 Python 依赖
# --no-cache-dir: 不缓存 pip 包,减小镜像体积
RUN pip install --no-cache-dir -r requirements.txt
# 复制应用代码
COPY . .
# 创建数据目录(SQLite 数据库文件存放位置)
RUN mkdir -p /app/data
# 暴露端口(Flask 默认 5000
EXPOSE 5000
# 启动命令:初始化数据库 + 启动 Flask
# waitress-serve 是生产级 WSGI 服务器,比 flask run 更稳定
CMD ["sh", "-c", "python seed_data.py && python -m flask run --host=0.0.0.0 --port=5000"]
+674
View File
@@ -0,0 +1,674 @@
"""
高考志愿家庭门户 — Flask 后端
功能: 问卷 API + SQLite 数据存储 + 多成员结果汇总
数据库表:
- questionnaires: 问卷定义(标题、描述、题目 JSON)
- responses: 成员提交的答卷(成员名、问卷ID、答案 JSON、提交时间)
"""
import sqlite3
import json
import os
from datetime import datetime, timezone, timedelta
from flask import Flask, request, jsonify
from flask_cors import CORS
# ==================== 应用初始化 ====================
app = Flask(__name__)
CORS(app) # 允许前端跨域请求(Nginx 反向代理场景也需要)
# 数据库路径:挂载卷 /app/data/gaokao.db
DB_PATH = os.path.join(os.path.dirname(__file__), "data", "gaokao.db")
# 上海时区
CST = timezone(timedelta(hours=8))
def get_db():
"""获取数据库连接(每次请求新建,自动提交)"""
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row # 让查询结果可以用列名访问
conn.execute("PRAGMA journal_mode=WAL") # WAL 模式提升并发性能
conn.execute("PRAGMA foreign_keys=ON")
return conn
def init_db():
"""初始化数据库表(幂等操作——表不存在才创建)"""
conn = get_db()
conn.executescript("""
CREATE TABLE IF NOT EXISTS questionnaires (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL, -- 问卷标题
description TEXT, -- 问卷说明
questions TEXT NOT NULL, -- 题目列表(JSON 数组)
target_user TEXT DEFAULT '天恒', -- 目标填写人:天恒 / 父母
created_at TEXT DEFAULT (datetime('now', 'localtime'))
);
CREATE TABLE IF NOT EXISTS responses (
id INTEGER PRIMARY KEY AUTOINCREMENT,
questionnaire_id INTEGER NOT NULL, -- 关联问卷
user_name TEXT NOT NULL, -- 填写人姓名(如"爸爸""天恒"
answers TEXT NOT NULL, -- 答案(JSON 对象: {题号: 答案}
created_at TEXT DEFAULT (datetime('now', 'localtime')),
FOREIGN KEY (questionnaire_id) REFERENCES questionnaires(id)
);
""")
conn.commit()
# 迁移:为已有表添加 target_user 列(如果不存在)
try:
conn.execute("ALTER TABLE questionnaires ADD COLUMN target_user TEXT DEFAULT '天恒'")
conn.commit()
except sqlite3.OperationalError:
pass # 列已存在
# 独立测评结果表(霍兰德/MBTI 自评HTML提交)
conn.executescript("""
CREATE TABLE IF NOT EXISTS standalone_results (
id INTEGER PRIMARY KEY AUTOINCREMENT,
test_name TEXT NOT NULL, -- 'holland''mbti'
user_name TEXT NOT NULL DEFAULT '天恒',
result TEXT NOT NULL, -- JSON: 霍兰德三字母代码 或 MBTI四字母类型
full_data TEXT, -- JSON: 完整结果数据
created_at TEXT DEFAULT (datetime('now', 'localtime'))
);
""")
conn.close()
# ==================== API 路由 ====================
@app.route("/api/health")
def health():
"""健康检查(用于确认服务启动成功)"""
return jsonify({"status": "ok", "time": datetime.now(CST).isoformat()})
@app.route("/api/questionnaires", methods=["GET"])
def list_questionnaires():
"""获取所有问卷列表"""
conn = get_db()
rows = conn.execute(
"SELECT id, title, description, target_user, created_at FROM questionnaires ORDER BY id"
).fetchall()
conn.close()
return jsonify([dict(r) for r in rows])
@app.route("/api/questionnaire/<int:qid>", methods=["GET"])
def get_questionnaire(qid):
"""获取单个问卷的完整内容(含所有题目)"""
conn = get_db()
row = conn.execute(
"SELECT * FROM questionnaires WHERE id = ?", (qid,)
).fetchone()
conn.close()
if not row:
return jsonify({"error": "问卷不存在"}), 404
result = dict(row)
# questions 在数据库中存储为 JSON 字符串,解析后返回
result["questions"] = json.loads(result["questions"])
return jsonify(result)
@app.route("/api/questionnaire/<int:qid>/submit", methods=["POST"])
def submit_response(qid):
"""
提交问卷答案
请求体 JSON: {"user_name": "爸爸", "answers": {"1": "A", "2": "B", ...}}
设计考量:
- 同一用户对同一问卷可以多次提交(保留最新+历史记录)
- user_name 用于区分不同家庭成员
"""
data = request.get_json()
if not data:
return jsonify({"error": "请求体为空"}), 400
user_name = data.get("user_name", "").strip()
answers = data.get("answers", {})
if not user_name:
return jsonify({"error": "请填写您的称呼(如:爸爸、妈妈、天恒)"}), 400
if not answers:
return jsonify({"error": "请至少回答一题"}), 400
# 验证问卷是否存在
conn = get_db()
q = conn.execute("SELECT id FROM questionnaires WHERE id = ?", (qid,)).fetchone()
if not q:
conn.close()
return jsonify({"error": "问卷不存在"}), 404
# 插入答卷(JSON 序列化答案对象)
conn.execute(
"INSERT INTO responses (questionnaire_id, user_name, answers) VALUES (?, ?, ?)",
(qid, user_name, json.dumps(answers, ensure_ascii=False))
)
conn.commit()
conn.close()
return jsonify({"success": True, "message": f"{user_name} 的答卷已保存"})
@app.route("/api/results/<int:qid>", methods=["GET"])
def view_results(qid):
"""
查看某问卷的所有答卷(按时间倒序)
返回包含解析后的 answers 字段
"""
conn = get_db()
rows = conn.execute(
"""SELECT id, questionnaire_id, user_name, answers, created_at
FROM responses
WHERE questionnaire_id = ?
ORDER BY created_at DESC""",
(qid,)
).fetchall()
conn.close()
results = []
for r in rows:
d = dict(r)
d["answers"] = json.loads(d["answers"])
results.append(d)
return jsonify(results)
@app.route("/api/results/<int:qid>/summary", methods=["GET"])
def view_summary(qid):
"""
查看某问卷的汇总分析
对于选择题问卷(如 Holland 评估),按选项计算得分/分布
对于偏好问卷,展示每个成员的答案对比
"""
conn = get_db()
qrow = conn.execute(
"SELECT questions FROM questionnaires WHERE id = ?", (qid,)
).fetchone()
if not qrow:
conn.close()
return jsonify({"error": "问卷不存在"}), 404
questions = json.loads(qrow["questions"])
rows = conn.execute(
"""SELECT user_name, answers, created_at
FROM responses
WHERE questionnaire_id = ?
ORDER BY created_at DESC""",
(qid,)
).fetchall()
conn.close()
# 构建汇总:每个成员 × 每道题
members = {}
for r in rows:
name = r["user_name"]
answers = json.loads(r["answers"])
members[name] = {
"answers": answers,
"submitted_at": r["created_at"]
}
return jsonify({
"questionnaire_id": qid,
"question_count": len(questions),
"member_count": len(members),
"members": members
})
# ==================== 报告生成 API ====================
@app.route("/api/report/<int:qid>", methods=["GET"])
def generate_report(qid):
"""
生成问卷分析报告(核心功能)
- Holland评估:生成六维雷达图数据 + 职业推荐
- 偏好问卷:生成差异分析 + 共识点
- 支持导出为后续分析可用的JSON格式
"""
conn = get_db()
qrow = conn.execute(
"SELECT * FROM questionnaires WHERE id = ?", (qid,)
).fetchone()
if not qrow:
conn.close()
return jsonify({"error": "问卷不存在"}), 404
questions = json.loads(qrow["questions"])
rows = conn.execute(
"""SELECT user_name, answers, created_at
FROM responses
WHERE questionnaire_id = ?
ORDER BY created_at DESC""",
(qid,)
).fetchall()
conn.close()
if not rows:
return jsonify({"error": "暂无答卷数据", "message": "请先填写问卷"}), 400
# 解析所有答卷
members = {}
for r in rows:
name = r["user_name"]
answers = json.loads(r["answers"])
members[name] = {
"answers": answers,
"submitted_at": r["created_at"]
}
# 根据问卷类型生成报告
report = {
"questionnaire_id": qid,
"questionnaire_title": qrow["title"],
"generated_at": datetime.now(CST).isoformat(),
"member_count": len(members),
"report_type": None,
"data": {},
"conclusions": [],
"next_input": {} # 供后续分析使用的结构化数据
}
# Holland 评估报告(问卷ID=1
if qid == 1:
report["report_type"] = "holland"
report["data"], report["conclusions"], report["next_input"] = _generate_holland_report(members, questions)
# 院校偏好问卷报告(问卷ID=2
elif qid == 2:
report["report_type"] = "preference"
report["data"], report["conclusions"], report["next_input"] = _generate_preference_report(members, questions)
# 家庭期望问卷报告(问卷ID=3
elif qid == 3:
report["report_type"] = "expectation"
report["data"], report["conclusions"], report["next_input"] = _generate_expectation_report(members, questions)
else:
report["report_type"] = "generic"
report["data"] = {"members": list(members.keys())}
return jsonify(report)
def _generate_holland_report(members, questions):
"""生成 Holland 职业兴趣报告"""
# RIASEC 六维定义
riasec_dims = ["R", "I", "A", "S", "E", "C"]
riasec_names = {
"R": "现实型", "I": "研究型", "A": "艺术型",
"S": "社会型", "E": "企业型", "C": "常规型"
}
# 计算每个成员的维度得分
results = {}
for name, data in members.items():
answers = data["answers"]
scores = {dim: 0 for dim in riasec_dims}
count = {dim: 0 for dim in riasec_dims}
for q_id, answer in answers.items():
q_num = int(q_id.replace("q", ""))
# 题目1-4: R, 5-8: I, 9-12: A, 13-16: S, 17-20: E, 21-24: C
if 1 <= q_num <= 4:
dim = "R"
elif 5 <= q_num <= 8:
dim = "I"
elif 9 <= q_num <= 12:
dim = "A"
elif 13 <= q_num <= 16:
dim = "S"
elif 17 <= q_num <= 20:
dim = "E"
else:
dim = "C"
scores[dim] += int(answer) if answer.isdigit() else 3
count[dim] += 1
# 计算平均分
avg_scores = {
dim: round(scores[dim] / count[dim], 1) if count[dim] > 0 else 0
for dim in riasec_dims
}
results[name] = avg_scores
# 差异分析
all_dims_avg = {dim: 0 for dim in riasec_dims}
for dim in riasec_dims:
total = sum(results[m][dim] for m in results)
all_dims_avg[dim] = round(total / len(results), 1) if results else 0
# 职业推荐(取Top3维度)
conclusions = []
for name, scores in results.items():
sorted_dims = sorted(scores.items(), key=lambda x: x[1], reverse=True)
top3 = [f"{d[0]}({riasec_names[d[0]]}:{d[1]})" for d in sorted_dims[:3]]
conclusions.append({
"member": name,
"top_dims": top3,
"career_type": "".join([d[0] for d in sorted_dims[:3]])
})
# 后续分析输入
next_input = {
"holland_code": conclusions[0]["career_type"] if conclusions else "",
"primary_type": riasec_names.get(conclusions[0]["career_type"][0], "") if conclusions else "",
"dimension_scores": results,
"family_avg": all_dims_avg
}
return {"dimension_scores": results, "family_avg": all_dims_avg}, conclusions, next_input
def _generate_preference_report(members, questions):
"""生成院校偏好报告"""
# 分析每个问题的答案分布
question_analysis = {}
for q_id, data in members.items():
answers = data["answers"]
for q, ans in answers.items():
if q not in question_analysis:
question_analysis[q] = {}
question_analysis[q][q_id] = ans
# 差异点识别
conflicts = []
consensus = []
for q, ans_dict in question_analysis.items():
unique_ans = set(ans_dict.values())
if len(unique_ans) > 1:
conflicts.append({"question": q, "answers": ans_dict})
else:
consensus.append({"question": q, "answer": list(unique_ans)[0]})
conclusions = [
{"type": "conflict", "count": len(conflicts), "details": conflicts[:3]},
{"type": "consensus", "count": len(consensus), "details": consensus[:3]}
]
# 后续分析输入
next_input = {
"conflict_count": len(conflicts),
"consensus_count": len(consensus),
"key_conflicts": [c["question"] for c in conflicts[:3]]
}
return {"conflicts": conflicts, "consensus": consensus}, conclusions, next_input
def _generate_expectation_report(members, questions):
"""生成家庭期望对齐报告"""
# 类似偏好报告,但强调开放式问题
text_answers = {}
choice_answers = {}
for name, data in members.items():
answers = data["answers"]
for q, ans in answers.items():
if len(str(ans)) > 50: # 开放式长文本
text_answers.setdefault(q, {})[name] = ans
else:
choice_answers.setdefault(q, {})[name] = ans
# 核心差异
conflicts = []
for q, ans_dict in choice_answers.items():
if len(set(ans_dict.values())) > 1:
conflicts.append({"question": q, "answers": ans_dict})
conclusions = [
{"type": "text_response", "count": len(text_answers)},
{"type": "choice_conflict", "count": len(conflicts), "details": conflicts[:3]}
]
next_input = {
"text_count": len(text_answers),
"choice_conflicts": len(conflicts),
"requires_discussion": [c["question"] for c in conflicts[:3]]
}
return {"text": text_answers, "choices": choice_answers}, conclusions, next_input
@app.route("/api/export/<int:qid>", methods=["GET"])
def export_report(qid):
"""
导出报告为JSON(供后续分析使用)
"""
import io
conn = get_db()
qrow = conn.execute("SELECT * FROM questionnaires WHERE id = ?", (qid,)).fetchone()
if not qrow:
conn.close()
return jsonify({"error": "问卷不存在"}), 404
rows = conn.execute(
"SELECT user_name, answers, created_at FROM responses WHERE questionnaire_id = ?",
(qid,)
).fetchall()
conn.close()
members = {}
for r in rows:
members[r["user_name"]] = {
"answers": json.loads(r["answers"]),
"submitted_at": r["created_at"]
}
# 导出结构
export_data = {
"questionnaire": dict(qrow),
"responses": members,
"exported_at": datetime.now(CST).isoformat(),
"version": "1.0"
}
# 返回JSON下载
return jsonify(export_data)
# ==================== 独立测评提交 API(霍兰德/MBTI ====================
@app.route("/api/standalone/submit", methods=["POST"])
def submit_standalone():
"""
接收独立HTML自评测试的结果提交
请求体 JSON: {"test_name": "holland", "user_name": "天恒", "result": "RIC", "full_data": {...}}
"""
data = request.get_json()
if not data:
return jsonify({"error": "请求体为空"}), 400
test_name = data.get("test_name", "").strip()
user_name = data.get("user_name", "天恒").strip()
result = data.get("result", "").strip()
full_data = data.get("full_data", {})
if test_name not in ("holland", "mbti"):
return jsonify({"error": "test_name 必须为 holland 或 mbti"}), 400
if not result:
return jsonify({"error": "请提供 result"}), 400
conn = get_db()
# 同一用户同一测试的最新结果(替换旧结果)
conn.execute(
"INSERT INTO standalone_results (test_name, user_name, result, full_data) VALUES (?, ?, ?, ?)",
(test_name, user_name, result, json.dumps(full_data, ensure_ascii=False))
)
conn.commit()
conn.close()
return jsonify({"success": True, "message": f"{test_name} 结果已保存"})
@app.route("/api/standalone/status", methods=["GET"])
def standalone_status():
"""查询独立测评完成状态"""
conn = get_db()
rows = conn.execute(
"""SELECT test_name, user_name, result, created_at
FROM standalone_results
ORDER BY created_at DESC"""
).fetchall()
conn.close()
status = {"holland": None, "mbti": None}
for r in rows:
name = r["test_name"]
if status[name] is None: # 取最新一条
status[name] = {
"completed": True,
"user_name": r["user_name"],
"result": r["result"],
"completed_at": r["created_at"]
}
for k in status:
if status[k] is None:
status[k] = {"completed": False}
return jsonify(status)
# ==================== 完成状态总览 API ====================
@app.route("/api/completion-status", methods=["GET"])
def completion_status():
"""
返回所有6个测评的完成状态总览
"""
conn = get_db()
# 1. 4份API问卷状态
q_rows = conn.execute(
"""SELECT q.id, q.title, q.target_user,
(SELECT COUNT(*) FROM responses r WHERE r.questionnaire_id = q.id) as response_count
FROM questionnaires q
ORDER BY q.id"""
).fetchall()
questionnaires_status = []
for qr in q_rows:
q = dict(qr)
# 取最新答卷人
latest = conn.execute(
"SELECT user_name, created_at FROM responses WHERE questionnaire_id = ? ORDER BY created_at DESC LIMIT 1",
(q["id"],)
).fetchone()
q["latest_respondent"] = dict(latest) if latest else None
q["completed"] = q["response_count"] > 0
questionnaires_status.append(q)
# 2. 独立测评状态
standalone_rows = conn.execute(
"""SELECT test_name, user_name, result, created_at
FROM standalone_results
ORDER BY created_at DESC"""
).fetchall()
standalone = {"holland": {"completed": False}, "mbti": {"completed": False}}
for r in standalone_rows:
name = r["test_name"]
if not standalone[name]["completed"]:
standalone[name] = {
"completed": True,
"user_name": r["user_name"],
"result": r["result"],
"completed_at": r["created_at"]
}
conn.close()
# 3. 汇总统计
api_completed = sum(1 for q in questionnaires_status if q["completed"])
standalone_completed = sum(1 for s in standalone.values() if s["completed"])
total = 6 # 4 API + 2 standalone
all_completed = (api_completed + standalone_completed) == total
return jsonify({
"total": total,
"completed_count": api_completed + standalone_completed,
"all_completed": all_completed,
"questionnaires": questionnaires_status,
"standalone": standalone,
"updated_at": datetime.now(CST).isoformat()
})
# ==================== 汇总报告 API ====================
@app.route("/api/summary", methods=["GET"])
def generate_summary():
"""
汇总所有已完成问卷和测评的结果,生成可下载的结构化数据
"""
conn = get_db()
# API问卷结果
q_rows = conn.execute(
"""SELECT q.id, q.title, q.target_user, q.description
FROM questionnaires q ORDER BY q.id"""
).fetchall()
questionnaires = []
for qr in q_rows:
q = dict(qr)
responses = conn.execute(
"SELECT user_name, answers, created_at FROM responses WHERE questionnaire_id = ? ORDER BY created_at DESC",
(q["id"],)
).fetchall()
q["responses"] = []
for r in responses:
rd = dict(r)
rd["answers"] = json.loads(rd["answers"])
q["responses"].append(rd)
questionnaires.append(q)
# 独立测评结果
standalone_rows = conn.execute(
"""SELECT test_name, user_name, result, full_data, created_at
FROM standalone_results ORDER BY created_at DESC"""
).fetchall()
standalone = {"holland": None, "mbti": None}
for r in standalone_rows:
name = r["test_name"]
if standalone[name] is None:
standalone[name] = {
"user_name": r["user_name"],
"result": r["result"],
"full_data": json.loads(r["full_data"]) if r["full_data"] else {},
"completed_at": r["created_at"]
}
conn.close()
# 完成统计
q_completed = sum(1 for q in questionnaires if len(q["responses"]) > 0)
s_completed = sum(1 for v in standalone.values() if v is not None)
return jsonify({
"generated_at": datetime.now(CST).isoformat(),
"status": {
"total": 6,
"completed": q_completed + s_completed,
"questionnaires_done": q_completed,
"standalone_done": s_completed
},
"questionnaires": questionnaires,
"standalone_tests": standalone,
"note": "此数据可用于生成志愿填报分析报告。如独立测评(霍兰德/MBTI)未提交,请通过测试页面的提交按钮上传结果。"
})
# ==================== 启动入口 ====================
if __name__ == "__main__":
init_db()
# Flask 开发服务器,生产环境建议用 gunicorn(但 Alpine + 轻量场景 flask run 足够)
app.run(host="0.0.0.0", port=5000, debug=False)
+2
View File
@@ -0,0 +1,2 @@
flask==3.1.0
flask-cors==5.0.1
+384
View File
@@ -0,0 +1,384 @@
"""
问卷种子数据 — 首次启动时自动初始化
重新设计:开放式问题,不预设立场,不提具体专业名
"""
import sqlite3
import json
import os
DB_PATH = os.path.join(os.path.dirname(__file__), "data", "gaokao.db")
def seed():
conn = sqlite3.connect(DB_PATH)
conn.execute("PRAGMA journal_mode=WAL")
conn.execute("PRAGMA foreign_keys=ON")
# 初始化表结构
conn.executescript("""
CREATE TABLE IF NOT EXISTS questionnaires (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
description TEXT,
questions TEXT NOT NULL,
target_user TEXT DEFAULT '天恒',
created_at TEXT DEFAULT (datetime('now', 'localtime'))
);
CREATE TABLE IF NOT EXISTS responses (
id INTEGER PRIMARY KEY AUTOINCREMENT,
questionnaire_id INTEGER NOT NULL,
user_name TEXT NOT NULL,
answers TEXT NOT NULL,
created_at TEXT DEFAULT (datetime('now', 'localtime')),
FOREIGN KEY (questionnaire_id) REFERENCES questionnaires(id)
);
""")
# 迁移:为已有表添加 target_user 列(如果不存在)
try:
conn.execute("ALTER TABLE questionnaires ADD COLUMN target_user TEXT DEFAULT '天恒'")
conn.commit()
except sqlite3.OperationalError:
pass # 列已存在
# 检查是否已有问卷数据(幂等:不重复插入)
existing = conn.execute("SELECT COUNT(*) FROM questionnaires").fetchone()[0]
if existing > 0:
print(f"[seed] 问卷已存在({existing}份),跳过初始化")
conn.close()
return
# ================================================================
# 问卷1: 综合兴趣和偏好问卷(合并版)
# 将原"职业兴趣体验问卷"+"性格与行为模式问卷"+"院校偏好问卷"合并为一份
# 内分三个分区,带分区标题
# ================================================================
merged_qs = [
# ---- 第一分区:职业兴趣体验 ----
{"id": "SEC1", "type": "section", "title": "第一分区:职业兴趣体验",
"desc": "以下问题没有标准答案,请根据你的真实感受和经历回答,不需要考虑应该怎么回答,只描述你最真实的想法。"},
{"id": "E1", "text": "做什么事情的时候,你会觉得时间过得特别快?",
"type": "text", "placeholder": "比如:写代码、设计海报、打游戏、和朋友聊天..."},
{"id": "E2", "text": "当你完成了一件很有挑战性的事情,你通常是什么感受?",
"type": "text", "placeholder": "比如:很有成就感、想再挑战更高的、没什么特别..."},
{"id": "E3", "text": "如果让你连续做同一件事10个小时,你最不可能选择做什么?",
"type": "text", "placeholder": "写下你不会做的事情..."},
{"id": "C1", "text": "在团队合作中,你通常扮演什么角色?",
"type": "single",
"options": [
{"value": "A", "label": "我来做决定,大家听我的"},
{"value": "B", "label": "我听大家的,协调配合"},
{"value": "C", "label": "我负责具体执行"},
{"value": "D", "label": "我负责发现问题,提醒大家"},
{"value": "E", "label": "看情况,不一定"},
]},
{"id": "C2", "text": "你更倾向于独立工作还是团队协作?",
"type": "single",
"options": [
{"value": "A", "label": "独立完成,更有掌控感"},
{"value": "B", "label": "团队协作,分工合作"},
{"value": "C", "label": "两者都可以,看任务性质"},
]},
{"id": "S1", "text": "什么事情做得好会让你觉得自己很厉害?",
"type": "text", "placeholder": "比如:解出一道难题、完成一个作品..."},
{"id": "S2", "text": "什么东西没做好会让你想放弃?",
"type": "text", "placeholder": "比如:反复失败、被人否定、看不到进步..."},
{"id": "L1", "text": "学习新东西时,你更习惯于?",
"type": "single",
"options": [
{"value": "A", "label": "先看教程/文档,搞懂原理再动手"},
{"value": "B", "label": "直接动手做,遇到问题再查"},
{"value": "C", "label": "有人教我,带着我做"},
{"value": "D", "label": "边做边学,一起进行"},
]},
{"id": "L2", "text": "面对一个完全陌生的事物,你会怎么做?",
"type": "single",
"options": [
{"value": "A", "label": "先搜索相关资料,全面了解"},
{"value": "B", "label": "直接尝试,不懂就问"},
{"value": "C", "label": "找有经验的人带路"},
{"value": "D", "label": "先观望,等别人先试"},
]},
{"id": "W1", "text": "你理想中最完美的一天是什么样的?",
"type": "text", "placeholder": "描述你理想的一天..."},
{"id": "W2", "text": "你更看重工作/学习的哪个方面?",
"type": "multiple",
"options": [
{"value": "A", "label": "有挑战,能成长"},
{"value": "B", "label": "有成就感,被认可"},
{"value": "C", "label": "能发挥创意"},
{"value": "D", "label": "稳定,不担心失业"},
{"value": "E", "label": "收入高"},
{"value": "F", "label": "时间灵活"},
]},
# ---- 第二分区:性格与行为模式 ----
{"id": "SEC2", "type": "section", "title": "第二分区:性格与行为模式",
"desc": "以下问题了解你在日常生活中的行为偏好,没有好坏对错之分,请如实选择最符合你实际情况的选项。"},
{"id": "B1", "text": "在社交场合中,你的能量通常从哪里来?",
"type": "single",
"options": [
{"value": "A", "label": "和很多人在一起,聊天交流"},
{"value": "B", "label": "独处,安静思考"},
{"value": "C", "label": "看情况,有时喜欢热闹有时喜欢安静"},
]},
{"id": "B2", "text": "做重要决定时,你通常会?",
"type": "single",
"options": [
{"value": "A", "label": "和很多人讨论后再决定"},
{"value": "B", "label": "自己仔细想清楚再决定"},
{"value": "C", "label": "凭直觉,先做了再说"},
]},
{"id": "B3", "text": "你更容易注意到事物的哪些方面?",
"type": "single",
"options": [
{"value": "A", "label": "具体的事实和细节"},
{"value": "B", "label": "整体的可能性和想象力"},
{"value": "C", "label": "两者差不多"},
]},
{"id": "B4", "text": "描述一个你经历过的事情,你通常会?",
"type": "single",
"options": [
{"value": "A", "label": "详细还原过程和细节"},
{"value": "B", "label": "讲重点和感受,略过细节"},
{"value": "C", "label": "加入自己的理解和联想"},
]},
{"id": "B5", "text": "当你和别人的观点不同,你会怎么做?",
"type": "single",
"options": [
{"value": "A", "label": "坚持我的观点,给出逻辑理由"},
{"value": "B", "label": "考虑对方的感受,寻求共识"},
{"value": "C", "label": "看谁的理由更充分"},
{"value": "D", "label": "先听大家的,之后再决定"},
]},
{"id": "B6", "text": "你更容易被什么说服?",
"type": "single",
"options": [
{"value": "A", "label": "数据和逻辑分析"},
{"value": "B", "label": "情感故事和共情"},
{"value": "C", "label": "实际案例和效果"},
{"value": "D", "label": "权威人士的意见"},
]},
{"id": "B7", "text": "你更喜欢什么样的计划?",
"type": "single",
"options": [
{"value": "A", "label": "提前做好计划,按部就班"},
{"value": "B", "label": "有个大概方向,灵活调整"},
{"value": "C", "label": "不做计划,随机应变"},
]},
{"id": "B8", "text": "面对截止日期,你通常会?",
"type": "single",
"options": [
{"value": "A", "label": "提前完成,避免意外"},
{"value": "B", "label": "最后几天集中完成"},
{"value": "C", "label": "截止前一晚通宵搞定"},
{"value": "D", "label": "通常会拖到截止后"},
]},
{"id": "B9", "text": "你通常如何认识新朋友?",
"type": "single",
"options": [
{"value": "A", "label": "主动搭话,主动组织活动"},
{"value": "B", "label": "等别人来认识我"},
{"value": "C", "label": "通过共同朋友介绍"},
{"value": "D", "label": "在共同活动中自然认识"},
]},
{"id": "B10", "text": "当你遇到问题时,你通常会找谁?",
"type": "multiple",
"options": [
{"value": "A", "label": "自己想办法解决"},
{"value": "B", "label": "找父母或家人"},
{"value": "C", "label": "找朋友帮忙"},
{"value": "D", "label": "上网搜索答案"},
{"value": "E", "label": "找老师或权威人士"},
]},
# ---- 第三分区:院校偏好 ----
{"id": "SEC3", "type": "section", "title": "第三分区:院校偏好与未来规划",
"desc": "以下问题了解你对大学和未来的想法。没有对错,请选择最贴近你真实想法的选项。"},
{"id": "SCH1", "text": "你更倾向于在哪类城市上大学?",
"type": "single",
"options": [
{"value": "A", "label": "杭州/宁波等省内大城市"},
{"value": "B", "label": "省内其他城市(温州/嘉兴/台州等)"},
{"value": "C", "label": "省外一线城市(北京/上海/广州/深圳)"},
{"value": "D", "label": "省外新一线或二线城市(成都/武汉/西安等)"},
{"value": "E", "label": "哪里都行,不挑城市"},
]},
{"id": "SCH2", "text": "对离家距离的接受度?",
"type": "single",
"options": [
{"value": "A", "label": "最好省内,周末能回家"},
{"value": "B", "label": "江浙沪范围内就行,小长假能回"},
{"value": "C", "label": "全国都行,寒暑假回一次就够了"},
{"value": "D", "label": "越远越好,想看看外面的世界"},
]},
{"id": "SCH3", "text": "学校层次 vs 专业实力,怎么选?",
"type": "single",
"options": [
{"value": "A", "label": "优先学校牌子(985/211/双一流光环)"},
{"value": "B", "label": "优先专业实力(哪怕学校名气差一点)"},
{"value": "C", "label": "两者兼顾,取中间值"},
{"value": "D", "label": "无所谓,看缘分"},
]},
{"id": "SCH4", "text": "用一个词或一句话描述你感兴趣的方向(可以天马行空):",
"type": "text",
"placeholder": "比如:做游戏、设计产品、搞技术、研究 AI..."},
{"id": "SCH5", "text": "你为什么对这个方向感兴趣?",
"type": "text",
"placeholder": "可以是你的经历、性格、或者单纯的喜欢..."},
{"id": "SCH6", "text": "对大学教学方式的偏好?",
"type": "single",
"options": [
{"value": "A", "label": "小班制+项目制+导师制(像工作室一样)"},
{"value": "B", "label": "大班理论课为主也可以接受"},
{"value": "C", "label": "无所谓,能学到东西就行"},
]},
{"id": "SCH7", "text": "对学费的承受范围?",
"type": "single",
"options": [
{"value": "A", "label": "公办普通学费(5000~8000/年)"},
{"value": "B", "label": "可以接受稍高(1~3万/年)"},
{"value": "C", "label": "中外合作也可考虑(4~8万/年)"},
]},
{"id": "SCH8", "text": "大学毕业后第一优先级是?",
"type": "single",
"options": [
{"value": "A", "label": "直接就业,尽快经济独立"},
{"value": "B", "label": "考研深造,提升学历"},
{"value": "C", "label": "考公/考编,追求稳定"},
{"value": "D", "label": "还没想好"},
]},
{"id": "SCH9", "text": "你对大学校园氛围的偏好?",
"type": "single",
"options": [
{"value": "A", "label": "学术氛围浓厚,图书馆经常找不到座位"},
{"value": "B", "label": "创新创业活跃,社团和比赛多"},
{"value": "C", "label": "文艺气息重,艺术展演和创作空间多"},
{"value": "D", "label": "轻松自在就行,不要太大压力"},
]},
{"id": "SCH10", "text": "你对未来的薪资预期(毕业5年内)?",
"type": "single",
"options": [
{"value": "A", "label": "8~12万/年,能养活自己就行"},
{"value": "B", "label": "12~20万/年,中等偏上"},
{"value": "C", "label": "20万+/年,越高越好"},
{"value": "D", "label": "没概念,不太关心"},
]},
]
# ================================================================
# 问卷2: 家庭期望对齐问卷(去掉具体专业名)
# 只问抽象的期望,不预设任何方向
# ================================================================
family_qs = [
{
"id": "F1", "text": "你认为天恒大学毕业后最理想的去向是?",
"type": "single",
"options": [
{"value": "A", "label": "在杭州/宁波就业,离家近"},
{"value": "B", "label": "去一线城市闯一闯"},
{"value": "C", "label": "考研/深造后再决定"},
{"value": "D", "label": "尊重天恒自己的选择"},
]
},
{
"id": "F2", "text": "天恒现在感兴趣的方向,你了解多少?",
"type": "single",
"options": [
{"value": "A", "label": "非常了解,经常和他讨论"},
{"value": "B", "label": "大概知道,但不深入"},
{"value": "C", "label": "不太清楚,他没说过"},
{"value": "D", "label": "他感兴趣的方向我不太支持"},
]
},
{
"id": "F3", "text": "你对'好工作'的核心定义是?(选最重要的1~2个)",
"type": "multiple",
"options": [
{"value": "A", "label": "收入高"},
{"value": "B", "label": "稳定(不容易失业)"},
{"value": "C", "label": "天恒做得开心、有成就感"},
{"value": "D", "label": "有社会地位、体面"},
{"value": "E", "label": "有成长空间,能不断提升"},
]
},
{
"id": "F4", "text": "如果中外合作办学(学费4~8万/年)是找到好专业的最佳途径,你支持吗?",
"type": "single",
"options": [
{"value": "A", "label": "支持,教育投资值得"},
{"value": "B", "label": "可以考虑,但要看具体项目和回报"},
{"value": "C", "label": "经济压力大,尽量不选"},
]
},
{
"id": "F5", "text": "你认为天恒最大的优势是什么?(选1~2个)",
"type": "multiple",
"options": [
{"value": "A", "label": "创造力强,有艺术感觉"},
{"value": "B", "label": "社交能力强,能搞定人际关系"},
{"value": "C", "label": "动手实践能力强"},
{"value": "D", "label": "逻辑思维好,数理基础扎实"},
{"value": "E", "label": "有主见,知道自己要什么"},
{"value": "F", "label": "适应能力强,什么环境都能活"},
]
},
{
"id": "F6", "text": "你最担心天恒大学生涯可能出什么问题?",
"type": "single",
"options": [
{"value": "A", "label": "对专业失去兴趣,混日子"},
{"value": "B", "label": "沉迷游戏或其他娱乐,荒废学业"},
{"value": "C", "label": "社交孤立或不适应集体生活"},
{"value": "D", "label": "毕业找不到工作"},
{"value": "E", "label": "不太担心,相信他能搞定"},
]
},
{
"id": "F7", "text": "志愿填报时,你认为最终决定权应该?",
"type": "single",
"options": [
{"value": "A", "label": "天恒自己决定,父母只提供信息"},
{"value": "B", "label": "全家协商,共同决策"},
{"value": "C", "label": "父母有最终否决权"},
]
},
{
"id": "F8", "text": "用一句话描述你对天恒大学四年的期望:",
"type": "text",
"placeholder": "请输入你的期望...",
},
]
# ---------- 插入数据 ----------
questionnaires = [
{
"title": "综合兴趣和偏好问卷",
"description": "合并了职业兴趣体验、性格行为模式和院校偏好三大板块,一份问卷全面了解天恒的个人画像。含3个分区共31题,完成时间约15分钟。",
"questions": json.dumps(merged_qs, ensure_ascii=False),
"target_user": "天恒"
},
{
"title": "家庭期望对齐问卷",
"description": "请每位家庭成员独立填写,表达你对天恒大学生涯的真实想法。去掉预设的专业立场,只聊最真实的期望。完成后可以查看汇总,发现家人之间的共同点和差异点。",
"questions": json.dumps(family_qs, ensure_ascii=False),
"target_user": "父母"
},
]
for q in questionnaires:
conn.execute(
"INSERT INTO questionnaires (title, description, questions, target_user) VALUES (?, ?, ?, ?)",
(q["title"], q["description"], q["questions"], q["target_user"])
)
conn.commit()
conn.close()
print(f"[seed] 已初始化 {len(questionnaires)} 份开放式问卷")
if __name__ == "__main__":
seed()