「可以拓展出哪些課程?」
使用者:中台課程企劃、主管。
- 輸出延伸/重組/全新課程,附受眾、學習目標、單元與時數。
- 對照既有課程、法規變更、市場趨勢與競品缺口。
- 每個建議帶原始文件、版本、頁碼/投影片與信心。
PostgreSQL 保存可稽核的正本知識卡,Neo4j 保存有證據的關係圖,OpenSearch 提供 chunk 級 BM25+語意混合檢索。圖與搜尋索引都是可重建的 projection,不反向成為真相來源。
設計底線:先權限、後檢索;先證據、後生成;只使用 approved 且未失效的內容。無足夠證據時必須拒答或明確標示「待人工查核」。
本頁是目標架構與資料契約,不代表任何資料庫、索引、排程或 connector 已上線。現有盤點基線為 2,345 筆,實際匯入前須依 manifest 重數、去重與對帳。
使用者:中台課程企劃、主管。
使用者:課程 PM、業務與主管。
使用者:中台企劃、聯誼會負責人。
flowchart LR classDef src fill:#e4f3f7,stroke:#253047,stroke-width:2px,color:#172033; classDef core fill:#f6d0c4,stroke:#cf5f3a,stroke-width:3px,color:#172033,font-weight:bold; classDef proj fill:#e5ddf1,stroke:#7c6fab,stroke-width:2px,color:#172033,font-weight:bold; classDef gate fill:#dcecd0,stroke:#5f9b75,stroke-width:3px,color:#172033; SRC["內外部來源
connectors/檔案/API"]:::src --> ING["進料、正規化、驗證"]:::src ING --> PG[("PostgreSQL
正本 CardEnvelope/版本/ACL/審核")]:::core PG --> OUT["transactional outbox"]:::core OUT --> OS[("OpenSearch
ChunkProjection/BM25/向量")]:::proj OUT --> NEO[("Neo4j
RelationshipAssertion/證據圖")]:::proj PG --> GOV{"審核與時效閥門"}:::gate OS --> QA["混合檢索與問答"]:::gate NEO --> QA GOV --> QA QA --> AUD[("引用/稽核紀錄")]:::core
卡片內容、版本、content hash、來源、ACL、狀態與審核紀錄。所有變更先在單一 transaction 寫入卡片與 outbox。
只接受通過契約驗證的 RelationshipAssertion;每個主張可逆查到 chunk 與文件。可由 PostgreSQL 全量重建。
保存可搜尋文字、欄位、ACL projection 與 embedding;回傳 chunkId 不當作最終證據正本。
eventId 冪等,projection 延遲可觀測,也可一鍵重放。.ppt/.pptx)/PDF:課程計畫、講義、教材與附件。FSC(金管會公開資訊)、TFF(本會公開課程)、TABF(金研院)、SFI(證基會)、TII(保發中心)。
除此以外均不得標示為現行 connector。內部檔案仍須走受管上傳、權限確認與人工核可,不因位於共享槽就視為可自動進料。
每個 connector 必須先登錄 owner、使用條款、robots/API 限制、擷取頻率、去重鍵、schema mapping、失敗告警與停用程序;完成驗收前只列為 planned。
FPAT 名稱待確認,禁止排程與自動進料。
| 目錄 | 完整來源清單 | connector 狀態 |
|---|---|---|
| 內部來源 | 講師名單、課程清單、Word/PowerPoint(PPT/PPTX)/PDF、課程成效、研討會主題、風格樣本。 | 受管上傳/批次匯入候選;逐批確認 ACL、授權與 owner。 |
| 外部來源 | 金管會、央行、證交所、台經院、中經院、公開資訊觀測站、行政院、國發會、財政部、數位發展部、經濟日報、工商時報、公司治理平台、FPAT(名稱待確認、不得自動進料)、四大會計師事務所、智庫、金研院、保發中心、證基會、犯防中心、信託公會、公司治理協會、永續金融評鑑平台、各金融機構(銀保證)公開網頁。 | FSC/TABF/SFI/TII 為現況;其餘逐站規劃。TFF 為現況的本會公開來源。 |
| 來源預設側寫 | 典型來源 | 使用規則 |
|---|---|---|
| A 官方/一手 | 主管機關、政府機關、法規與依法揭露平台。 | 只表示該來源通常適合提供官方事實;不能取代主張層級的適用性判斷。 |
| B 專業/研究 | 研究機構、智庫、專業機構、評鑑與會計專業內容。 | 通常用於解釋、方法與趨勢佐證;仍須檢查日期、方法與原始證據。 |
| C 媒體/市場訊號 | 新聞媒體、金融機構公開網頁與一般公開頁面。 | 適合發現議題與市場熱度,不應單獨承擔高風險法規結論。 |
defaultAuthority 預設側寫;每一個 RelationshipAssertion 都必須另存 authorityFit、支持/反駁方向 polarity、證據 chunk 與人工 reviewStatus,依「這個來源是否有權、且是否足以支持這個主張」判定。相同來源可對不同主張得到不同結果,衝突時並列並送人工審核,不以預設級別自動勝出。
flowchart LR
classDef src fill:#e4f3f7,stroke:#253047,stroke-width:2px,color:#172033;
classDef proc fill:#fff7e4,stroke:#253047,stroke-width:2px,color:#172033;
classDef gate fill:#dcecd0,stroke:#5f9b75,stroke-width:3px,color:#172033;
classDef store fill:#f6d0c4,stroke:#cf5f3a,stroke-width:3px,color:#172033,font-weight:bold;
classDef proj fill:#e5ddf1,stroke:#7c6fab,stroke-width:2px,color:#172033,font-weight:bold;
classDef err fill:#f7d7cf,stroke:#b85c63,stroke-width:2px,color:#172033;
SRC["內部受管資料/已核准 connector"]:::src --> RAW[("raw snapshot
原始位元組+擷取 manifest")]:::store
RAW --> DRAFT["draft card
先建不可檢索草稿"]:::proc
DRAFT --> PARSE["parse/OCR
版面、頁碼、投影片定位"]:::proc
PARSE --> SEC["PII/ACL
偵測、分級、遮罩與權限"]:::proc
SEC --> EXT["chunk/entity/assertion extraction
切塊、實體消歧、候選主張"]:::proc
EXT --> HUMAN{"human approval
內容、證據、權限、有效期"}:::gate
HUMAN -->|核准| PG[("PostgreSQL
CardEnvelope/版本/審核")]:::store
HUMAN -->|退回補正| DRAFT
PG --> OUT[("transactional outbox")]:::store
OUT --> OS[("OpenSearch
ChunkProjection")]:::proj
OUT --> NEO[("Neo4j
RelationshipAssertion")]:::proj
PARSE -.可重試失敗.-> RETRY["retry
退避、上限、idempotency key"]:::err
SEC -.可重試失敗.-> RETRY
EXT -.可重試失敗.-> RETRY
OUT -.投影失敗.-> RETRY
RETRY -->|未達上限| AGAIN["回到原失敗步驟"]:::proc
AGAIN --> PARSE
RETRY -->|超過上限| DLQ[("DLQ
保留 payload、錯誤與 traceId")]:::err
DLQ --> REPLAY["人工修正/核准 replay"]:::gate
REPLAY --> RAW
idempotencyKey。DLQ 重放仍須重新執行 PII/ACL 與人工核可,projection 可由 PostgreSQL 正本與 outbox 全量重建。identity
cardId, cardType, schemaVersion
canonicalId, externalIds[]
content
title, body, language
taxonomy, attributes
source + lineage
sourceId, sourceUrl, contentHash
rawSnapshotUri, documentId
parentCardId, supersedes, version
publishedAt, observedAt, ingestedAt
validFrom, validTo, isCurrent
governance
status, owner, reviewer
reviewedAt, reviewDueAt
aclGroups[], piiLevel, confidentiality
license, copyrightOwner, retentionPolicy
audit
createdAt, createdBy, updatedAt, traceIdidentity + join
chunkId, cardId, cardVersion, cardType
sourceId, documentId, contentHash
text + location
text, title, language
pageNumber, slideNumber, sectionPath
quoteSpan, tokenCount
retrieval
keywords[], category[], tags[], synonyms[]
embedding, embeddingModel, embeddingVersion
dimensions, chunkStrategyVersion
indexVersion, indexedAt
security + lifecycle
aclGroups[], piiLevel, piiStatus, confidentiality
status, validFrom, validTo, isCurrent
publishedAt, observedAtidentity + endpoints
relationId, subjectId, predicate
objectId, objectType
claim semantics
polarity, confidence
defaultAuthority, authorityFit
validFrom, validTo
evidence + extraction
evidenceChunkIds[]
extractionMethod, extractionModel, extractionVersion
corroboration, conflictStatus
governance
reviewStatus, reviewer, reviewedAt
sourceCardId, sourceCardVersion
schemaVersion, createdAt, traceId同一 cardId 的版本採 append-only;contentHash 與來源外部 ID 共同支援冪等與去重。只有 approved、isCurrent=true、仍在有效期且 ACL 允許的版本能進 outbox。退件、撤銷與到期保留稽核軌跡,不覆寫歷史。
它是 CardEnvelope 的可丟棄讀模型,不是新真相來源。chunk 必須能以 cardId + cardVersion + quoteSpan 回到正本;先套 ACL、有效期與核可狀態,再做 BM25/向量召回。模型、切塊或索引版本變更時可全量重建。
每條邊都是可審核主張,不是無來源的關聯。至少一個有效 evidenceChunkIds,並保存主張專屬 authorityFit、方向 polarity 與有效期;只有 reviewStatus=approved 的邊可供正式回答,衝突邊並存並交由人判定。
eventId/relationId/chunkId 冪等。Neo4j 與 OpenSearch 不接受繞過 CardEnvelope 審核流程的直接寫入,任何 citation 都必須可逆查到 raw snapshot、文件版本與原始位置。flowchart LR ORG[Organization] -->|PUBLISHED| DOC[Document] DOC -->|DERIVED_FROM| SRC[Source] DOC -->|HAS_CHUNK| CH[Chunk] CH -->|NEXT_CHUNK| CH2[Chunk] CH -->|MENTIONS| TOP[Topic] AS[Assertion] -->|SUBJECT| TOP AS -->|OBJECT| REG[Regulation] AS -->|SUPPORTED_BY| CH TREND[Trend] -->|IMPACTS| TOP COURSE[Course] -->|HAS_MODULE| MOD[CourseModule] COURSE -->|HAS_OFFERING| OFF[CourseOffering] COURSE -->|COVERS| TOP COURSE -->|TARGETS| AUD[Audience] COURSE -->|SATISFIES| COMP[Competency] COURSE -->|USES| MAT[Material] OFF -->|TAUGHT_BY| INS[Instructor] INS -->|EXPERT_IN| TOP SEM[SeminarTheme] -->|DERIVED_FROM| TREND SEM -->|NAMED_BY| STYLE[StyleProfile]
| 類別 | Ontology 白名單 |
|---|---|
| Nodes | Organization · Source · Document · Chunk · Assertion · Topic · Trend · Regulation · Course · CourseOffering · CourseModule · Instructor · Material · SeminarTheme · Audience · Competency · StyleProfile |
| Relationships | PUBLISHED · HAS_CHUNK · NEXT_CHUNK · MENTIONS · SUBJECT · OBJECT · SUPPORTED_BY · HAS_OFFERING · HAS_MODULE · COVERS · TAUGHT_BY · EXPERT_IN · USES · TARGETS · SATISFIES · IMPACTS · DERIVED_FROM · NAMED_BY |
| Edge minimum | relationId · confidence · evidenceChunkIds · reviewStatus · validFrom/To · extractionMethod/version |
Assertion 中介節點,不建無來源的 RELATED_TO。Course 放穩定課程設計;CourseOffering 才放開課日、講師、價格、形式、招生與評價。CREATE CONSTRAINT document_id IF NOT EXISTS
FOR (d:Document) REQUIRE d.id IS UNIQUE;
CREATE CONSTRAINT chunk_id IF NOT EXISTS
FOR (c:Chunk) REQUIRE c.id IS UNIQUE;
CREATE CONSTRAINT assertion_id IF NOT EXISTS
FOR (a:Assertion) REQUIRE a.id IS UNIQUE;// 呼叫前由應用 schema 拒絕 null/空字串 ID;
// UNIQUE constraint 防重,但不代表屬性必然存在。
MERGE (d:Document {id: $documentId})
ON CREATE SET d.createdAt = datetime()
SET d.revisionId = $revisionId,
d.status = $status;
MERGE (c:Chunk {id: $chunkId})
SET c.text = $text;
MERGE (d)-[r:HAS_CHUNK]->(c)
SET r.relationId = $lineageRelationId,
r.confidence = 1.0,
r.evidenceChunkIds = [$chunkId],
r.reviewStatus = 'approved',
r.extractionMethod = 'deterministic-parser',
r.extractionVersion = $parserVersion;MATCH (s:Trend {id: $subjectId}),
(o:Topic {id: $objectId})
WITH s, o, $evidenceChunkIds AS evidenceChunkIds
WHERE size(evidenceChunkIds) > 0
UNWIND evidenceChunkIds AS evidenceChunkId
MATCH (c:Chunk {id: evidenceChunkId})
WITH s, o, evidenceChunkIds,
collect(DISTINCT c) AS evidenceChunks
WHERE size(evidenceChunks) = size(evidenceChunkIds)
MERGE (a:Assertion {id: $relationId})
SET a.predicate = $validatedPredicate,
a.confidence = $confidence,
a.polarity = $polarity,
a.authorityFit = $authorityFit,
a.evidenceChunkIds = $evidenceChunkIds,
a.reviewStatus = $reviewStatus,
a.extractionMethod = $extractionMethod,
a.extractionModel = $extractionModel,
a.extractionVersion = $extractionVersion,
a.validFrom = CASE WHEN $validFrom IS NULL
THEN null ELSE datetime($validFrom) END,
a.validTo = CASE WHEN $validTo IS NULL
THEN null ELSE datetime($validTo) END
MERGE (a)-[:SUBJECT]->(s)
MERGE (a)-[:OBJECT]->(o)
FOREACH (c IN evidenceChunks |
MERGE (a)-[:SUPPORTED_BY]->(c)
);UNWIND $hits AS hit
MATCH (c:Chunk {id: hit.chunkId})
MATCH (d:Document)-[:HAS_CHUNK]->(c)
OPTIONAL MATCH (a:Assertion)-[:SUPPORTED_BY]->(c)
WHERE a.reviewStatus = 'approved'
AND (a.validFrom IS NULL OR a.validFrom <= datetime())
AND (a.validTo IS NULL OR datetime() < a.validTo)
OPTIONAL MATCH (a)-[:SUBJECT|OBJECT]->(e)
RETURN hit.rank, c.id, d.id,
[item IN collect(DISTINCT CASE WHEN a IS NULL
THEN null ELSE {
assertionId: a.id, entityId: e.id,
polarity: a.polarity,
conflictStatus: a.conflictStatus,
confidence: a.confidence
} END)
WHERE item IS NOT NULL] AS evidenceGraph;IS UNIQUE 防止重複。evidenceChunkIds 同時要求 minItems: 1 與 uniqueItems: true;Cypher 只有在全部 chunk 存在時才建立 Assertion,服務層必須把零筆更新視為驗證失敗,而非成功。$validatedPredicate 必須先通過 ontology 白名單,不接受使用者自由輸入 label 或 relationship type。| 用途 | Physical index | Stable alias |
|---|---|---|
| 來源/文件級投影 | tff-kb-sources-v1 | tff-kb-sources-read |
| chunk 全文/向量/ACL | tff-kb-chunks-v1 | tff-kb-chunks-read |
以下 768 是可解析的範例值;正式 dimension 必須與選定 embedding model 完全一致。範例中的 cjk 只代表待評測的候選 mapping,不代表已勝出;若 ICU 測試較佳,須以已安裝並鎖版的 ICU analyzer 建立新版本索引。mapping 改型別或更換模型時建 v2、reindex,驗證後以 POST /_aliases 原子切換。
{
"settings": { "index.knn": true },
"mappings": {
"dynamic": "strict",
"properties": {
"chunk_id": { "type": "keyword" },
"card_id": { "type": "keyword" },
"revision_id": { "type": "keyword" },
"card_type": { "type": "keyword" },
"source_id": { "type": "keyword" },
"document_id": { "type": "keyword" },
"language": { "type": "keyword" },
"title": {
"type": "text", "analyzer": "cjk",
"fields": { "raw": { "type": "keyword", "ignore_above": 512 } }
},
"content": { "type": "text", "analyzer": "cjk" },
"section_path": { "type": "text", "analyzer": "cjk" },
"topic_ids": { "type": "keyword" },
"neo4j_node_ids": { "type": "keyword" },
"keywords": { "type": "keyword" },
"category": { "type": "keyword" },
"tags": { "type": "keyword" },
"synonyms": { "type": "keyword" },
"status": { "type": "keyword" },
"is_current": { "type": "boolean" },
"pii_level": { "type": "keyword" },
"pii_status": { "type": "keyword" },
"confidentiality": { "type": "keyword" },
"acl_groups": { "type": "keyword" },
"page_number": { "type": "integer" },
"slide_number": { "type": "integer" },
"quote_span": {
"properties": {
"start": { "type": "integer" },
"end": { "type": "integer" }
}
},
"valid_from": { "type": "date" },
"valid_to": { "type": "date" },
"published_at": { "type": "date" },
"observed_at": { "type": "date" },
"content_hash": { "type": "keyword" },
"embedding_model_id": { "type": "keyword" },
"embedding_version": { "type": "keyword" },
"embedding_dimensions": { "type": "integer" },
"chunk_strategy_version": { "type": "keyword" },
"token_count": { "type": "integer" },
"index_version": { "type": "keyword" },
"indexed_at": { "type": "date" },
"embedding": {
"type": "knn_vector", "dimension": 768,
"method": {
"name": "hnsw", "engine": "lucene",
"space_type": "cosinesimil",
"parameters": { "ef_construction": 100, "m": 16 }
}
}
}
}
}Serializer 契約:canonical camelCase 轉成 index snake_case,例如 cardVersion→revision_id、text→content、embeddingModel→embedding_model_id;pii_status 是由核可流程產生的安全投影欄位。送入 dynamic: strict 前先以同版 JSON Schema 驗證,不傳未映射欄位。
{
"phase_results_processors": [
{
"normalization-processor": {
"normalization": { "technique": "min_max" },
"combination": {
"technique": "arithmetic_mean",
"parameters": { "weights": [0.4, 0.6] }
}
}
}
]
}{
"size": 20,
"_source": { "excludes": ["embedding"] },
"query": {
"hybrid": {
"filter": {
"bool": { "filter": [
{ "term": { "status": "approved" } },
{ "term": { "is_current": true } },
{ "term": { "pii_status": "cleared" } },
{ "bool": { "should": [
{ "bool": { "must_not": { "exists": { "field": "valid_from" } } } },
{ "range": { "valid_from": { "lte": "now" } } }
], "minimum_should_match": 1 } },
{ "bool": { "should": [
{ "bool": { "must_not": { "exists": { "field": "valid_to" } } } },
{ "range": { "valid_to": { "gt": "now" } } }
], "minimum_should_match": 1 } },
{ "terms": { "acl_groups": ["ACL_GROUP_FROM_TRUSTED_ADAPTER"] } }
] }
},
"queries": [
{ "multi_match": {
"query": "永續金融公司治理課程",
"fields": ["title^3", "section_path^2", "content"]
} },
{ "neural": { "embedding": {
"query_text": "永續金融公司治理課程",
"model_id": "DEPLOYED_MODEL_ID", "k": 100
} } }
]
}
}
}{
"actions": [
{ "remove": {
"index": "tff-kb-chunks-v1",
"alias": "tff-kb-chunks-read"
} },
{ "add": {
"index": "tff-kb-chunks-v2",
"alias": "tff-kb-chunks-read"
} }
]
}| 安全層 | 部署契約 | 不可替代項 |
|---|---|---|
| DLS | approved ∧ is_current ∧ effective-now ∧ pii_status=cleared ∧ acl_groups∩effectiveGroups≠∅ | 由 Security plugin role 或可信 adapter 強制;群組只取自登入身分,不採用 prompt/前端參數。 |
| FLS | 回答服務採 allowlist,只回傳正文、引用定位、核可後實體 ID 與可揭露 metadata;隱藏 embedding、原始 PII 與治理內欄位。 | _source.excludes 只是縮小回應,不是 FLS。 |
| 版本鎖定 | 正式上線前固定 OpenSearch/Security plugin version、edition 與 role API,建立未授權帳號的負向測試。 | 未完成鎖版前,不把示意 query 當 production role 設定。 |
POST /_analyze 與 TFF 金融詞彙判斷集比較 Recall@k、MRR/nDCG 與引用正確率。查詢中的 ACL_GROUP_FROM_TRUSTED_ADAPTER 是 server-side 注入位置,不可原樣送出,也不可取自使用者問題。ACL 在召回前過濾,並以 DLS 限制可見文件、FLS 隱藏無需回傳的敏感欄位;未採用 Security plugin 時,必須由可信服務層執行同等政策。post-filter、filtered alias 與前端 filter 都不是資安邊界。_bulk 寫入新的 physical index,逐筆檢查 bulk item error 並依 chunk_id+revision_id 冪等重試;需要回灌時使用 _reindex 或由 PostgreSQL 正本全量重建。完成 mapping、ACL、抽樣查詢與引用驗證後,在同一個 POST /_aliases action 內移除舊索引並加入新索引,才算 atomic alias switch。
flowchart LR
Q["使用者問題/一句趨勢"] --> I["意圖/實體/同義詞正規化"]
I --> O["OpenSearch BM25+vector+ACL prefilter"]
O --> H["hits: chunkId/rank/score"]
H --> N["Neo4j 展開主題/法規/課程/講師/Assertion"]
N --> F["RRF/各源正規化"]
F --> RR["rerank/交叉佐證/衝突保留"]
RR --> G{"approved/未失效/ACL 可見/PII 已處理?"}
G -->|yes| A["答案+理由+頁碼/投影片引用+信心"]
G -->|no| GAP["資料不足/無權限/待人工查核"]
approved、未失效、未被 supersede 且 ACL 可見的 revision。RELATED_TO。建正本契約、解析 Word/PPT/PDF、整理講師與 CourseOffering,完成三個黃金查詢與引用驗收。
逐站完成授權/更新頻率,加入法規失效、衝突與交叉佐證治理,建立 ICU/CJK 與混合權重判斷集。
將人工採用、招生、滿意度、續約與點閱回饋模型,監測檢索漂移、資料鮮度與 projection lag。