软件工程实践第二次作业——与 AI 结对编程

022401208范敬翔 2026-09-24 23:39:15
项目内容
这个作业属于哪个课程202601福大-软件工程实践-W班
这个作业要求在哪里软件工程实践第二次作业
这个作业的目标学习 NABCD 与 PSP,在专用工具中完成交互原型,并与 AI 编程助手协作实现一个可获取、管理和分析 CVPR、ICCV、ECCV 论文的 Web 应用;同时实践测试、Git 分支、CodeArts 协作和软件交付。
学号022401208
CodeArts 仓库https://codehub.devcloud.cn-north-4.huaweicloud.com/671d5eb394014b14846f3c38afb52d1f/022401208.git
其他参考资料《构建之法》相关章节、邹欣的 PSP 与源代码管理文章、Flask/SQLite 官方文档、CVF Open Access、ECVA、OpenAlex 和 arXiv API 文档等;完整清单见文末。

目录

一、项目概况与AI协作工具说明
二、PSP耗时统计与偏差分析
三、NABCD需求分析模型
四、交互原型设计
五、成品功能展示
六、人机结对协作全过程
七、系统设计与技术实现
八、核心代码逻辑说明
九、测试验证与云端部署
十、Git与CodeArts工程实践
十一、开发心路与实践收获
十二、AI结对伙伴综合评价
十三、参考资料


一、项目概况与AI协作工具说明

img

1.1 项目访问入口

img

1.2 项目定位

本平台面向计算机视觉方向入门学习者与选题调研者,聚焦CVPR、ICCV、ECCV三大顶级学术会议,打造“论文采集—数据入库—检索管理—统计分析—可视化交互—原文溯源”的完整闭环链路。用户既可以通过标题检索单篇论文或批量导入文献列表,也可以对本地论文库进行全生命周期管理;系统基于统一可解释的统计口径,输出Top10热点方向、关键词共现图谱、跨会议跨年热度走势三类分析结果。Top10榜单与关系图谱均支持跳转至对应关联论文,趋势页面公开每个统计节点的样本分子与分母,避免将演示样本包装为会议全量结论。

img

当前版本已完成全部五项基础功能,内置800篇可溯源论文与299个经过清洗的核心关键词。原型设计过程、关键协作截图与AI交互记录均已整理归档,代码版本与交付记录详见第十章。

1.3 技术选型与边界说明

技术层级选型方案选型依据
Web后端Python 3.13、Flask 3.1应用工厂与Blueprint模式结构清晰层级分明,项目规模可控,便于快速迭代测试与功能扩展
数据存储Python标准库sqlite3无需额外部署数据库服务,适配课程项目演示场景;通过唯一约束、索引、WAL机制与busy_timeout参数保障基础数据一致性
网络检索OpenAlex为主、arXiv为备用源根据论文标题补全摘要、作者与原文链接;外部服务出现超时、限流或无结果时执行明确降级策略,不生成伪造数据
前端实现Jinja2模板、原生JavaScript、SVG、Canvas不依赖Node构建链路与第三方图表CDN,断网环境下本地管理与统计功能仍可正常使用
测试体系unittest单元测试、Flask测试客户端、Mock模拟、HTTP冒烟测试覆盖核心算法、数据仓储、接口路由、外部异常与全量只读访问链路
部署方案华为云Flexus L、Ubuntu、Gunicorn、Nginx、systemd正式v1.0.0版本已上线运行,服务器内功能校验与公网访问验证全部通过

项目明确边界:当前内置种子数据为每届100篇、总计800篇的功能演示样本,并非三大顶会全量文献,也非随机抽样结果;趋势分析仅代表当前载入样本内的关键词覆盖率,不可直接外推为整个领域的真实研究热度。

1.4 AI工具与分工说明

协作工具版本说明分工界定
墨刀AI原型内置AI助手,具体模型版本未对外展示本人提出原型需求与验收标准,AI输出五页面方案与可交互原型
DeepSeek代码端结对助手AI承担主体代码实现、数据处理、测试用例与文档初稿撰写;本人提供需求约束、修改目标与决策取舍

本人主导需求定义、交互反馈与方案决策,AI承担具体落地实现、测试执行与材料整理。详细协作分工与案例见第六章。


二、PSP耗时统计与偏差分析

项目计划周期4个自然日,按每日8小时工作量预估总投入1920分钟(32小时);最终实际总投入1200分钟(20小时)。完整统计如下表。

PSP阶段个人软件过程环节预估耗时(分钟)实际耗时(分钟)
Planning计划阶段合计1515
· Estimate· 任务拆解与工期估算1515
Development开发阶段合计16951035
· Analysis· 需求分析与新技术学习15090
· Design Spec· 设计文档输出9060
· Design Review· 设计评审与沟通修改4530
· Coding Standard· 编码规范制定3015
· Design· 原型设计与交互细化270180
· Coding· 功能编码实现720420
· Code Review· 代码评审9090
· Test· 功能测试与回归修复300150
Reporting报告阶段合计210150
· Test Report· 测试报告整理6045
· Size Measurement· 工作量统计3015
· Postmortem & Process Improvement Plan· 事后总结与过程优化方案12090
总计(仅累加12项明细)19201200

注:Estimate的15分钟仅为计划制定环节的投入,并非整个作业的预估总时长。开发小计为需求分析到功能测试8项的总和,不重复叠加计算。预估总时长为15 + 1695 + 210 = 1920分钟,实际总时长为15 + 1035 + 150 = 1200分钟。

2.1 预估偏差分析

最终实际投入时长较预估减少720分钟(12小时),偏差率为(1200 − 1920) ÷ 1920 × 100% = −37.5%。AI承担了大量重复性编码、脚本开发、测试用例撰写与文档初稿工作,有效减少了重复实现与材料整理的时间消耗;分项对比来看,编码环节节省300分钟,测试环节节省150分钟,原型与设计环节节省90分钟。

img

实际投入已覆盖论文样本从120篇扩充至800篇、原始3410个词项精简至299个核心关键词、以及图谱与趋势交互的多轮迭代优化。上述偏差为基于预估基准的回顾对比,不可全部归因于AI的效率提升。后续项目将在启动前登记预估工时,各阶段同步记录实际投入,为数据质量优化与界面迭代预留缓冲时间。


三、NABCD需求分析模型

3.1 N — Need(需求痛点)

目标用户覆盖三类人群:刚接触计算机视觉、希望快速搭建领域认知框架的学生;正在选题、撰写综述或准备开题的研究者;需要对比会议偏好与跨年变化的教师与开发者。

核心痛点可归纳为四点:

  • CVPR、ICCV、ECCV年均发文量巨大,逐篇阅读梳理的入门门槛与时间成本较高;
  • 会议官方平台侧重论文入口提供,无法直接回答“哪些方向更受关注、研究趋势如何变化”的问题;
  • 论文标题、摘要、关键词与原文链接分散在不同平台,个人文献管理与检索容易产生重复劳动;
  • 单一词频榜单可解释性不足,用户无法从热点词反向追溯支撑该结论的具体论文。

用户真正需要的并非单纯的可视化词云,而是一条可验证的任务链路:输入标题或导入列表,获得结构化论文数据;在本地完成增删改查全管理;按会议与年份筛选查看Top10热点;在共现图谱中点击关键词查看关联论文;最后播放跨年趋势动画并查看每个节点的样本分母。

项目设计重点包括研究方向短语的可读性、覆盖率与热度的解释逻辑、批量结果的清晰度,以及“会议未举办”与“数据缺失”的状态区分。现有分析仅覆盖论文数量与关键词噪声维度的需求,不做用户满意度的主观推断。

3.2 A — Approach(解决方案)

采用“数据采集—规范化清洗—持久化存储—统计分析—可视化交互—论文下钻”的全链路闭环方案。

img

  • 数据采集:演示样本取自CVF Open Access与ECVA官方公开论文页面;临时标题检索以OpenAlex为主,出现限流、服务故障或无结果时切换至arXiv备用源。
  • 规范化去重:统一标题Unicode编码、空白字符与大小写格式,通过normalized_title字段建立唯一约束,同时对“来源+外部编号”建立第二组唯一索引。
  • 本地管理:SQLite存储论文标题、摘要、关键词、作者、会议、年份、来源链接与获取时间;API支持完整CRUD操作、分页查询、精确匹配与模糊检索。
  • 关键词处理:保留每篇论文的原始导入词项,在独立分析层去除泛化词汇、保守合并单复数与明确缩写,并从原文中匹配完整专业短语;仅保留全库至少3篇不同论文支撑的核心词,再应用会议与年份筛选。
  • 热点统计:同时采用覆盖率与相对词项强度双重指标,避免单纯按出现次数排序;每项均保留对应论文编号,支持下钻验证。
  • 共现图谱:同一篇论文中的核心关键词两两构成共现边,边权重为共同出现的论文数量;首页最多展示36个节点,支持缓慢自动漫游,选中节点后放大并暂停动画,关联论文在固定高度面板内支持滚动与分页。
  • 趋势动画:按“包含该词的论文数/当届已载入论文总数”计算覆盖率;区分会议未举办、该届无载入数据与真实零值三种状态。
  • 降级与边界:网络请求设置超时并映射友好错误提示;本地数据与统计功能不依赖公网;所有API返回统一结构报文。

功能优先级划分:P0级完成五项基础功能、异常处理与全量测试;P1级完成来源说明、响应式适配、可解释下钻;P2级考虑导出、收藏等扩展功能,避免截止前范围失控。

3.3 B — Benefit(用户价值)

平台将“多平台搜索+人工整理表格”的传统模式升级为“先看趋势、再定位论文”的高效模式。具体价值包括:

  • 在单一页面体系内完成论文获取、管理、检索、统计与展示全流程;
  • 热点词不仅输出热度分值,同时提供覆盖论文数、筛选范围与关联论文入口;
  • 采用覆盖率指标弱化不同年份样本量差异带来的统计偏差;
  • 将三大顶会置于同一时间轴对比,帮助识别持续热点与会议偏好差异;
  • 数据来源、计算公式与缺失状态全部公开,便于复算与质疑。

img

价值判断基于已实现功能与自动化检查记录,以及本人的实际使用反馈;未实测的任务耗时、成功率与满意度不做已达成声明。

3.4 C — Competitors(竞品分析)

以下竞品信息由AI于2026-09-23查阅官方资料整理,非本人亲自试用结论,也不额外增加竞品体验任务。对比仅限本项目场景,不对官方文档未载明的功能做主观推定。

竞品平台官方定位与核心能力本项目差异化定位
CVF Open Access按会议届次提供CVPR、ICCV等开放获取论文保留官方来源,在抽样数据基础上提供统一统计与趋势分析
ECVA Papers按ECCV届次提供论文、作者与原文入口与CVF样本统一管理,不替代官方全量数据库
DBLP计算机科学文献书目与元数据服务聚焦所选样本的管理与统计,不追求全库覆盖度
Semantic ScholarAI辅助学术搜索、文献发现与关联推荐提供小范围、规则透明的三大会议专项分析路径

项目核心差异在于将“范围筛选—Top10/图谱—关联论文—年度趋势”整合在同一交互界面。优势在于垂直领域聚焦、结果可追溯、统计口径透明;局限在于每届仅100篇样本,规则可能遗漏低频方向,不宣称检索质量优于成熟学术平台。

3.5 D — Delivery(交付推广)

原型通过墨刀分享链接与关键交流截图展示;代码通过CodeArts交付,已完成dev到main的非压缩合并,并创建v1.0.0版本标签。相同版本成品已部署至华为云Flexus L服务器,提供公网访问地址与验证截图。本文通过产品截图、趋势GIF、关键代码与三组AI案例完整说明需求、实现与交付全过程。

img


四、交互原型设计

img

4.1 工具与发布地址

原型工具:墨刀AI辅助设计
分享链接:https://modao.cc/ai/share/6ab5426375d0275ef6fdb8fc
原型启动时间:2026-09-21
本次要求的原型分享链接与关键交流截图均已提供。墨刀生成的HTML仅用于原型表达,未接入正式Flask应用;成品代码采用独立后端、Jinja模板与原生SVG/Canvas实现。

img

img

4.2 五页面架构体系

平台共设五个核心页面,通过顶部导航栏全局联动:首页承载数据概览、Top10榜单与关系图谱;论文管理页负责文献检索与全生命周期管理;获取导入页提供单篇与批量采集入口;趋势动画页展示年度热度变化;方法说明页解释数据来源与统计口径。

初始对话采用深色主视觉搭配绿色、紫色强调色,成品迭代为深色首屏+浅色内容区的分层设计,避免全页面浅色的视觉疲劳。原型侧重交互逻辑表达,正式应用进一步实现了持久化CRUD、真实检索与分析计算。

4.3 原型协作关键节点

需求提出阶段:本人明确提出电脑端五页面结构、导航体系、各页面布局、统计逻辑与交互要求。
初稿生成阶段:墨刀AI输出首页、论文、导入、趋势、方法五个页面与导航框架,本人采纳基础结构。
补全优化阶段:本人对照作业要求核查缺项,AI补充Top10榜单、关键词图谱与论文增删改操作。


五、成品功能展示

成品链接:http://114.116.252.147/

img

5.1 首页:Top10榜单与关键词共现图谱

首页直观展示当前库内论文总量、核心关键词数、覆盖届次数及数据最近更新时间;首屏热度值、论文数量与年份分布均由真实接口计算生成,非固定装饰数值。切换会议与年份后点击“应用筛选”即可同步更新榜单与图谱。

img

榜单展示核心专业术语、热度分值与覆盖论文数,采用公开统一公式;当前全库共299个核心词,首页仅展示Top10。图谱进一步筛选最多36个高排名词,避免节点过多造成视觉拥挤。

img

节点大小对应覆盖论文数量,连线代表同一篇论文中的真实共现关系;漫游仅为视觉位置变化,不改变底层数据。点击节点会放大并暂停动画,右侧列表在固定高度内支持滚动,通过“加载更多”可查看完整关联论文。

5.2 论文管理与检索

论文资料库集中管理论文元数据,支持完整标题精确检索,以及编号、标题、原始关键词、摘要的模糊检索。

img

论文管理支持分页浏览、会议与年份筛选、完整标题精确匹配,以及多维度模糊查询。列表查询针对已保存的论文记录,与核心分析词表的筛选为两套独立功能。

5.3 单篇获取与批量导入

导入页支持单篇标题输入、TXT/CSV文件上传与多行文本粘贴三种模式。批量输入先校验原始条数在1~100范围内,再执行规范化去重,逐条返回created、existing、found、not_found或error状态;外部超时与限流不会包装为成功结果。网页端最多并发3个单篇请求,避免整批任务因单个同步请求长时间阻塞。

img

5.4 三大会年度趋势动画

趋势页采用Canvas绘制覆盖率曲线与年份高亮,右侧会议卡片同步显示占比数值与“含词篇数/当届样本数”。年份时间轴支持播放、暂停、重播、点击跳转、滑块拖动与键盘方向键控制;悬浮提示与下方数据表用于读取精确数值。

ICCV偶数年、ECCV奇数年标注“未举办”;举办但无对应样本的届次标注“缺数据”。两类状态均不视为0%,不会跨缺失位置绘制连续折线。

5.5 方法说明与论文详情

方法说明页公开core-v1词表规则、全库至少3篇论文支持的阈值、原始词项保留机制与样本局限。明确说明800篇不等于全量数据,299个核心词也不等于官方研究方向分类;低频词的遗漏是精简规则的取舍选择。

论文详情页集中展示标题、作者、会议、年份、摘要、原始/导入关键词与来源链接。用户可从Top10榜单或图谱关联论文进入详情页,再跳转至官方网站或PDF原文。

5.6 响应式适配

页面支持窄视口下的重排、平移、滚动与图表布局自适应。

![img](

#https://i-blog.csdnimg.cn/communtity/0def30dadf95423ca89e3a3db6885854.png "#left")

六、人机结对协作全过程

以下结合三个典型案例,说明需求提出、AI方案判断与迭代优化的完整过程。

img

6.1 案例一:需求拆解与原型方案生成

本人提出需求:将作业要求文档发送给墨刀AI,要求其解读评分标准,输出可直接用于原型生成的完整指令。

img

AI输出核心要点:生成面向CV三大顶会的论文热词平台电脑端原型,包含五个带全局导航的联动页面,采用绿色与紫色强调色,覆盖核心交互逻辑;使用专业原型工具生成可分享链接并保留协作痕迹;生成的HTML为原型产物,不接入生产环境。

本人决策与调整:直接采纳整体框架。该方案将作业描述拆解为明确的可执行任务,后续本人进一步细化布局细节,并要求墨刀对照课程要求补充Top10榜单、关键词图谱与论文操作功能。最终产出为第四章的分享链接与三张关键交流截图。

6.2 案例二:项目整体规划与信息补全

本人提出需求:“完整规划本次作业的全部流程,AI承担可自动化的部分,必须人工操作的环节明确标注,先输出整体计划。”

AI输出核心要点:详细拆解功能模块、数据方案、测试体系、CodeArts流程与博客结构,并询问缺失条件。本人提供学号、CodeArts仓库地址、原型工具选择与教师数据集情况等信息。

本人决策与调整:采纳计划框架并补充基础信息,后续根据实际情况取消了额外体验评审事项,确保工作范围严格限定在课程要求与项目需求内。

计划与最终实现的差异:早期方案中的ECharts、DBLP均为候选技术,最终实际采用Flask、SQLite、原生SVG/Canvas、CVF/ECVA样本,以及OpenAlex/arXiv标题补查。最终技术方案以README为准。

6.3 案例三:界面迭代优化与数据粒度调优

第一轮:交互细节反馈。本人指出首页数据卡片是否随导入动态变化、标题位置偏移、点击图谱后容器高度突变、关联论文展示不全、趋势文字位置偏差等问题,并提出节点缓慢漫游、点击后放大暂停、右侧论文独立滚动的交互要求。

AI针对性调整了真实统计逻辑、图谱容器高度、交互状态、关联论文区域与趋势展示,本人验证后继续提出数据层面的优化需求。

第二轮:样本规模扩充。本人认为初始120篇样本量不足,询问扩充可行性以及其他模块是否同步更新。AI将样本扩充至800篇并保留原始记录:CVPR400篇、ICCV200篇、ECCV200篇,明确每届100篇仍为演示样本,非会议全量数据。

本人采纳结果,同时明确“重新统计”不等于“页面自动实时刷新”:当前需手动刷新、应用筛选或更新趋势,无后台自动推送机制。

第三轮:关键词粒度精简。样本扩充后产生3410个原始词项,本人认为粒度太粗泛词过多,要求精简优化。AI保留全部论文与原始词项,在分析层去除泛词、保守合并别名、保留专业短语,采用全库至少3篇论文支持的阈值规则,最终得到299个核心词,统一应用于首页、Top10、图谱、关联论文与趋势模块。

img

本人决策与调整:采纳优化方案并继续明确迭代方向。本人负责问题识别与需求取舍,AI负责具体规则实现、代码开发与验证。阈值3等细节参数由AI设计。

对应产出:提交f61df6d对应800篇样本扩充,c038c90对应核心词表与交互优化,f7f7e29对应验证工具。对话截图中的测试数量对应各次迭代;v1.0.0的测试与部署结果集中在第九章说明。

6.4 人机贡献划分

工作范围本人实际贡献AI实际贡献
原型与规划提出需求、选择工具、补充约束、方案决策原型生成、任务拆解、模块划分
交互与数据优化定义图谱交互逻辑、提出样本扩充与词表精简目标实现状态机制、采集规则与统计算法
主体代码开发明确边界条件、提供迭代反馈后端、数据库、检索、统计与大部分页面实现
测试与材料提供截图、总工时、判断标准与账号信息自动化检查、证据整理、文稿撰写

七、系统设计与技术实现

7.1 功能模块结构

img

7.2 技术架构与数据流向

img

后端路由负责输入校验、业务调用与HTTP映射,仓储层封装SQLite操作,keyword_policy.py负责生成全库统一核心词表,analytics.py设计为不依赖Flask的纯计算模块,外部检索逻辑独立封装在retrieval.py。分析请求先读取全库生成派生副本,再应用范围筛选;不回写原始关键词。分层设计使得统计逻辑与网络异常可独立测试,也避免了单路由函数堆积所有逻辑。

7.3 数据模型设计

papers表核心字段设计如下:

字段名功能作用约束说明
id本地自增主键主键,页面展示为稳定编号
title展示用标题非空
normalized_title去重用标准化标题Unicode/空白/大小写规范化后建立唯一约束
abstract论文摘要缺失时保留空字符串,不生成伪造摘要
keywords_json原始/导入关键词JSON数组;保留来源记录,分析层另生成核心词项,不覆盖此字段
authors_json作者列表JSON数组
conference / year会议与年份用于筛选与趋势分组
source_url / pdf_url来源页与PDF链接仅接受安全的HTTP(S)地址
source / external_id外部来源标识两者组合建立条件唯一索引
created_at / updated_at时间戳便于追踪本地变更记录

数据库同时建立conference + year联合索引与updated_at索引;连接采用WAL模式、5秒busy_timeout、请求级连接与显式事务。该方案适配小型课程演示,但不等同于生产级数据库,多实例写入场景建议切换至PostgreSQL等服务型数据库。

7.4 核心页面与接口清单

页面/接口功能说明
/首页:Top10榜单、关键词图谱与关联论文
/papers论文列表:CRUD操作、精确/模糊查询、联网补查
/papers/<id>论文详情页与原始来源跳转
/import导入页:单篇获取、TXT/CSV/多行批量获取
/trends趋势页:三大会跨年覆盖率趋势动画
/about关于页:数据来源、计算公式、方法说明与局限
GET/POST /api/papers分页查询或创建论文
GET/PATCH/DELETE /api/papers/<id>读取、修改或删除单篇论文
POST /api/retrieval/lookup本地优先的单篇标题检索
POST /api/retrieval/batch1~100条原始标题,规范化去重后的故障隔离批处理
GET /api/analytics/top-keywordsTop10关键词与公式信息
GET /api/analytics/graph图谱节点、共现边与论文ID
GET /api/analytics/related-papers同一词表与筛选范围下的关联论文分页
GET /api/analytics/trends三大会逐年覆盖率与状态
GET /api/meta/filters可用会议、年份、论文数与届次数
/healthz、/api/version健康检查与版本信息

img

所有业务JSON接口统一返回格式:

{
  "ok": true,
  "data": {},
  "message": "",
  "errors": []
}
### 7.4 核心页面与接口清单

| 页面/接口 | 功能说明 |
| --- | --- |
| `/` | 首页:Top10榜单、关键词图谱与关联论文 |
| `/papers` | 论文列表:CRUD操作、精确/模糊查询、联网补查 |
| `/papers/<id>` | 论文详情页与原始来源跳转 |
| `/import` | 导入页:单篇获取、TXT/CSV/多行批量获取 |
| `/trends` | 趋势页:三大会跨年覆盖率趋势动画 |
| `/about` | 关于页:数据来源、计算公式、方法说明与局限 |
| `GET/POST /api/papers` | 分页查询或创建论文 |
| `GET/PATCH/DELETE /api/papers/<id>` | 读取、修改或删除单篇论文 |
| `POST /api/retrieval/lookup` | 本地优先的单篇标题检索 |
| `POST /api/retrieval/batch` | 1~100条原始标题,规范化去重后的故障隔离批处理 |
| `GET /api/analytics/top-keywords` | Top10关键词与公式信息 |
| `GET /api/analytics/graph` | 图谱节点、共现边与论文ID |
| `GET /api/analytics/related-papers` | 同一词表与筛选范围下的关联论文分页 |
| `GET /api/analytics/trends` | 三大会逐年覆盖率与状态 |
| `GET /api/meta/filters` | 可用会议、年份、论文数与届次数 |
| `/healthz`、`/api/version` | 健康检查与版本信息 |

所有业务JSON接口统一返回格式:

{
"ok": true,
"data": {},
"message": "",
"errors": []
}


### 7.5 数据来源、范围与清洗规则

教师数据集截至2026-09-22未发布,因此演示数据从CVF Open Access与ECVA官方公开页面采集:

| 会议 | 覆盖届次 | 每届样本量 | 合计 |
| --- | --- | --- | --- |
| CVPR | 2022、2023、2024、2025 | 100 | 400 |
| ICCV | 2023、2025 | 100 | 200 |
| ECCV | 2022、2024 | 100 | 200 |
| 总计 | 8届 | - | 800 |

初始每届保留官方列表前15篇;扩充时保留该120条记录,在各届剩余列表中按位置等间距选取补充样本,新增680条,总计800条。采集脚本记录抓取时间、官方页面、页面可见数量、载入数量与错误信息。数据清洗包括HTML解码与空白折叠、标题规范化去重、安全URL校验;缺失字段保留空值,不生成补充摘要。

800篇论文的摘要、来源链接、原始关键词与种子/数据库一致性已全部核验;数据扩充报告同时验证了原120条记录未被改写。但每届100篇样本仍受官方排序与确定性选样影响,因此Top10与趋势仅代表当前演示样本,不可表述为“CVPR全部论文统计结果”。

### 7.6 关键词、Top10、图谱与趋势统计口径

项目明确区分两层词项,不可将采集脚本派生词表述为官方作者关键词:

- **原始/导入词项**:800条种子数据由采集脚本按标题一元词权重4、标题二元短语权重5、摘要一元词权重1派生,每篇最多8项。本批次共6400项分配、3410个去重词项,详情页保留这些原始记录,便于复查与编辑。
- **核心分析词项**:`core-v1`版本从原始词项与真实标题/摘要生成派生结果:去除`image`、`model`等泛化词汇,按显式规则合并单复数与明确缩写,在原文中匹配专业短语并优先较长匹配,不通过删停用词拼接不相邻词汇。相同词的保存标签与原文次数取最大值,不累加,每篇出现次数上限为20。最终要求全库至少3篇不同论文支持,再执行会议/年份筛选。

当前得到299个核心词,779篇论文至少包含一个核心词;剩余21篇仍计入论文总数与覆盖率分母。299是数据与规则计算的结果,非固定上限;阈值会过滤掉部分罕见或新兴方向,因此保留原始词项用于追溯。首页数量、Top10、图谱、关联论文与趋势共享这套核心词表;图谱仅展示其中最多36个高排名词,不代表全库仅有36个。新增、修改或删除论文后,下次请求会重新计算全库词表;页面需重新打开、刷新或应用筛选读取最新结果,当前无后台推送式实时刷新。

Top10热度计算公式:

覆盖率 = 包含该词的论文数 / 筛选范围内总论文数
强度 = 该词出现次数 / 筛选范围内最大出现次数
热度 = 100 × (0.7 × 覆盖率 + 0.3 × 强度)


图谱从筛选结果的高排名关键词构建节点,同一论文内不同核心词的组合生成边;接口硬上限为40个节点、120条边,首页实际请求36个节点、100条边。图谱在固定高度容器内轻微漫游,选中节点放大并暂停;右侧每页20篇,滚动浏览并通过“加载更多”查看全部关联论文,不再仅截取少量标题。

趋势计算公式:

某会议某年某关键词的普及率
= 当届包含该关键词的已载入论文数 / 当届已载入论文总数


ICCV仅奇数年举办、ECCV仅偶数年举办。非举办年份标记为`held=false`,举办但无数据标记为`missing=true`,两者的普及率均为`null`;仅当确实有样本且无论文包含该词时才为0。

### 7.7 问题记录与解决方案

| 问题现象 | 根本原因 | 最终方案 | 验证方式 |
| --- | --- | --- | --- |
| OpenAlex单一venue/source ID会遗漏部分届次 | 数据源内部来源标识并非稳定的一会一ID | CVF/ECVA官方页面负责演示种子数据,OpenAlex仅用于标题补查 | 来源清单、数量核对、解析与导入测试 |
| 匿名外部API可能出现限流或超时 | 共享配额与网络不稳定性 | 可选API密钥、固定超时、明确错误提示、arXiv备用、本地功能独立 | Mock模拟429/超时/坏JSON;联网仅作补查 |
| 不同届论文量差异导致计数不可比 | 绝对次数受样本分母影响 | 趋势改用覆盖率指标,同时显示分子与分母 | 趋势单元测试与页面数据表 |
| “未举办”容易被绘制成0值 | 缺失状态与数值混同 | `held`、`missing`状态与数值分离 | 覆盖三种状态的专项测试 |
| 图谱过密导致交互卡顿 | 关键词两两组合数量增长快 | 节点/边硬上限、筛选后生成、原生SVG实现 | 上限测试、桌面/移动端人工检查 |
| 重复标题与并发保存冲突 | 标题大小写/空白差异、查询后同时写入 | 规范化唯一约束,写入冲突后回读已有记录 | 仓储与检索路由测试 |
| 3410个原始词项过多且泛词靠前 | 导入标签混有泛词、词形变体与短语片段 | 保留原始字段;新增全库统一核心分析层,当前299个 | 15项核心词策略测试、全量数据与API核验 |
| 筛选响应期间改动下拉会错标会议 | 已提交请求与控件待应用值混用 | 按已提交条件快照渲染标签、榜单与关联论文 | 延迟真实GET的专项浏览器回归 |
| 键盘选中节点后焦点丢失 | 选中时移动SVG节点改变了DOM焦点 | 保持交互节点顺序与焦点,仅更新选中样式;重建时按关键词恢复焦点 | Enter、空格、Tab、Shift+Tab专项回归 |

---

## 八、核心代码逻辑说明

以下代码节选自实际项目源码,共约280行(含注释与空行)。各段依赖所在模块的辅助函数,无法独立拼接运行。CodeArts同步状态以本次操作记录与线上分支为准。

![img](https://i-blog.csdnimg.cn/communtity/4d9cb5a625864d1b844bbb8836cd25eb.png "#left")

### 8.1 本地缓存优先的论文检索机制

来源:`app/routes/retrieval.py`的`_lookup_one`函数。只有本地未命中时才调用外部服务;并发唯一键冲突时回读已有记录。批量接口额外校验去重前1~100条输入并逐条隔离故障,网页批量流程最多并发3个单篇请求。

def _lookup_one(
title: str,
*,
save: bool,
repository: PaperRepository,
client: Any,
) -> dict[str, Any]:
"""执行单篇论文的本地优先检索通用流程。"""

existing = repository.get_by_normalized_title(title)
if existing is not None:
    return {
        "paper": existing,
        "saved": False,
        "duplicate": True,
        "from_cache": True,
        "status": "existing",
        "message": "本地已有相同标题的论文",
    }

external_paper = client.lookup_by_title(title)
saved = False
duplicate = False
if save:
    try:
        paper = repository.create(external_paper)
        saved = True
        status = "created"
    except DuplicatePaperError:
        # 本地预检查后可能有其他请求保存了相同记录,
        # 或OpenAlex对已知ID返回了标题变体。
        paper = repository.get_by_normalized_title(external_paper["title"])
        if (
            paper is None
            and external_paper.get("source")
            and external_paper.get("external_id")
        ):
            paper = repository.get_by_external(
                external_paper["source"], external_paper["external_id"]
            )
        if paper is None:
            raise
        duplicate = True
        status = "existing"

else:
    paper = _preview_paper(external_paper)
    status = "found"

return {
    "paper": paper,
    "saved": saved,
    "duplicate": duplicate,
    "from_cache": False,
    "status": status,
    "message": "论文获取并保存成功" if saved else "论文获取成功",
}

### 8.2 核心关键词生成与Top10排序

来源:`app/keyword_policy.py`。`canonical_keyword`使用显式变体表,`_informative`过滤泛词,`_MATCHERS`来自透明专业短语表与明确别名。标题与摘要分别匹配,重叠位置优先长短语,避免跨句、跨字段或删词后拼接不存在的概念。保存标签与原文计数取最大值,每篇上限20;缓存仅按实际标题、摘要、词项内容复用候选计算,全库支持度仍随请求重新计算。

@lru_cache(maxsize=8192)
def _candidate_counts(title, abstract, raw_terms):
counts = Counter()
for raw in raw_terms:
term = canonical_keyword(raw)
if _informative(term):
# 同一关键词的不同拼写变体统一为一个标签
counts[term] = 1

text_counts = Counter()
for text in (title, abstract):
    source = _normalize_source(text)
    matches = []
    for term, pattern in _MATCHERS:
        matches.extend((match.start(), match.end(), term) for match in pattern.finditer(source))
    end = -1

    for start, stop, term in sorted(matches, key=lambda item: (item[0], -(item[1] - item[0]), item[2])):
        if start < end:
            continue  # 重叠位置优先匹配完整短语

        end = stop
        text_counts[term] += 1

for term, count in text_counts.items():
    counts[term] = max(counts[term], count)
return tuple(sorted((term, min(count, 20)) for term, count in counts.items()))

def prepare_analysis_records(records, min_documents=MIN_DOCUMENT_FREQUENCY):
"""生成共享全库核心词表的派生分析记录。"""

papers = list(records or [])
if min_documents < 1:
    raise ValueError("min_documents必须为正整数")
candidates = []
frequency = Counter()
for record in papers:
    # 原始词项保留用于溯源与编辑
    original = {key: value for key, value in record.items() if key != "_analysis_occurrences"}
    raw = tuple(extract_keyword_occurrences(original, fallback=False))
    counts = dict(_candidate_counts(str(record.get("title") or ""), str(record.get("abstract") or ""), raw))
    candidates.append(counts)
    frequency.update(counts.keys())
vocabulary = {term for term, count in frequency.items() if count >= min_documents}
return [
    {**record, "_analysis_occurrences": [term for term, count in counts.items() if term in vocabulary for _ in range(count)]}
    for record, counts in zip(papers, candidates)
]

调用方先对全库执行`prepare_analysis_records`,再按会议/年份筛选;`_analysis_occurrences`为派生副本字段。原始关键词与不含核心词的论文均保留,不采用旧的“缺失则补12个词”回退方案填充噪声。

来源:`app/analytics.py`的`top_keywords`函数。出现次数与不同论文数分别累计,分母始终为筛选范围内全部论文;公式与排序规则如下:

def top_keywords(
records: Iterable[Mapping[str, Any]], limit: int = 10
) -> list[dict[str, Any]]:
"""基于覆盖率与强度的双重热度排名。

``coverage``为筛选范围内包含该关键词的论文占比。
``intensity``为该词出现次数除以筛选范围内最大出现次数。
最终热度由70%覆盖率与30%强度加权得到,映射至0-100分值。
"""

papers = list(records or [])
if not papers:
    return []
requested_limit = max(0, int(limit))
if requested_limit == 0:
    return []

document_counts: Counter[str] = Counter()
occurrence_counts: Counter[str] = Counter()
paper_ids: defaultdict[str, list[Any]] = defaultdict(list)

for index, record in enumerate(papers):
    occurrences = extract_keyword_occurrences(record)
    occurrence_counts.update(occurrences)
    identifier = _paper_id(record, index)
    for keyword in set(occurrences):
        document_counts[keyword] += 1
        _append_unique(paper_ids[keyword], identifier)

if not document_counts:
    return []
maximum_occurrences = max(occurrence_counts.values(), default=1)
items: list[dict[str, Any]] = []
for keyword, paper_count in document_counts.items():
    coverage = paper_count / len(papers)
    intensity = occurrence_counts[keyword] / maximum_occurrences
    heat = 100 * (0.7 * coverage + 0.3 * intensity)
    items.append(
        {
            "keyword": keyword,
            "paper_count": paper_count,
            "occurrence_count": occurrence_counts[keyword],
            "coverage": round(coverage, 6),
            "intensity": round(intensity, 6),
            "heat": round(heat, 2),
            "paper_ids": _sorted_ids(paper_ids[keyword]),
        }
    )

items.sort(
    key=lambda item: (
        -item["heat"],
        -item["paper_count"],
        -item["occurrence_count"],
        item["keyword"],
    )
)
return items[:requested_limit]

### 8.3 筛选快照:标签、统计与关联论文范围一致

来源:`app/static/js/dashboard.js`。早期版本请求发出后,标签仍读取可继续改动的下拉值,可能出现CVPR数据标注为ICCV的问题。当前版本在提交时、第一次`await`前冻结查询字符串;成功与失败标签均由该快照生成,关联论文也使用`appliedQuery`。下拉新选择仅为待应用草稿,会显示提示;响应版本号防止旧请求覆盖新结果。

const scopeLabel = (query) => {
const params = new URLSearchParams(query);
return [params.get("conference") || "三大顶会", params.get("year_from") || "全部年份"].join(" · ");
};

async function refresh() {
const version = ++refreshVersion;
// 在第一个异步等待前捕获提交状态,包含元数据请求

// 可编辑控件在用户显式提交前均为草稿状态
const requestQuery = queryString();
appliedQuery = requestQuery;
updateFilterDraft();
paused = reducedMotion.matches;
activeKeyword = null;
layout = null;
currentGraph = null;
updateMotion();
resetRelated();
ranking.hidden = true;
graphSvg.toggleAttribute("hidden", true);
graphTable.replaceChildren();
VP.setState(topState, { message: "正在计算热词排名…" });
VP.setState(graphState, { message: "正在构建关系图谱…" });
VP.$("[data-metric-status]").textContent = "读取中";
VP.$("[data-stat-updated]").textContent = "正在更新当前范围";
["papers", "keywords", "editions"].forEach((name) => {
VP.$([data-stat-${name}]).textContent = "—";
});
["score", "paper-count", "years"].forEach((name) => {
VP.$([data-hero-${name}]).textContent = "—";
});
VP.$(".hero-card-keyword").textContent = "正在读取数据";
VP.$("[data-hero-history]").replaceChildren();
VP.$("[data-scope-label]").textContent = "正在更新范围…";
await loadFilters(version);
if (version !== refreshVersion) return;
updateFilterDraft();
const topParams = new URLSearchParams(requestQuery);
topParams.set("limit", "10");
const graphParams = new URLSearchParams(requestQuery);
graphParams.set("limit_nodes", "36");
graphParams.set("limit_edges", "100");
const [top, graph] = await Promise.allSettled([
VP.api(/api/analytics/top-keywords?${topParams}),
VP.api(/api/analytics/graph?${graphParams})
]);
if (version !== refreshVersion) return;
if (top.status === "fulfilled") renderRanking(top.value, requestQuery);
else {
VP.$(".hero-card-keyword").textContent = "统计暂不可用";
VP.$("[data-scope-label]").textContent = scopeLabel(requestQuery);
VP.setState(topState, { type: "error", title: "统计载入失败", message: VP.errorMessage(top.reason), retry: refresh });
}
if (graph.status === "fulfilled") renderGraph(graph.value);
else VP.setState(graphState, { type: "error", title: "图谱载入失败", message: VP.errorMessage(graph.reason), retry: refresh });
const success = top.status === "fulfilled" && graph.status === "fulfilled";
VP.$("[data-metric-status]").textContent = success ? "已同步" : "待重试";
VP.$("[data-stat-updated]").textContent = success ? 最近读取 ${new Intl.DateTimeFormat("zh-CN", { hour: "2-digit", minute: "2-digit" }).format(new Date())} : "部分数据未能读取";
}


### 8.4 图谱漫游、键盘焦点与关联论文分页

来源:`app/static/js/dashboard.js`。布局先执行220次本地迭代,再围绕锚点以有限振幅漫游;`paintGraph`同步更新节点与真实共现边。减少动态效果、图谱离屏、页面隐藏或主动暂停时停止动画。

选中节点仅更新样式、半径与`aria-pressed`状态,不再重新追加交互节点,因此保留键盘焦点与Tab顺序。Enter/空格仍走相同选择流程;窗口变化重建图谱时,按原关键词恢复焦点。节点大小或移动不改变论文数据。

function animateGraph(time) {
frame = 0;
if (paused || reducedMotion.matches || !visible || document.hidden || !layout) return;
if (lastFrame) motionTime += Math.min(time - lastFrame, 48) / 1000;
lastFrame = time;
layout.nodes.forEach((node) => {
node.x = Math.max(64, Math.min(layout.width - 64, node.anchorX + Math.sin(motionTime * .42 + node.phase) * 9));
node.y = Math.max(74, Math.min(layout.height - 70, node.anchorY + Math.cos(motionTime * .34 + node.phase) * 7));
});
paintGraph();
frame = requestAnimationFrame(animateGraph);
}

function highlightSelection() {
const adjacent = new Set();
if (layout) {
layout.links.forEach((edge) => {
const selectedEdge = keywordOf(edge.sourceNode) === activeKeyword || keywordOf(edge.targetNode) === activeKeyword;
edge.element.classList.toggle("is-active", selectedEdge);
edge.element.classList.toggle("is-muted", Boolean(activeKeyword) && !selectedEdge);
if (selectedEdge) {
adjacent.add(keywordOf(edge.sourceNode));
adjacent.add(keywordOf(edge.targetNode));
}
});
layout.nodes.forEach((node) => {
const selected = keywordOf(node) === activeKeyword;
node.element.classList.toggle("is-active", selected);
node.element.classList.toggle("is-muted", Boolean(activeKeyword) && !selected && !adjacent.has(keywordOf(node)));
node.element.setAttribute("aria-pressed", String(selected));
node.circle.setAttribute("r", selected ? node.radius * 1.4 : node.radius);
node.label.setAttribute("y", (selected ? node.radius * 1.4 : node.radius) + 16);
// 保持交互节点原有DOM顺序,重排聚焦节点会丢失焦点并改变Tab导航顺序
});
}
VP.$$(".ranking-item", ranking).forEach((row) => row.classList.toggle("is-active", row.dataset.keyword === activeKeyword));
}

function selectKeyword(keyword) {
activeKeyword = keyword;
paused = true;
updateMotion();
highlightSelection();
}


右侧面板采用独立滚动区域,每页20条,“加载更多”直至全部加载完成;每次选择拥有独立版本号,旧词响应不会污染新词列表。以下为同一文件中的分页实现:

async function showRelated(node) {
const keyword = keywordOf(node), version = ++relatedVersion;
const params = new URLSearchParams(appliedQuery);
params.set("keyword", keyword);
params.set("page_size", "20");
let nextPage = 1, loaded = 0, busy = false;
relatedPanel.innerHTML = `

加载更多 `; const list = relatedPanel.querySelector(".related-list"), status = relatedPanel.querySelector("[data-related-state]"), count = relatedPanel.querySelector("[data-related-count]"), more = relatedPanel.querySelector(".related-more"); async function loadPage() { if (busy || version !== relatedVersion) return; busy = true; more.hidden = true; VP.setState(status, { type: "loading", message: "正在读取关联论文…" }); params.set("page", String(nextPage)); try { const data = await VP.api(`/api/analytics/related-papers?${params}`); if (version !== relatedVersion) return; data.items.forEach((paper) => { const li = document.createElement("li"), link = document.createElement("a"), meta = document.createElement("small"); link.href = VP.paperHref(paper.id); link.textContent = paper.title; meta.textContent = `${paper.conference || "未标注"} · ${paper.year || "年份未知"}`; li.append(link, meta); list.append(li); }); loaded += data.items.length; count.textContent = `覆盖 ${data.total} 篇 · 已显示 ${loaded} 篇`; status.hidden = true; if (!loaded) VP.setState(status, { type: "empty", message: "当前范围没有关联论文。" }); if (data.page < data.pages) { nextPage = data.page + 1; more.hidden = false; more.textContent = `加载更多(还有 ${data.total - loaded} 篇)`; } } catch (error) { if (version !== relatedVersion) return; count.textContent = loaded ? `已显示 ${loaded} 篇,后续载入失败` : "关联论文载入失败"; VP.setState(status, { type: "error", message: VP.errorMessage(error), retry: loadPage }); } finally { busy = false; } } more.addEventListener("click", loadPage); await loadPage(); } ```

8.5 趋势状态与时间轴动画

来源:app/static/js/trends.js。后端将未举办与缺数据标记为null,前端validPoint仅绘制有效覆盖率,不跨缺失年份连线;右侧卡片显示该年命中数/样本数。动画仅移动年份标记与切换真实统计,不插值生成虚拟论文数。以下代码使用requestAnimationFrame实现650ms标记缓动,采用单个超时链按年播放。

function animateMarker(target) {
  window.cancelAnimationFrame(animationFrame);
  if (motionPreference.matches || markerIndex === target) {
    markerIndex = target;
    drawChart();
    return;
  }
  const from = markerIndex;
  const started = performance.now();
  function frame(now) {
    const fraction = Math.min(1, (now - started) / 650);
    const eased = 1 - (1 - fraction) ** 3;
    markerIndex = from + (target - from) * eased;
    drawChart();
    animationFrame = fraction < 1 ? window.requestAnimationFrame(frame) : null;
  }
  animationFrame = window.requestAnimationFrame(frame);
}

function play() {
  if (!trendData || !hasSamples || workspace.hidden || trendData.years.length < 2) return;
  if (playing) {
    stop();
    return;
  }
  if (currentIndex >= trendData.years.length - 1) renderRace(0);
  playing = true;
  playButton.setAttribute("aria-pressed", "true");
  playButton.textContent = "Ⅱ 暂停";
  playbackStatus.textContent = `正在播放 · ${trendData.years[currentIndex]} 年`;
  function nextYear() {
    if (!playing) return;
    if (currentIndex >= trendData.years.length - 1) {
      stop();
      return;
    }
    renderRace(currentIndex + 1, true);
    timer = window.setTimeout(nextYear, 1800);
  }
  timer = window.setTimeout(nextYear, 1300);
}

stop()函数会清理定时器与动画帧,并更新播放状态;暂停、重播、滑块、键盘切年、页面隐藏与切换条件均复用该停止逻辑。减少动态效果偏好下不执行标记缓动,数据表仍可读取每年精确值。


九、测试验证与云端部署

9.1 测试体系

测试分为四个层级:

  • 纯函数测试:核心词规范化、短语匹配、全库支持阈值、热度公式、图谱生成、趋势状态;
  • 仓储测试:表结构、CRUD操作、唯一约束、分页、精确与模糊查询、非法JSON处理;
  • 路由与集成测试:所有页面、统一响应格式、外部请求Mock、跨模块读写;
  • HTTP冒烟测试:对运行中站点执行健康检查、首页、论文页、Top10、图谱、趋势六项只读请求。
功能模块正常场景边界/失败场景对应测试文件
单篇/批量获取外部字段映射、保存、预览、去重超时、429、坏JSON、无结果、批内单条失败tests/test_retrieval.py
CRUD与查询新增、读取、更新、删除、分页空标题、重复、非法URL、无效页码、404tests/test_repository.py
Top10与核心词筛选、排序、公式字段、核心归一空数据、上限、反向年份、全库阈值、修改后重算、原始字段保留tests/test_analytics.py、tests/test_keyword_policy.py
图谱节点、边、论文关联去重边、节点/边硬上限tests/test_analytics.py
趋势三大会跨年覆盖率未举办、缺数据、真实零值tests/test_analytics.py、tests/test_integration.py
数据工具CVF/ECVA解析、导入HTML清洗、重复、详情错误tests/test_data_tools.py

2026-09-23,为博客补充结果图时,使用与Git Tag v1.0.0一致的应用、脚本、测试与依赖清单,在本地Windows/Python 3.13.9环境重新执行自动化测试,95项全部通过,失败0项。随后对公网站点执行只读HTTP冒烟测试,6项全部通过,失败0项,并核对版本接口返回v1.0.0与对应提交哈希。

项目还保存了51项浏览器交互验收记录,覆盖图谱漫游、选择、分页、趋势、窄屏、筛选快照与键盘焦点;正常路径使用真实页面与请求,部分异常分支使用标注用途的模拟响应。

9.2 本地运行方式

python -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install -r requirements.txt
python scripts/import_seed.py
python wsgi.py

启动后,在本地浏览器打开[http://127.0.0.1:5000](http://127.0.0.1:5000)即可访问。种子导入会按规范化标题跳过重复记录。该地址仅用于本地复现;在线体验请使用公网地址。可选环境配置见仓库.env.example与运行说明。

9.3 华为云部署

项目已在华为云Flexus L实例部署正式v1.0.0版本。实例位于华北-北京四,配置为2核CPU、2GiB内存、2Mbps带宽、40GiB系统盘、100GB/月流量包,操作系统为Ubuntu 24.04.4 LTS。

目前服务器内运行与访问保护检查全部通过;2026-09-23从本地对公网地址进行验证,6项冒烟测试与21项页面及写限制检查全部通过。公开访问链路为:

浏览器 → Nginx → 127.0.0.1:8000 → Gunicorn → Flask → SQLite

仓库提供deploy/nginx.conf、deploy/visionpulse.service、Dockerfile与docker-compose.yml。默认采用Nginx+Gunicorn+systemd运行,Gunicorn使用1个worker、4个线程;SQLite数据库保存在独立持久化目录,避免更新代码时覆盖数据。具体操作见华为云部署操作手册。

采用公开浏览、仅管理员管理的访问模式。Nginx拒绝公网非GET/HEAD请求并限制请求频率;新增、修改、删除及联网导入操作通过SSH加密连接进入服务器回环端口执行,前端同步显示只读提示。8000端口不对外公开,应用以非root账号运行,环境文件与私钥均不纳入仓库。

正式版本在服务器实际通过95项Python测试、29项页面/访问控制/管理CRUD检查与6项冒烟测试。管理检查仅创建、修改并删除一条UUID测试记录,原有800篇论文完整保留。公网6项冒烟测试与21项页面及写限制检查全部通过,五个公开页面均返回200并启用只读模式,受检POST/PATCH/PUT/DELETE请求及伪造只读头的写请求均返回403。代码更新前已做SQLite一致性备份,服务重启后核对运行版本与Git提交一致。

部署项实际情况
云服务器与系统华北-北京四,Flexus L,2核2GiB,Ubuntu 24.04.4 LTS
公网访问地址http://公网IP/,公开浏览已验证
当前运行版本v1.0.0,对应提交哈希与main分支及v1.0.0标签一致
公网验证2026-09-23,6/6冒烟测试、21/21公开页面与写限制检查通过,800篇论文;原始日志留存

9.4 已知局限

当前种子数据为每届100篇的非随机样本,不代表会议全量论文;核心词表采用可解释规则,至少3篇论文支持的阈值可能排除低频方向。SQLite适配当前单机演示场景,外部论文API受网络与配额影响,批量查询建议拆分为小批次。

数据变化后需要刷新页面、应用筛选或重新请求趋势,非后台实时推送。系统区分“会议未举办”“无载入数据”与真实零覆盖率三种状态,避免不同情况混淆。


十、Git与CodeArts工程实践

10.1 分支与提交管理

项目采用CodeArts托管源码,执行dev开发、main稳定发布的分支策略。dev分支成果通过合并请求合入main;当前默认分支为main,发布核对时该分支包含29条提交记录,dev分支包含28条。

仓库提供README.md、codestyle.md、依赖清单、测试用例与部署配置。.gitignore排除虚拟环境、运行数据库、日志与真实环境配置,避免本地运行数据或密钥混入源码。代码入口为main分支。

以下列出v1.0.0包含的部分提交及对应产出:

提交哈希前缀提交内容对应产出
f61df6d扩充八届会议至800篇并保留原始样本每届100篇,来源可追溯
c038c90统一核心词表并完成图谱与趋势交互核心关键词、图谱与年度分析共用统计口径
f7f7e29新增数据审计与浏览器交互回归工具数据核验、筛选与键盘交互检查
86b13e9归档数据界面与交互验收证据产品页面与交互结果材料
a4f78ad完成云端只读部署并同步作业材料公开访问保护、部署工具与交付说明
7ea5bf1合并dev到main分支将交付代码纳入发布分支

需求取舍由本人负责,例如样本扩充、关键词精简与图谱交互调整;AI主要负责对应代码、测试与文档实现。提交内容与第六章协作案例共同说明人机分工。

10.2 合并请求交付

在CodeArts创建并合并了MR !1:合并平台功能与作业材料。源分支为dev,目标分支为main;合并不采用压缩合并,保留dev分支用于后续维护。

合并请求集中列出功能、文件变更与验证结果,便于核对提交范围;页面中的“已合并”状态确认操作完成。

10.3 版本标签与部署对应

基于main分支创建Git Tag v1.0.0,用于固定本次交付的源码版本。

版本标签对应提交哈希,服务器版本接口返回同一提交与版本号v1.0.0,实现仓库源码与在线成品的对应核验。后续维护通过追加提交与新版本标签进行。


十一、开发心路与实践收获

11.1 数据规模不等于信息价值

最初认为120篇论文样本量不足,要求扩充至800篇;随后发现3410个词项过于冗余,要求精简为更具参考价值的核心方向。最终采用保留原始数据、新增299个核心词分析层的方案。这一过程让我意识到,“数据量更大”不代表“结论更易理解”,样本规模与信息噪声需要分别管控。

11.2 具象反馈提升协作效率

没有笼统提出“图谱效果不好”,而是具体指出点击后容器高度突变、论文展示不全等现象,并明确提出节点漫游、选中放大暂停、右侧列表独立滚动的预期效果。这样AI可以将反馈精准对应到状态与布局层面。本人主导需求定义与决策,AI负责落地实现,两者权责清晰。

11.3 需求、结对与过程管理

结合《构建之法》第三章个人能力相关主题,清晰定义问题、管控范围、承担交付责任同等重要。结合第八章需求分析主题,样本扩充与关键词精简体现了从需求到方案再到价值的转化逻辑;结合第四章结对编程思想,本人更多承担方向判断与偏差修正,AI更多承担执行落地。这是方法论层面的类比,而非将人机协作等同于两名同学共同手写代码。

计划也需要主动裁剪:当AI将额外体验评审列为待办项时,本人明确取消,避免将优化建议当作硬性要求。本次按4天、每天约8小时预估总投入1920分钟,实际总投入1200分钟,各阶段按工作内容回顾分配。这也提醒后续项目要同时记录工期预估与实际投入两类指标,不以工具运行时长或版本记录替代人工工时。


十二、AI 结对工具的综合评估

img

12.1 核心应用价值

AI 结对工具可快速完成任务拆解与技术选型落地,快速搭建基于 Flask 框架与 SQLite 存储的应用基础架构,同步覆盖数据清洗处理、关键词关系图谱生成、年度热度趋势动画、自动化测试用例及项目文档撰写等全链路工作。
其最显著的效率提升体现在:当提出样本扩容或关键词体系优化这类需求时,AI 可同步联动调整数据采集逻辑、热度统计算法、前端展示逻辑及对应测试用例,而非仅修改单一页面的展示数值,减少了多模块同步修改的重复工作量。

12.2 输出局限与人工把关责任

AI 生成的初始页面效果、数据粒度与方案规划,未必完全匹配实际开发预期;部分规划建议也可能将可选优化项表述为硬性要求,容易造成范围膨胀。
开发者需要提供明确的效果参照与交互预期,逐项校验输出是否匹配实际需求。自动化测试全部通过不代表代码合并或课程提交自动完成;PSP 统计口径、文稿表述是否贴合本意、云服务账号配置与部署操作的最终结果,都需要开发者人工最终确认与把关。

12.3 与传统人人结对模式的差异

两种协作模式都遵循分工协作、交叉检查、持续沟通的基本原则,但人机结对模式中,AI 无法像真人搭档那样独立承担完整的作业责任。
开发者的核心优势在于掌握课程背景要求、做需求取舍与方案决策;AI 的优势在于快速输出多版方案、高效处理重复性实现工作。高效的人机协作需要清晰界定「AI 建议」「人工决策」「最终结果」三层边界,不能直接将 AI 生成内容全部当作已验证的事实使用。

12.4 后续协作优化方向

后续项目实践中,会先明确数据范围、统计口径与核心交互规则,再分小步迭代落地;每轮反馈同时附带现象描述与预期目标,同步留存修改过程与验证依据。
项目材料随功能迭代同步更新,开发时长及时登记。对于 AI 生成的代码模块,需做到能独立解释关键词筛选规则、热度统计算法,以及数据变动后需要同步刷新的界面范围。


十三、参考资料列表

### 参考资料

(1)软件工程方法论

[1] 邹欣。构建之法 [M]. 书中第 3 章需求分析模型、第 4 章结对编程协作模式、第 8 章原型设计与迭代方法,为本次作业的需求拆解、人机结对流程与原型设计提供了完整的方法论框架。
[2] 邹欣。工程师的能力评估和发展 [EB/OL]. 用于梳理个人软件过程管理逻辑,区分预估工时与实际工作记录的统计边界,建立 PSP 耗时统计的基本认知。
[3] 邹欣。源代码管理 [EB/OL]. 用于理解版本控制、分支协作与可复现交付的核心逻辑,明确提交记录与开发质量的对应关系。

(2)后端与数据库技术文档

[4] Flask 官方开发文档 [EB/OL]. https://flask.palletsprojects.com/
涵盖应用工厂、蓝图路由、请求响应处理、测试客户端等全模块 API 说明,是后端框架选型与功能开发的核心参考依据。

[5] Python 标准库 sqlite3 官方文档 [EB/OL]. https://docs.python.org/3/library/sqlite3.html
详细说明嵌入式数据库的连接管理、表结构操作、事务控制与异常处理机制,为本地数据持久化层实现提供了完整的接口参考。

(3)学术数据来源与接口规范

[6] CVF Open Access 开放论文平台 [EB/OL]. https://openaccess.thecvf.com/
计算机视觉基金会官方开放获取论文站点,提供 CVPR、ICCV 等会议的公开论文元数据,是本次项目种子数据的核心来源之一。

[7] 欧洲计算机视觉协会官网 [EB/OL]. https://www.ecva.net/
ECCV 系列会议的官方主办机构站点,提供各届会议论文入口与相关学术信息,为 ECCV 样本数据采集提供了官方来源。

[8] OpenAlex API 官方文档 [EB/OL]. https://docs.openalex.org/
开放学术文献索引平台的接口说明,涵盖检索语法、分页机制与返回字段规范,用于实现按标题补查论文信息的联网检索功能。

[9] arXiv API 用户使用手册 [EB/OL]. https://info.arxiv.org/help/api/user-manual.html
预印本平台的程序访问接口规范,作为联网检索的备用数据源,在主源限流或不可用时提供补充支持。

(4)编码与风格规范

[10] PEP 8 Python 代码风格指南 [EB/OL]. https://peps.python.org/pep-0008/
Python 语言官方编码规范,涵盖缩进、命名、导入、注释等全维度风格要求,是后端代码编写的统一规范依据。

[11] Google Python 风格指南 [EB/OL]. https://google.github.io/styleguide/pyguide.html
工业界通用 Python 开发规范,在 PEP8 基础上补充了工程实践层面的代码组织、异常处理、类型注解等最佳实践。

[12] Airbnb JavaScript 风格指南 [EB/OL]. https://github.com/airbnb/javascript
前端原生 JS 代码的统一编写规范,涵盖变量声明、函数写法、对象数组操作等常用场景的风格约定。

[13] Google HTML/CSS 风格指南 [EB/OL]. https://google.github.io/styleguide/htmlcssguide.html
页面结构与样式编写的通用规范,用于指导前端页面的语义化结构与样式组织。

...全文
133 回复 打赏 收藏 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

101

社区成员

发帖
与我相关
我的任务
社区描述
202601福大-软件工程实践-W班
软件工程 高校 福建省·福州市
社区管理员
  • 202601福大-软件工程实践-W班
  • 李之尹
  • 123
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧