Files
draft-closing-report-default/scripts/db/connection.py
chenfuqiang 7b24521b43
All checks were successful
技能自动化发布 / release (push) Successful in 6s
feat: 结题报告初稿助手首版 v1.0.1(draft-closing-report-default)
2026-09-13 16:04:01 +08:00

263 lines
11 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.
"""SQLite 连接与幂等初始化(结题报告初稿助手)。
业务表 4 张:
- closing_projects(结题项目,含预期指标,可编辑)
- achievements(成果清单,可编辑)
- expense_items(决算明细,可编辑)
- report_drafts(初稿记录,只读,生成自动写入)
表间禁止 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())
_CLOSING_PROJECTS_DDL = """
CREATE TABLE IF NOT EXISTS closing_projects (
id INTEGER PRIMARY KEY AUTOINCREMENT,
project_no TEXT NOT NULL UNIQUE,
project_name TEXT,
pi_name TEXT,
department TEXT,
program TEXT,
start_date TEXT,
end_date TEXT,
expected_json TEXT,
annual_report_excerpt TEXT,
status TEXT NOT NULL DEFAULT '待结题',
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
updated_at INTEGER NOT NULL DEFAULT (unixepoch())
)
"""
_ACHIEVEMENTS_DDL = """
CREATE TABLE IF NOT EXISTS achievements (
id INTEGER PRIMARY KEY AUTOINCREMENT,
project_no TEXT NOT NULL,
achievement_type TEXT NOT NULL,
title TEXT,
venue_or_no TEXT,
ach_date TEXT,
is_representative INTEGER NOT NULL DEFAULT 0,
note TEXT,
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
updated_at INTEGER NOT NULL DEFAULT (unixepoch())
)
"""
_EXPENSE_ITEMS_DDL = """
CREATE TABLE IF NOT EXISTS expense_items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
project_no TEXT NOT NULL,
category TEXT NOT NULL,
amount REAL NOT NULL,
occurred_date TEXT,
description TEXT,
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
updated_at INTEGER NOT NULL DEFAULT (unixepoch())
)
"""
_REPORT_DRAFTS_DDL = """
CREATE TABLE IF NOT EXISTS report_drafts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
batch_no TEXT NOT NULL,
project_no TEXT,
project_name TEXT,
completion_json TEXT,
narrative_points_json TEXT,
draft_file TEXT,
preview_file TEXT,
created_at INTEGER NOT NULL DEFAULT (unixepoch())
)
"""
_REPORT_DRAFTS_INDEX = """
CREATE INDEX IF NOT EXISTS idx_report_drafts_batch
ON report_drafts(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
_CLOSING_PROJECTS_COLUMNS: tuple[dict[str, object], ...] = (
_col("id", "编号", "主键", searchable=1),
_col("project_no", "项目编号", "项目唯一编号。", searchable=1),
_col("project_name", "项目名称", "项目全称。", searchable=1, editable=1),
_col("pi_name", "负责人", "项目负责人姓名。", searchable=1, editable=1),
_col("department", "所属学院", "二级学院/单位。", searchable=1, editable=1),
_col("program", "项目类型", "如 国家自然科学基金(青年/面上)。", searchable=1, editable=1),
_col("start_date", "执行期开始", "YYYY-MM-DD。", searchable=1, editable=1),
_col("end_date", "执行期结束", "YYYY-MM-DD。", searchable=1, editable=1),
_col("expected_json", "预期成果指标", "如 [{\"type\":\"论文\",\"target\":3,\"unit\":\"篇\"}]。", visible=0, display_type="textarea", editable=1),
_col("annual_report_excerpt", "年度报告摘句", "年度/中期报告关键句(分号分隔),供叙事素材。", visible=0, display_type="textarea", editable=1),
_col("status", "状态", "待结题 / 初稿已生成 / 已提交。", searchable=1, editable=1, options_json='[{"value":"待结题","label":"待结题"},{"value":"初稿已生成","label":"初稿已生成"},{"value":"已提交","label":"已提交"}]'),
_col("created_at", "创建时间", "记录创建时间(Unix 秒级,自动生成)。"),
_col("updated_at", "更新时间", "记录最后更新时间(Unix 秒级,自动维护)。"),
)
_ACHIEVEMENTS_COLUMNS: tuple[dict[str, object], ...] = (
_col("id", "编号", "主键", searchable=1),
_col("project_no", "项目编号", "所属项目编号。", searchable=1),
_col("achievement_type", "成果类型", "论文 / 专利 / 人才培养 / 其他。", searchable=1, editable=1, options_json='[{"value":"论文","label":"论文"},{"value":"专利","label":"专利"},{"value":"人才培养","label":"人才培养"},{"value":"其他","label":"其他"}]'),
_col("title", "成果名称", "论文标题/专利名/成果描述。", searchable=1, editable=1),
_col("venue_or_no", "期刊/专利号", "发表期刊或专利号。", editable=1),
_col("ach_date", "日期", "发表/授权日期(YYYY-MM-DD)。", editable=1),
_col("is_representative", "代表性成果", "1=代表本项目的标志性成果。", searchable=1, editable=1),
_col("note", "备注", "说明。", editable=1),
_col("created_at", "创建时间", "记录创建时间(Unix 秒级,自动生成)。"),
_col("updated_at", "更新时间", "记录最后更新时间(Unix 秒级,自动维护)。"),
)
_EXPENSE_ITEMS_COLUMNS: tuple[dict[str, object], ...] = (
_col("id", "编号", "主键", searchable=1),
_col("project_no", "项目编号", "所属项目编号。", searchable=1),
_col("category", "科目", "决算科目,如 差旅费、设备费。", searchable=1, editable=1),
_col("amount", "金额", "支出金额(元)。", searchable=1, editable=1, display_type="number"),
_col("occurred_date", "发生日期", "YYYY-MM-DD。", editable=1),
_col("description", "原始摘要", "流水描述,供语义化转换参考。", visible=0, display_type="textarea", editable=1),
_col("created_at", "创建时间", "记录创建时间(Unix 秒级,自动生成)。"),
_col("updated_at", "更新时间", "记录最后更新时间(Unix 秒级,自动维护)。"),
)
_REPORT_DRAFTS_COLUMNS: tuple[dict[str, object], ...] = (
_col("id", "记录编号", "主键,初稿记录编号。", searchable=1),
_col("batch_no", "生成批次号", "同一次生成请求的批次号。", searchable=1),
_col("project_no", "项目编号", "初稿对应项目。", searchable=1),
_col("project_name", "项目名称", "初稿对应项目名。", searchable=1),
_col("completion_json", "指标对比", "预期 vs 实际对比(JSON)。", visible=0, display_type="textarea"),
_col("narrative_points_json", "叙事素材要点", "从成果与年度报告提取的要点(JSON)。", visible=0, display_type="textarea"),
_col("draft_file", "Word 初稿", "生成的 docx 文件路径。", visible=0),
_col("preview_file", "预览文件", "HTML 预览文件路径。", visible=0),
_col("created_at", "创建时间", "记录创建时间(Unix 秒级,自动生成)。"),
)
def _init_closing_projects_metadata(cursor) -> None:
cursor.execute(_CLOSING_PROJECTS_DDL)
init_updated_at_trigger(cursor, "closing_projects")
upsert_table_metadata(
cursor,
table_name="closing_projects",
display_name="结题项目",
description="待结题项目与预期成果指标;初稿生成的数据源。",
grid_help="预期指标为 JSON 数组(type/target/unit);顶栏按钮可录入,行内可编辑。",
visible=1,
readonly=0,
default_sort_json='[{"column":"id","direction":"desc"}]',
default_visible_columns_json='["project_no","project_name","pi_name","program","end_date","status"]',
)
seed_column_metadata_batch(
cursor,
table_name="closing_projects",
columns=_CLOSING_PROJECTS_COLUMNS,
valid_column_names=[str(c["column_name"]) for c in _CLOSING_PROJECTS_COLUMNS],
)
def _init_achievements_metadata(cursor) -> None:
cursor.execute(_ACHIEVEMENTS_DDL)
init_updated_at_trigger(cursor, "achievements")
upsert_table_metadata(
cursor,
table_name="achievements",
display_name="成果清单",
description="实际取得的论文/专利/人才培养等成果;完成情况章节的数据源。",
grid_help="按项目编号关联;代表性成果会优先进入初稿素材。",
visible=1,
readonly=0,
)
seed_column_metadata_batch(
cursor,
table_name="achievements",
columns=_ACHIEVEMENTS_COLUMNS,
valid_column_names=[str(c["column_name"]) for c in _ACHIEVEMENTS_COLUMNS],
)
def _init_expense_items_metadata(cursor) -> None:
cursor.execute(_EXPENSE_ITEMS_DDL)
init_updated_at_trigger(cursor, "expense_items")
upsert_table_metadata(
cursor,
table_name="expense_items",
display_name="决算明细",
description="经费流水明细;决算章节的数据源。",
grid_help="金额单位元;按科目汇总后自动生成规范描述句。",
visible=1,
readonly=0,
)
seed_column_metadata_batch(
cursor,
table_name="expense_items",
columns=_EXPENSE_ITEMS_COLUMNS,
valid_column_names=[str(c["column_name"]) for c in _EXPENSE_ITEMS_COLUMNS],
)
def _init_report_drafts_metadata(cursor) -> None:
cursor.execute(_REPORT_DRAFTS_DDL)
cursor.execute(_REPORT_DRAFTS_INDEX)
upsert_table_metadata(
cursor,
table_name="report_drafts",
display_name="初稿记录",
description="每次生成的初稿记录:指标对比、素材要点与文件路径。",
grid_help="Word 文件在 output 目录可下载;HTML 预览在侧栏查看。",
visible=1,
readonly=1,
default_sort_json='[{"column":"id","direction":"desc"}]',
default_visible_columns_json='["id","project_no","project_name","created_at"]',
)
seed_column_metadata_batch(
cursor,
table_name="report_drafts",
columns=_REPORT_DRAFTS_COLUMNS,
valid_column_names=[str(c["column_name"]) for c in _REPORT_DRAFTS_COLUMNS],
)
def init_db() -> dict[str, int]:
"""幂等创建/迁移本地 SQLite 与数据管理元数据。"""
conn = get_conn()
try:
cur = conn.cursor()
init_display_metadata(cur)
_init_closing_projects_metadata(cur)
_init_achievements_metadata(cur)
_init_expense_items_metadata(cur)
_init_report_drafts_metadata(cur)
conn.commit()
return {}
finally:
conn.close()