thread-master/apps/backend/generate/api/radar.api

635 lines
25 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

syntax = "v1"
// demand-radar: service profile / radar watches / sweeps / opportunities / replies
// spec: docs/product/demand-radar/spec.md §5.2
// 未實作能力一律回 501radarDomain.ErrNotReady禁止 102000 空成功。
type (
// ---------- ServiceProfile ----------
ServiceItem {
Name string `json:"name"`
PriceMin float64 `json:"price_min,optional"`
PriceMax float64 `json:"price_max,optional"`
Currency string `json:"currency,optional"`
}
ServiceCasePublic {
Title string `json:"title"`
Summary string `json:"summary,optional"`
Link string `json:"link,optional"`
}
FaqItem {
Question string `json:"question"`
Answer string `json:"answer"`
}
ServiceProfilePublic {
Exists bool `json:"exists"`
Services []ServiceItem `json:"services"`
Cases []ServiceCasePublic `json:"cases"`
Forbidden []string `json:"forbidden"`
Faq []FaqItem `json:"faq"`
ServiceAreas []string `json:"service_areas"`
RemoteOk bool `json:"remote_ok"`
Availability string `json:"availability,optional"`
ToneNote string `json:"tone_note,optional"`
UpdatedAt int64 `json:"updated_at,optional"`
}
UpsertServiceProfileReq {
Services []ServiceItem `json:"services"`
Cases []ServiceCasePublic `json:"cases,optional"`
Forbidden []string `json:"forbidden,optional"`
Faq []FaqItem `json:"faq,optional"`
ServiceAreas []string `json:"service_areas,optional"`
RemoteOk bool `json:"remote_ok,optional"`
Availability string `json:"availability,optional"`
ToneNote string `json:"tone_note,optional"`
}
// ---------- RadarWatch ----------
RadarWatchPublic {
Id string `json:"id"`
Terms []string `json:"terms"`
ExcludeTerms []string `json:"exclude_terms"`
Regions []string `json:"regions"`
Status string `json:"status"` // active | paused | archived
ContextMode string `json:"context_mode"` // generic | product
BrandId string `json:"brand_id,optional"`
ProductId string `json:"product_id,optional"`
BrandNameSnapshot string `json:"brand_name_snapshot,optional"`
ProductLabelSnapshot string `json:"product_label_snapshot,optional"`
ContextBoundAt int64 `json:"context_bound_at,optional"`
PauseReason string `json:"pause_reason,optional"`
LastSweptAt int64 `json:"last_swept_at,optional"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
// 只在建立當下這是使用者第一組訂閱、且已排入首巡時為 true不是持久狀態僅供前端顯示一次性提示。
FirstSweepTriggered bool `json:"first_sweep_triggered,optional"`
}
ListWatchesReq {
Page int `form:"page,default=1"`
PageSize int `form:"pageSize,default=20"`
Status string `form:"status,optional"`
ContextMode string `form:"context_mode,optional"`
BrandId string `form:"brand_id,optional"`
ProductId string `form:"product_id,optional"`
}
WatchListData {
List []RadarWatchPublic `json:"list"`
Pagination Pagination `json:"pagination"`
ActiveCount int `json:"active_count"`
MaxActive int `json:"max_active"`
ProfileExists bool `json:"profile_exists"`
}
CreateWatchReq {
Terms []string `json:"terms"`
ExcludeTerms []string `json:"exclude_terms,optional"`
Regions []string `json:"regions,optional"`
Enabled bool `json:"enabled,optional"`
BrandId string `json:"brand_id,optional"`
ProductId string `json:"product_id,optional"`
}
UpdateWatchReq {
Id string `path:"id"`
Terms []string `json:"terms,optional"`
ExcludeTerms []string `json:"exclude_terms,optional"`
Regions []string `json:"regions,optional"`
}
WatchIdReq {
Id string `path:"id"`
}
AssignProductReq {
Id string `path:"id"`
BrandId string `json:"brand_id"`
ProductId string `json:"product_id"`
}
SuggestWatchTermsReq {
Limit int `json:"limit,optional"`
BrandId string `json:"brand_id,optional"`
ProductId string `json:"product_id,optional"`
}
WatchTermSuggestion {
Term string `json:"term"`
Reason string `json:"reason"`
Usage string `json:"usage"` // include | exclude
BasisKind string `json:"basis_kind,optional"`
BasisText string `json:"basis_text,optional"`
}
WatchSuggestData {
List []WatchTermSuggestion `json:"list"`
}
TriggerSweepData {
JobId string `json:"job_id"`
SweepId string `json:"sweep_id,optional"`
}
// ---------- Opportunity ----------
OpportunityReason {
Dimension string `json:"dimension"` // authenticity | intent | region | freshness | fit
Score int `json:"score"`
Reason string `json:"reason"`
}
OpportunityOverride {
FromBand string `json:"from_band,optional"`
ToBand string `json:"to_band,optional"`
FromStatus string `json:"from_status,optional"`
ToStatus string `json:"to_status,optional"`
ActorUid int64 `json:"actor_uid"`
At int64 `json:"at"`
}
ProductFitReasonPublic {
Dimension string `json:"dimension"` // pain | scenario | audience | capability
Score int `json:"score"`
Reason string `json:"reason"`
CandidateExcerpt string `json:"candidate_excerpt,optional"`
ProductBasis string `json:"product_basis,optional"`
}
ProductMatchPublic {
BrandId string `json:"brand_id"`
ProductId string `json:"product_id"`
BrandNameSnapshot string `json:"brand_name_snapshot"`
ProductLabelSnapshot string `json:"product_label_snapshot"`
BrandUpdatedAt int64 `json:"brand_updated_at"`
ProductUpdatedAt int64 `json:"product_updated_at"`
ProductFitScore int `json:"product_fit_score"`
ProductFitBand string `json:"product_fit_band"` // strong | possible | weak
Eligible bool `json:"eligible"`
Excluded bool `json:"excluded"`
ExcludeReason string `json:"exclude_reason,optional"`
Reasons []ProductFitReasonPublic `json:"reasons"`
Risks []string `json:"risks"`
WatchIds []string `json:"watch_ids"`
MatchedTerms []string `json:"matched_terms"`
MatchedAt int64 `json:"matched_at"`
}
OpportunityPublic {
Id string `json:"id"`
WatchId string `json:"watch_id,optional"`
Source string `json:"source"` // threads | manual | scout_promote
SourceScoutPostId string `json:"source_scout_post_id,optional"`
ExternalId string `json:"external_id"`
Permalink string `json:"permalink"`
AuthorHandle string `json:"author_handle"`
Text string `json:"text"`
PostedAt int64 `json:"posted_at"`
Status string `json:"status"` // judging | qualified | rejected | accepted | dismissed
IntentScore int `json:"intent_score"`
IntentBand string `json:"intent_band"` // high | mid | low
Reasons []OpportunityReason `json:"reasons"`
RegionDetected string `json:"region_detected,optional"`
RegionMatch string `json:"region_match"` // match | mismatch | unknown
FreshnessHours int `json:"freshness_hours"`
MatchedService string `json:"matched_service,optional"`
MatchedTerms []string `json:"matched_terms"`
RejectReason string `json:"reject_reason,optional"`
Override *OpportunityOverride `json:"override,optional"`
ContactId string `json:"contact_id,optional"`
DefaultReply *ReplyVariantPublic `json:"default_reply,optional"`
PrimaryBrandId string `json:"primary_brand_id,optional"`
PrimaryProductId string `json:"primary_product_id,optional"`
PrimaryBrandName string `json:"primary_brand_name,optional"`
PrimaryProductLabel string `json:"primary_product_label,optional"`
PrimaryProductFitScore int `json:"primary_product_fit_score,optional"`
PrimaryProductFitBand string `json:"primary_product_fit_band,optional"`
PrimaryProductOverridden bool `json:"primary_product_overridden,optional"`
ProductMatches []ProductMatchPublic `json:"product_matches"`
ReviewState string `json:"review_state"` // pending | completed | removed
PreviousReviewState string `json:"previous_review_state,optional"`
RemovalReason string `json:"removal_reason,optional"`
RemovalNote string `json:"removal_note,optional"`
RemovedAt int64 `json:"removed_at,optional"`
RemovedBy int64 `json:"removed_by,optional"`
LastMatchedAt int64 `json:"last_matched_at,optional"`
PriorityScore int `json:"priority_score"`
PriorityBand string `json:"priority_band"` // high | review | low
PainFitScore int `json:"pain_fit_score,optional"`
DemandIntentScore int `json:"demand_intent_score,optional"`
EvidenceQualityScore int `json:"evidence_quality_score,optional"`
FreshnessScore int `json:"freshness_score,optional"`
DemandEvidence []string `json:"demand_evidence,optional"`
DemandInputVersion string `json:"demand_input_version,optional"`
DemandMapVersion int64 `json:"demand_map_version,optional"`
CreatedAt int64 `json:"created_at"`
}
RadarTodayStats {
Total int `json:"total"`
High int `json:"high"`
Mid int `json:"mid"`
Low int `json:"low"`
}
RadarTodayData {
Stats RadarTodayStats `json:"stats"`
High []OpportunityPublic `json:"high"`
Mid []OpportunityPublic `json:"mid"`
Low []OpportunityPublic `json:"low"`
TruncatedCount int `json:"truncated_count"`
LastSweptAt int64 `json:"last_swept_at,optional"`
// EmptyReason: not_swept_yet | all_watches_paused | no_watch | no_profile | sweep_failed | no_hit | no_eligible_product_match
EmptyReason string `json:"empty_reason,optional"`
EmptyHint string `json:"empty_hint,optional"`
}
RadarTodayReq {
BrandId string `form:"brand_id,optional"`
ProductId string `form:"product_id,optional"`
FitBand string `form:"fit_band,optional"`
}
ListOpportunitiesReq {
Page int `form:"page,default=1"`
PageSize int `form:"pageSize,default=20"`
Band string `form:"band,optional"`
Status string `form:"status,optional"`
WatchId string `form:"watch_id,optional"`
BrandId string `form:"brand_id,optional"`
ProductId string `form:"product_id,optional"`
FitBand string `form:"fit_band,optional"`
MatchState string `form:"match_state,optional"`
ReviewState string `form:"review_state,optional"`
TimeScope string `form:"time_scope,optional"` // today | 7d | all
PriorityBand string `form:"priority_band,optional"` // high | review | low
Sort string `form:"sort,optional"`
From int64 `form:"from,optional"`
To int64 `form:"to,optional"`
}
OpportunityListData {
List []OpportunityPublic `json:"list"`
Pagination Pagination `json:"pagination"`
}
OpportunityIdReq {
Id string `path:"id"`
}
AcceptOpportunityReq {
Id string `path:"id"`
}
AcceptOpportunityData {
OpportunityId string `json:"opportunity_id"`
ContactId string `json:"contact_id,optional"`
Status string `json:"status"`
}
DismissOpportunityReq {
Id string `path:"id"`
Reason string `json:"reason,optional"`
}
ReviewStateReq {
Id string `path:"id"`
State string `json:"state"` // pending | completed | removed
RemovalReason string `json:"removal_reason,optional"`
RemovalNote string `json:"removal_note,optional"`
DuplicateOpportunityId string `json:"duplicate_of_opportunity_id,optional"`
}
DemandMapProductReq {
ProductId string `path:"productId"`
}
DemandMapPhrase {
Text string `json:"text"`
Kind string `json:"kind"`
BasisKind string `json:"basis_kind,optional"`
BasisText string `json:"basis_text,optional"`
Origin string `json:"origin"` // product | user | ai
Enabled bool `json:"enabled"`
}
DemandMapPublic {
ProductId string `json:"product_id"`
DemandInputVersion string `json:"demand_input_version"`
MapVersion int64 `json:"map_version"`
State string `json:"state"` // ready | incomplete | stale
PainPhrases []DemandMapPhrase `json:"pain_phrases"`
ScenarioPhrases []DemandMapPhrase `json:"scenario_phrases"`
DesiredOutcomes []DemandMapPhrase `json:"desired_outcomes"`
SolutionSignals []DemandMapPhrase `json:"solution_signals"`
ExclusionSignals []DemandMapPhrase `json:"exclusion_signals"`
SourceBasis []string `json:"source_basis"`
CustomPhrases []DemandMapPhrase `json:"custom_phrases"`
AiEnrichedAt int64 `json:"ai_enriched_at,optional"`
UpdatedAt int64 `json:"updated_at"`
}
UpdateDemandMapReq {
ProductId string `path:"productId"`
ExpectedMapVersion int64 `json:"expected_map_version"`
PainPhrases []DemandMapPhrase `json:"pain_phrases,optional"`
ScenarioPhrases []DemandMapPhrase `json:"scenario_phrases,optional"`
DesiredOutcomes []DemandMapPhrase `json:"desired_outcomes,optional"`
SolutionSignals []DemandMapPhrase `json:"solution_signals,optional"`
ExclusionSignals []DemandMapPhrase `json:"exclusion_signals,optional"`
CustomPhrases []DemandMapPhrase `json:"custom_phrases,optional"`
}
CostPreviewReq {
Action string `form:"action"` // sweep | explore | demand_map_enrich | reply
WatchId string `form:"watch_id,optional"`
ProductId string `form:"product_id,optional"`
CandidateLimit int `form:"candidate_limit,optional"`
}
CostPreviewPublic {
PreviewId string `json:"preview_id"`
Action string `json:"action"`
KeyMode string `json:"key_mode"` // platform | byok
FixedCredits int `json:"fixed_credits"`
MinCredits int `json:"min_credits"`
MaxCredits int `json:"max_credits"`
SearchCalls int `json:"search_calls"`
MaxAiCandidates int `json:"max_ai_candidates"`
RemainingCredits int `json:"remaining_credits"`
EstimateBasis string `json:"estimate_basis"`
ExpiresAt int64 `json:"expires_at"`
}
EnrichDemandMapReq {
ProductId string `path:"productId"`
PreviewId string `json:"preview_id"`
CreditCeiling int `json:"credit_ceiling"`
ExpectedMapVersion int64 `json:"expected_map_version"`
}
OverrideOpportunityReq {
Id string `path:"id"`
Band string `json:"band,optional"`
Status string `json:"status,optional"`
Note string `json:"note,optional"`
}
SetPrimaryProductReq {
Id string `path:"id"`
ProductId string `json:"product_id"`
Reason string `json:"reason"`
}
// ---------- ReplyVariant ----------
ReplyVariantPublic {
Id string `json:"id"`
OpportunityId string `json:"opportunity_id"`
Variant string `json:"variant"` // public_comment | dm | no_sales | professional | humorous
Text string `json:"text"`
UsedAt int64 `json:"used_at,optional"`
SentChannel string `json:"sent_channel,optional"` // outbox | manual_copy
// 只在 sent_channel=outbox 且真的排入既有 Outbox 佇列時才有值。
OutboxId string `json:"outbox_id,optional"`
CreatedAt int64 `json:"created_at"`
}
ListRepliesReq {
Id string `path:"id"`
}
ReplyListData {
List []ReplyVariantPublic `json:"list"`
}
CreateReplyReq {
Id string `path:"id"`
Variant string `json:"variant"`
}
// MarkReplyUsedReq: channel=outbox|manual_copydm 僅 manual_copy
MarkReplyUsedReq {
Id string `path:"id"`
ReplyId string `path:"replyId"`
Channel string `json:"channel"` // outbox | manual_copy
// channel=outbox 時必填:要用哪個 Threads 帳號送出。
AccountId string `json:"account_id,optional"`
}
MarkReplyUsedData {
Reply ReplyVariantPublic `json:"reply"`
HealthAdvice string `json:"health_advice,optional"`
}
// ---------- RadarSweep ----------
RadarSweepPublic {
Id string `json:"id"`
WatchId string `json:"watch_id"`
JobId string `json:"job_id,optional"`
Path string `json:"path"` // api | crawler
HitCount int `json:"hit_count"`
JudgedCount int `json:"judged_count"`
CreatedCount int `json:"created_count"`
TruncatedCount int `json:"truncated_count"`
MatchEvaluatedCount int `json:"match_evaluated_count"`
MatchMergedCount int `json:"match_merged_count"`
FitRejectedCount int `json:"fit_rejected_count"`
FailedReason string `json:"failed_reason,optional"`
CreditsUsed int `json:"credits_used"`
StartedAt int64 `json:"started_at"`
EndedAt int64 `json:"ended_at,optional"`
DemandInputVersion string `json:"demand_input_version,optional"`
DemandMapVersion int64 `json:"demand_map_version,optional"`
DedupedCount int `json:"deduped_count"`
PrefilterPassCount int `json:"prefilter_pass_count"`
PrefilterReviewCount int `json:"prefilter_review_count"`
PrefilterRejectedCount int `json:"prefilter_rejected_count"`
CachedJudgmentCount int `json:"cached_judgment_count"`
TombstoneMatchedCount int `json:"tombstone_matched_count"`
BudgetDeferredCount int `json:"budget_deferred_count"`
SweepStatus string `json:"status"` // complete | partial_budget | blocked_budget | failed
CreditSearch int `json:"credit_search"`
CreditDemandMap int `json:"credit_demand_map"`
CreditJudge int `json:"credit_judge"`
CreditReply int `json:"credit_reply"`
}
ListSweepsReq {
Page int `form:"page,default=1"`
PageSize int `form:"pageSize,default=20"`
WatchId string `form:"watch_id,optional"`
From int64 `form:"from,optional"`
To int64 `form:"to,optional"`
}
SweepListData {
List []RadarSweepPublic `json:"list"`
Pagination Pagination `json:"pagination"`
}
// ---------- Manual ImportP1不承諾全平台自動抓取的合規補位 ----------
ImportOpportunityItem {
Url string `json:"url"`
// 貼文內文;沒有官方 API爬蟲可讀任意網址判定一律吃使用者貼上的文字。
Text string `json:"text"`
// 缺省時從 threads 網址猜 @handle猜不到就留空。
Author string `json:"author,optional"`
// unix nanoseconds UTC缺省用匯入當下時間新鮮度以匯入時間計
PostedAt int64 `json:"posted_at,optional"`
}
ImportOpportunitiesReq {
Items []ImportOpportunityItem `json:"items"`
BrandId string `json:"brand_id,optional"`
ProductId string `json:"product_id,optional"`
}
ImportedOpportunityResult {
Url string `json:"url"`
OpportunityId string `json:"opportunity_id,optional"`
Status string `json:"status"` // qualified | rejected | skipped | failed
IntentBand string `json:"intent_band,optional"`
IntentScore int `json:"intent_score,optional"`
// skippedfailed 時的人話原因qualifiedrejected 時通常留空。
Error string `json:"error,optional"`
}
ImportOpportunitiesData {
Results []ImportedOpportunityResult `json:"results"`
}
// ---------- Explore商機頁立即探索短詞 fan-out → 五問判定) ----------
ExploreOpportunitiesReq {
// 16 組 Threads 短詞(契約 A不合規整組擋下不默默修正。
Terms []string `json:"terms"`
BrandId string `json:"brand_id,optional"`
ProductId string `json:"product_id,optional"`
PreviewId string `json:"preview_id,optional"`
CreditCeiling int `json:"credit_ceiling,optional"`
DemandInputVersion string `json:"demand_input_version,optional"`
DemandMapVersion int64 `json:"demand_map_version,optional"`
}
ExploreOpportunitiesData {
SweepId string `json:"sweep_id"`
HitCount int `json:"hit_count"`
JudgedCount int `json:"judged_count"`
CreatedCount int `json:"created_count"`
MatchedCount int `json:"matched_count"`
MergedCount int `json:"merged_count"`
TruncatedCount int `json:"truncated_count"`
CreditsUsed int `json:"credits_used"`
DedupedCount int `json:"deduped_count"`
PrefilterPassCount int `json:"prefilter_pass_count"`
PrefilterReviewCount int `json:"prefilter_review_count"`
PrefilterRejectedCount int `json:"prefilter_rejected_count"`
CachedJudgmentCount int `json:"cached_judgment_count"`
TombstoneMatchedCount int `json:"tombstone_matched_count"`
BudgetDeferredCount int `json:"budget_deferred_count"`
SweepStatus string `json:"status"`
}
)
@server (
group: radar
prefix: /api/v1/radar
middleware: AuthJWT
)
service gateway {
@handler GetServiceProfile
get /service-profile returns (ServiceProfilePublic)
@handler UpsertServiceProfile
put /service-profile (UpsertServiceProfileReq) returns (ServiceProfilePublic)
@handler ListWatches
get /watches (ListWatchesReq) returns (WatchListData)
@handler CreateWatch
post /watches (CreateWatchReq) returns (RadarWatchPublic)
@handler SuggestWatchTerms
post /watches/suggest (SuggestWatchTermsReq) returns (WatchSuggestData)
@handler AssignWatchProduct
post /watches/:id/assign-product (AssignProductReq) returns (RadarWatchPublic)
@handler GetWatch
get /watches/:id (WatchIdReq) returns (RadarWatchPublic)
@handler UpdateWatch
put /watches/:id (UpdateWatchReq) returns (RadarWatchPublic)
@handler ArchiveWatch
delete /watches/:id (WatchIdReq) returns (OkData)
@handler DeleteArchivedWatch
delete /watches/:id/purge (WatchIdReq) returns (OkData)
@handler PauseWatch
post /watches/:id/pause (WatchIdReq) returns (RadarWatchPublic)
@handler ResumeWatch
post /watches/:id/resume (WatchIdReq) returns (RadarWatchPublic)
@handler TriggerWatchSweep
post /watches/:id/sweep (WatchIdReq) returns (TriggerSweepData)
@handler GetRadarToday
get /today (RadarTodayReq) returns (RadarTodayData)
@handler ListOpportunities
get /opportunities (ListOpportunitiesReq) returns (OpportunityListData)
@handler GetOpportunity
get /opportunities/:id (OpportunityIdReq) returns (OpportunityPublic)
@handler AcceptOpportunity
post /opportunities/:id/accept (AcceptOpportunityReq) returns (AcceptOpportunityData)
@handler DismissOpportunity
post /opportunities/:id/dismiss (DismissOpportunityReq) returns (OpportunityPublic)
@handler UpdateOpportunityReviewState
put /opportunities/:id/review-state (ReviewStateReq) returns (OpportunityPublic)
@handler OverrideOpportunity
post /opportunities/:id/override (OverrideOpportunityReq) returns (OpportunityPublic)
@handler SetPrimaryProduct
put /opportunities/:id/primary-product (SetPrimaryProductReq) returns (OpportunityPublic)
@handler GetDemandMap
get /products/:productId/demand-map (DemandMapProductReq) returns (DemandMapPublic)
@handler UpdateDemandMap
put /products/:productId/demand-map (UpdateDemandMapReq) returns (DemandMapPublic)
@handler EnrichDemandMap
post /products/:productId/demand-map/enrich (EnrichDemandMapReq) returns (DemandMapPublic)
@handler GetRadarCostPreview
get /cost-preview (CostPreviewReq) returns (CostPreviewPublic)
@handler ListOpportunityReplies
get /opportunities/:id/replies (ListRepliesReq) returns (ReplyListData)
@handler CreateOpportunityReply
post /opportunities/:id/replies (CreateReplyReq) returns (ReplyVariantPublic)
@handler MarkOpportunityReplyUsed
post /opportunities/:id/replies/:replyId/mark-used (MarkReplyUsedReq) returns (MarkReplyUsedData)
@handler ListSweeps
get /sweeps (ListSweepsReq) returns (SweepListData)
@handler ImportOpportunities
post /import (ImportOpportunitiesReq) returns (ImportOpportunitiesData)
@handler ExploreOpportunities
post /explore (ExploreOpportunitiesReq) returns (ExploreOpportunitiesData)
}