feat(frontend+api): 因子清单加批次分割线——台账反查出生批次分组显示 [vps]
用户复验追加小需求:21 条因子平铺看不出批次归属。 - 后端:hypotheses_list 每条 factor 附 batch(台账 registered name→job 映射,新→旧先见者胜;查不到=null 归「早期」); 清单排序改按批次新→旧,无批次垫底 - 前端:批次切换处插虚线行「── 第 MM-DD HH:MM 批 · job-… ──」, 无批次段前插「── 早期(无批次记录) ──」 测试:factors[0].batch.jobId 断言;后端 28 绿+前端 76 绿+构建过
This commit is contained in:
@@ -123,3 +123,4 @@ spec 逐节拍板 → writing-plans 出实施计划 → 分域派单:②权重
|
||||
- 2026-10-09:LLM 链两连修上产(端点错配 85d14a1 + client 全流式化 acc273b,QA 同款);**生产首批 origin:decomposer 因子落册**(高管增持卡,D7 零实绩破局);终局诊断=响应孤儿化(服务端成功+浏览器断连无感知),治本=**分解 job 化**(QA 任务字典范式:202 立返+后台任务+卡片 decomposeJob 轮询+decompose_jobs 台账表)——§7 件④地基落,总设计 §13 P5.3 同步;生产全程验证通过(job-20261009101513:202 秒回→3 轮 2m43s→8 因子 0 失败);件①扩为三件套(产物清单+批次历史+轮次进度,QA 调研:progress 字段结构/tasks-list 端点照抄,currentRound 摆设我们用真轮数,WS 直播归 D2)
|
||||
- 2026-10-09 晚:**D7 真实现落地**(8823 体验稿逐条过审拍板后动工):件①1a=列表项内联 `factors` 清单(registry hypothesis 血统反查:名/表达式/origin/落成日;卡片单因子名 factorId 前端退役);1b=新端点 `GET /pipeline/hypotheses/{id}/decompose-jobs`(台账新→旧,QA tasks/list 形状)+卡片折叠区按需拉取;1c=`run_decompose` 每轮 `on_progress` 回调→内存 job `progress`(currentRound/totalRounds/passed/regen/message;台账仍只记起跑+终态),worker 协议改收 job_id;件②=factors 端点带 origin/hypothesis+工厂「来源」列(⚡decomposer/✍manual 徽标+回链假设池);件③=todos 聚合加 queued/data_check 卡琥珀待办(分解转 building 即消行,人工卡点②显性化);件④=前端一次性结果弹层退役,结果持久落卡。测试=后端 TestD7Visibility 六件+前端三件套三测
|
||||
- 2026-10-09 深夜:**D7 VPS 验收反馈四条处理**(877723a 首班上产后用户实测):①因子详情页落地——`GET /pipeline/factors/{name}/detail`(出生档案:origin/来源假设/出生批次=台账含该名最新 job/表达式/落成日)+前端 `FactorDetail.vue`(`/pipeline/factors/:name` 路由),卡片因子点名与工厂行点击均进详情;②总览无变化=数据驱动非缺陷(唯一卡已 building,无 queued 卡时琥珀项不出现——落新卡即现;地铁图属 D1-D6);③工厂来源列改 min-width 200+徽标/回链竖排;④轮次进度起跑即报「第 1/3 轮 · 生成候选中…」(初代生成期不带轮次字样的体验缺陷)
|
||||
- 2026-10-09 深夜补:件①1a 因子清单加**批次分割线**(用户复验追加)——每条因子附出生批次(台账 registered 反查 name→job 映射,查不到=null 归「早期」),清单按批次新→旧分组,批间虚线行「── 第 MM-DD HH:MM 批 · job-… ──」
|
||||
|
||||
@@ -34,6 +34,7 @@ export interface HypothesisFactor {
|
||||
origin: string
|
||||
createdAt: string | null
|
||||
status: string
|
||||
batch?: { jobId: string; startedAt: string } | null // 出生批次(台账反查,null=早期/无记录)
|
||||
}
|
||||
|
||||
export type FactorStatus = 'incubating' | 'assessable' | 'promoted' | 'decaying' | 'retired'
|
||||
|
||||
@@ -250,12 +250,22 @@ function progText(h: Hypothesis): string {
|
||||
<span class="hint">点名进工厂 · 血统=registry 反查</span>
|
||||
</div>
|
||||
<div v-if="openFac[h.id]" class="sec-body">
|
||||
<div v-for="f in h.factors" :key="f.name" class="fac-row">
|
||||
<span class="fac-name" title="看出生档案"
|
||||
@click="router.push(`/pipeline/factors/${f.name}`)">{{ f.name }}</span>
|
||||
<code class="fac-expr">{{ f.expression }}</code>
|
||||
<span class="fac-date">{{ f.createdAt }}</span>
|
||||
</div>
|
||||
<template v-for="(f, i) in h.factors" :key="f.name">
|
||||
<!-- 批次分割线:批次切换处插行;首条无批次而清单非空=早期段 -->
|
||||
<div v-if="f.batch && (i === 0 || h.factors[i - 1].batch?.jobId !== f.batch.jobId)"
|
||||
class="fac-batch">
|
||||
── 第 {{ f.batch.startedAt.slice(5, 16).replace('T', ' ') }} 批 · {{ f.batch.jobId }} ──
|
||||
</div>
|
||||
<div v-else-if="!f.batch && (i === 0 || h.factors[i - 1].batch)" class="fac-batch early">
|
||||
── 早期(无批次记录) ──
|
||||
</div>
|
||||
<div class="fac-row">
|
||||
<span class="fac-name" title="看出生档案"
|
||||
@click="router.push(`/pipeline/factors/${f.name}`)">{{ f.name }}</span>
|
||||
<code class="fac-expr">{{ f.expression }}</code>
|
||||
<span class="fac-date">{{ f.createdAt }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 件①1b:分解批次历史(decompose_jobs 台账,按需拉取) -->
|
||||
@@ -359,6 +369,9 @@ function progText(h: Hypothesis): string {
|
||||
.fac-name:hover { text-decoration: underline; }
|
||||
.fac-expr { flex: 1; min-width: 0; font-family: var(--mono); font-size: 10px; color: var(--text-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.fac-date { color: var(--text-3); font-size: 10px; white-space: nowrap; }
|
||||
.fac-batch { font-family: var(--mono); font-size: 9.5px; color: var(--cyan-dim); border-top: 1px dashed rgba(0, 184, 204, 0.35); margin-top: 4px; padding-top: 3px; }
|
||||
.fac-batch:first-child { border-top: none; margin-top: 0; padding-top: 0; }
|
||||
.fac-batch.early { color: var(--text-3); border-top-color: var(--border); }
|
||||
.batch { border: 1px solid var(--border); border-radius: var(--r-sm); margin-bottom: 5px; }
|
||||
.batch-h { display: flex; align-items: center; gap: 7px; padding: 5px 8px; cursor: pointer; font-size: 11px; flex-wrap: wrap; }
|
||||
.batch-h:hover { background: var(--cyan-soft); }
|
||||
|
||||
@@ -955,9 +955,22 @@ def hypotheses_list() -> dict:
|
||||
"status": e.get("status")})
|
||||
except Exception: # noqa: BLE001 registry 读失败不拖垮卡片列表
|
||||
logger.warning("hypotheses factors 反查失败,如实空清单", exc_info=True)
|
||||
by_hyp = {}
|
||||
# 批次归属(验收追加:清单加批次分割线)——台账新→旧先见者胜,
|
||||
# 每条因子附 birth batch;台账查不到(超 limit/远古)=None 归「早期」
|
||||
batch_of: dict[tuple[str, str], dict[str, Any]] = {}
|
||||
for hyp_id in by_hyp:
|
||||
for job in pipeline_store.list_decompose_jobs(_pipeline_db(), hyp_id):
|
||||
for c in job.get("registered") or []:
|
||||
batch_of.setdefault((hyp_id, c["name"]),
|
||||
{"jobId": job["jobId"],
|
||||
"startedAt": job["startedAt"]})
|
||||
for it in items:
|
||||
fs = by_hyp.get(it["id"]) or []
|
||||
fs.sort(key=lambda f: f["createdAt"] or "", reverse=True)
|
||||
for f in fs:
|
||||
f["batch"] = batch_of.get((it["id"], f["name"]))
|
||||
fs.sort(key=lambda f: (f["batch"] or {}).get("startedAt") or "", # noqa: E501 批次新→旧,无批次垫底
|
||||
reverse=True)
|
||||
it["factors"] = fs
|
||||
return {"items": items}
|
||||
|
||||
|
||||
@@ -382,13 +382,16 @@ class TestD7Visibility:
|
||||
_, queue = fake_llm
|
||||
queue.append(DECOMP_GOOD)
|
||||
hyp = _seed_card(client)
|
||||
assert _wait_job(client, hyp)["status"] == "completed"
|
||||
job = _wait_job(client, hyp)
|
||||
assert job["status"] == "completed"
|
||||
items = client.get("/api/v1/pipeline/hypotheses").json()["items"]
|
||||
item = next(i for i in items if i["id"] == hyp)
|
||||
assert [f["name"] for f in item["factors"]] == ["llm_mom20"]
|
||||
assert item["factors"][0]["origin"] == "decomposer"
|
||||
assert item["factors"][0]["expression"].startswith("cs_rank")
|
||||
assert item["factors"][0]["createdAt"] # 落成日=首版 effective_from
|
||||
# 批次归属(分割线数据源):registered 含该名的最新 job
|
||||
assert item["factors"][0]["batch"]["jobId"] == job["jobId"]
|
||||
|
||||
def test_progress_reports_rounds_while_running(self, client, fake_llm,
|
||||
tmp_path, monkeypatch):
|
||||
|
||||
Reference in New Issue
Block a user