@geoly-ai/social-hub-sdk 0.1.12 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -132,6 +132,8 @@ export type ListCalendarEntriesParams = {
132
132
  from?: string;
133
133
  to?: string;
134
134
  limit?: number;
135
+ /** 服务端上限 200;超过就得靠它翻页,别用时间窗二分绕(会漏)。 */
136
+ offset?: number;
135
137
  };
136
138
  /** Body for PATCH /calendar-entries/:id */
137
139
  export type UpdateCalendarEntryBody = {
@@ -196,6 +198,12 @@ export type ListRedditPostSnapshotsParams = {
196
198
  repostType?: string;
197
199
  containsBrandKeyword?: "true" | "false" | "unset";
198
200
  primaryProduct?: string;
201
+ /**
202
+ * 序列号闭区间 `seqNoMin <= seqNo <= seqNoMax`(两端各自可选)。
203
+ * 任一端给定时,`seqNo` 为 null 的行都会落选;min > max 是 400。
204
+ */
205
+ seqNoMin?: number;
206
+ seqNoMax?: number;
199
207
  /** 时间段(与 stats 同口径:COALESCE(postedAt, capturedAt));dateFrom/dateTo 优先。 */
200
208
  range?: number | "all";
201
209
  dateFrom?: string;
@@ -215,6 +223,9 @@ export type RedditPostSnapshotStatsParams = {
215
223
  repostType?: string;
216
224
  containsBrandKeyword?: "true" | "false" | "unset";
217
225
  primaryProduct?: string;
226
+ /** 序列号闭区间(与列表同口径)。 */
227
+ seqNoMin?: number;
228
+ seqNoMax?: number;
218
229
  range?: number | "all";
219
230
  /** Custom period bounds (YYYY-MM-DD, inclusive). Take precedence over range. */
220
231
  dateFrom?: string;
@@ -732,6 +743,173 @@ export type SubredditMonitorConfig = {
732
743
  fetchLimit: number;
733
744
  createdAt: string;
734
745
  updatedAt: string;
746
+ /** 当前生效的关键词规则集(0168);null = **不设关键词门**,报告放行全部新帖。 */
747
+ activeRuleSetId?: string | null;
748
+ activeRuleSetVersion?: number | null;
749
+ /** 该监听的回看窗口(分钟)。无 active 规则集 → 720(12h)。 */
750
+ lookbackMinutes?: number;
751
+ };
752
+ export type SubredditMonitorRuleScope = "title" | "body" | "both";
753
+ /**
754
+ * `word` 带词边界(🔴 `art` 不会命中 `earth`);`substring` 明确放弃边界;
755
+ * `phrase` 词序固定、中间允许任意标点换行。
756
+ * ⚠️ CJK 没有词边界概念,`word` 在 CJK 上退化为子串 —— 否则「咖啡」永远匹配不上「咖啡机」。
757
+ */
758
+ export type SubredditMonitorMatchMode = "word" | "substring" | "phrase";
759
+ export type SubredditMonitorKeywordRuleInput = {
760
+ /** 原样字面值;服务端归一化(NFKC / 去重音 / 小写)后才进唯一键。 */
761
+ term: string;
762
+ scope?: SubredditMonitorRuleScope;
763
+ matchMode?: SubredditMonitorMatchMode;
764
+ /** 只影响卡片里命中词的展示顺序,不影响是否命中(本期只做 include)。 */
765
+ weight?: number;
766
+ /** 品牌词导入的**来源溯源**;⛔ 不建立同步语义。 */
767
+ sourceBrandKeywordId?: string | null;
768
+ sourceBrandKeywordValue?: string | null;
769
+ };
770
+ export type SubredditMonitorKeywordRule = Required<Pick<SubredditMonitorKeywordRuleInput, "term">> & {
771
+ id: string;
772
+ ruleSetId: string;
773
+ normalizedTerm: string;
774
+ scope: SubredditMonitorRuleScope;
775
+ /** 本期只有 `include`(排除词是 P2,契约与 DDL 层都表达不出来)。 */
776
+ polarity: "include";
777
+ matchMode: SubredditMonitorMatchMode;
778
+ weight: number;
779
+ sourceBrandKeywordId: string | null;
780
+ sourceBrandKeywordValue: string | null;
781
+ };
782
+ export type SubredditMonitorRuleSet = {
783
+ id: string;
784
+ monitorId: string;
785
+ version: number;
786
+ /** 🔴 `active` 之后不可原地修改,只能新建版本。`retired` 是终态。 */
787
+ status: "draft" | "active" | "retired";
788
+ lookbackMinutes: number;
789
+ /** 激活时物化的规则指纹;draft 恒为 null。 */
790
+ ruleHash: string | null;
791
+ note: string | null;
792
+ ruleCount: number;
793
+ createdBy: string | null;
794
+ activatedBy: string | null;
795
+ retiredBy: string | null;
796
+ createdAt: string;
797
+ updatedAt: string;
798
+ activatedAt: string | null;
799
+ retiredAt: string | null;
800
+ };
801
+ export type SubredditMonitorRuleSetDetail = SubredditMonitorRuleSet & {
802
+ rules: SubredditMonitorKeywordRule[];
803
+ };
804
+ /**
805
+ * 近义词扩展集(0169)。🔴 **固化的**词集 —— approved 之后内容不可原地改。
806
+ *
807
+ * ⛔ 它**不参与运行时匹配**:approved 的词要被显式**物化**(复制)成真实的
808
+ * keyword rule 行才会生效。生产匹配路径上没有任何即时展开。
809
+ */
810
+ export type SubredditMonitorExpansionSet = {
811
+ id: string;
812
+ monitorId: string;
813
+ baseTerm: string;
814
+ normalizedBaseTerm: string;
815
+ /** BCP-47 风格;`und` = 未指定。同一原词在不同语言下各批一套。 */
816
+ language: string;
817
+ /** 🔴 固化的扩展词。approved 之后一个字都改不了。 */
818
+ expansions: Array<{
819
+ term: string;
820
+ normalizedTerm: string;
821
+ }>;
822
+ expansionCount: number;
823
+ /** 固化内容的指纹;激活规则集时会被折进 `ruleHash` 的内容摘要。 */
824
+ contentHash: string;
825
+ model: string | null;
826
+ promptVersion: string | null;
827
+ generatedAt: string | null;
828
+ /**
829
+ * 🔴 `proposed` 进不了任何 active 规则集。
830
+ * `retired` 有两种来历:从 `proposed` 来是**否决**(`approvedAt` 恒为 null,
831
+ * 永远进不了 active);从 `approved` 来是**下架**(`approvedAt` 仍在,
832
+ * 已复制出去的规则行不受影响)。两者只有 `approvedAt` 分得开。
833
+ */
834
+ status: "proposed" | "approved" | "retired";
835
+ approvedBy: string | null;
836
+ approvedAt: string | null;
837
+ retiredBy: string | null;
838
+ retiredAt: string | null;
839
+ reviewNote: string | null;
840
+ createdBy: string | null;
841
+ createdAt: string;
842
+ updatedAt: string;
843
+ };
844
+ /**
845
+ * 标签 spec / taxonomy(0169,系统级)。
846
+ * ⚠️ `taxonomyVersion` **全局唯一** —— 标签行的唯一键带的是它。
847
+ */
848
+ export type SubredditMonitorLabelSpec = {
849
+ id: string;
850
+ version: number;
851
+ status: "draft" | "active" | "retired";
852
+ taxonomyVersion: string;
853
+ /** 稳定 key 白名单。模型自创的 key 一律丢弃。 */
854
+ labelKeys: string[];
855
+ promptVersion: string;
856
+ model: string | null;
857
+ temperature: number | null;
858
+ specHash: string | null;
859
+ note: string | null;
860
+ createdBy: string | null;
861
+ activatedBy: string | null;
862
+ retiredBy: string | null;
863
+ createdAt: string;
864
+ updatedAt: string;
865
+ activatedAt: string | null;
866
+ retiredAt: string | null;
867
+ };
868
+ export type SubredditMonitorDryRunResult = {
869
+ monitorId: string;
870
+ ruleSetId: string;
871
+ ruleSetVersion: number;
872
+ ruleSetStatus: "draft" | "active" | "retired";
873
+ subreddit: string;
874
+ lookbackMinutes: number;
875
+ /**
876
+ * 样本量。⚠️ 必须先看它:`sampled === 0` 时「0 命中」说明的是**没有样本**
877
+ * (板块太冷 / 库里没这段时间的数据),不是「规则太窄」—— 两者在结果列表里长得一样。
878
+ */
879
+ sampled: number;
880
+ matched: number;
881
+ /** 正文拿不到、判不了的条数。**不算不命中**,上线后会照发并在卡片上标注。 */
882
+ indeterminate: number;
883
+ screenedOut: number;
884
+ /** 逐词命中数:一眼看出哪个词过宽(几乎命中所有帖子)。 */
885
+ termHitCounts: Array<{
886
+ term: string;
887
+ count: number;
888
+ }>;
889
+ /** 一次都没命中的词:配错/拼错的第一手信号。 */
890
+ unusedTerms: string[];
891
+ results: Array<{
892
+ permalink: string;
893
+ title: string | null;
894
+ postedAt: string | null;
895
+ score: number | null;
896
+ commentsCount: number | null;
897
+ decision: "matched" | "indeterminate" | "no_match";
898
+ matchedTerms: string[];
899
+ matchedFields: Array<"title" | "body">;
900
+ evidence: Array<{
901
+ ruleId: string;
902
+ term: string;
903
+ field: "title" | "body";
904
+ matchMode: SubredditMonitorMatchMode;
905
+ weight: number;
906
+ start: number;
907
+ end: number;
908
+ snippet: string;
909
+ }>;
910
+ bodyAvailability: "present" | "absent_link_post" | "missing";
911
+ indeterminateReason: "body_missing" | null;
912
+ }>;
735
913
  };
736
914
  /**
737
915
  * 监听列表的一行 = 配置行 + 三个派生水位字段。
@@ -2529,6 +2707,177 @@ export declare class SocialHubClient {
2529
2707
  jobId?: string;
2530
2708
  warning?: string;
2531
2709
  }>;
2710
+ listSubredditMonitorRuleSets(monitorId: string, params?: {
2711
+ status?: "draft" | "active" | "retired";
2712
+ limit?: number;
2713
+ }): Promise<{
2714
+ items: SubredditMonitorRuleSet[];
2715
+ }>;
2716
+ getSubredditMonitorRuleSet(monitorId: string, ruleSetId: string): Promise<SubredditMonitorRuleSetDetail>;
2717
+ /** 新建 draft 版本(可带初始规则)。**不会自动激活** —— 激活是单独的显式动作。 */
2718
+ createSubredditMonitorRuleSet(monitorId: string, body?: {
2719
+ /** 有限枚举:720(12h)/ 1440(24h)。run 窗口取所有启用监听中最长的一档。 */
2720
+ lookbackMinutes?: 720 | 1440;
2721
+ note?: string | null;
2722
+ rules?: SubredditMonitorKeywordRuleInput[];
2723
+ }): Promise<SubredditMonitorRuleSetDetail>;
2724
+ /** 整批替换 draft 的规则。非 draft → 409(active 版本不可原地改)。 */
2725
+ replaceSubredditMonitorRules(monitorId: string, ruleSetId: string, body: {
2726
+ rules: SubredditMonitorKeywordRuleInput[];
2727
+ lookbackMinutes?: 720 | 1440;
2728
+ }): Promise<SubredditMonitorRuleSetDetail>;
2729
+ /** 激活:当前 active 版本自动 retire。空规则集会被拒(400)。 */
2730
+ activateSubredditMonitorRuleSet(monitorId: string, ruleSetId: string): Promise<{
2731
+ activated: SubredditMonitorRuleSetDetail;
2732
+ retiredRuleSetId: string | null;
2733
+ }>;
2734
+ /** 停用当前版本 ⇒ 该监听回到「不设关键词门」,报告**恢复全量新帖**。 */
2735
+ retireSubredditMonitorRuleSet(monitorId: string, ruleSetId: string): Promise<{
2736
+ ok: boolean;
2737
+ retired: boolean;
2738
+ }>;
2739
+ /** 删 draft。active/retired 会被服务端拒绝(历史版本必须留痕)。 */
2740
+ deleteSubredditMonitorRuleSetDraft(monitorId: string, ruleSetId: string): Promise<{
2741
+ ok: boolean;
2742
+ deleted: boolean;
2743
+ }>;
2744
+ /**
2745
+ * 🔴 **试跑**:拿该板块过去 lookback 内的历史帖子跑一遍规则,看会命中什么。
2746
+ * **只读:不写命中账本、不推进水位、不发任何通知。**
2747
+ * 没有它,配关键词就是盲填 —— 要等下一轮 cron 才知道效果,而一个宽词的代价是
2748
+ * 负责人被刷屏一整天。`termHitCounts` / `unusedTerms` 是判断词过宽/拼错的直接信号。
2749
+ */
2750
+ dryRunSubredditMonitorRules(monitorId: string, ruleSetId: string, body?: {
2751
+ lookbackMinutes?: 720 | 1440;
2752
+ limit?: number;
2753
+ /** 默认 false —— 运营需要看到「没命中的长什么样」才能判断词是不是配窄了。 */
2754
+ matchedOnly?: boolean;
2755
+ }): Promise<SubredditMonitorDryRunResult>;
2756
+ /**
2757
+ * 从品牌词取**导入候选**(只读)。
2758
+ * ⚠️ 语义是**快照复制**,不是订阅:导入后品牌词再变,已激活的规则集不会跟着变
2759
+ * (响应里的 `semantics` 字段把这一点写死)。要跟进就再导一次并新建版本。
2760
+ */
2761
+ listSubredditMonitorBrandKeywordCandidates(monitorId: string, params?: {
2762
+ brandIds?: string[];
2763
+ limit?: number;
2764
+ }): Promise<{
2765
+ monitorId: string;
2766
+ watchlistId: string;
2767
+ brandIds: string[];
2768
+ semantics: "snapshot_copy_not_synced";
2769
+ items: Array<{
2770
+ brandKeywordId: string;
2771
+ brandId: string;
2772
+ keyword: string;
2773
+ weight: number;
2774
+ kind: string;
2775
+ }>;
2776
+ }>;
2777
+ listSubredditMonitorExpansionSets(monitorId: string, params?: {
2778
+ status?: "proposed" | "approved" | "retired";
2779
+ limit?: number;
2780
+ }): Promise<{
2781
+ items: SubredditMonitorExpansionSet[];
2782
+ }>;
2783
+ getSubredditMonitorExpansionSet(monitorId: string, expansionSetId: string): Promise<SubredditMonitorExpansionSet>;
2784
+ /** 人工录入候选词表(provider 没配时的路径)。产出恒为 `proposed`。 */
2785
+ createSubredditMonitorExpansionSet(monitorId: string, body: {
2786
+ baseTerm: string;
2787
+ language?: string;
2788
+ terms: string[];
2789
+ model?: string | null;
2790
+ promptVersion?: string | null;
2791
+ }): Promise<SubredditMonitorExpansionSet>;
2792
+ /**
2793
+ * 🔴 **离线生成**候选(走仓内统一 llm-review runner)。产出恒为 `proposed`。
2794
+ * provider 没配 ⇒ 409(功能未启用,可改用 `createSubredditMonitorExpansionSet`
2795
+ * 手工录词),⛔ 不是 500。
2796
+ */
2797
+ generateSubredditMonitorExpansion(monitorId: string, body: {
2798
+ baseTerm: string;
2799
+ language?: string;
2800
+ }): Promise<SubredditMonitorExpansionSet & {
2801
+ nextStep: string;
2802
+ }>;
2803
+ /** 批准。🔴 批准之后**内容就冻住了**(DB 焊死)—— 这是「固化」的落点。 */
2804
+ approveSubredditMonitorExpansionSet(monitorId: string, expansionSetId: string, body?: {
2805
+ reviewNote?: string | null;
2806
+ }): Promise<{
2807
+ ok: boolean;
2808
+ approved: boolean;
2809
+ }>;
2810
+ /**
2811
+ * 否决 / 下架。⚠️ 两者共用一条状态转移但痕迹不同:
2812
+ * `proposed → retired` 是**否决**(此后永远进不了 active 规则集);
2813
+ * `approved → retired` 是**下架**(已复制出去的规则行不受影响 —— 它们拿的是拷贝)。
2814
+ */
2815
+ retireSubredditMonitorExpansionSet(monitorId: string, expansionSetId: string, body?: {
2816
+ reviewNote?: string | null;
2817
+ }): Promise<{
2818
+ ok: boolean;
2819
+ retired: boolean;
2820
+ }>;
2821
+ /** 删候选。approved/retired 会被服务端拒绝(审核过的必须留痕)。 */
2822
+ deleteSubredditMonitorExpansionSet(monitorId: string, expansionSetId: string): Promise<{
2823
+ ok: boolean;
2824
+ deleted: boolean;
2825
+ }>;
2826
+ /**
2827
+ * 🔴 **物化**:把 approved 的扩展词复制成 draft 规则集里的真实规则行。
2828
+ * ⚠️ 物化 ≠ 生效 —— 之后还要显式 `activateSubredditMonitorRuleSet`。
2829
+ * ⚠️ 点名的扩展集里有未批准的 ⇒ 409(⛔ 不静默少物化几条)。
2830
+ */
2831
+ applyExpansionsToRuleSet(monitorId: string, ruleSetId: string, body?: {
2832
+ expansionSetIds?: string[];
2833
+ }): Promise<{
2834
+ ruleSetId: string;
2835
+ inserted: number;
2836
+ skippedDuplicate: number;
2837
+ appliedSets: Array<{
2838
+ expansionSetId: string;
2839
+ baseTerm: string;
2840
+ contentHash: string;
2841
+ terms: number;
2842
+ }>;
2843
+ /**
2844
+ * 🔴 被跳过的扩展集会**显式报出来**(原词不在这个 draft 里)。
2845
+ * ⛔ 不静默少物化 —— 那会让你以为词已经生效了,而卡片上什么都不会变。
2846
+ */
2847
+ skippedSets: Array<{
2848
+ expansionSetId: string;
2849
+ baseTerm: string;
2850
+ reason: "base_term_not_in_rule_set";
2851
+ }>;
2852
+ semantics: "materialized_copy_into_draft_not_active";
2853
+ }>;
2854
+ listSubredditMonitorLabelSpecs(params?: {
2855
+ status?: "draft" | "active" | "retired";
2856
+ limit?: number;
2857
+ }): Promise<{
2858
+ items: SubredditMonitorLabelSpec[];
2859
+ }>;
2860
+ createSubredditMonitorLabelSpec(body: {
2861
+ taxonomyVersion: string;
2862
+ labelKeys: string[];
2863
+ promptVersion: string;
2864
+ model?: string | null;
2865
+ temperature?: number | null;
2866
+ note?: string | null;
2867
+ }): Promise<SubredditMonitorLabelSpec>;
2868
+ activateSubredditMonitorLabelSpec(specId: string): Promise<{
2869
+ activated: SubredditMonitorLabelSpec;
2870
+ retiredSpecId: string | null;
2871
+ }>;
2872
+ /** ⚠️ 停用 = 标签功能整体关闭(卡片不再显示标签行)。 */
2873
+ retireSubredditMonitorLabelSpec(specId: string): Promise<{
2874
+ ok: boolean;
2875
+ retired: boolean;
2876
+ }>;
2877
+ deleteSubredditMonitorLabelSpecDraft(specId: string): Promise<{
2878
+ ok: boolean;
2879
+ deleted: boolean;
2880
+ }>;
2532
2881
  listHotPosts(params?: ListHotPostsParams): Promise<{
2533
2882
  items: HotPostListItem[];
2534
2883
  total: number;