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)
|
||
}
|