Compare commits

..
Author SHA1 Message Date
saint 0cdde7e8a7 docs: keep material and experiment drafts evidence-bound 2026-10-22 09:32:49 +08:00
yxxandClaude Code 6268824581 docs: add test question set and scoring rubric, finalize review rules
- add test-question-set.md (24 questions across 4 categories)
- add scoring-rubric.md (A/B/C comparison dimensions and scale)
- finalize review-rules.md (draft -> confirmed)
- update TASKS.md: mark T04 and T09 done

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-22 09:30:04 +08:00
yxxandClaude Code 7d5244cc34 docs: keep K-0015/K-0016 as distinct (content not fully identical)
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-22 08:52:57 +08:00
yxxandClaude Code 574d3c91f7 docs: resolve outstanding ledger notes (category, duplicate, source)
- K-0014/K-0017: confirm category as 旅游资源 (tourism theme)
- K-0015/K-0016: note as same-title intro, prefer 2026-06 K-0015 at import
- K-0033: note publisher/date, gov original not separately indexed

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-22 08:49:55 +08:00
yxxandClaude Code 50959efa79 docs: reject K-0006 (event-period traffic), verify K-0043 (regular traffic)
- K-0006: 2025 music festival traffic guide is time-sensitive, reject instead of pending
- K-0043: Nalati scenic regular self-driving/traffic info, verified after human review

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-22 00:09:49 +08:00
yxxandClaude Code 1296a25c16 docs: add general transport entry K-0043 to replace activity-period K-0006
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-22 00:02:32 +08:00
yxxandClaude Code 948466f516 merge dev: sync source-selection and reconcile ledger clarifications
- bring in source-selection feature and clarify commits from dev
- K-0006: correct to 2025 music-festival traffic guide (activity-period,
  time-sensitive), keep pending, not a general transport guide
- K-0008: rename to "风光名胜" directory page
- keep human-verified statuses for remaining pilot entries

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-21 23:59:10 +08:00
yxxandClaude Code 713e4a06b2 docs: replace expired/off-topic sources, finalize phase-2 ledger
- reject K-0019 (Baidu Baike, expired), K-0022 (Xinhua, expired), K-0025 (aid report, off-topic)
- add K-0040 (red culture), K-0041 (tourism), K-0042 (homestay) as replacements
- verify K-0038~K-0042; all four categories now meet targets (8/10/7/6)

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-21 23:37:05 +08:00
yxxandClaude Code 76b690370f docs: add Xinyuan bus station transport entry (K-0039)
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-21 23:09:40 +08:00
yxxandClaude Code 6322ae3782 docs: drop out-of-scope Dulku road entry (K-0028), annotate K-0027
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-21 23:05:41 +08:00
yxxandClaude Code 43fca2e34d docs: add 71st regiment red-culture entry (K-0038)
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-21 21:28:31 +08:00
yxxandClaude Code afabee9bb4 docs: mark verified entries after human review, reject broken links
- K-0003 to K-0037 (except K-0031, K-0036): verified (reviewer 杨星萱, 2026-10-21)
- K-0031, K-0036: rejected (link unreachable)
- K-0001, K-0002, K-0008 to K-0010: keep pending

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-21 21:26:02 +08:00
yxxandClaude Code e93adfb5af docs: register more red-culture, homestay and transport sources (K-0030 to K-0037)
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-21 21:14:00 +08:00
yxxandClaude Code 5543b32ca8 docs: register red-culture, homestay and transport sources (K-0020 to K-0029)
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-21 21:00:46 +08:00
yxxandClaude Code b4fcaaeafc docs: register phase 2 material articles (K-0011 to K-0019)
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-21 20:54:36 +08:00
saint 7eac019dd2 docs: clarify phase 2 material evidence boundaries 2026-10-20 14:40:07 +08:00
yxxandClaude Code 3ad879309e docs: register phase 2 material source directories
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-20 14:40:07 +08:00
yxxandClaude Code deb21b9044 docs: register phase 2 material pilot entries
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-20 14:40:07 +08:00
Kronecker 4654f2e486 fix: respect document-bound knowledge sources 2026-10-20 14:40:07 +08:00
yxxandClaude Code 0673932c45 docs: register phase 2 material source directories
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-19 23:45:02 +08:00
yxxandClaude Code cbddceb0c6 docs: register phase 2 material pilot entries
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-19 23:28:45 +08:00
saint 1430d35196 docs: record knowledge traceability decisions
Reviewed documentation-only decision record.
2026-10-19 22:46:54 +08:00
saint 7cd4dd2f38 docs: record knowledge traceability decisions 2026-10-19 22:46:22 +08:00
saint 8cb60cc53a feat: support knowledge source selection
Reviewed frontend source selection and display.
2026-10-19 22:44:50 +08:00
saint 88b1952107 feat: support knowledge source selection 2026-10-19 22:44:02 +08:00
saint a1b3147f09 merge: knowledge collection review batch
Reviewed and verified knowledge collection batch.
2026-10-19 22:26:52 +08:00
saint 965613319b docs: clarify stage 2 collection acceptance 2026-10-19 22:15:22 +08:00
saint 0e1642eeab merge: accept stage 2 knowledge collection plan 2026-10-19 22:10:05 +08:00
saint cb1b64d5b0 merge: accept knowledge registration documentation 2026-10-19 22:10:05 +08:00
saint dfef3bc471 merge: accept knowledge collection timestamp persistence fix 2026-10-19 22:10:05 +08:00
yxxandClaude Code 009997e479 docs: add stage 2 knowledge collection plan
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-18 22:03:22 +08:00
yxxandClaude Code 01d006d55f docs: add material traceability field feedback
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-17 21:32:09 +08:00
yxxandClaude Code 60082532a9 docs: register phase 1 test entry and add update-time field
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-17 21:32:09 +08:00
Kronecker aad79aea5f fix: persist cleared knowledge collection timestamps 2026-10-17 15:31:03 +08:00
saint fb51081750 Merge pull request 'fix: 统一检索式聊天输入校验并补齐后端验收' (#48) from kronecker/fix/backend-chat-validation into dev 2026-10-17 14:29:12 +08:00
Kronecker 4efcf83e4c fix: validate retrieval-only chat questions before dispatch 2026-10-17 12:17:52 +08:00
saint 1398938437 Merge pull request 'style: 统一注册页与登录页视觉布局' (#47) from feat/register-layout-clean into dev 2026-10-17 11:29:16 +08:00
25 changed files with 816 additions and 40 deletions
+5 -3
View File
@@ -1,6 +1,6 @@
# 那拉提文旅智能问答 # 那拉提文旅智能问答
Vue 3 + TypeScript + Vite 前端;FastAPI + Pydantic + SQLAlchemy 后端;PostgreSQL + pgvector。当前后端提供知识管理、来源、文档、人工审核、关键词/向量/RRF 检索、文本与向量发布、历史记录及认证/角色权限,问答仍为占位接口。 Vue 3 + TypeScript + Vite 前端;FastAPI + Pydantic + SQLAlchemy 后端;PostgreSQL + pgvector。当前后端提供知识管理、来源、文档、人工审核、关键词/向量/RRF 检索、文本与向量发布、历史记录及认证/角色权限,`/api/chat` 提供条目级关键词证据检索,不生成模型答案。
## 启动数据库 ## 启动数据库
需要可用的 Docker Engine(Windows 可使用 Docker Desktop),在项目根目录运行: 需要可用的 Docker Engine(Windows 可使用 Docker Desktop),在项目根目录运行:
@@ -48,7 +48,7 @@ Vite 通常使用 http://localhost:5173。
- 注册/登录/退出、当前会话、资料修改、改密与管理员用户列表,详见 [认证接口契约](docs/AUTH_API.md) - 注册/登录/退出、当前会话、资料修改、改密与管理员用户列表,详见 [认证接口契约](docs/AUTH_API.md)
- GET `/api/health` - GET `/api/health`
- GET `/api/ready`:检查数据库连接 - GET `/api/ready`:检查数据库连接
- POST `/api/chat`:占位接口,尚未接入检索和 LLM - POST `/api/chat`:已核验条目关键词检索,问题1–500字符;返回 retrieval_only/no_evidence 和来源,详见 [CHAT_API.md](docs/CHAT_API.md)
- GET `/api/knowledge` - GET `/api/knowledge`
- POST `/api/knowledge` - POST `/api/knowledge`
- GET `/api/knowledge/page`、GET/PATCH/DELETE `/api/knowledge/{id}` - GET `/api/knowledge/page`、GET/PATCH/DELETE `/api/knowledge/{id}`
@@ -68,6 +68,8 @@ Vite 通常使用 http://localhost:5173。
- 来源分页支持 `q`;文档分页支持 `q`、`source_id`、`category`;知识分页支持 `q`、`category`、`verification_status`、`source_id`、`document_id`。`q` 为字面文本子串搜索,尚非论文中的关键词检索基线。 - 来源分页支持 `q`;文档分页支持 `q`、`source_id`、`category`;知识分页支持 `q`、`category`、`verification_status`、`source_id`、`document_id`。`q` 为字面文本子串搜索,尚非论文中的关键词检索基线。
- 创建知识沿用 `title/category/content/source_url`,新增可选 `source_id/document_id/collected_at`;状态固定为 `pending`。关联文档时自动解析其来源,填写的来源或网址若冲突会报错。直接来源链接的旧调用仍受支持。 - 创建知识沿用 `title/category/content/source_url`,新增可选 `source_id/document_id/collected_at`;状态固定为 `pending`。关联文档时自动解析其来源,填写的来源或网址若冲突会报错。直接来源链接的旧调用仍受支持。
- 来源创建字段:`name`、可选 `source_url`、`notes`。文档创建字段:`source_id/title/content/collected_at`,可选 `category/notes`。文档来源归属固定,换来源需新建文档。 - 来源创建字段:`name`、可选 `source_url`、`notes`。文档创建字段:`source_id/title/content/collected_at`,可选 `category/notes`。文档来源归属固定,换来源需新建文档。
- 知识修改时省略 `collected_at` 表示保留原值,明确传 `null` 表示清空;清空后回到待审核,补全真实采集时间前不能核验通过。文档的采集时间仍是必填字段,不接受 `null`。
- 知识管理页可为未关联文档的条目选择或解除登记来源;关联文档的条目沿用文档所属来源,来源选择和网址只读,保存其他字段时保留关联。来源选择与边界验收见 [SOURCE_SELECTION_ACCEPTANCE.md](docs/SOURCE_SELECTION_ACCEPTANCE.md)。
- PATCH 请求必须携带读取时获得的 `expected_version`,并至少提交一个修改字段;DELETE 使用查询参数 `expected_version`。版本过期返回 409,防止覆盖他人修改。 - PATCH 请求必须携带读取时获得的 `expected_version`,并至少提交一个修改字段;DELETE 使用查询参数 `expected_version`。版本过期返回 409,防止覆盖他人修改。
- 审核请求字段见下方示例。`verification_status` 只允许 `pending/verified/rejected`;核验人和说明必填。通过审核前必须有来源网址及真实采集时间。这里只记录人工结论,不会自动核实网页。 - 审核请求字段见下方示例。`verification_status` 只允许 `pending/verified/rejected`;核验人和说明必填。通过审核前必须有来源网址及真实采集时间。这里只记录人工结论,不会自动核实网页。
- 修改条目、关联来源或关联文档都会将相关知识转为 `pending`,清除当前审核字段并记录失效原因。历史快照保留当时的条目、来源和文档。 - 修改条目、关联来源或关联文档都会将相关知识转为 `pending`,清除当前审核字段并记录失效原因。历史快照保留当时的条目、来源和文档。
@@ -107,7 +109,7 @@ Vite 通常使用 http://localhost:5173。
前端包括问答、知识库管理、来源与更新、实验评估四个页面。实验评估页面没有虚构数据;所有页面明确标记当前功能状态。 前端包括问答、知识库管理、来源与更新、实验评估四个页面。实验评估页面没有虚构数据;所有页面明确标记当前功能状态。
注意:当前已实现本地 Embedding、文本/向量快照、精确向量及 RRF 混合检索;`/api/chat` 仍是占位接口,生成模型实际调用暂缓,网络搜索、后台更新任务和真实领域评测尚未实现。管理接口由服务端认证与角色校验;审核表单的核验人仍是人工输入字段。已完成隔离环境的浏览器 Cookie/CSRF、角色与真实检索核心流程验收,见 [浏览器验收记录](docs/BROWSER_ACCEPTANCE.md) 与 [导出/采集时间复核](docs/EXPORT_BROWSER_ACCEPTANCE.md);完整生产部署、Docker 数据库与 HTTPS 验收仍待完成。`.env` 不要提交到 Git。 注意:当前已实现本地 Embedding、文本/向量快照、精确向量及 RRF 混合检索;`/api/chat` 已接入独立关键词检索,生成模型实际调用暂缓,网络搜索、后台更新任务和真实领域评测尚未实现。管理接口由服务端认证与角色校验;审核表单的核验人仍是人工输入字段。已完成隔离环境的浏览器 Cookie/CSRF、角色与真实检索核心流程验收,见 [浏览器验收记录](docs/BROWSER_ACCEPTANCE.md) 与 [导出/采集时间复核](docs/EXPORT_BROWSER_ACCEPTANCE.md);完整生产部署、Docker 数据库与 HTTPS 验收仍待完成。`.env` 不要提交到 Git。
### 后端测试和迁移回滚 ### 后端测试和迁移回滚
+1 -4
View File
@@ -26,9 +26,6 @@ def chat(payload: ChatRequest, db: Session = Depends(get_db)) -> ChatResponse:
"因此系统仅返回可追溯证据,不生成或改写答案。" "因此系统仅返回可追溯证据,不生成或改写答案。"
), ),
status="retrieval_only", status="retrieval_only",
sources=[ sources=[{"title": hit.title, "source_url": hit.source_url} for hit in result.results],
{"title": hit.title, "source_url": hit.source_url}
for hit in result.results
],
retrieval_run_id=result.run_id, retrieval_run_id=result.run_id,
) )
+8 -2
View File
@@ -41,8 +41,7 @@ class Patch(Input):
@classmethod @classmethod
def disallow_required_null(cls, value, info): def disallow_required_null(cls, value, info):
if ( if (
info.field_name info.field_name in {"name", "title", "category", "content", "notes", "expected_version"}
in {"name", "title", "category", "content", "notes", "collected_at", "expected_version"}
and value is None and value is None
): ):
raise ValueError("该字段不能为 null") raise ValueError("该字段不能为 null")
@@ -92,6 +91,13 @@ class DocumentUpdate(Patch):
collected_at: AwareDatetime | None = None collected_at: AwareDatetime | None = None
notes: Notes | None = None notes: Notes | None = None
@field_validator("collected_at", mode="before")
@classmethod
def collection_time_is_required(cls, value):
if value is None:
raise ValueError("文档采集时间不能为 null")
return value
class DocumentRead(Read): class DocumentRead(Read):
source_id: int source_id: int
+6 -3
View File
@@ -1,11 +1,14 @@
from typing import Literal from typing import Literal
from uuid import UUID from uuid import UUID
from pydantic import BaseModel, Field from pydantic import BaseModel
from app.schemas.catalog import Input
from app.schemas.retrieval import QueryText
class ChatRequest(BaseModel): class ChatRequest(Input):
question: str = Field(min_length=1, max_length=2000) question: QueryText
class SourcePreview(BaseModel): class SourcePreview(BaseModel):
+4 -2
View File
@@ -1,14 +1,16 @@
from datetime import datetime from datetime import datetime
from typing import Literal from typing import Annotated, Literal
from uuid import UUID from uuid import UUID
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from app.schemas.catalog import Category, Input from app.schemas.catalog import Category, Input
QueryText = Annotated[str, Field(min_length=1, max_length=500)]
class RetrievalRequest(Input): class RetrievalRequest(Input):
query: str = Field(min_length=1, max_length=500) query: QueryText
mode: Literal["keyword"] = "keyword" mode: Literal["keyword"] = "keyword"
top_k: int = Field(default=5, ge=1, le=20) top_k: int = Field(default=5, ge=1, le=20)
category: Category | None = None category: Category | None = None
+46
View File
@@ -156,6 +156,52 @@ def test_review_edit_and_history(client):
assert events["items"][1]["snapshot"]["item"]["verification_status"] == "verified" assert events["items"][1]["snapshot"]["item"]["verification_status"] == "verified"
def test_clear_knowledge_collection_time_requires_new_evidence(client):
verified = review(client, knowledge(client)).json()
path = f"/api/knowledge/{verified['id']}"
before = client.get(path + "/history").json()["items"]
response = client.patch(
path, json={"expected_version": verified["version"], "collected_at": None}
)
assert response.status_code == 200
cleared = response.json()
assert cleared["collected_at"] is None
assert cleared["version"] == verified["version"] + 1
assert cleared["verification_status"] == "pending"
assert cleared["reviewer"] is None and cleared["reviewed_at"] is None
assert cleared["verification_notes"] is None
assert review(client, cleared).status_code == 422
assert client.get(path).json() == cleared
events = client.get(path + "/history").json()["items"]
assert events[0]["snapshot"]["item"]["collected_at"] is None
assert events[1:] == before
assert (
client.patch(
path, json={"expected_version": verified["version"], "collected_at": COLLECTED}
).status_code
== 409
)
restored = client.patch(
path, json={"expected_version": cleared["version"], "collected_at": COLLECTED}
).json()
assert review(client, restored).status_code == 200
def test_document_collection_time_cannot_be_cleared(client):
doc = document(client, source(client)["id"])
path = f"/api/documents/{doc['id']}"
before = client.get(path + "/history").json()
response = client.patch(path, json={"expected_version": doc["version"], "collected_at": None})
assert response.status_code == 422
assert client.get(path).json() == doc
assert client.get(path + "/history").json() == before
def test_source_document_changes_invalidate_evidence(client): def test_source_document_changes_invalidate_evidence(client):
src = source(client) src = source(client)
doc = document(client, src["id"]) doc = document(client, src["id"])
+161
View File
@@ -0,0 +1,161 @@
"""Retrieval-only chat integration; synthetic evidence, never a generation call."""
import copy
import pytest
from fastapi.testclient import TestClient
from sqlalchemy import func, select, text
from sqlalchemy.orm import Session
from test_catalog import document, source
from test_retrieval import add_item
from test_runtime import server
from app.models.rag import RagContext
from app.models.retrieval import RetrievalRun
from app.services import retrieval
def test_overlong_chat_question_is_rejected_at_the_http_boundary(client):
with TestClient(
client.app, base_url="https://testserver", raise_server_exceptions=False
) as public:
response = public.post("/api/chat", json={"question": "q" * 501})
assert response.status_code == 422, response.text
@pytest.mark.parametrize(
"payload",
[
{},
{"question": ""},
{"question": " \t\n "},
{"question": None},
{"question": 123},
{"question": "q" * 2000},
{"question": "q" * 2001},
{"question": "alpha", "model": "not-accepted"},
{"question": "alpha", "messages": []},
],
)
def test_invalid_chat_input_creates_no_retrieval_or_context(client, migrated_database, payload):
response = client.post("/api/chat", json=payload)
assert response.status_code == 422, response.text
assert all("input" not in error for error in response.json()["detail"])
with Session(migrated_database) as db:
assert db.scalar(select(func.count()).select_from(RetrievalRun)) == 0
assert db.scalar(select(func.count()).select_from(RagContext)) == 0
def test_chat_query_boundary_normalization_and_public_history_permissions(client):
response = client.post("/api/chat", json={"question": "q" * 500})
assert response.status_code == 200, response.text
assert response.json()["status"] == "no_evidence"
client.cookies.clear()
public = client.post("/api/chat", json={"question": " alpha "})
assert public.status_code == 200
assert public.json()["status"] == "no_evidence" and public.json()["sources"] == []
assert client.get(f"/api/retrieval/runs/{public.json()['retrieval_run_id']}").status_code == 401
def test_chat_sources_match_audited_snapshot_and_history_stays_immutable(client, migrated_database):
approved = add_item(client, title="verified alpha", content="alpha synthetic evidence")
add_item(client, status="pending", title="pending alpha", content="alpha unreviewed")
add_item(client, status="rejected", title="rejected alpha", content="alpha rejected")
deleted = add_item(client, title="deleted alpha", content="alpha deleted")
assert (
client.delete(
f"/api/knowledge/{deleted['id']}?expected_version={deleted['version']}"
).status_code
== 204
)
result = client.post("/api/chat", json={"question": " alpha "}).json()
assert result["status"] == "retrieval_only" and "不生成" in result["answer"]
history = client.get(f"/api/retrieval/runs/{result['retrieval_run_id']}").json()
assert history["query"] == "alpha" and history["mode"] == "keyword"
assert [h["knowledge_id"] for h in history["results"]] == [approved["id"]]
assert result["sources"] == [
{"title": h["title"], "source_url": h["source_url"]} for h in history["results"]
]
saved = copy.deepcopy(history)
assert (
client.patch(
f"/api/knowledge/{approved['id']}",
json={"expected_version": approved["version"], "content": "revised alpha"},
).status_code
== 200
)
assert client.post("/api/chat", json={"question": "alpha"}).json()["status"] == "no_evidence"
assert client.get(f"/api/retrieval/runs/{history['run_id']}").json() == saved
assert (
client.post(
"/api/rag/contexts",
json={"retrieval_run_id": history["run_id"], "completion": {"model": "contract-model"}},
).status_code
== 422
)
with Session(migrated_database) as db:
assert db.scalar(select(func.count()).select_from(RagContext)) == 0
@pytest.mark.parametrize("change", ["source", "document"])
def test_chat_cannot_reuse_evidence_after_catalog_revision(client, change):
src = source(client)
doc = document(client, src["id"])
add_item(client, document_id=doc["id"], content="alpha synthetic catalog evidence")
assert client.post("/api/chat", json={"question": "alpha"}).json()["status"] == "retrieval_only"
path, version, fields = (
(f"sources/{src['id']}", src["version"], {"source_url": "https://example.com/new"})
if change == "source"
else (f"documents/{doc['id']}", doc["version"], {"content": "revised synthetic document"})
)
assert (
client.patch("/api/" + path, json={"expected_version": version, **fields}).status_code
== 200
)
result = client.post("/api/chat", json={"question": "alpha"}).json()
assert result["status"] == "no_evidence" and result["sources"] == []
def test_chat_capacity_failure_is_not_a_successful_empty_answer(
client, migrated_database, monkeypatch
):
add_item(client, content="alpha synthetic capacity evidence")
monkeypatch.setattr(retrieval, "MAX_ITEMS", 0)
response = client.post("/api/chat", json={"question": "alpha"})
assert response.status_code == 503 and "sources" not in response.json()
with Session(migrated_database) as db:
assert db.scalar(select(func.count()).select_from(RetrievalRun)) == 0
def test_chat_failed_history_insert_does_not_leave_partial_rows(client, migrated_database):
with migrated_database.begin() as db:
db.execute(
text("ALTER TABLE retrieval_runs ADD CONSTRAINT reject_chat_run CHECK(false) NOT VALID")
)
assert client.post("/api/chat", json={"question": "alpha"}).status_code == 409
with Session(migrated_database) as db:
assert db.scalar(select(func.count()).select_from(RetrievalRun)) == 0
def test_chat_retrieval_history_survives_real_http_restart(migrated_database):
with server(migrated_database) as client:
assert client.post("/api/chat", json={"question": "q" * 501}).status_code == 422
item = add_item(client, content="alpha real HTTP synthetic evidence")
response = client.post("/api/chat", json={"question": " alpha "})
assert response.status_code == 200
result = response.json()
history = client.get(f"/api/retrieval/runs/{result['retrieval_run_id']}").json()
assert history["results"][0]["knowledge_id"] == item["id"]
assert history["query"] == "alpha"
with server(migrated_database) as client:
assert client.get(f"/api/retrieval/runs/{result['retrieval_run_id']}").json() == history
exported = client.post(
"/api/retrieval/exports",
json={"run_ids": [result["retrieval_run_id"]], "include_contexts": True},
)
assert exported.status_code == 200
client.cookies.clear()
assert (
client.post("/api/chat", json={"question": "alpha"}).json()["status"]
== "retrieval_only"
)
+1 -1
View File
@@ -14,7 +14,7 @@ FastAPI + Pydantic
|-- 来源获取 -> 人工核验 -> 文本切分 -> 生成向量 -> 发布版本 |-- 来源获取 -> 人工核验 -> 文本切分 -> 生成向量 -> 发布版本
``` ```
当前后端实现健康/数据库就绪检查、来源与文档管理、知识增删改查、分页筛选、人工审核、历史快照、认证会话/角色权限、文本/向量快照及独立关键词/向量/RRF 检索;问答仍为占位接口。数据库由 Alembic 显式迁移并启用 pgvector,DeepSeek RAG 待明确恢复后实现。前端已接入登录/注册、个人中心、账户设置、会话恢复、路由守卫和角色菜单,以及来源、文档的创建、编辑、软删除、分页搜索与历史快照;实验页不伪造数据,知识审核与浏览器 Cookie/PostgreSQL 联调仍需验收。 当前后端实现健康/数据库就绪检查、来源与文档管理、知识增删改查、分页筛选、人工审核、历史快照、认证会话/角色权限、文本/向量快照及独立关键词/向量/RRF 检索;`/api/chat` 为独立条目关键词检索适配,返回检索状态与来源,不调用生成模型(见 [CHAT_API.md](CHAT_API.md))。数据库由 Alembic 显式迁移并启用 pgvector,DeepSeek RAG 待明确恢复后实现。前端已接入登录/注册、个人中心、账户设置、会话恢复、路由守卫和角色菜单,以及来源、文档的创建、编辑、软删除、分页搜索与历史快照;实验页不伪造数据,知识审核与浏览器 Cookie/PostgreSQL 联调仍需验收。
## 阶段 2 数据边界 ## 阶段 2 数据边界
+30
View File
@@ -0,0 +1,30 @@
# 检索式聊天接口
`POST /api/chat` 是已核验知识的条目级关键词检索适配,不调用生成模型。它与问答页面直接使用的 chunk/RRF 接口不同,也不会创建 Responses 准备快照。生成模型与真实服务接入继续延期。
## 请求
```json
{"question":"那拉提示例关键词"}
```
接口公开可用,无需登录;只接受 question。服务端去除首尾空白后校验1–500个字符,与底层 RetrievalRequest 共用同一字段约束;字符数不代表模型token数。缺失、空白、错误类型、超长输入或额外字段(包括 model、messages、凭据)均返回422,不写检索记录。只有停用词或标点、没有有效检索词时同样返回422。错误不包含原始输入值。
原适配曾允许2000字符,内层检索只允许500字符,导致501–2000字符请求返回500。现在该范围在HTTP入参处明确返回422,不截断问题,也不绕过原检索限制。
## 响应与证据
成功返回 answer、status、sources 和 retrieval_run_id。answer 为固定的检索状态说明,**不是生成式回答**。
- status=retrieval_only:返回最多5条来源预览(title/source_url),来自同一条目关键词检索历史的排名结果。说明中的命中总数可以大于来源预览数;完整排名、正文片段、来源/知识版本、配置、语料指纹和耗时保存在原检索记录中。
- status=no_evidence:没有命中的有效已核验依据,sources为空;仍保存该次真实无命中检索的run ID。
待审核、拒绝、软删除、来源/文档失效或修订后等待重新审核的条目不作为来源。资料修订影响后续请求,不重写已经保存的历史快照。没有LLM调用量、生成答案或费用字段,不能把固定说明用于领域质量评分。
检索历史及JSONL导出仍要求管理员权限;公开获取run ID不授予历史读取权限。该run的mode=keyword、证据粒度为knowledge_item,不能传入只接收chunk检索的 `POST /api/rag/contexts`。需要Responses准备时先使用chunks/vector/hybrid检索。
容量超限/数据库不可用保留503,历史保存约束冲突返回409;错误不会伪装成200无命中,也不会保存部分历史。仅成功完成检索与历史事务后返回来源。无额外数据库迁移。
## 验证边界
测试使用合成条目、真实隔离PostgreSQL和默认应用HTTP生命周期,覆盖500字符边界、501字符HTTP失败复现与修正、参数拒绝不写记录、公开调用与历史权限、审核筛选、资料修订/历史不变、容量与事务失败,以及进程重启后的历史和导出。没有模型调用或新增浏览器验收。
+45
View File
@@ -0,0 +1,45 @@
# 2026-10-17 知识编辑与采集时间验收
PR #48 已真正合入 dev,head 与 merge 均已核对为最新 dev 祖先。本轮从该基线创建独立阶段分支,补齐此前仅通过共享函数测试的知识编辑页实际浏览器验收。
使用隔离 PostgreSQL/pgvector、默认生命周期 FastAPI、Vite 和真实浏览器。账号、来源、知识和查询全部是合成数据;没有运行 Embedding 或生成模型。初始化来源、两条知识、文本发布和一条检索记录属于独立 HTTP 准备,不算浏览器录入。
## 实际浏览器结果
| 操作 | 页面结果与独立核对 |
|---|---|
| 匿名打开知识管理,合成管理员登录 | 回跳知识管理页,读取两条真实保存的合成知识 |
| 编辑已有采集时间的知识 | 原 `2026-10-16T12:34:56.789123+08:00` 按当地时钟回填为 `12:34:56.789` |
| 只改标题并保存 | 页面显示成功并由 verified 变为 pending;API 确认版本递增、原微秒时间字符串完整保留、审核字段清空 |
| 打开历史并展开快照 | 页面显示新标题、pending、原微秒采集时间和版本;旧审核快照继续保留 |
| 编辑原采集时间为空的草稿正文 | 空表单保存成功,API 确认仍为 null,没有补写当前时间 |
| 明确修改当地采集时间 | 页面保存成功,`13:45:12.345` 存为 `2026-10-16T13:45:12.345000+08:00` |
| 清空已有采集时间 | 修正后保存成功,历史显示 null;再次打开编辑器仍为空,API 确认持久化 null |
最终修正版本中,只改标题保留微秒的历史:
![知识编辑保留微秒采集时间](assets/knowledge-edit-acceptance/title-history.png)
清空时间后记录 null 的历史:
![知识采集时间清空并保留历史](assets/knowledge-edit-acceptance/cleared-history.png)
## 发现的缺陷与修正
原知识页面清空时间后省略 `collected_at`,服务端按 PATCH 语义保留旧值;页面却提示保存成功。明确传 null 后,又暴露出共享 PATCH 校验错误地把知识的可空时间按文档必填字段拒绝,返回 422。
现在知识编辑器在空输入时明确传 `collected_at: null`;后端允许清空知识采集时间,仍执行版本检查、审核失效和历史保存。文档的非空时间校验放到文档更新模型,文档传 null 继续返回 422,不产生修改或历史。未改时间仍复用原字符串保留微秒,明确改时仍按本地时钟转换。没有猜测或批量修订业务资料。
缺陷复现和最终修正分别使用独立的合成 Schema。修正后重新执行了上表全部编辑路径,没有把首次环境的成功结果代替最终版本复核。接口路径、字段和迁移不变;知识 PATCH 中省略时间与明确 null 的行为见 README。
## 独立 HTTP 与自动化验证
- 修改知识后,旧文本发布检索返回 409;旧检索记录和此前全部审核快照保持原值。
- 清空后核验通过请求返回 422,当前版本和历史不变;过期编辑返回 409,不能覆盖新值。补回真实时间并重新审核的恢复路径由后端回归覆盖。
- 文档采集时间传 null 返回 422,文档与历史均不变。
- `pytest -q backend/tests/test_catalog.py backend/tests/test_indexing.py backend/tests/test_exports.py`:**75 passed**。新增回归在修正前复现知识清空返回 422,修正后通过;同时覆盖审核前提、旧快照、过期版本与文档必填约束。
- `npm run test:datetime`:**6 passed**;`npm run build`(含类型检查)、`ruff check backend`、`ruff format --check backend` 和 `git diff --check`:通过。本轮没有重跑此前 PR #48 的全量 312 项测试,不沿用为本轮结果。
本轮完成知识编辑页的实际修订验收。原生删除确认、Docker、生产 HTTPS、多浏览器和浏览器改密仍按既有待办处理;没有重试原生确认失败方法或代填新密码。正式领域评测仍需要已核验资料和独立问题集,生成模型实际接入继续延期。
复核时在隔离测试库准备一条已核验、带微秒时间的知识和一条无时间草稿,依次执行上表操作,再读取知识详情、历史和旧检索记录比较。原始 HTTP 结果、环境、密码、日志和数据库仅保存在本机 Git 元数据目录;截图只有合成数据。结束后关闭测试服务,删除两轮隔离 Schema 并独立核对。
+1 -1
View File
@@ -2,7 +2,7 @@
本阶段按用户要求先实现通用请求格式,生成模型选择和真实调用延期。新准备请求采用非流式 **Responses API**(服务端点为 `<base_url>/responses`):`model/instructions/input/max_output_tokens`,从结构化 `output` 读取 assistant 文本。依据 [OpenAI Responses 迁移说明](https://developers.openai.com/api/docs/guides/migrate-to-responses)、[DeepSeek Responses 兼容表](https://api-docs.deepseek.com/guides/responses_api/) 与[百炼 Responses 文档](https://help.aliyun.com/zh/model-studio/qwen-api-via-openai-responses)。当前仍为纯组包和解析,不绑定实际服务地址、密钥或模型,不发出 HTTP 生成请求。 本阶段按用户要求先实现通用请求格式,生成模型选择和真实调用延期。新准备请求采用非流式 **Responses API**(服务端点为 `<base_url>/responses`):`model/instructions/input/max_output_tokens`,从结构化 `output` 读取 assistant 文本。依据 [OpenAI Responses 迁移说明](https://developers.openai.com/api/docs/guides/migrate-to-responses)、[DeepSeek Responses 兼容表](https://api-docs.deepseek.com/guides/responses_api/) 与[百炼 Responses 文档](https://help.aliyun.com/zh/model-studio/qwen-api-via-openai-responses)。当前仍为纯组包和解析,不绑定实际服务地址、密钥或模型,不发出 HTTP 生成请求。
本阶段提供管理员上下文准备与历史接口、完整证据快照、固定提示词及纯响应解析器。`POST /api/chat` 仍保留原占位状态,尚未成为生成式问答接口;准备记录固定为 `kind=prepared_request, model_called=false`,不能作为实际模型回答、耗时、费用或领域实验结果。 本阶段提供管理员上下文准备与历史接口、完整证据快照、固定提示词及纯响应解析器。`POST /api/chat` 是独立的条目级关键词检索适配,返回检索状态和来源,尚未成为生成式问答接口(见 [CHAT_API.md](CHAT_API.md));准备记录固定为 `kind=prepared_request, model_called=false`,不能作为实际模型回答、耗时、费用或领域实验结果。
## 准备与读取 ## 准备与读取
+48
View File
@@ -0,0 +1,48 @@
# 来源选择与文档关联边界验收
## 范围与前置
原知识编辑修正 PR #50 已由真正合入 dev 的替代 PR #56 完整交付;PR #58 增加来源选择与名称展示,PR #59 维持草稿时间可空、审核前必填,并暂缓知识通用备注。本轮针对新来源选择路径开展浏览器及独立 HTTP 验收,承接 Issue #57 和 #52,不重复实现已有后端字段。
使用隔离数据库 Schema、默认 FastAPI 生命周期与 Vite;账户、101个来源、一个文档和三个知识条目均为明确标注的合成数据。网址使用 `example.com`,不代表真实文旅资料或领域质量。修正前的复现与最终验收分别使用两个全新 Schema。
## 复现与修正
1. 为一个已核验知识条目关联文档;该文档属于一个已登记来源。
2. 原页面允许将来源选为“不关联已登记来源”并保存,提示成功,但后端会根据文档恢复原来源。条目还会回到待审核;页面没有解释无法解除的原因。
3. 现在,文档关联知识的来源选择禁用、网址只读,并显示文档决定来源的说明。保存标题等其他字段时省略 `source_id` 和 `source_url`,不会提交来源重绑意图。
4. 未关联文档的条目仍能选择或解除登记来源,解除后可保留独立网址。后端仍负责关联一致性、版本和权限校验;本页不增加文档解绑功能。
![文档关联来源锁定说明](assets/source-selection-acceptance/document-source-locked.png)
## 最终浏览器验收
| 操作 | 实际结果 |
|---|---|
| 管理员打开 `/knowledge`,载入101个来源 | 下拉框包含101个来源及“不关联”选项,覆盖来源API的两页数据 |
| 普通知识选择来源API第二页的合成来源 | 网址正确回填,保存后列表显示正确来源名称与链接,条目回到 `pending` |
| 该条目的来源网址改为与登记来源冲突的值 | 显示后端错误,编辑表单保留,不提示保存成功 |
| 普通知识解除登记来源 | 列表变为“未关联”,仍保留原独立网址;重新查询确认 `source_id=null` |
| 编辑文档关联知识 | 来源控件禁用、网址只读,说明清晰;仍能保存标题修订 |
| 展开普通条目的历史 | 旧版本仍显示其当时来源名称及原微秒采集时间 |
![第二页来源实际保存后的名称与链接](assets/source-selection-acceptance/source-selected.png)
## 独立 HTTP 核对
- 来源切换、解除与文档关联标题修订的版本、来源ID/网址、审核回退均与页面结果一致;标题修订和来源操作保留原采集时间的全部微秒。
- 文档来源冲突、登记来源网址冲突返回422;过期编辑返回409;这些失败请求不改变当前条目或审计历史。
- 普通用户不能读取管理来源列表或修改知识(403);客户端控件不代替后端权限与关联校验。
- 修改后旧文本发布返回409;保存的检索记录及原有审计事件保持一致。独立网址草稿未受其他条目操作影响。
HTTP仅用于准备合成数据、独立查询及负向接口验证,不冒充浏览器交互;浏览器操作与截图均来自实际页面。
## 验证与清理
- `cd frontend && node --test tests/datetime.test.mjs`:**6 passed**。
- `cd frontend && npm run build`:类型检查与生产构建通过。
- `git diff --check`:通过。
- 本轮没有后端代码或迁移变更,未重跑全量pytest;未运行Embedding或生成模型。
- 两个隔离Schema已分别独立查询确认不存在;自有FastAPI、Vite及数据库服务停止,临时浏览器标签关闭。原始合成记录、运行环境、账户和本机进度不入库。
仅覆盖上述来源编辑路径。原生删除确认、Docker/生产HTTPS/多浏览器/浏览器改密与真实领域资料验收继续单列;Issue #5、#52、#57 已关闭的远程状态不能替代这些剩余验收。
+30 -4
View File
@@ -7,13 +7,13 @@
| T01 | 前端 | 验证 Vue 页面启动、补充截图 | 吉海燕 | 进行中(生产构建及登录/角色/核心管理流程浏览器联调已通过;账户设置等剩余验收见下文) | | T01 | 前端 | 验证 Vue 页面启动、补充截图 | 吉海燕 | 进行中(生产构建及登录/角色/核心管理流程浏览器联调已通过;账户设置等剩余验收见下文) |
| T02 | 后端 | 验证 FastAPI `/docs`、`/api/health` | 范涵宇 | 已完成自动化/真实 HTTP 验证;待目标 Docker 环境复核 | | T02 | 后端 | 验证 FastAPI `/docs`、`/api/health` | 范涵宇 | 已完成自动化/真实 HTTP 验证;待目标 Docker 环境复核 |
| T03 | 数据库 | 启动 PostgreSQL 并验证知识入库 | 范涵宇 | 已用隔离 PostgreSQL 完成迁移与回归;本机 Docker Compose 验收受阻 | | T03 | 数据库 | 启动 PostgreSQL 并验证知识入库 | 范涵宇 | 已用隔离 PostgreSQL 完成迁移与回归;本机 Docker Compose 验收受阻 |
| T04 | 文档 | 明确接口字段、知识来源审核规则 | 杨星萱 | 待办 | | T04 | 文档 | 明确接口字段、知识来源审核规则 | 杨星萱 | 进行中(字段核对见 field-feedback.md;审核规则草案待三位成员确认并定稿) |
| T05 | 知识库 | 设计 sources/documents/chunks/version 表 | 范涵宇 | 已完成(迁移、发布快照和接口已合入;待 Docker 联调) | | T05 | 知识库 | 设计 sources/documents/chunks/version 表 | 范涵宇 | 已完成(迁移、发布快照和接口已合入;待 Docker 联调) |
| T06 | 检索 | 实现关键词检索基线 | 范涵宇 | 已完成(已核验知识 BM25 与检索历史已合入) | | T06 | 检索 | 实现关键词检索基线 | 范涵宇 | 已完成(已核验知识 BM25 与检索历史已合入) |
| T07 | 向量 | 评估 Embedding 并实现 pgvector | 范涵宇 | 已完成(本地模型、精确向量和 RRF 已合入;待真实领域评测) | | T07 | 向量 | 评估 Embedding 并实现 pgvector | 范涵宇 | 已完成(本地模型、精确向量和 RRF 已合入;待真实领域评测) |
| T08 | RAG | 集成 LLM 与来源展示 | 范涵宇 | 协议基础已合入(Responses 请求/解析调整已在独立分支通过验证,待审;生成模型选型与实际调用延期) | | T08 | RAG | 集成 LLM 与来源展示 | 范涵宇 | Responses 协议已合入(请求/解析与旧快照兼容已完成;生成模型选型与实际调用延期) |
| T09 | 实验 | 构建可核验问题集及评分规范 | 杨星萱 | 待办 | | T09 | 实验 | 构建可核验问题集及评分规范 | 杨星萱 | 进行中(题集和评分规范草案已建;待真实资料完成核验、题目完成证据绑定后才能执行评测) |
| T10 | 工程 | 补充测试、Alembic、鉴权与部署 | 范涵宇(主)、全员 | 进行中(迁移、认证、CSRF、224项回归及浏览器核心流程已通过;Docker/HTTPS/部署待验收) | | T10 | 工程 | 补充测试、Alembic、鉴权与部署 | 范涵宇(主)、全员 | 进行中(聊天边界及知识编辑修正已合入;来源选择浏览器复核与文档来源锁定修正待审,Docker/HTTPS/部署待验收) |
## 2026-10-15 首次联调记录 ## 2026-10-15 首次联调记录
@@ -126,3 +126,29 @@
- 本轮 `pytest -q backend/tests/test_rag.py backend/tests/test_exports.py` **113 passed**;完整 `pytest -q backend/tests` **294 passed, 1 skipped**。skip 为显式启用的真实 Embedding 专项,本轮不运行模型。ruff检查/格式检查及前端类型/生产构建通过;未修改页面交互,不宣称新增浏览器验收。 - 本轮 `pytest -q backend/tests/test_rag.py backend/tests/test_exports.py` **113 passed**;完整 `pytest -q backend/tests` **294 passed, 1 skipped**。skip 为显式启用的真实 Embedding 专项,本轮不运行模型。ruff检查/格式检查及前端类型/生产构建通过;未修改页面交互,不宣称新增浏览器验收。
- PR #44 合入后同步最新 dev,保留两份阶段记录并解决本文件追加冲突。此次同步后重新执行前端时区6项、会话3项及类型/生产构建,均通过;backend树与此前294项回归时一致,本次未重复运行全量后端测试,也未新增模型调用或浏览器验收。 - PR #44 合入后同步最新 dev,保留两份阶段记录并解决本文件追加冲突。此次同步后重新执行前端时区6项、会话3项及类型/生产构建,均通过;backend树与此前294项回归时一致,本次未重复运行全量后端测试,也未新增模型调用或浏览器验收。
## 2026-10-17 T10:检索式聊天边界与后端验收
- PR #45 的 head/merge 均已核对进入 dev,再从最新 dev 创建 `fix/backend-chat-validation`。新合入 PR #46 已实现 `/api/chat` 的检索式适配,本轮补上后端边界修正和集成验收,不重复实现检索、不接入生成模型。
- 已复现501字符问题返回500:外层ChatRequest允许2000字符,内层RetrievalRequest只允许500字符。现在两者共用QueryText与输入规范,问题去首尾空白后1–500字符,超长/空白/错误类型/额外字段在HTTP边界返回422,不静默截断,也不写检索历史。
- 补充有效审核来源筛选、资料修订后重新审核、公开聊天/私有历史权限、历史与导出持久化、容量503及写入409完整回滚验证;真实HTTP重启后检索历史保持一致。Chat仅生成固定检索状态说明,条目级记录不能传给chunk级Responses准备。完整契约见 [CHAT_API.md](CHAT_API.md)。
- 专项 `pytest -q backend/tests/test_chat.py backend/tests/test_retrieval.py` **35 passed**;完整 `pytest -q backend/tests` **312 passed, 1 skipped**,skip为显式启用的真实Embedding专项,本轮未运行模型。ruff检查/格式检查、diff检查和最新前端类型/生产构建通过。API路径、响应字段和数据库迁移不变;README/架构文档更正已过期的聊天占位说明。无新增浏览器验收或领域分数。
## 2026-10-17 T05/T10:知识编辑页实际验收
- PR #48 真正合入 dev,head/merge 均已核对为 dev 祖先,再创建唯一当前阶段分支 `test/backend-knowledge-edit-acceptance`。
- 真实浏览器完成知识编辑的当地时间回填、只改标题保留微秒、审核回退、空时间草稿保存、明确改时、清空时间与历史展开;独立 HTTP 核对版本、原时间与历史快照不变、旧发布409和缺时间审核422。
- 修正清空知识时间却静默保留旧值的问题:前端明确发送 null,后端将文档必填时间约束与知识可空时间分开;文档仍拒绝 null,审核、版本和历史保护保留。无迁移或模型调用。
- 后端目录/索引/导出专项 **75 passed**、前端时区 **6 passed**,ruff检查/格式检查、前端类型/生产构建与diff检查通过;没有把此前312项全量结果当作本轮重跑。可复现步骤、合成截图和浏览器/HTTP证据边界见 [KNOWLEDGE_EDIT_ACCEPTANCE.md](KNOWLEDGE_EDIT_ACCEPTANCE.md)。
- 本阶段提交后等待真正合入,再继续剩余合理后端工作;原生确认、Docker/HTTPS/多浏览器/改密及领域资料待办保留,生成模型和凭据继续延期。
## 2026-10-20 T05/T10:来源选择与文档关联边界验收
- 原 PR #50 关闭未标记合并,但全部修正通过真正合入 dev 的 PR #56 进入集成分支;已核对精确 head、完整阶段树、8个改动文件及替代 head/merge 的祖先关系。保留原编号与替代依据,再从最新 dev 创建唯一后端验收分支。
- PR #58 已实现知识管理页来源选择与名称展示,PR #59 已记录字段决定。本轮仅验收新来源路径及修正其文档关联边界;草稿时间仍可空,审核前真实时间必填,知识通用 notes 暂缓。
- 浏览器复现:文档关联知识选择“不关联来源”后提示保存成功,但后端按文档恢复原来源。现在页面锁定此类条目的来源选择与网址并解释原因;保存其他字段时省略来源重绑字段。后端关联、版本和审核规则保持原有行为,无迁移。
- 在全新隔离合成数据中,真实浏览器完成101个来源跨两页加载、第二页来源选择/名称/链接保存、普通条目解除来源并保留独立网址、文档关联条目的只读说明及标题保存、冲突链接错误提示与历史快照展示。HTTP独立核对微秒保留、旧历史/检索记录不变、来源/网址冲突422、过期编辑409、旧发布409和普通用户403;失败请求不改变条目或审计历史。
- 前端时区 **6 passed**、类型/生产构建与 `git diff --check` 通过;本轮没有后端代码变更或全量pytest重跑,也没有Embedding/生成调用。复现、截图及验证边界见 [SOURCE_SELECTION_ACCEPTANCE.md](SOURCE_SELECTION_ACCEPTANCE.md)。
- 修正前及最终两轮Schema均经独立查询确认删除,自有服务停止、临时浏览器标签关闭。此阶段提交后等待真正合入;Docker/生产HTTPS/多浏览器/浏览器改密、原生确认和领域资料等剩余条件单列保留,Issue关闭不替代实际验收。
Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

+64
View File
@@ -0,0 +1,64 @@
# 阶段 2 知识收集清单(计划)
> 阶段:2026-10-19 至 10-23(阶段 2:知识管理)
> 目标:收集并登记首批 30–50 条可核验资料,完成初步分类与来源核验
> 维护人:杨星萱(知识库、实验与文档负责人)
## 一、阶段 1 知识交付物汇总(截至 10-18)
| 交付物 | 位置 | 状态 |
|---|---|---|
| 资料台账模板 | [material-ledger.md](material-ledger.md) | 已合入 `dev`;包含「更新时间」列与明确标注的测试条目 K-0002。 |
| 审核规则 | [review-rules.md](review-rules.md) | 草案,已合入 dev,待定稿 |
| 联调验收记录模板 | [acceptance-record.md](acceptance-record.md) | 已合入 dev |
| 字段核对反馈 | [field-feedback.md](field-feedback.md) | 已合入 `dev`;3 项字段建议待范涵宇/吉海燕确认。 |
## 二、收集目标与口径
- 总量:30–50 条,覆盖红色文化 / 旅游资源 / 民宿服务 / 交通出行四类。
- 每条资料必须来源可回访、事实可核验;未核验保持 `pending`,不进入正式检索。
- 只登记真实资料;不编造来源、事实或数据。演示/测试条目明确标注,不与真实资料混淆。
## 三、四类收集方向与数量分配
| 类别 | 建议数量 | 收集方向 | 来源优先级 |
|---|---|---|---|
| 红色文化 | 8–12 条 | 边疆红驿相关历史背景、红色纪念地、戍边/英烈事迹、红色场馆介绍 | 政府/文旅局/纪念馆官方 > 权威媒体 |
| 旅游资源 | 10–15 条 | 那拉提景区概况、景点介绍、游览路线、开放时间、票务/预约说明(静态)、适宜季节 | 景区官网/文旅局 > 权威媒体 |
| 民宿服务 | 6–10 条 | 民宿类型、服务内容、位置、入住须知(静态说明) | 官方平台/文旅局 > 权威媒体 |
| 交通出行 | 6–10 条 | 抵达交通方式、班车/自驾路线、景区内交通、天气与安全提示 | 交通部门/文旅局 > 权威媒体 |
> 首版不承诺实时房态、实时价格、自动联网核验;此类内容不纳入收集范围。
## 四、来源类型与核验要点
1. 官方/权威优先:文旅局、景区官网、政府公告、交通部门。
2. 权威媒体次之;一般网站/自媒体仅作线索,不作为 `verified` 依据,除非能追溯到官方来源。
3. 关键事实需至少一个独立来源交叉印证后方可 `verified`(详见 [审核规则](review-rules.md))。独立来源应来自不同发布主体或不同原始材料;转载同一公告的多个页面不视为独立来源。
4. 记录资料版本与更新时间,便于后续向量重建与数据集说明。
## 五、字段与登记要求
- 沿用 [资料台账](material-ledger.md) 字段:标题、类别、来源名称、URL、获取日期、更新时间、正文摘要、真实性判断、核验日期、核验人、状态、备注。
- 状态三值 `pending` / `verified` / `rejected`;只把 `verified` 条目交给范涵宇入库。
- 每条先登记为 `pending`,完成来源与真实性核验后再定状态。
## 六、执行顺序(建议)
1. 优先收集官方来源的景区概况与红色文化(权威性高、量大)。
2. 补充交通与民宿的静态说明。
3. 逐条交叉核验;信息缺失或来源存疑保持 `pending`。
## 七、阶段验收标准
阶段结束时应提交以下可复核交付物:
1. 30–50 条台账记录及四类资料的数量统计,真实资料与测试条目必须分开标识。
2. 每条资料的 URL 可访问检查结果;无法回访或来源存疑的记录维持 `pending` 或标记 `rejected`。
3. `pending` / `verified` / `rejected` 的数量统计,以及可交给后端入库的 `verified` 清单。
4. 已入库 `verified` 条目的联调验收记录,使用 [acceptance-record.md](acceptance-record.md) 记录环境、操作、结果与证据位置。
## 八、待定事项(依赖确认)
- 字段是否调整:知识表单暴露 `source_id` 选择、`collected_at` 是否必填、知识条目是否增加 `notes` —— 见字段核对反馈,定稿后同步本清单的字段要求。
- 以上数据库/前端改动以范涵宇、吉海燕评估为准。
+49
View File
@@ -0,0 +1,49 @@
# 资料追溯字段核对(阶段1 · 2026-10-17)
> 用途:核对资料台账所需字段与系统(后端数据模型 + 前端页面)实际字段的对应关系,确认是否满足「来源可回访、核验可追溯」的要求,并给出需补齐的字段建议。
> 对照对象:[资料台账](material-ledger.md) 的字段 vs 后端 `knowledge_items` / `sources` / `documents` 三张表,以及前端知识管理、来源管理、知识审核页面。
> 结论:核心追溯字段已基本覆盖,存在 5 处需补齐/确认的差异。
## 一、字段对照表
| 台账字段 | 系统对应字段(表) | 覆盖情况 |
|---|---|---|
| 编号 | `id`(自动生成) | ✅ |
| 标题 | `title`(knowledge / documents) | ✅ |
| 类别 | `category`(knowledge / documents,默认「未分类」) | ✅ |
| 来源名称 | `sources.name`(经 `knowledge.source_id` 关联) | ⚠️ 见差异 1 |
| URL | `source_url`(knowledge / sources) | ✅ |
| 获取日期 | `collected_at`(documents 必填,knowledge 可空) | ⚠️ 见差异 2 |
| 更新时间 | `updated_at`(自动维护) | ✅ 系统有,台账此前缺此列,已补 |
| 正文摘要 | `content`(正文,最长 10 万字符) | ⚠️ 见差异 3 |
| 真实性判断 | `verification_notes`(核验说明) | ✅ |
| 核验日期 | `reviewed_at` | ✅ |
| 核验人 | `reviewer` | ✅ |
| 状态 | `verification_status`(pending / verified / rejected) | ✅ 三值与台账完全一致 |
| 备注 | 无独立字段(sources / documents 有 `notes`,knowledge 无) | ⚠️ 见差异 4 |
## 二、差异与建议
1. **来源名称是独立实体**:知识条目本身只有 `source_url`,来源名称需先在「来源管理」登记 `sources`,再通过 `source_id` 关联。前端知识管理页目前只展示「来源网址」链接,未显示来源名称,也未在知识表单暴露 `source_id` 选择。建议:知识录入时允许选择已登记来源(`source_id`),并在列表同时显示来源名称与网址。
2. **采集时间在知识条目上可空**:`documents.collected_at` 必填、`knowledge.collected_at` 可空。前端知识表单已有「采集时间」输入,但后端允许为空。为满足追溯,建议知识条目也要求填写采集时间,或强制关联到含采集时间的文档。
3. **「正文摘要」无独立字段**:系统只有正文 `content`,无单独摘要列。台账中「正文摘要」作为登记用摘要;正式入库时以 `content`(正文/摘要原文)为准,两者须在核验时对齐,避免摘要与正文不一致。
4. **知识条目缺通用「备注」**:`knowledge_items` 没有 `notes`,只有 `verification_notes`(核验说明)。若需在审核之外记录备注(如过期时间、待二次确认),目前只能写入核验说明或关联文档的 `notes`。建议:给知识条目增加 `notes` 字段(需范涵宇评估,仅建议,不直接改库)。
5. **来源名称回填**:现有直接填 `source_url` 的条目,来源名称缺失;后续入库前应补登记到 `sources` 并关联。
## 三、确认决定(2026-10-19)
1. **来源选择与展示:采纳。** 管理员知识管理页已支持选择已登记来源并展示来源名称与链接;仍由后端校验来源、链接与权限。
2. **采集时间:维持现有分层约束。** 草稿知识条目的 `collected_at` 可以为空;标记为 `verified` 前必须具备可核验的来源网址与采集时间。文档采集时间继续为必填。
3. **通用备注:暂缓。** 当前不为 `knowledge_items` 新增 `notes` 字段;待出现稳定业务需求后再评估数据库迁移。审核说明与关联文档备注按现有职责使用。
以上确认仅涉及字段与流程,不包含真实资料、账号、凭据或环境信息。
## 四、核对结论
- 追溯核心字段(标题、类别、URL、获取/更新时间、核验状态、核验人、核验依据)系统均已支持,状态三值与审核规则一致。
- 来源名称的独立登记与前端展示已完成;通用备注字段暂缓,摘要与正文的一致性仍须在核验时确认。
- 数据库结构改动仍需独立评估,不由本文档直接执行。
+52 -9
View File
@@ -19,21 +19,64 @@
|---|---|---| |---|---|---|
| 编号 | 是 | 唯一序号,格式 `K-0001`,按登记顺序递增 | | 编号 | 是 | 唯一序号,格式 `K-0001`,按登记顺序递增 |
| 标题 | 是 | 资料的简短标题,便于检索和引用 | | 标题 | 是 | 资料的简短标题,便于检索和引用 |
| 类别 | 是 | 四选一:红色文化 / 旅游资源 / 民宿服务 / 交通出行 | | 类别 | 是 | 四选一:红色文化 / 旅游资源 / 民宿服务 / 交通出行;测试条目用「其他」并注明测试 |
| 来源名称 | 是 | 发布方,如机构/媒体/网站名称 | | 来源名称 | 是 | 发布方,如机构/媒体/网站名称;无来源的测试条目填「无(测试)」 |
| URL | 是 | 来源链接;无法溯源时填「无链接」并在真实性判断中说明 | | URL | 否 | 来源链接;无法溯源时留空并说明 |
| 获取日期 | 是 | 采集日期,格式 `YYYY-MM-DD` | | 获取日期 | 是 | 采集日期,格式 `YYYY-MM-DD` |
| 更新时间 | 是 | 最近一次更新该条目的日期 `YYYY-MM-DD` |
| 正文摘要 | 是 | 资料关键内容的一两句话摘要,不改变原意 | | 正文摘要 | 是 | 资料关键内容的一两句话摘要,不改变原意 |
| 真实性判断 | 是 | 核验结论依据,如「与官方公告一致」「与另一独立来源交叉印证」「无法核实」 | | 真实性判断 | 是 | 核验结论依据,如「与官方公告一致」「与另一独立来源交叉印证」「无法核实」;测试条目填「无需核验(测试)」 |
| 核验日期 | 否 | 完成核验的日期 `YYYY-MM-DD`;`pending` 时可为空 | | 核验日期 | 否 | 完成核验的日期 `YYYY-MM-DD`;`pending` 时可为空 |
| 核验人 | 否 | 完成核验的人;`pending` 时可为空 | | 核验人 | 否 | 完成核验的人;`pending` 时可为空 |
| 状态 | 是 | `pending` / `verified` / `rejected` | | 状态 | 是 | `pending` / `verified` / `rejected` |
| 备注 | 否 | 补充说明,如过期时间、需二次确认的事项 | | 备注 | 否 | 补充说明,如过期时间、需二次确认的事项、测试标记 |
## 登记表 ## 登记表
| 编号 | 标题 | 类别 | 来源名称 | URL | 获取日期 | 正文摘要 | 真实性判断 | 核验日期 | 核验人 | 状态 | 备注 | | 编号 | 标题 | 类别 | 来源名称 | URL | 获取日期 | 更新时间 | 正文摘要 | 真实性判断 | 核验日期 | 核验人 | 状态 | 备注 |
|---|---|---|---|---|---|---|---|---|---|---|---| |---|---|---|---|---|---|---|---|---|---|---|---|---|
| K-0001 | 【示例·占位】某景区简介 | 旅游资源 | 【示例来源,非真实】示例站点 | https://example.com/ | 2026-10-15 | 【示例】此处为占位摘要,非真实资料 | 待核验 | | | pending | 示例行,仅演示填写格式,不构成真实资料 | | K-0001 | 【示例·占位】某景区简介 | 旅游资源 | 【示例来源,非真实】示例站点 | https://example.com/ | 2026-10-15 | 2026-10-15 | 【示例】此处为占位摘要,非真实资料 | 待核验 | | | pending | 示例行,仅演示填写格式,不构成真实资料 |
| K-0002 | 阶段1联调测试条目 | 其他(测试) | 无(测试) | | 2026-10-17 | 2026-10-17 | 仅用于验证资料登记与持久化流程,不构成任何那拉提文旅事实 | 无需核验(测试) | | | pending | 阶段1测试条目,仅验证登记流程,不进入正式检索库,不伪装为真实资料 |
| K-0003 | 那拉提草原景区概况(国家5A级旅游景区) | 旅游资源 | 中国日报网(govt.chinadaily.com.cn) | https://govt.chinadaily.com.cn/s/201812/28/WS5c2592c6498eb4f01ff25691/nalati-grassland-nalati-prairie-ili-kazak-autonomous-prefecture.html | 2026-10-19 | 2026-10-19 | 那拉提草原位于新疆伊犁哈萨克自治州新源县,为国家5A级旅游景区、新疆首个国家级旅游度假区,地处天山腹地、伊犁河谷东端,属亚高山草甸草原(摘自检索摘要,待核实) | 待人工核验(尚未记录正文核验与独立交叉来源) | | | pending | 试点条目;来源页面为2018年发布,票价等时效信息以景区最新官方发布为准 |
| K-0004 | 那拉提镇红色记忆馆 | 红色文化 | 天山网(新疆权威媒体) | https://www.ts.cn/xwzx/shxw/202608/t20260810_35862803.shtml | 2026-10-19 | 2026-10-19 | 新源县那拉提镇塔依阿苏村退役老兵自建红色记忆馆,藏品超5000件(军功章、旧军服、烈士信函、历史报刊等),为当地爱国主义教育、国防教育阵地(摘自检索摘要,待核实) | 待人工核验(尚未记录正文核验与独立交叉来源) | | | pending | 试点条目 |
| K-0005 | 自治区文旅厅红色旅游主题线路(独库公路串联那拉提) | 红色文化 | 新疆维吾尔自治区文化和旅游厅(天山融媒发布) | https://tnews.xjmty.com/app/slyapp/hdf/202310/t20231001_16431821.html | 2026-10-19 | 2026-10-19 | 自治区文旅厅推出6大主题13条红色旅游主题线路,其中以独库公路为主线的线路将乔尔玛革命烈士陵园与那拉提景区等串联(摘自检索摘要,待核实) | 待人工核验(尚未记录正文核验与独立交叉来源) | | | pending | 试点条目 |
| K-0006 | 2025那拉提音乐节交通安全指南(活动期) | 交通出行 | 新源县人民政府 | https://www.xinyuan.gov.cn/xinyuan/c113900/202508/6785b61206e94151886c1be0b27f5a90.shtml | 2026-10-19 | 2026-10-19 | 来源为2025年那拉提音乐节期间发布的交通安全提示,含活动期易拥堵路段、临时停车和到达建议;仅作为该活动期交通信息线索,不可泛化为常规景区交通指南(待人工核验) | 时效性强(活动期信息),不进入通用问答或正式检索,不采用 | 2026-10-21 | 杨星萱 | rejected | 活动期信息,不采用;常规交通资料见 K-0043 |
| K-0007 | 新源县旅游产业基本情况(含那拉提住宿与民宿) | 民宿服务 | 新源县人民政府(县文化体育广播电视和旅游局) | https://www.xinyuan.gov.cn/xinyuan/c113962/202505/74a8a8423c7b4f7c906aaa491900d96c.shtml | 2026-10-19 | 2026-10-19 | 新源县旅游产业基本情况:那拉提旅游风景区为5A级景区,那拉提镇为全国特色小镇,阿尔善村、拜依盖托别村为国家级乡村旅游重点村;全县住宿单位1414家,其中那拉提区域1204家(摘自检索摘要,待核实) | 待人工核验(尚未记录正文核验与独立交叉来源) | | | pending | 试点条目 |
| K-0008 | 新源县"风光名胜"栏目(政府网目录页) | 旅游资源 | 新源县人民政府 | https://www.xinyuan.gov.cn/xinyuan/c113892/common_list.shtml | 2026-10-19 | 2026-10-19 | 新源县政府网「风光名胜」栏目入口,目录中含外部平台链接;仅作后续检索线索,不作为单条可核验资料 | 待人工核验(栏目页已可访问,具体篇目需逐条访问) | | | pending | 栏目目录页,非单条资料;仅登记已核对的栏目名称与入口 |
| K-0009 | 新源县民宿服务栏目(政府网目录页) | 民宿服务 | 新源县人民政府 | https://www.xinyuan.gov.cn/xinyuan/xyz/common_list.shtml | 2026-10-19 | 2026-10-19 | 新源县政府网民宿服务相关栏目列表页,收录民宿/乡村旅游相关文章(栏目入口,具体篇目待逐个打开收集) | 待人工核验(栏目页未打开,需人工访问核实) | | | pending | 栏目目录页,非单条资料;需逐个打开收集其中具体文章 |
| K-0010 | 新源县交通出行栏目(政府网目录页) | 交通出行 | 新源县人民政府 | https://www.xinyuan.gov.cn/xinyuan/xyx/common_list.shtml | 2026-10-19 | 2026-10-19 | 新源县政府网交通出行相关栏目列表页,收录交通出行相关文章(栏目入口,具体篇目待逐个打开收集) | 待人工核验(栏目页未打开,需人工访问核实) | | | pending | 栏目目录页,非单条资料;需逐个打开收集其中具体文章 |
| K-0011 | 秋入草原山有色 那拉提的限定秋色美到“出圈” | 旅游资源 | 新源县人民政府 | https://www.xinyuan.gov.cn/xinyuan/c113892/202609/4c805c9d83a34d2cadd5c2808d753a59.shtml | 2026-10-21 | 2026-10-21 | 那拉提秋季景色主题文章(正文待打开核实) | 待人工核验(尚未记录正文核验与独立交叉来源) | | | pending | |
| K-0012 | 这才是新疆最适宜度假的地方 | 旅游资源 | 新源县人民政府 | https://www.xinyuan.gov.cn/xinyuan/c113892/202207/bc1406792ab241478e04243f18c10e1e.shtml | 2026-10-21 | 2026-10-21 | 那拉提度假相关文章(正文待打开核实) | 待人工核验(尚未记录正文核验与独立交叉来源) | | | pending | |
| K-0013 | 那拉提旅游风景区介绍 | 旅游资源 | 新源县人民政府 | https://www.xinyuan.gov.cn/xinyuan/xuny/202207/d8e1dbc4b9a146cbac8eadd26c9d8a96.shtml | 2026-10-21 | 2026-10-21 | 那拉提旅游风景区介绍(正文待打开核实) | 待人工核验(尚未记录正文核验与独立交叉来源) | | | pending | |
| K-0014 | 醉美新源丨“网红公路”带火沿途旅游业态 | 旅游资源 | 新源县人民政府 | https://www.xinyuan.gov.cn/xinyuan/xuny/202308/a4d3edf28e69418ca4260ad3be6b941a.shtml | 2026-10-21 | 2026-10-21 | 网红公路带动沿途旅游业态相关文章(正文待打开核实) | 待人工核验(尚未记录正文核验与独立交叉来源) | | | pending | 类别已确认归旅游资源(正文为网红公路带动沿途旅游业态,属旅游主题) |
| K-0015 | 那拉提旅游风景区简介 | 旅游资源 | 新源县人民政府 | https://www.xinyuan.gov.cn/xinyuan/c113962/202606/01a5d90c98c24896beefaf5590850482.shtml | 2026-10-21 | 2026-10-21 | 那拉提旅游风景区简介(正文待打开核实) | 待人工核验(尚未记录正文核验与独立交叉来源) | | | pending | 与 K-0016 同名但内容不完全相同,经人工核对非重复,均保留 |
| K-0016 | 那拉提旅游风景区简介 | 旅游资源 | 新源县人民政府 | https://www.xinyuan.gov.cn/xinyuan/c113962/202511/5f1c03b6ef994245bd3584f3555c3a1d.shtml | 2026-10-21 | 2026-10-21 | 那拉提旅游风景区简介(正文待打开核实) | 待人工核验(尚未记录正文核验与独立交叉来源) | | | pending | 与 K-0015 同名但内容不完全相同,经人工核对非重复,均保留 |
| K-0017 | 辉煌70年·行走伊犁河丨那拉提:从偏远牧区到享誉世界 | 旅游资源 | 腾讯新闻 | https://news.qq.com/rain/a/20240831A07VIB00 | 2026-10-21 | 2026-10-21 | 那拉提发展历程(从偏远牧区到享誉世界)相关文章(正文待打开核实) | 待人工核验(尚未记录正文核验与独立交叉来源) | | | pending | 类别已确认归旅游资源(那拉提发展历程/旅游成就主题,非红色文化) |
| K-0018 | 新疆伊犁那拉提旅游风景区 | 旅游资源 | 新疆旅游网 | https://www.xjlxw.com/lvyoujingdian/yl/2769.html | 2026-10-21 | 2026-10-21 | 那拉提旅游风景区介绍(正文待打开核实) | 待人工核验(尚未记录正文核验与独立交叉来源) | | | pending | |
| K-0019 | 伊犁哈萨克自治州那拉提旅游风景区 | 旅游资源 | 百度百科 | https://baike.baidu.com/item/%E4%BC%8A%E7%8A%81%E5%93%88%E8%90%A8%E5%85%8B%E8%87%AA%E6%B2%BB%E5%B7%9E%E9%82%A3%E6%8B%89%E6%8F%90%E6%97%85%E6%B8%B8%E9%A3%8E%E6%99%AF%E5%8C%BA/64997769 | 2026-10-21 | 2026-10-21 | 百度百科「伊犁哈萨克自治州那拉提旅游风景区」词条(正文待打开核实) | 链接已过期(词条失效/无法访问),不采用 | 2026-10-21 | 杨星萱 | rejected | 百度百科词条已过期失效,已另补官方来源(见 K-0041) |
| K-0020 | 七十二团红军团博物馆 | 红色文化 | 中新网新疆 | http://www.xj.chinanews.com.cn/dizhou/2023-07-06/detail-ihcqyhim4801882.shtml | 2026-10-21 | 2026-10-21 | 七十二团红军团博物馆位于新源县肖尔布拉克72团中心镇区,始建于1991年,展陈分土地革命、抗日战争、解放战争、屯垦肖尔布拉克等8部分,弘扬兵团精神、红军团精神,为革命传统教育和爱国主义教育基地(摘自检索摘要,待核实) | 待人工核验(尚未记录正文核验与独立交叉来源) | | | pending | 位于新源县肖尔布拉克,属那拉提周边红色景点 |
| K-0021 | 乔尔玛烈士陵园(国家AAA级景区) | 红色文化 | 尼勒克县人民政府 | https://www.xjnlk.gov.cn/xjnlk/c112921/202512/0cb1b453e2654a1cba3bd724c9836a20.shtml | 2026-10-21 | 2026-10-21 | 乔尔玛革命烈士陵园位于独库公路(217国道)乔尔玛,纪念1974—1983年修建独库公路牺牲的168名解放军筑路官兵,由烈士纪念碑、168座烈士墓冢和纪念馆组成,为国家AAA级景区、自治区红色旅游经典景区、爱国主义教育基地(摘自检索摘要,待核实) | 待人工核验(尚未记录正文核验与独立交叉来源) | | | pending | 位于尼勒克县,为独库公路串联那拉提的红色景点(见 K-0005 红色线路) |
| K-0022 | 尼勒克县乔尔玛革命烈士陵园成为红色旅游胜地 | 红色文化 | 新华网(新疆频道) | http://www.xj.xinhuanet.com/zt/2023-07/10/c_1129741652.htm | 2026-10-21 | 2026-10-21 | 乔尔玛革命烈士陵园成为独库公路自驾游重要打卡地和红色研学基地,年接待游客约40万人次,游客通过瞻仰纪念碑、重温入党誓词、参观纪念馆、为烈士扫墓等接受红色教育;守墓老兵陈俊贵义务守陵30余年(摘自检索摘要,待核实) | 链接已过期(新华网旧文失效/无法访问),不采用 | 2026-10-21 | 杨星萱 | rejected | 与 K-0021 交叉印证同一红色景点,已另补权威来源(见 K-0040) |
| K-0023 | 独库公路乔尔玛烈士纪念馆改造升级向公众开放 | 红色文化 | 尼勒克县人民政府 | https://www.xjnlk.gov.cn/xjnlk/c112935/202107/896b697c1052410889c09c7f68a499ac.shtml | 2026-10-21 | 2026-10-21 | 独库公路乔尔玛烈士纪念馆完成改造升级并向公众开放,展陈分五个篇章,展出筑路工具、施工图纸、烈士遗物等(摘自检索摘要,待核实) | 待人工核验(尚未记录正文核验与独立交叉来源) | | | pending | 与 K-0021 同景点之纪念馆动态 |
| K-0024 | 英加尔村民宿建设项目 | 民宿服务 | 新源县人民政府 | https://www.xinyuan.gov.cn/xinyuan/c113901/202409/0fb0845a4a6d40f9a0e8caf0ff0aa360.shtml | 2026-10-21 | 2026-10-21 | 那拉提镇英加尔村旅游民宿建设项目总建筑面积约957.85平方米,2024年5月动工,建成后预计为村集体增收20万元以上、解决15—20名群众就业(摘自检索摘要,待核实) | 待人工核验(尚未记录正文核验与独立交叉来源) | | | pending | |
| K-0025 | 扬州援疆擦亮旅游小镇名片 | 民宿服务 | 伊犁新闻网 | https://www.ylxw.com.cn/yj/2025-09-12/1279763.html | 2026-10-21 | 2026-10-21 | 江苏援伊·扬州工作组推动那拉提镇旅游布局优化,构建以民宿旅游带动多业融合的乡村旅游新业态,那拉提镇民宿增至1089家、五星/四星民宿分别19/67家,带动就业1.5万余人(摘自检索摘要,待核实) | 与民宿服务主题不符(主体为援疆工作报道),不采用 | 2026-10-21 | 杨星萱 | rejected | 原条目为扬州援疆报道,非民宿服务主题,已另补(见 K-0042) |
| K-0026 | "空中草原"飘来新牧歌 | 民宿服务 | 扬州市人民政府 | http://yangzhou.gov.cn/xwzx/zwyw/art/2025/art_28c78ae7dfb94359a4af55426d2365d8.html | 2026-10-21 | 2026-10-21 | 扬州对口支援伊犁新源县纪事:推动那拉提"景镇融合",2020—2021两年新建民宿389家,涌现3000多名个体工商户(摘自检索摘要,待核实) | 待人工核验(尚未记录正文核验与独立交叉来源) | | | pending | 与 K-0025 交叉印证那拉提民宿发展 |
| K-0027 | 独库公路恢复通行 这份出行提示请收好 | 交通出行 | 中国网 | http://guoqing.china.com.cn/2026-06/08/content_118535949.shtml | 2026-10-21 | 2026-10-21 | 独库公路通行管控提示:库如力至那拉提入口段每日8:00—19:00通行(视车流可延至21:00),7座及以下小客车可通行,那拉提至乌苏驿入口、乔尔玛入口19:00后社会车辆不得进入(摘自检索摘要,待核实) | 待人工核验(尚未记录正文核验与独立交叉来源) | | | pending | 独库公路串联那拉提,为自驾进入那拉提主要通道;全线信息,入库时取那拉提段 |
| K-0028 | 官宣!独库公路通车时间定了! | 交通出行 | 和静县人民政府 | http://www.xjhegs.gov.cn/xjhegs/c114344/202606/8db10d66f9dd43d6aade74a14944f012.shtml | 2026-10-21 | 2026-10-21 | 独库公路2026年6月1日恢复开放通行(封闭近8个月后通车),通行窗口期约4个月,全线统一禁止7座以上车辆驶入(摘自检索摘要,待核实) | 与那拉提/新源/伊犁非直接相关,不采用 | 2026-10-21 | 杨星萱 | rejected | 独库公路全线通车,发布方为巴州和静县(非伊犁),那拉提仅为其途经点,不纳入 |
| K-0029 | 独库公路北段预约制正式实施 | 交通出行 | 阿克苏新闻网 | https://pghl.aksxw.com/sy/rdxw/aks/202607/t20260713_35333851.html | 2026-10-21 | 2026-10-21 | 独库公路北段(乌苏驿—乔尔玛—那拉提约185公里)实行预约制通行,预约入口含那拉提(G217与G218交汇处),未预约或未按预约时段/入口驶入将按禁令标志处罚(摘自检索摘要,待核实) | 待人工核验(尚未记录正文核验与独立交叉来源) | | | pending | 自驾那拉提段需预约,出行前以官方通告为准 |
| K-0030 | 兵团·美丽休闲乡村体验游 | 红色文化 | 文化和旅游部(官网) | https://zhuanti.mct.gov.cn/rxhmxjgn2022/beijing/detail_g7yU_504/3447.html | 2026-10-21 | 2026-10-21 | 文旅部推荐"兵团·美丽休闲乡村体验游"线路:伊宁市区→72团→71团7连→那拉提风景区→乔尔玛烈士陵园,融合田园风光、红色教育(红军团、烈士陵园)与那拉提草原景观(摘自检索摘要,待核实) | 待人工核验(尚未记录正文核验与独立交叉来源) | | | pending | 官方推荐线路,可与 K-0005 自治区红色线路互相印证 |
| K-0031 | 红军团网上纪念馆开馆 | 红色文化 | 兵团日报(电子报) | http://epaper.bingtuannet.com/pad/cont/202012/22/c161905.html | 2026-10-21 | 2026-10-21 | 四师七十二团与新源县联合打造的红军团网上纪念馆于2020年12月开馆试运行,新源县投资近10万元,实现红色场馆线上展陈(摘自检索摘要,待核实) | 链接无法访问(打不开),不采用 | 2026-10-21 | 杨星萱 | rejected | 与 K-0020 七十二团红军团博物馆为同一主体的线上展馆 |
| K-0032 | 新源县高品质民宿 | 民宿服务 | 新源县人民政府 | https://www.xinyuan.gov.cn/xinyuan/xyz/202511/8a42f195be5b40f19ae3fcd3a47bd0c5.shtml | 2026-10-21 | 2026-10-21 | 新源县政府网发布"高品质民宿"名录,收录那拉提镇音塔勒村、英加尔村、拜依盖托别村、那拉提社区、喀拉苏村等村民宿,含房间、床位等信息(如夏尔部落民宿、那野山河民宿、乌孙夏勒·艺宿、橙野山奢酒店、那兰野舍精品民宿等)(摘自检索摘要,待核实) | 待人工核验(尚未记录正文核验与独立交叉来源) | | | pending | 官方民宿名录;含民宿负责人及联系电话,入库时须脱敏(个人电话不得进入知识库) |
| K-0033 | 新源县那拉提镇2024年民宿客栈等级评定结果公示 | 民宿服务 | 腾讯新闻(转载新源新闻) | https://news.qq.com/rain/a/20240802A08R2100 | 2026-10-21 | 2026-10-21 | 新源县文化体育广播电视和旅游局2024年8月公示那拉提镇民宿客栈等级评定结果,共评出三星级以上民宿客栈160家,其中五星级12家、四星级44家、三星级104家(摘自检索摘要,待核实) | 待人工核验(尚未记录正文核验与独立交叉来源) | | | pending | 发布单位为新源县文化体育广播电视和旅游局(2024-08-02),腾讯新闻(新源新闻栏目)转载;政府网原文未单独检索到,暂以腾讯新闻为来源 |
| K-0034 | 新源县那拉提镇农家乐、马队专业合作社、毡房专业合作社管理办法(试行) | 民宿服务 | 新源县人民政府 | http://www.xinyuan.gov.cn/xinyuan/c118119/202310/22d24e22561e49b18b924553d434faf3/files/80c6389256ea4025b8282fd2c539d1ea.pdf | 2026-10-21 | 2026-10-21 | 那拉提镇农家乐实行星级等级评定(一星至五星),依据《新疆维吾尔自治区农家乐旅游服务质量等级标准》,由县旅游局和那拉提镇人民政府牵头,规范农家乐、马队、毡房专业合作社管理(摘自检索摘要,待核实) | 待人工核验(尚未记录正文核验与独立交叉来源) | | | pending | 政策文件(PDF),规定农家乐星级评定标准,与民宿服务相关 |
| K-0035 | 云端览胜:探秘低空经济下的新疆绝美航线 | 交通出行 | 伊犁哈萨克自治州人民政府 | https://www.xjyl.gov.cn/xjylz/c112427/202506/130c8fd52113405eab9f8e36737f00aa.shtml | 2026-10-21 | 2026-10-21 | 新疆低空旅游航线(伊宁—那拉提、博乐—那拉提、喀纳斯—博乐—那拉提、伊宁—昭苏—那拉提等),由天缘通航、新疆通航以皮拉图斯PC12机型执飞,单程约30分钟至2小时(摘自检索摘要,待核实) | 待人工核验(尚未记录正文核验与独立交叉来源) | | | pending | 低空旅游航线,为那拉提补充交通方式 |
| K-0036 | 新疆机场集团那拉提机场加密乌鲁木齐往返航班 | 交通出行 | 中国民航网 | https://m.ccaonline.cn/zhuanqu/%E6%96%B0%E7%96%86%E6%9C%BA%E5%9C%BA%E9%9B%86%E5%9B%A2/zxxwdt/1136659.html | 2026-10-21 | 2026-10-21 | 新疆机场集团那拉提机场冬春航季加密乌鲁木齐往返航班,天津航空执飞,新增每周一三五日及现有每周二四六往返(摘自检索摘要,待核实) | 链接无法访问(打不开),不采用 | 2026-10-21 | 杨星萱 | rejected | 注意:那拉提机场2026-03-29至2026-12-31改扩建停航,本文航班为停航前/换季安排,核验时以机场最新公告为准 |
| K-0037 | 新疆机场集团那拉提机场喜迎天缘航空低空旅游包机首航成功 | 交通出行 | 中国民航网 | http://www.caacnews.com.cn/1/5/202505/t20250506_1387143.html | 2026-10-21 | 2026-10-21 | 新疆机场集团那拉提机场迎来天缘航空低空旅游包机首航成功(摘自检索摘要,待核实) | 待人工核验(尚未记录正文核验与独立交叉来源) | | | pending | 低空旅游包机业务,与 K-0035 低空航线交叉印证 |
| K-0038 | 第四师七十一团(骑兵连)团场概况 | 红色文化 | 可克达拉市人民政府(兵团第四师) | http://www.cocodala.gov.cn/html/1832/2023-04-20/content-74402.html | 2026-10-21 | 2026-10-21 | 七十一团位于新源县境内巩乃斯河畔,前身为解放军六军骑兵团("骑兵连"),1949年随王震挺进新疆、1951年屯垦新源;其七连地处218国道沿线、东距那拉提景区约28公里,称"那拉提的后花园",建有军垦文化旅游景区,为全国乡村旅游重点村(摘自检索摘要,待核实) | 待人工核验(尚未记录正文核验与独立交叉来源) | | | pending | 71团与72团同为那拉提周边兵团红色景点,补齐文旅部线路(K-0030)中"71团7连"一站 |
| K-0039 | 新源县汽车客运站有限责任公司(含那拉提分公司) | 交通出行 | 新源县人民政府 | http://www.xinyuan.gov.cn/xinyuan/c117717/202309/cc31f1eaf1204e2b887e2cd2178be266.shtml | 2026-10-21 | 2026-10-21 | 新源县汽车客运站有限责任公司位于新源县新源镇青年街074号,上级主管部门为新源县交通运输局,服务投诉电话0999-5022519;设那拉提分公司、喀拉布拉分站等,为县城至那拉提等方向客运班车运营主体(摘自检索摘要,待核实) | 待人工核验(尚未记录正文核验与独立交叉来源) | | | pending | 企业公开信息页,非班次时刻表;伊宁—那拉提、新源—那拉提具体班次/票价以车站最新公示或"新疆客运联网售票"为准 |
| K-0040 | 陈俊贵:一个馒头,一生守候 | 红色文化 | 退役军人事务部(官网) | https://www.mva.gov.cn/sy/zt/xxdtyjrdhyz/jscc/202603/t20260302_532207.html | 2026-10-21 | 2026-10-21 | 乔尔玛烈士陵园守墓人陈俊贵事迹:1980年4月独库公路筑路任务中班长郑林书把最后一个馒头让给他,班长与副班长牺牲;他退役后1985年携妻儿重返天山义务守墓40余年,2013年获评全国道德模范、"感动中国2013年度人物"(摘自检索摘要,待核实) | 待人工核验(尚未记录正文核验与独立交叉来源) | | | pending | 与 K-0021/K-0023 乔尔玛烈士陵园交叉印证;替补已过期的 K-0022 |
| K-0041 | 央媒看伊犁丨新疆那拉提——"最先见到太阳的地方" | 旅游资源 | 伊犁哈萨克自治州人民政府 | https://www.xjyl.gov.cn/xjylz/c112427/202605/89ae4da7f30e4bf89c430de4e8c86276.shtml | 2026-10-21 | 2026-10-21 | 那拉提旅游风景区:国家5A级景区、新疆首家国家级旅游度假区,地处天山腹地、伊犁河谷东端;那拉提意为"最先见到太阳的地方",拥有空中草原、河谷草原、盘龙古道等游览线路及哈萨克族民俗活动(摘自检索摘要,待核实) | 待人工核验(尚未记录正文核验与独立交叉来源) | | | pending | 替补已过期的百度百科词条 K-0019;另有文旅部5A级景区登记(sjfw.mct.gov.cn)可作交叉印证 |
| K-0042 | 新源县那拉提镇:提质赋能优服务 精致旅居暖宾客 | 民宿服务 | 新华网 | https://app.xinhuanet.com/news/article.html?articleId=20260901766fd9e2cbf14816996e92507fee44c0 | 2026-10-21 | 2026-10-21 | 那拉提镇引导酒店、民宿推进硬件提质、服务提标,打造"草原明珠·醉美新源"文旅品牌(摘自检索摘要,待核实) | 待人工核验(尚未记录正文核验与独立交叉来源) | | | pending | 替补不符合主题的 K-0025;新华网转载新源县信息,核验时可找回政府网原文 |
| K-0043 | 多措并举优服务 平安畅游大草原——那拉提景区全力筑牢暑期旅游高峰安全防线 | 交通出行 | 新源县人民政府 | https://www.xinyuan.gov.cn/xinyuan/c113898/202608/54b1638c74d24d6089fd60ac0280c1c5.shtml | 2026-10-22 | 2026-10-22 | 那拉提景区在出入口、停车场、盘山公路、网红打卡点等区域专人疏导;塔依阿苏自驾线路实行分时单向通行(每日15时前可驶入,15时后单向驶出),弯多临崖、限速慢行(摘自检索摘要,待核实) | 待人工核验(尚未记录正文核验与独立交叉来源) | | | pending | 替补 K-0006(音乐节活动期信息);为那拉提景区常规自驾/交通疏导信息 |
> 上表仅含一条**示例占位行**,用于演示填写格式,不包含任何真实文旅信息。正式收集的资料按此格式逐行追加。 > K-0001 为**示例占位行**、K-0002 为**测试条目**,均不含真实文旅信息,保持 `pending`;K-0008 至 K-0010 为政府网栏目目录页(入口,非单条资料),保持 `pending`。K-0006(活动期信息)、K-0019(词条已过期)、K-0022(旧文已过期)、K-0025(与民宿服务主题不符)、K-0028(与项目区域非直接相关)、K-0031、K-0036(链接无法访问)共 7 条为 `rejected`,不采用。其余候选资料均为 `pending`:尚未在台账中记录正文核验和独立交叉来源,不能进入正式检索、问答、论文实验或作为测试题集答案依据。完成逐条核验后,才可按[审核规则](review-rules.md)更新为 `verified`。
+2 -2
View File
@@ -1,7 +1,7 @@
# 审核规则 # 审核规则
> 用途:规定资料的核验状态、判定标准与核验流程,是资料能否进入正式检索库的统一依据。 > 用途:规定资料的核验状态、判定标准与核验流程,是资料能否进入正式检索库的统一依据。
> 状态:本规则为**草案**,由杨星萱起草,需经确认后定稿。 > 状态:**草案**,由杨星萱起草;需经三位成员确认后定稿。
> 维护人:杨星萱 > 维护人:杨星萱
## 一、状态定义 ## 一、状态定义
@@ -50,4 +50,4 @@
- 时效性:注明信息发布时间,避免使用明显过时内容。 - 时效性:注明信息发布时间,避免使用明显过时内容。
- 边界一致:首版不承诺实时房态、实时价格、自动联网核验等,此类信息不按可靠事实收录。 - 边界一致:首版不承诺实时房态、实时价格、自动联网核验等,此类信息不按可靠事实收录。
> 本规则为草案,判定标准与流程以负责人确认为准。 > 本规则定稿前,所有候选资料均须保守地保持 `pending`;定稿不替代逐条正文核验和独立交叉印证。
+70
View File
@@ -0,0 +1,70 @@
# 评分规范(A/B/C 对照实验 · 草案)
> 用途:规定 LLM-only、关键词 RAG、混合检索 RAG 三种配置的评分维度、打分标准与统计方式,保证实验结论可复现、可回溯。
> 维护人:杨星萱(知识库、实验与文档负责人)
> 关联:[测试问题集](test-question-set.md)、[资料台账](material-ledger.md)
> 原则:任何分数必须来自实际运行、检索日志与人工评分,不得先写结论后补数据。本规范在测试问题集完成证据绑定前不得用于生成或汇总实验分数。
## 一、对照配置
| 代号 | 配置 | 说明 |
|---|---|---|
| A | LLM-only | 不接检索,仅凭模型参数知识回答 |
| B | 关键词 RAG | BM25 关键词检索 + 上下文 + LLM |
| C | 混合检索 RAG | BM25 + 向量 RRF 融合检索 + 上下文 + LLM |
## 二、评分维度与权重
| 维度 | 权重 | 说明 |
|---|---|---|
| 正确性 | 40% | 回答要点与题目「预期知识点」一致 |
| 可追溯性 | 30% | 引用来源真实可回访,且确实支持该回答 |
| 无依据回答 | 20% | 资料不足时明确说明「不确定」,不编造 |
| 响应时间 | 5% | 端到端耗时(记录实测值,分档计分) |
| 成本 | 5% | 模型调用用量/费用(记录实测值,分档计分) |
## 三、打分标准(每维度 0–2 分)
### 正确性(0–2)
| 分值 | 标准 |
|---|---|
| 2 | 答案要点与预期知识点一致,无事实错误 |
| 1 | 部分要点正确,或存在遗漏、表述偏差 |
| 0 | 答非所问、关键事实错误或与来源冲突 |
### 可追溯性(0–2)
| 分值 | 标准 |
|---|---|
| 2 | 引用来源真实、可回访,且确实支持该回答 |
| 1 | 有引用但不完全支持,或来源层级/内容与结论不符 |
| 0 | 无引用,或引用来源不支持回答 |
### 无依据回答(0–2)
| 分值 | 标准 |
|---|---|
| 2 | 资料不足时明确说明「不确定」,并只给出可核验的部分 |
| 1 | 存在臆测、推断成分,未明确标注不确定性 |
| 0 | 资料不足仍给出确定性编造(如 Q24 用活动期信息冒充常规指南) |
### 响应时间 / 成本(各 0–2,分档)
| 分值 | 响应时间(单题端到端) | 成本(相对) |
|---|---|---|
| 2 | ≤ 2 秒 | 最低档 |
| 1 | 2–10 秒 | 中间档 |
| 0 | > 10 秒或超时 | 最高档 |
> 响应时间与成本的分档阈值在阶段 6 冻结实验配置时按实测分布确定,不预设结论;本表阈值为占位示例。
## 四、加权总分与统计
- 单题加权分 = 正确性×0.4 + 可追溯性×0.3 + 无依据回答×0.2 + 响应时间×0.05 + 成本×0.05。
- 按 24 题汇总,计算 A/B/C 三组的平均分与各维度平均分。
- 除总分外,单列「无依据回答 = 0」的题数与案例(体现「不足时不编造」的达标情况)。
## 五、评分记录要求
- 每题记录:配置(A/B/C)、原始回答、检索命中的 K 编号、引用来源、各维度得分、判分依据。
- 原始回答、检索日志与评分记录须可回溯到题目编号与运行配置;不在评分表里填虚构数值。
- 正反例单独整理,作为阶段 4 案例分析的素材。
> 本规范为草案,维度与权重在阶段 3 联机验证后、阶段 6 实验前定稿。
+181
View File
@@ -0,0 +1,181 @@
# 测试问题集(阶段 3 · 草案)
> 用途:提供关键词/向量/混合检索与 RAG 问答的**候选**测试问题结构;待台账建立可用 `verified` 证据后,逐题复核答案要点、绑定来源并用于人工评分。
> 维护人:杨星萱(知识库、实验与文档负责人)
> 关联:[资料台账](material-ledger.md)与[评分规范](scoring-rubric.md)
> 当前状态:台账尚无可用于实验的 `verified` 资料。下列考点和 K 编号仅是待复核的候选绑定,不构成事实答案、正式检索语料或实验金标准;不得据此生成评分、论文结论或用户问答。
## 一、口径与使用
- 共 24 题,四类各 6 题,编号 Q01–Q24。
- 每题暂列**候选知识点**、**候选引用来源**(K 编号)和**资料不足情形**;每个 K 编号转为 `verified` 后,须重新阅读原文、确认题干与来源匹配,再解除该题的草案状态。
- 仅在所有相关引用来源均为 `verified` 后,方可用于阶段 3 的检索命中检查和阶段 4 的 RAG 评分;未满足时,系统应按资料不足处理。
- 来源页更新、失效或台账状态变化时,须同步复核本题;不能沿用本文件中的候选表述作为事实结论。
---
## 二、红色文化(Q01–Q06)
### Q01 · 那拉提周边有哪些红色文化纪念场所?
- **类别**:红色文化
- **候选知识点(待核验)**:那拉提镇塔依阿苏村红色记忆馆(民间自建)、新源县肖尔布拉克七十二团红军团博物馆、独库公路乔尔玛烈士陵园(尼勒克县)、第四师七十一团(骑兵连)等。
- **候选引用来源(待核验)**:K-0004、K-0020、K-0021、K-0038
- **资料不足情形**:若检索未命中上述任一来源,不得凭空列举,应说明「未检索到相关红色场所」并给出可补充的检索方向。
### Q02 · 七十二团红军团博物馆位于哪里、主要展陈什么?
- **类别**:红色文化
- **候选知识点(待核验)**:位于新源县肖尔布拉克 72 团中心镇区,始建于 1991 年;展陈分土地革命、抗日战争、解放战争、屯垦肖尔布拉克等板块,弘扬兵团精神、红军团精神,为爱国主义教育基地。
- **候选引用来源(待核验)**:K-0020
- **资料不足情形**:具体展厅数量、展品件数等细节若来源页未载明,不臆造数字。
### Q03 · 乔尔玛烈士陵园纪念的是哪段历史?
- **类别**:红色文化
- **候选知识点(待核验)**:纪念 1974—1983 年修建独库公路牺牲的 168 名解放军筑路官兵,由烈士纪念碑、168 座墓冢和纪念馆组成,为国家 AAA 级景区、自治区红色旅游经典景区、爱国主义教育基地。
- **候选引用来源(待核验)**:K-0021、K-0023
- **资料不足情形**:牺牲人数等关键数字须与来源一致;来源间若口径冲突,应如实标注并保持「待确认」。
### Q04 · 守墓老兵陈俊贵的事迹是什么?
- **类别**:红色文化
- **候选知识点(待核验)**:1980 年 4 月独库公路筑路任务中班长郑林书把最后一个馒头让给他,班长与副班长牺牲;他退役后 1985 年携妻儿重返天山义务守墓 40 余年,2013 年获评全国道德模范、「感动中国 2013 年度人物」。
- **候选引用来源(待核验)**:K-0040
- **资料不足情形**:细节(如具体年份、获奖)须与来源一致;无来源时不得转述网络传闻。
### Q05 · 自治区红色旅游主题线路如何串联那拉提?
- **类别**:红色文化
- **候选知识点(待核验)**:自治区文旅厅推出红色旅游主题线路,其中以独库公路为主线的线路将乔尔玛革命烈士陵园与那拉提景区等串联。
- **候选引用来源(待核验)**:K-0005、K-0030
- **资料不足情形**:线路数量(如「6 大主题 13 条」)以来源为准;未载明时不做具体数字表述。
### Q06 · 文旅部推荐的「兵团·美丽休闲乡村体验游」线路经过哪些站点?
- **类别**:红色文化
- **候选知识点(待核验)**:伊宁市区 → 72 团 → 71 团 7 连 → 那拉提风景区 → 乔尔玛烈士陵园,融合田园风光、红色教育与草原景观。
- **候选引用来源(待核验)**:K-0030
- **资料不足情形**:站点顺序与名称须与官方线路一致,不自行增删。
---
## 三、旅游资源(Q07–Q12)
### Q07 · 那拉提草原是什么级别的景区?
- **类别**:旅游资源
- **候选知识点(待核验)**:国家 5A 级旅游景区、新疆首个国家级旅游度假区,地处天山腹地、伊犁河谷东端,属亚高山草甸草原。
- **候选引用来源(待核验)**:K-0003、K-0041
- **资料不足情形**:等级/称号须与来源一致;对「首个」「唯一」等表述仅在来源明确时使用。
### Q08 · 「那拉提」名称的含义是什么?
- **类别**:旅游资源
- **候选知识点(待核验)**:意为「最先见到太阳的地方」(一说与蒙古语「太阳」相关)。
- **候选引用来源(待核验)**:K-0041
- **资料不足情形**:词源存在多种说法时,如实呈现「一说/另说」,不武断定论。
### Q09 · 那拉提有哪些主要游览线路或景观?
- **类别**:旅游资源
- **候选知识点(待核验)**:空中草原、河谷草原、盘龙古道等游览线路,天界台、游牧人家、塔吾萨尼等景点,保留赛马、叼羊、姑娘追、阿肯弹唱会等民俗活动。
- **候选引用来源(待核验)**:K-0041、K-0013、K-0015、K-0016
- **资料不足情形**:线路/景点名称以来源为准;未载明的景点不自行补入。
### Q10 · 那拉提的地理位置与地貌特征是什么?
- **类别**:旅游资源
- **候选知识点(待核验)**:位于新疆伊犁哈萨克自治州新源县那拉提镇,天山腹地、伊犁河谷东端,平均海拔约 1800 米,三面环山、巩乃斯河蜿蜒流过,属高山河谷草原。
- **候选引用来源(待核验)**:K-0003、K-0041
- **资料不足情形**:海拔、面积等存在多个口径时,以来源页当前表述为准,不合并不同口径。
### Q11 · 那拉提秋季景色有什么特点?
- **类别**:旅游资源
- **候选知识点(待核验)**:那拉提秋季有「限定秋色」主题内容,草原秋色为季节特色景观。
- **候选引用来源(待核验)**:K-0011
- **资料不足情形**:仅能转述来源页对秋季景色的描述,不扩展为具体日期或最佳观赏时段。
### Q12 · 那拉提的旅游发展历程大致是怎样的?
- **类别**:旅游资源
- **候选知识点(待核验)**:从偏远牧区逐步发展为享誉世界的旅游目的地(那拉提意为「最先见到太阳的地方」,为古丝绸之路北线要地),近年打造天界台索道、哈茵赛滑雪场等四季旅游项目。
- **候选引用来源(待核验)**:K-0017、K-0041
- **资料不足情形**:具体年份、投资额等数字须回到来源页核对,不凭记忆填写。
---
## 四、民宿服务(Q13–Q18)
### Q13 · 那拉提有哪些民宿聚集的村落?
- **类别**:民宿服务
- **候选知识点(待核验)**:音塔勒村、英加尔村、拜依盖托别村、那拉提社区、喀拉苏村等;其中阿尔善村、拜依盖托别村为国家级乡村旅游重点村。
- **候选引用来源(待核验)**:K-0032、K-0007
- **资料不足情形**:村落名称以官方名录为准,不自行列举未在来源中出现的村名。
### Q14 · 新源县/那拉提区域的住宿规模大概是多少?
- **类别**:民宿服务
- **候选知识点(待核验)**:新源县全县住宿单位 1414 家,其中那拉提区域 1204 家(核验时点口径)。
- **候选引用来源(待核验)**:K-0007
- **资料不足情形**:该数字为来源页发布时点的统计口径,须注明「截至发布时点」,不当作实时数据。
### Q15 · 那拉提民宿的等级评定是怎样的?
- **类别**:民宿服务
- **候选知识点(待核验)**:新源县文旅局公示那拉提镇民宿客栈等级评定结果,评出三星级以上民宿客栈 160 家,其中五星级 12 家、四星级 44 家、三星级 104 家(2024 年公示口径)。
- **候选引用来源(待核验)**:K-0033
- **资料不足情形**:评定年份与数字以来源为准;注意原文为新源县政府评定、媒体转载,评分时须注明来源层级。
### Q16 · 那拉提农家乐如何分级管理?
- **类别**:民宿服务
- **候选知识点(待核验)**:那拉提镇农家乐实行星级等级评定(一星至五星),依据《新疆维吾尔自治区农家乐旅游服务质量等级标准》,由县旅游局和那拉提镇人民政府牵头。
- **候选引用来源(待核验)**:K-0034
- **资料不足情形**:标准名称与牵头单位须与政策文件一致,不缩写或改动。
### Q17 · 英加尔村民宿建设项目的基本情况?
- **类别**:民宿服务
- **候选知识点(待核验)**:那拉提镇英加尔村旅游民宿建设项目总建筑面积约 957.85 平方米,2024 年 5 月动工,建成后预计为村集体增收 20 万元以上、解决 15—20 名群众就业。
- **候选引用来源(待核验)**:K-0024
- **资料不足情形**:面积、增收、就业等数字以来源为准;「预计」等表述须保留,不写成已实现。
### Q18 · 那拉提有哪些民宿品牌或名称?
- **类别**:民宿服务
- **候选知识点(待核验)**:官方「高品质民宿」名录中含夏尔部落民宿、那野山河民宿、乌孙夏勒·艺宿、橙野山奢酒店、那兰野舍精品民宿等。
- **候选引用来源(待核验)**:K-0032
- **资料不足情形**:名录含民宿负责人及联系电话,作答时**不得输出个人电话**等隐私信息。
---
## 五、交通出行(Q19–Q24)
### Q19 · 自驾经独库公路进入那拉提有哪些通行管控?
- **类别**:交通出行
- **候选知识点(待核验)**:库如力至那拉提入口段每日 8:00—19:00 通行(视车流可延至 21:00),7 座及以下小客车可通行;那拉提至乌苏驿入口、乔尔玛入口 19:00 后社会车辆不得进入。
- **候选引用来源(待核验)**:K-0027
- **资料不足情形**:通行时段为动态管控,须注明「以最新官方通告为准」;不得把旧时段当作固定规则。
### Q20 · 独库公路北段是否需要预约通行?
- **类别**:交通出行
- **候选知识点(待核验)**:独库公路北段(乌苏驿—乔尔玛—那拉提约 185 公里)实行预约制通行,预约入口含那拉提(G217 与 G218 交汇处),未预约或未按预约时段/入口驶入将按禁令标志处罚。
- **候选引用来源(待核验)**:K-0029
- **资料不足情形**:预约规则以来源为准;不承诺具体预约操作步骤(若来源未载明)。
### Q21 · 到那拉提有哪些低空旅游航线?
- **类别**:交通出行
- **候选知识点(待核验)**:新疆低空旅游航线含伊宁—那拉提、博乐—那拉提、喀纳斯—博乐—那拉提、伊宁—昭苏—那拉提等,由天缘通航、新疆通航以皮拉图斯 PC12 机型执飞,单程约 30 分钟至 2 小时。
- **候选引用来源(待核验)**:K-0035
- **资料不足情形**:航线、执飞方、机型以来源为准;班次/票价不承诺实时信息。
### Q22 · 那拉提机场是否有低空旅游包机业务?
- **类别**:交通出行
- **候选知识点(待核验)**:新疆机场集团那拉提机场迎来天缘航空低空旅游包机首航成功。
- **候选引用来源(待核验)**:K-0037
- **资料不足情形**:注意那拉提机场曾公告改扩建停航(2026-03-29 至 2026-12-31),回答须以机场最新公告为准,不得据此承诺当前可乘机。
### Q23 · 新源县汽车客运站负责哪些方向的班车?
- **类别**:交通出行
- **候选知识点(待核验)**:新源县汽车客运站有限责任公司设那拉提分公司、喀拉布拉分站等,为县城至那拉提等方向客运班车运营主体。
- **候选引用来源(待核验)**:K-0039
- **资料不足情形**:具体班次/票价以车站最新公示或「新疆客运联网售票」为准,来源未载明时不填时刻表。
### Q24 · 那拉提景区日常的交通/停车指南是什么?
- **类别**:交通出行
- **候选知识点(待核验)**:(当前无已核验的通用景区交通指南)
- **候选引用来源(待核验)**:(无——K-0006 为「2025 那拉提音乐节交通安全指南」,属活动期、时效性强,不进入通用问答)
- **资料不足情形**:**这是典型的「资料不足」用例**——系统应明确回答「暂无核验过的常规景区交通指南」,不得用音乐节活动期信息冒充常规指南;后续需另收集常规交通官方资料后再回答。
---
## 六、启用条件与后续
- 对每道题逐一补齐:已复核的正文要点、至少一个独立交叉来源、状态为 `verified` 的 K 编号、复核日期和复核人;缺任一项不得启用。
- 启用后,阶段 3 检查关键词/向量/混合检索命中是否落在允许来源内;阶段 4 才可评分 RAG 回答的正确性和引用支持度。
- K-0006 为活动期交通信息,不能用作常规交通答案;待收集并核验常规景区交通资料后,再补充 Q24 的正式答案依据。
+1 -1
View File
@@ -10,7 +10,7 @@ export const createKnowledge = (payload:{title:string;category:string;content:st
export type VerificationStatus='pending'|'verified'|'rejected' export type VerificationStatus='pending'|'verified'|'rejected'
export type KnowledgeItem={id:number;version:number;title:string;category:string;content:string;source_url:string|null;source_id:number|null;document_id:number|null;collected_at:string|null;verification_status:VerificationStatus;reviewer:string|null;reviewed_at:string|null;verification_notes:string|null;created_at:string;updated_at:string;is_deleted:boolean} export type KnowledgeItem={id:number;version:number;title:string;category:string;content:string;source_url:string|null;source_id:number|null;document_id:number|null;collected_at:string|null;verification_status:VerificationStatus;reviewer:string|null;reviewed_at:string|null;verification_notes:string|null;created_at:string;updated_at:string;is_deleted:boolean}
export function getKnowledgePage(filters:{q?:string;category?:string;verification_status?:VerificationStatus;page?:number;page_size?:number}={}){const params=new URLSearchParams();for(const [key,value] of Object.entries(filters)){if(value!==undefined&&value!=='')params.set(key,String(value))}return apiRequest<{items:KnowledgeItem[];total:number;page:number;page_size:number}>(`/api/knowledge/page?${params}`)} export function getKnowledgePage(filters:{q?:string;category?:string;verification_status?:VerificationStatus;page?:number;page_size?:number}={}){const params=new URLSearchParams();for(const [key,value] of Object.entries(filters)){if(value!==undefined&&value!=='')params.set(key,String(value))}return apiRequest<{items:KnowledgeItem[];total:number;page:number;page_size:number}>(`/api/knowledge/page?${params}`)}
export function updateKnowledge(id:number,payload:{expected_version:number;title?:string;category?:string;content?:string;source_url?:string|null;collected_at?:string|null}){return apiRequest<KnowledgeItem>(`/api/knowledge/${id}`,{method:'PATCH',body:JSON.stringify(payload)},true)} export function updateKnowledge(id:number,payload:{expected_version:number;title?:string;category?:string;content?:string;source_id?:number|null;source_url?:string|null;collected_at?:string|null}){return apiRequest<KnowledgeItem>(`/api/knowledge/${id}`,{method:'PATCH',body:JSON.stringify(payload)},true)}
export function deleteKnowledge(id:number,expectedVersion:number){return apiRequest<void>(`/api/knowledge/${id}?expected_version=${expectedVersion}`,{method:'DELETE'},true)} export function deleteKnowledge(id:number,expectedVersion:number){return apiRequest<void>(`/api/knowledge/${id}?expected_version=${expectedVersion}`,{method:'DELETE'},true)}
export function getKnowledgeHistory(id:number,page=1,pageSize=20){return apiRequest<{items:AuditItem[];total:number;page:number;page_size:number}>(`/api/knowledge/${id}/history?page=${page}&page_size=${pageSize}`)} export function getKnowledgeHistory(id:number,page=1,pageSize=20){return apiRequest<{items:AuditItem[];total:number;page:number;page_size:number}>(`/api/knowledge/${id}/history?page=${page}&page_size=${pageSize}`)}
export type SourceItem={id:number;version:number;name:string;source_url:string|null;notes:string;created_at:string} export type SourceItem={id:number;version:number;name:string;source_url:string|null;notes:string;created_at:string}
+11 -8
View File
@@ -1,19 +1,22 @@
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, ref } from 'vue' import { onMounted, ref } from 'vue'
import { deleteKnowledge, getKnowledgeHistory, getKnowledgePage, updateKnowledge, type AuditItem, type KnowledgeItem, type VerificationStatus } from '../api' import { deleteKnowledge, getKnowledgeHistory, getKnowledgePage, getSources, updateKnowledge, type AuditItem, type KnowledgeItem, type SourceItem, type VerificationStatus } from '../api'
import { collectedAtFromInput, localDateTimeInput } from '../utils/datetime' import { collectedAtFromInput, localDateTimeInput } from '../utils/datetime'
type Form={title:string;category:string;content:string;source_url:string;collected_at:string} type Form={title:string;category:string;content:string;source_id:string;source_url:string;collected_at:string}
const items=ref<KnowledgeItem[]>([]), selected=ref<KnowledgeItem>(), historyFor=ref<KnowledgeItem>(), historyItems=ref<AuditItem[]>([]) const items=ref<KnowledgeItem[]>([]), sources=ref<SourceItem[]>([]), selected=ref<KnowledgeItem>(), historyFor=ref<KnowledgeItem>(), historyItems=ref<AuditItem[]>([])
const query=ref(''), category=ref(''), status=ref<VerificationStatus|''>(''), page=ref(1), total=ref(0), loading=ref(true), saving=ref(false), historyLoading=ref(false), error=ref(''), notice=ref('') const query=ref(''), category=ref(''), status=ref<VerificationStatus|''>(''), page=ref(1), total=ref(0), loading=ref(true), saving=ref(false), historyLoading=ref(false), error=ref(''), notice=ref('')
const form=ref<Form>({title:'',category:'',content:'',source_url:'',collected_at:''}) const form=ref<Form>({title:'',category:'',content:'',source_id:'',source_url:'',collected_at:''})
function failure(cause:unknown,fallback:string){return cause instanceof Error?cause.message:fallback} function failure(cause:unknown,fallback:string){return cause instanceof Error?cause.message:fallback}
function validUrl(value:string){if(!value)return true;try{const url=new URL(value);return url.protocol==='http:'||url.protocol==='https:'}catch{return false}} function validUrl(value:string){if(!value)return true;try{const url=new URL(value);return url.protocol==='http:'||url.protocol==='https:'}catch{return false}}
async function refresh(next=page.value){loading.value=true;error.value='';try{const result=await getKnowledgePage({q:query.value,category:category.value,verification_status:status.value||undefined,page:next});items.value=result.items;page.value=result.page;total.value=result.total}catch(cause){error.value=failure(cause,'无法读取知识条目')}finally{loading.value=false}} async function refresh(next=page.value){loading.value=true;error.value='';try{const result=await getKnowledgePage({q:query.value,category:category.value,verification_status:status.value||undefined,page:next});items.value=result.items;page.value=result.page;total.value=result.total}catch(cause){error.value=failure(cause,'无法读取知识条目')}finally{loading.value=false}}
function edit(item:KnowledgeItem){selected.value=item;form.value={title:item.title,category:item.category,content:item.content,source_url:item.source_url??'',collected_at:item.collected_at?localDateTimeInput(item.collected_at):''};error.value='';notice.value=''} async function loadSources(){try{const collected:SourceItem[]=[];let next=1,total=0;do{const result=await getSources('',next,100);collected.push(...result.items);total=result.total;next+=1}while(collected.length<total);sources.value=collected}catch(cause){error.value=failure(cause,'无法读取已登记来源')}}
function sourceName(item:KnowledgeItem){if(!item.source_id)return '未关联';return sources.value.find(source=>source.id===item.source_id)?.name??`已关联来源 #${item.source_id}`}
function edit(item:KnowledgeItem){selected.value=item;form.value={title:item.title,category:item.category,content:item.content,source_id:item.source_id?String(item.source_id):'',source_url:item.source_url??'',collected_at:item.collected_at?localDateTimeInput(item.collected_at):''};error.value='';notice.value=''}
function chooseSource(){const source=sources.value.find(entry=>entry.id===Number(form.value.source_id));if(source)form.value.source_url=source.source_url??''}
function cancel(){selected.value=undefined} function cancel(){selected.value=undefined}
async function save(){if(!selected.value)return;error.value='';notice.value='';if(!form.value.title.trim()||!form.value.category.trim()||!form.value.content.trim()){error.value='标题、分类和正文不能为空。';return}if(!validUrl(form.value.source_url.trim())){error.value='来源网址必须是有效的 http(s) 地址。';return}saving.value=true;try{const payload={expected_version:selected.value.version,title:form.value.title.trim(),category:form.value.category.trim(),content:form.value.content.trim(),source_url:form.value.source_url.trim()||null,...(form.value.collected_at?{collected_at:collectedAtFromInput(form.value.collected_at,selected.value.collected_at??undefined)}: {})};await updateKnowledge(selected.value.id,payload);notice.value='知识条目已更新,后端已将其重新置为待审核。';cancel();await refresh()}catch(cause){error.value=failure(cause,'更新知识条目失败')}finally{saving.value=false}} async function save(){if(!selected.value)return;error.value='';notice.value='';if(!form.value.title.trim()||!form.value.category.trim()||!form.value.content.trim()){error.value='标题、分类和正文不能为空。';return}if(!validUrl(form.value.source_url.trim())){error.value='来源网址必须是有效的 http(s) 地址。';return}saving.value=true;try{const payload={expected_version:selected.value.version,title:form.value.title.trim(),category:form.value.category.trim(),content:form.value.content.trim(),...(selected.value.document_id===null?{source_id:form.value.source_id?Number(form.value.source_id):null,source_url:form.value.source_url.trim()||null}:{}),collected_at:form.value.collected_at?collectedAtFromInput(form.value.collected_at,selected.value.collected_at??undefined):null};await updateKnowledge(selected.value.id,payload);notice.value='知识条目已更新,后端已将其重新置为待审核。';cancel();await refresh()}catch(cause){error.value=failure(cause,'更新知识条目失败')}finally{saving.value=false}}
async function remove(item:KnowledgeItem){if(!window.confirm(`确定删除“${item.title}”吗?该操作为软删除,历史记录会保留。`))return;error.value='';notice.value='';try{await deleteKnowledge(item.id,item.version);notice.value='知识条目已软删除。';if(selected.value?.id===item.id)cancel();await refresh(items.value.length===1&&page.value>1?page.value-1:page.value)}catch(cause){error.value=failure(cause,'删除知识条目失败')}} async function remove(item:KnowledgeItem){if(!window.confirm(`确定删除“${item.title}”吗?该操作为软删除,历史记录会保留。`))return;error.value='';notice.value='';try{await deleteKnowledge(item.id,item.version);notice.value='知识条目已软删除。';if(selected.value?.id===item.id)cancel();await refresh(items.value.length===1&&page.value>1?page.value-1:page.value)}catch(cause){error.value=failure(cause,'删除知识条目失败')}}
async function showHistory(item:KnowledgeItem){historyFor.value=item;historyLoading.value=true;error.value='';try{historyItems.value=(await getKnowledgeHistory(item.id)).items}catch(cause){error.value=failure(cause,'无法读取知识历史');historyFor.value=undefined}finally{historyLoading.value=false}} async function showHistory(item:KnowledgeItem){historyFor.value=item;historyLoading.value=true;error.value='';try{historyItems.value=(await getKnowledgeHistory(item.id)).items}catch(cause){error.value=failure(cause,'无法读取知识历史');historyFor.value=undefined}finally{historyLoading.value=false}}
onMounted(()=>refresh(1)) onMounted(()=>{void refresh(1);void loadSources()})
</script> </script>
<template><main><RouterLink to="/admin">返回管理员工作台</RouterLink><h1>知识管理</h1><p>列表、编辑、删除和历史均直接调用管理员受保护接口;编辑后条目会由后端重新置为待审核。</p><p v-if="error" role="alert">{{error}}</p><p v-if="notice" role="status">{{notice}}</p><form @submit.prevent="refresh(1)"><label>关键词<input v-model="query"/></label><label>分类<input v-model="category"/></label><label>状态<select v-model="status"><option value="">全部</option><option value="pending">待审核</option><option value="verified">已核验</option><option value="rejected">已驳回</option></select></label><button>筛选</button></form><p v-if="loading">正在读取知识条目…</p><template v-else><table><thead><tr><th>标题</th><th>分类</th><th>状态</th><th>来源</th><th>操作</th></tr></thead><tbody><tr v-for="item in items" :key="item.id"><td><b>{{item.title}}</b><p>{{item.content.slice(0,120)}}</p></td><td>{{item.category}}</td><td>{{item.verification_status}}</td><td><a v-if="item.source_url" :href="item.source_url" target="_blank" rel="noreferrer">打开来源</a><span v-else>未填写</span></td><td><button @click="edit(item)">编辑</button><button @click="showHistory(item)">历史</button><button @click="remove(item)">删除</button></td></tr><tr v-if="!items.length"><td colspan="5">暂无符合条件的知识条目。</td></tr></tbody></table><div><button :disabled="page===1" @click="refresh(page-1)">上一页</button><span>第 {{page}} 页,共 {{total}} 条</span><button :disabled="page*20>=total" @click="refresh(page+1)">下一页</button></div></template><form v-if="selected" @submit.prevent="save"><h2>编辑:{{selected.title}}</h2><label>标题<input v-model.trim="form.title" required maxlength="250"/></label><label>分类<input v-model.trim="form.category" required maxlength="80"/></label><label>正文<textarea v-model.trim="form.content" required maxlength="100000" rows="6"/></label><label>来源网址<input v-model.trim="form.source_url" type="url" maxlength="1000" placeholder="https://..."/></label><label>采集时间<input v-model="form.collected_at" type="datetime-local" step="any"/></label><button type="button" :disabled="saving" @click="cancel">取消</button><button :disabled="saving">{{saving?'保存中…':'保存修改'}}</button></form><section v-if="historyFor"><h2>“{{historyFor.title}}”的历史</h2><button @click="historyFor=undefined">关闭历史</button><p v-if="historyLoading">正在读取历史…</p><table v-else><thead><tr><th>操作</th><th>时间</th><th>操作者</th><th>版本快照</th></tr></thead><tbody><tr v-for="item in historyItems" :key="item.id"><td>{{item.action}}</td><td>{{new Date(item.created_at).toLocaleString()}}</td><td>{{item.actor||'系统'}}</td><td><details><summary>查看快照</summary><pre>{{JSON.stringify(item.snapshot,null,2)}}</pre></details></td></tr><tr v-if="!historyItems.length"><td colspan="4">暂无历史记录。</td></tr></tbody></table></section></main></template> <template><main><RouterLink to="/admin">返回管理员工作台</RouterLink><h1>知识管理</h1><p>列表、编辑、删除和历史均直接调用管理员受保护接口;编辑后条目会由后端重新置为待审核。</p><p v-if="error" role="alert">{{error}}</p><p v-if="notice" role="status">{{notice}}</p><form @submit.prevent="refresh(1)"><label>关键词<input v-model="query"/></label><label>分类<input v-model="category"/></label><label>状态<select v-model="status"><option value="">全部</option><option value="pending">待审核</option><option value="verified">已核验</option><option value="rejected">已驳回</option></select></label><button>筛选</button></form><p v-if="loading">正在读取知识条目…</p><template v-else><table><thead><tr><th>标题</th><th>分类</th><th>状态</th><th>来源</th><th>操作</th></tr></thead><tbody><tr v-for="item in items" :key="item.id"><td><b>{{item.title}}</b><p>{{item.content.slice(0,120)}}</p></td><td>{{item.category}}</td><td>{{item.verification_status}}</td><td><p>{{sourceName(item)}}</p><a v-if="item.source_url" :href="item.source_url" target="_blank" rel="noreferrer">打开来源</a><span v-else>未填写</span></td><td><button @click="edit(item)">编辑</button><button @click="showHistory(item)">历史</button><button @click="remove(item)">删除</button></td></tr><tr v-if="!items.length"><td colspan="5">暂无符合条件的知识条目。</td></tr></tbody></table><div><button :disabled="page===1" @click="refresh(page-1)">上一页</button><span>第 {{page}} 页,共 {{total}} 条</span><button :disabled="page*20>=total" @click="refresh(page+1)">下一页</button></div></template><form v-if="selected" @submit.prevent="save"><h2>编辑:{{selected.title}}</h2><label>标题<input v-model.trim="form.title" required maxlength="250"/></label><label>分类<input v-model.trim="form.category" required maxlength="80"/></label><label>正文<textarea v-model.trim="form.content" required maxlength="100000" rows="6"/></label><label>已登记来源<select v-model="form.source_id" :disabled="selected.document_id!==null" @change="chooseSource"><option value="">不关联已登记来源</option><option v-for="source in sources" :key="source.id" :value="String(source.id)">{{source.name}}</option></select></label><p v-if="selected.document_id!==null" role="note">已关联文档 #{{selected.document_id}},来源由该文档确定,本页仅可修改其他知识字段。</p><label>来源网址<input v-model.trim="form.source_url" :readonly="selected.document_id!==null" type="url" maxlength="1000" placeholder="https://..."/><small v-if="selected.document_id!==null">来源网址由文档所属来源维护。</small><small v-else>选择已登记来源时会自动回填其链接;可保留为空,由后端按关联来源处理。</small></label><label>采集时间<input v-model="form.collected_at" type="datetime-local" step="any"/></label><button type="button" :disabled="saving" @click="cancel">取消</button><button :disabled="saving">{{saving?'保存中…':'保存修改'}}</button></form><section v-if="historyFor"><h2>“{{historyFor.title}}”的历史</h2><button @click="historyFor=undefined">关闭历史</button><p v-if="historyLoading">正在读取历史…</p><table v-else><thead><tr><th>操作</th><th>时间</th><th>操作者</th><th>版本快照</th></tr></thead><tbody><tr v-for="item in historyItems" :key="item.id"><td>{{item.action}}</td><td>{{new Date(item.created_at).toLocaleString()}}</td><td>{{item.actor||'系统'}}</td><td><details><summary>查看快照</summary><pre>{{JSON.stringify(item.snapshot,null,2)}}</pre></details></td></tr><tr v-if="!historyItems.length"><td colspan="4">暂无历史记录。</td></tr></tbody></table></section></main></template>