syntax = "v1" // demand-radar: contacts / touches / follow-ups / conversion / stats // spec: docs/product/demand-radar/spec.md §5.2 // 未實作能力一律回 501(crmDomain.ErrNotReady),禁止 102000 空成功。 // 命名紅線:不得以 lead 指稱銷售線索。 type ( // ---------- Contact ---------- ContactPublic { Id string `json:"id"` SourcePlatform string `json:"source_platform"` AuthorHandle string `json:"author_handle"` DisplayName string `json:"display_name,optional"` Stage string `json:"stage"` // new_found | engaged | dm_sent | replied | quoted | won | lost NeedsFollowUp bool `json:"needs_follow_up"` FollowUpDays int `json:"follow_up_days"` LastTouchAt int64 `json:"last_touch_at,optional"` OpportunityIds []string `json:"opportunity_ids"` OpportunityCount int `json:"opportunity_count"` MergedFrom []string `json:"merged_from,optional"` TopIntentBand string `json:"top_intent_band,optional"` TopIntentScore int `json:"top_intent_score,optional"` CreatedAt int64 `json:"created_at"` UpdatedAt int64 `json:"updated_at"` } ContactBrief { Id string `json:"id"` SourcePlatform string `json:"source_platform"` AuthorHandle string `json:"author_handle"` DisplayName string `json:"display_name,optional"` Stage string `json:"stage"` } // StageCount — 八格視圖用;stage 值另含 needs_follow_up(跨階段檢視,非階段值) StageCount { Stage string `json:"stage"` Count int `json:"count"` } ListContactsReq { Page int `form:"page,default=1"` PageSize int `form:"pageSize,default=20"` Stage string `form:"stage,optional"` // FollowUp: true | false(留空=不篩選) FollowUp string `form:"follow_up,optional"` Band string `form:"band,optional"` // Sort: last_touch_at | intent_score(預設 last_touch_at 倒序) Sort string `form:"sort,optional"` } ContactListData { List []ContactPublic `json:"list"` Pagination Pagination `json:"pagination"` StageCounts []StageCount `json:"stage_counts"` } ContactTouchPublic { Id string `json:"id"` ContactId string `json:"contact_id"` Type string `json:"type"` // stage | reply | note | conversion FromStage string `json:"from_stage,optional"` ToStage string `json:"to_stage,optional"` Body string `json:"body,optional"` ActorUid int64 `json:"actor_uid"` CreatedAt int64 `json:"created_at"` } ContactOpportunityBrief { Id string `json:"id"` Permalink string `json:"permalink"` Text string `json:"text"` IntentScore int `json:"intent_score"` IntentBand string `json:"intent_band"` CreatedAt int64 `json:"created_at"` } GetContactReq { Id string `path:"id"` Page int `form:"page,default=1"` PageSize int `form:"pageSize,default=20"` } ContactDetailData { Contact ContactPublic `json:"contact"` Touches []ContactTouchPublic `json:"touches"` Pagination Pagination `json:"pagination"` Opportunities []ContactOpportunityBrief `json:"opportunities"` } ContactIdReq { Id string `path:"id"` } UpdateContactStageReq { Id string `path:"id"` Stage string `json:"stage"` Note string `json:"note,optional"` } SetContactFollowUpReq { Id string `path:"id"` NeedsFollowUp bool `json:"needs_follow_up"` Days int `json:"days,optional"` } CreateContactNoteReq { Id string `path:"id"` Body string `json:"body"` } MergeContactReq { Id string `path:"id"` SourceContactId string `json:"source_contact_id"` } UnmergeContactReq { Id string `path:"id"` MergedContactId string `json:"merged_contact_id"` } // ---------- Conversion(寫既有 growth_outcomes,不另建成交帳) ---------- CreateCrmConversionReq { Id string `path:"id"` Amount float64 `json:"amount,optional"` Currency string `json:"currency,optional"` Note string `json:"note,optional"` } UpdateCrmConversionReq { Id string `path:"id"` Amount float64 `json:"amount,optional"` Currency string `json:"currency,optional"` Note string `json:"note,optional"` } DeleteCrmConversionReq { Id string `path:"id"` } CrmConversionData { ContactId string `json:"contact_id"` OutcomeId string `json:"outcome_id,optional"` Stage string `json:"stage"` Amount float64 `json:"amount,optional"` Currency string `json:"currency,optional"` Note string `json:"note,optional"` UpdatedAt int64 `json:"updated_at"` } // ---------- FollowUp ---------- FollowUpPublic { Id string `json:"id"` ContactId string `json:"contact_id"` DueAt int64 `json:"due_at"` Status string `json:"status"` // scheduled | notified | done | snoozed | escalated NotifiedCount int `json:"notified_count"` Contact ContactBrief `json:"contact"` CreatedAt int64 `json:"created_at"` } ListFollowUpsReq { Page int `form:"page,default=1"` PageSize int `form:"pageSize,default=20"` Status string `form:"status,optional"` } FollowUpListData { List []FollowUpPublic `json:"list"` Pagination Pagination `json:"pagination"` } FollowUpIdReq { Id string `path:"id"` } SnoozeFollowUpReq { Id string `path:"id"` Days int `json:"days"` } GenerateFollowUpMessageReq { Id string `path:"id"` } GenerateFollowUpMessageData { Text string `json:"text"` } // ---------- Stats(樣本 < 5 只給絕對數) ---------- TermConversionStat { Term string `json:"term"` Accepted int `json:"accepted"` Replied int `json:"replied"` Won int `json:"won"` ConversionRate float64 `json:"conversion_rate,optional"` InsufficientSample bool `json:"insufficient_sample"` } VariantConversionStat { Variant string `json:"variant"` Used int `json:"used"` Replied int `json:"replied"` Won int `json:"won"` SuccessRate float64 `json:"success_rate,optional"` InsufficientSample bool `json:"insufficient_sample"` } SourceConversionStat { Source string `json:"source"` // radar | scout | manual_import Won int `json:"won"` InsufficientSample bool `json:"insufficient_sample"` } CrmStatsReq { From int64 `form:"from,optional"` To int64 `form:"to,optional"` } CrmStatsData { Terms []TermConversionStat `json:"terms"` Variants []VariantConversionStat `json:"variants"` Sources []SourceConversionStat `json:"sources"` } ) @server ( group: crm prefix: /api/v1/crm middleware: AuthJWT ) service gateway { @handler ListContacts get /contacts (ListContactsReq) returns (ContactListData) @handler GetContact get /contacts/:id (GetContactReq) returns (ContactDetailData) @handler UpdateContactStage post /contacts/:id/stage (UpdateContactStageReq) returns (ContactPublic) @handler SetContactFollowUp post /contacts/:id/follow-up (SetContactFollowUpReq) returns (ContactPublic) @handler CreateContactNote post /contacts/:id/notes (CreateContactNoteReq) returns (ContactTouchPublic) @handler MergeContact post /contacts/:id/merge (MergeContactReq) returns (ContactPublic) @handler UnmergeContact post /contacts/:id/unmerge (UnmergeContactReq) returns (ContactPublic) @handler CreateContactConversion post /contacts/:id/conversion (CreateCrmConversionReq) returns (CrmConversionData) @handler UpdateContactConversion put /contacts/:id/conversion (UpdateCrmConversionReq) returns (CrmConversionData) @handler DeleteContactConversion delete /contacts/:id/conversion (DeleteCrmConversionReq) returns (OkData) @handler ListFollowUps get /followups (ListFollowUpsReq) returns (FollowUpListData) @handler DoneFollowUp post /followups/:id/done (FollowUpIdReq) returns (FollowUpPublic) @handler SnoozeFollowUp post /followups/:id/snooze (SnoozeFollowUpReq) returns (FollowUpPublic) @handler GenerateFollowUpMessage post /followups/:id/message (GenerateFollowUpMessageReq) returns (GenerateFollowUpMessageData) @handler GetCrmStats get /stats (CrmStatsReq) returns (CrmStatsData) }