351 lines
11 KiB
Plaintext
351 lines
11 KiB
Plaintext
|
|
syntax = "v1"
|
|||
|
|
|
|||
|
|
// demand-radar: service profile / radar watches / sweeps / opportunities / replies
|
|||
|
|
// spec: docs/product/demand-radar/spec.md §5.2
|
|||
|
|
// 未實作能力一律回 501(radarDomain.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
|
|||
|
|
LastSweptAt int64 `json:"last_swept_at,optional"`
|
|||
|
|
CreatedAt int64 `json:"created_at"`
|
|||
|
|
UpdatedAt int64 `json:"updated_at"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
ListWatchesReq {
|
|||
|
|
Page int `form:"page,default=1"`
|
|||
|
|
PageSize int `form:"pageSize,default=20"`
|
|||
|
|
Status string `form:"status,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"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
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"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
SuggestWatchTermsReq {
|
|||
|
|
Limit int `json:"limit,optional"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
WatchTermSuggestion {
|
|||
|
|
Term string `json:"term"`
|
|||
|
|
Reason string `json:"reason"`
|
|||
|
|
Usage string `json:"usage"` // include | exclude
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
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"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
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"`
|
|||
|
|
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
|
|||
|
|
EmptyReason string `json:"empty_reason,optional"`
|
|||
|
|
EmptyHint string `json:"empty_hint,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"`
|
|||
|
|
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"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
OverrideOpportunityReq {
|
|||
|
|
Id string `path:"id"`
|
|||
|
|
Band string `json:"band,optional"`
|
|||
|
|
Status string `json:"status,optional"`
|
|||
|
|
Note string `json:"note,optional"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ---------- 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
|
|||
|
|
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_copy(dm 僅 manual_copy)
|
|||
|
|
MarkReplyUsedReq {
|
|||
|
|
Id string `path:"id"`
|
|||
|
|
ReplyId string `path:"replyId"`
|
|||
|
|
Channel string `json:"channel"` // outbox | manual_copy
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
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"`
|
|||
|
|
FailedReason string `json:"failed_reason,optional"`
|
|||
|
|
CreditsUsed int `json:"credits_used"`
|
|||
|
|
StartedAt int64 `json:"started_at"`
|
|||
|
|
EndedAt int64 `json:"ended_at,optional"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
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"`
|
|||
|
|
}
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
@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 GetWatch
|
|||
|
|
get /watches/:id (WatchIdReq) returns (RadarWatchPublic)
|
|||
|
|
|
|||
|
|
@handler UpdateWatch
|
|||
|
|
put /watches/:id (UpdateWatchReq) returns (RadarWatchPublic)
|
|||
|
|
|
|||
|
|
@handler ArchiveWatch
|
|||
|
|
delete /watches/:id (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 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 OverrideOpportunity
|
|||
|
|
post /opportunities/:id/override (OverrideOpportunityReq) returns (OpportunityPublic)
|
|||
|
|
|
|||
|
|
@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)
|
|||
|
|
}
|