Files
chenfuqiang 7c1f40b0e6
All checks were successful
技能自动化发布 / release (push) Successful in 6s
feat: 申报赛道推荐助手首版 v1.0.1(match-guide-track-default)
2026-09-13 15:50:33 +08:00

226 lines
9.8 KiB
Python
Raw Permalink 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.
"""SQLite 连接与幂等初始化(申报赛道推荐助手)。
业务表 3 张:
- teacher_profiles(教师画像,可编辑)
- guide_tracks(指南赛道,可编辑)
- match_reports(推荐报告,只读,匹配自动写入)
表间禁止 SQLite 外键,仅存业务 id(见 references/SCHEMA.md)。
"""
from __future__ import annotations
import sqlite3
from db.display_metadata import (
init_display_metadata,
seed_column_metadata_batch,
upsert_table_metadata,
)
from db.timestamp_columns import init_updated_at_trigger
from util.runtime_paths import get_db_path
def get_conn() -> sqlite3.Connection:
return sqlite3.connect(get_db_path())
_TEACHER_PROFILES_DDL = """
CREATE TABLE IF NOT EXISTS teacher_profiles (
id INTEGER PRIMARY KEY AUTOINCREMENT,
teacher_name TEXT NOT NULL UNIQUE,
department TEXT,
title TEXT,
research_tags TEXT,
recent_papers TEXT,
project_history TEXT,
sci_q1_count INTEGER,
core_paper_count INTEGER,
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
updated_at INTEGER NOT NULL DEFAULT (unixepoch())
)
"""
_GUIDE_TRACKS_DDL = """
CREATE TABLE IF NOT EXISTS guide_tracks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
year INTEGER NOT NULL,
agency TEXT,
program_name TEXT,
track_name TEXT NOT NULL,
track_tags TEXT,
priority_keywords TEXT,
min_title TEXT,
min_sci_q1 INTEGER NOT NULL DEFAULT 0,
quota INTEGER,
source_doc TEXT,
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
UNIQUE (year, agency, program_name, track_name)
)
"""
_MATCH_REPORTS_DDL = """
CREATE TABLE IF NOT EXISTS match_reports (
id INTEGER PRIMARY KEY AUTOINCREMENT,
batch_no TEXT NOT NULL,
teacher_name TEXT,
department TEXT,
season TEXT,
top_json TEXT,
tiers_json TEXT,
eligible_count INTEGER,
excluded_json TEXT,
report_file TEXT,
created_at INTEGER NOT NULL DEFAULT (unixepoch())
)
"""
_MATCH_REPORTS_INDEX = """
CREATE INDEX IF NOT EXISTS idx_match_reports_batch
ON match_reports(batch_no)
"""
_TS_COLUMN_NAMES = {"created_at", "updated_at"}
def _col(column_name: str, display_name: str, description: str, **extra: object) -> dict[str, object]:
base: dict[str, object] = {
"column_name": column_name,
"display_name": display_name,
"description": description,
"visible": 1,
"searchable": 0,
"editable": 0,
}
base.update(extra)
if column_name in _TS_COLUMN_NAMES:
base["display_type"] = "datetime_unix_seconds"
return base
_TEACHER_PROFILES_COLUMNS: tuple[dict[str, object], ...] = (
_col("id", "编号", "主键", searchable=1),
_col("teacher_name", "教师姓名", "教师姓名(唯一)。", searchable=1),
_col("department", "所属学院", "二级学院/单位。", searchable=1, editable=1),
_col("title", "职称", "讲师/副教授/教授等,用于职称门槛比对。", searchable=1, editable=1, options_json='[{"value":"讲师","label":"讲师"},{"value":"副教授","label":"副教授"},{"value":"教授","label":"教授"}]'),
_col("research_tags", "研究标签", "研究方向关键词,逗号分隔(匹配主依据)。", searchable=1, editable=1),
_col("recent_papers", "近3年论文", "近 3 年论文标题,分号分隔。", editable=1),
_col("project_history", "项目经历", "在研/结题项目名称,分号分隔。", editable=1),
_col("sci_q1_count", "SCI一区篇数", "SCI 一区论文数量(硬门槛比对)。", searchable=1, editable=1, display_type="number"),
_col("core_paper_count", "核心论文篇数", "核心期刊论文数量。", searchable=1, editable=1, display_type="number"),
_col("created_at", "创建时间", "记录创建时间(Unix 秒级,自动生成)。"),
_col("updated_at", "更新时间", "记录最后更新时间(Unix 秒级,自动维护)。"),
)
_GUIDE_TRACKS_COLUMNS: tuple[dict[str, object], ...] = (
_col("id", "编号", "主键", searchable=1),
_col("year", "指南年度", "指南发布/申报年度。", searchable=1),
_col("agency", "学部/归口", "如 数理学部、科研处。", searchable=1, editable=1),
_col("program_name", "项目类型", "如 面上项目、青年项目。", searchable=1, editable=1),
_col("track_name", "赛道名称", "指南中的方向/赛道名。", searchable=1, editable=1),
_col("track_tags", "需求标签", "赛道方向关键词,逗号分隔(匹配主依据)。", searchable=1, editable=1),
_col("priority_keywords", "优先方向", "优先资助方向关键词,逗号分隔(加分项)。", editable=1),
_col("min_title", "职称门槛", "讲师及以上 / 副高及以上 / 正高及以上。", editable=1, options_json='[{"value":"讲师及以上","label":"讲师及以上"},{"value":"副高及以上","label":"副高及以上"},{"value":"正高及以上","label":"正高及以上"}]'),
_col("min_sci_q1", "SCI一区门槛", "要求的 SCI 一区最少篇数。", editable=1, display_type="number"),
_col("quota", "拟资助数", "该赛道拟资助项数(参考)。", editable=1, display_type="number"),
_col("source_doc", "指南来源", "指南文件名/文号。", editable=1),
_col("created_at", "创建时间", "记录创建时间(Unix 秒级,自动生成)。"),
_col("updated_at", "更新时间", "记录最后更新时间(Unix 秒级,自动维护)。"),
)
_MATCH_REPORTS_COLUMNS: tuple[dict[str, object], ...] = (
_col("id", "报告编号", "主键,推荐报告编号。", searchable=1),
_col("batch_no", "匹配批次号", "同一次匹配请求的批次号。", searchable=1),
_col("teacher_name", "教师姓名", "被匹配教师。", searchable=1),
_col("department", "所属学院", "教师所属学院。", searchable=1),
_col("season", "指南年度", "匹配所用指南年度。", searchable=1),
_col("top_json", "Top3推荐", "Top 3 赛道(JSON,含得分与理由)。", visible=0, display_type="textarea"),
_col("tiers_json", "分层清单", "强烈推荐/可尝试/不建议分层(JSON)。", visible=0, display_type="textarea"),
_col("eligible_count", "可匹配赛道数", "通过硬门槛过滤后的赛道数。", searchable=1, display_type="number"),
_col("excluded_json", "剔除清单", "被硬门槛剔除的赛道及原因(JSON)。", visible=0, display_type="textarea"),
_col("report_file", "建议书文件", "《申报策略建议书》HTML 文件路径。", visible=0),
_col("created_at", "创建时间", "报告创建时间(Unix 秒级,自动生成)。"),
)
def _init_teacher_profiles_metadata(cursor) -> None:
cursor.execute(_TEACHER_PROFILES_DDL)
init_updated_at_trigger(cursor, "teacher_profiles")
upsert_table_metadata(
cursor,
table_name="teacher_profiles",
display_name="教师画像",
description="教师学术画像:研究标签、成果与职称,用于赛道匹配。",
grid_help="研究标签是匹配主依据(逗号分隔);顶栏按钮可录入,行内可编辑。",
visible=1,
readonly=0,
default_sort_json='[{"column":"id","direction":"desc"}]',
default_visible_columns_json='["teacher_name","department","title","research_tags","sci_q1_count"]',
)
seed_column_metadata_batch(
cursor,
table_name="teacher_profiles",
columns=_TEACHER_PROFILES_COLUMNS,
valid_column_names=[str(c["column_name"]) for c in _TEACHER_PROFILES_COLUMNS],
)
def _init_guide_tracks_metadata(cursor) -> None:
cursor.execute(_GUIDE_TRACKS_DDL)
init_updated_at_trigger(cursor, "guide_tracks")
upsert_table_metadata(
cursor,
table_name="guide_tracks",
display_name="指南赛道",
description="当年申报指南的结构化赛道库:需求标签、优先方向与硬门槛。",
grid_help="按年度管理;需求标签为匹配主依据,硬门槛用于过滤不满足条件的教师。",
visible=1,
readonly=0,
default_sort_json='[{"column":"year","direction":"desc"},{"column":"id","direction":"asc"}]',
default_visible_columns_json='["year","program_name","track_name","track_tags","min_title","min_sci_q1"]',
)
seed_column_metadata_batch(
cursor,
table_name="guide_tracks",
columns=_GUIDE_TRACKS_COLUMNS,
valid_column_names=[str(c["column_name"]) for c in _GUIDE_TRACKS_COLUMNS],
)
def _init_match_reports_metadata(cursor) -> None:
cursor.execute(_MATCH_REPORTS_DDL)
cursor.execute(_MATCH_REPORTS_INDEX)
upsert_table_metadata(
cursor,
table_name="match_reports",
display_name="推荐报告",
description="每次匹配的 Top 3 赛道、分层清单与剔除原因;可重建建议书。",
grid_help="推荐仅供参考;顶栏按钮可重建《申报策略建议书》。",
visible=1,
readonly=1,
default_sort_json='[{"column":"id","direction":"desc"}]',
default_visible_columns_json='["id","season","teacher_name","department","eligible_count"]',
)
seed_column_metadata_batch(
cursor,
table_name="match_reports",
columns=_MATCH_REPORTS_COLUMNS,
valid_column_names=[str(c["column_name"]) for c in _MATCH_REPORTS_COLUMNS],
)
def init_db() -> dict[str, int]:
"""幂等创建/迁移本地 SQLite 与数据管理元数据。"""
conn = get_conn()
try:
cur = conn.cursor()
init_display_metadata(cur)
_init_teacher_profiles_metadata(cur)
_init_guide_tracks_metadata(cur)
_init_match_reports_metadata(cur)
conn.commit()
return {}
finally:
conn.close()