feat(aihr): add experience library and community best answers as ask sources

- server-side employee position resolution (org snapshot) with hard
  position gate for EXPERIENCE/CASE sources; formal sources unaffected;
  unresolvable position fails closed to general-scope only
- merge published experiences (aihr_case_record, CASE_EXP) and moderated
  best answers (COMMUNITY, no submitter identity) into the citation pool
  as REFERENCE tier; auxiliary sources never alter formal answers and
  fail without breaking the main chain; grading re-grades ROUTE into
  GUIDANCE when only auxiliary evidence exists
- case recall endpoints enforce the same position gate
- moderator endpoint to promote a best answer into a CANDIDATE case
  record (idempotent via source_ref unique key, never auto-published)
- migration aihr_20260822_case_source_ref_and_best_answer_index_mysql8.sql
- COMMUNITY citations carry community-question:{id} in detailRef for
  direct navigation
This commit is contained in:
2026-08-05 02:45:57 +08:00
parent cd3acc90be
commit 8f9c244aeb
22 changed files with 1578 additions and 57 deletions
@@ -0,0 +1,74 @@
-- Community best-answer to experience-candidate traceability and auxiliary retrieval indexes.
-- Additive and idempotent on MySQL 8. The new source_ref column keeps the originating
-- community answer id so the moderator-triggered conversion stays idempotent; the unique key
-- only protects (tenant, source_type, source_ref) triples and allows multiple NULL refs.
-- No existing row is approved, published or modified by this migration.
SET @aihr_case_source_ref_table_exists := (
SELECT COUNT(*)
FROM information_schema.tables
WHERE table_schema = DATABASE()
AND table_name = 'aihr_case_record'
);
SET @aihr_case_source_ref_column_exists := (
SELECT COUNT(*)
FROM information_schema.columns
WHERE table_schema = DATABASE()
AND table_name = 'aihr_case_record'
AND column_name = 'source_ref'
);
SET @aihr_case_source_ref_ddl := IF(
@aihr_case_source_ref_table_exists = 1 AND @aihr_case_source_ref_column_exists = 0,
'ALTER TABLE `aihr_case_record` ADD COLUMN `source_ref` varchar(80) DEFAULT NULL COMMENT ''经验来源引用(如问题榜回答ID)'' AFTER `source_type`',
'SELECT 1'
);
PREPARE aihr_case_source_ref_stmt FROM @aihr_case_source_ref_ddl;
EXECUTE aihr_case_source_ref_stmt;
DEALLOCATE PREPARE aihr_case_source_ref_stmt;
SET @aihr_case_source_ref_index_exists := (
SELECT COUNT(*)
FROM information_schema.statistics
WHERE table_schema = DATABASE()
AND table_name = 'aihr_case_record'
AND index_name = 'uk_aihr_case_record_source'
);
SET @aihr_case_source_ref_ddl := IF(
@aihr_case_source_ref_table_exists = 1 AND @aihr_case_source_ref_index_exists = 0,
'ALTER TABLE `aihr_case_record` ADD UNIQUE KEY `uk_aihr_case_record_source` (`tenant_id`, `source_type`, `source_ref`)',
'SELECT 1'
);
PREPARE aihr_case_source_ref_stmt FROM @aihr_case_source_ref_ddl;
EXECUTE aihr_case_source_ref_stmt;
DEALLOCATE PREPARE aihr_case_source_ref_stmt;
SET @aihr_community_best_table_exists := (
SELECT COUNT(*)
FROM information_schema.tables
WHERE table_schema = DATABASE()
AND table_name = 'aihr_community_answer'
);
SET @aihr_community_best_index_exists := (
SELECT COUNT(*)
FROM information_schema.statistics
WHERE table_schema = DATABASE()
AND table_name = 'aihr_community_answer'
AND index_name = 'idx_aihr_community_answer_best'
);
SET @aihr_community_best_ddl := IF(
@aihr_community_best_table_exists = 1 AND @aihr_community_best_index_exists = 0,
'ALTER TABLE `aihr_community_answer` ADD KEY `idx_aihr_community_answer_best` (`tenant_id`, `is_best`, `answer_status`)',
'SELECT 1'
);
PREPARE aihr_community_best_stmt FROM @aihr_community_best_ddl;
EXECUTE aihr_community_best_stmt;
DEALLOCATE PREPARE aihr_community_best_stmt;
SET @aihr_case_source_ref_table_exists := NULL;
SET @aihr_case_source_ref_column_exists := NULL;
SET @aihr_case_source_ref_index_exists := NULL;
SET @aihr_case_source_ref_ddl := NULL;
SET @aihr_community_best_table_exists := NULL;
SET @aihr_community_best_index_exists := NULL;
SET @aihr_community_best_ddl := NULL;