Documentation
¶
Overview ¶
Package feedrank implements FluxRank, Fluxa's transparent feed-ranking algorithm. Pure and stdlib-only: no I/O, no clock, seeded randomness only, so identical inputs always produce identical feeds. See ALGORITHM.md for the full specification.
Example ¶
Example shows the full adapter flow: map hashtags to topics, build candidates, rank, and read the explainable output.
package main
import (
"fmt"
"time"
feedrank "github.com/Fluxa-org/Fluxa-PostTopics"
)
func main() {
now := time.Date(2026, 7, 21, 12, 0, 0, 0, time.UTC)
canonical := map[string]bool{"music": true, "food": true}
topics := feedrank.PostTopics(
"新曲できた! #音楽", nil, canonical, feedrank.DefaultAliases(), 3)
user := feedrank.UserContext{
UserID: "viewer",
Interests: []string{"music"},
Follows: map[string]bool{"alice": true},
}
candidates := []feedrank.Candidate{
{
Post: feedrank.Post{
ID: "post-song", AuthorID: "alice",
CreatedAt: now.Add(-2 * time.Hour),
Likes: 12, Replies: 3, Topics: topics,
},
Source: feedrank.SourceInNetwork,
},
{
Post: feedrank.Post{
ID: "post-viral", AuthorID: "stranger",
CreatedAt: now.Add(-20 * time.Hour),
Likes: 900, Reposts: 200,
},
Source: feedrank.SourceTrending,
},
}
page := feedrank.Rank(feedrank.DefaultConfig(), user, candidates, now, 2)
for _, r := range page {
fmt.Printf("%s reasons=%v\n", r.ID, r.Reasons)
}
}
Output: post-song reasons=[followed_author topic:music] post-viral reasons=[trending]
Index ¶
- func BuiltinProfiles() map[string]Config
- func DefaultAliases() map[string]string
- func ExtractHashtags(text string) []string
- func ExtractMentions(text string) []string
- func MapQuery(query string, canonical map[string]bool, aliases map[string]string, max int) []string
- func MapTopics(raw []string, canonical map[string]bool, aliases map[string]string, max int) []string
- func PostTopics(text string, authorInterests []string, canonical map[string]bool, ...) []string
- type Breakdown
- type Candidate
- type Config
- type EngagementWeights
- type Penalties
- type Post
- type Ranked
- type Source
- type UserContext
- type Weights
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func BuiltinProfiles ¶
BuiltinProfiles returns a fresh copy of the named profiles.
func DefaultAliases ¶
DefaultAliases maps common hashtags in ten language groups (en, ja, ko, zh, es, hi, vi, fr, de, pt) onto the canonical 30-tag taxonomy. Instances extend or replace it freely.
func ExtractHashtags ¶
ExtractHashtags returns lowercased hashtag bodies in order of first appearance, deduplicated. Supports CJK and other Unicode tags.
func ExtractMentions ¶ added in v0.2.0
ExtractMentions returns lowercased @-handles in order of first appearance, deduplicated. Handle syntax is platform-specific; callers should re-validate against their own username rules before use.
func MapQuery ¶ added in v0.2.0
MapQuery maps a free-text search query onto canonical topics: the query is lowercased, split on non-letter/digit runs, and each token is mapped like a hashtag. Unsegmented CJK compounds match only when a token equals an alias. Use it to turn per-user search history into SearchInterests and aggregate search logs into TrendingTopics.
Types ¶
type Breakdown ¶
type Breakdown struct {
Engagement float64 `json:"engagement"`
Affinity float64 `json:"affinity"`
Topic float64 `json:"topic"`
SocialProof float64 `json:"social_proof"`
Trending float64 `json:"trending"`
Core float64 `json:"core"`
Freshness float64 `json:"freshness"`
Penalty float64 `json:"penalty"`
Score float64 `json:"score"`
}
Breakdown decomposes a score: Score = Freshness × Core × Penalty. Penalty is the combined modifier and can exceed 1 when boosts apply.
type Candidate ¶
type Candidate struct {
Post
Source Source
// EngagedFollows counts followed accounts that engaged with the post.
EngagedFollows int
// AuthorPostsLast24h is the author's posting rate; 0 = unknown.
AuthorPostsLast24h int
MentionsViewer bool
}
Candidate is a post plus sourcing metadata.
type Config ¶
type Config struct {
Version string `json:"version"`
Weights Weights `json:"weights"`
Engagement EngagementWeights `json:"engagement_weights"`
Penalties Penalties `json:"penalties"`
EngagementCap float64 `json:"engagement_cap"`
FreshnessTauHours float64 `json:"freshness_tau_hours"`
FreshnessFloor float64 `json:"freshness_floor"`
MaxAgeHours float64 `json:"max_age_hours"`
FollowBonus float64 `json:"follow_bonus"`
SocialProofSaturation int `json:"social_proof_saturation"`
AuthorCapPerPage int `json:"author_cap_per_page"`
InNetworkTargetRatio float64 `json:"in_network_target_ratio"`
TopicRunCap int `json:"topic_run_cap"` // 0 disables
ExploreSlotEvery int `json:"explore_slot_every"` // 0 disables
MoreLikeThisBoost float64 `json:"more_like_this_boost"` // >= 1
MentionBoost float64 `json:"mention_boost"` // >= 1
ProlificDampThreshold int `json:"prolific_damp_threshold"`
LabelPenalties map[string]float64 `json:"label_penalties"`
// LanguageMismatchPenalty multiplies posts in a language the viewer does
// not read, in [0,1]; 1 disables language matching.
LanguageMismatchPenalty float64 `json:"language_mismatch_penalty"`
// RelaxMaxAgeWhenSparse readmits age-expired posts when fresh candidates
// cannot fill a page, so small communities never see an empty feed.
RelaxMaxAgeWhenSparse bool `json:"relax_max_age_when_sparse"`
}
Config holds every tunable of the algorithm. Load with ConfigFromJSON and log Version with each ranking pass.
func ConfigFromJSON ¶
ConfigFromJSON overlays a JSON document onto DefaultConfig, rejecting unknown fields so typos fail loudly.
func DefaultConfig ¶
func DefaultConfig() Config
func DiscoverConfig ¶
func DiscoverConfig() Config
DiscoverConfig is the out-of-network exploration feed: engagement- and recency-forward, low in-network quota, aggressive exploration.
func QuietPostersConfig ¶
func QuietPostersConfig() Config
QuietPostersConfig surfaces followed accounts that post rarely: affinity-forward, long freshness window, harsh prolific damp, no explore.
func SmallCommunityConfig ¶ added in v0.3.0
func SmallCommunityConfig() Config
SmallCommunityConfig tunes the feed for young or low-volume instances (roughly < 20 posts/hour): a 30-day window, gentle 48 h freshness decay so the page isn't dominated by the last few hours, a stricter prolific damp (one hyperactive account cannot flood a small feed), and more exploration.
type EngagementWeights ¶
type Penalties ¶
type Penalties struct {
Seen float64 `json:"seen"`
NotInterestedTopic float64 `json:"not_interested_topic"`
ReplyToNonFollowed float64 `json:"reply_to_non_followed"`
}
Penalties are multiplicative down-ranks in [0,1].
type Post ¶
type Post struct {
ID string
AuthorID string
CreatedAt time.Time
Likes int64
Reposts int64
Bookmarks int64
Replies int64
Views int64
// Topics are canonical taxonomy tags; see PostTopics.
Topics []string
// Labels are moderation labels matched against Config.LabelPenalties.
Labels []string
// Language is the post's lowercase primary language tag ("ja", "en");
// "" = unknown (never penalized).
Language string
IsReply bool
ReplyToAuthorID string
}
Post is the ranking view of a post.
type UserContext ¶
type UserContext struct {
UserID string
Interests []string
Follows map[string]bool
AuthorAffinity map[string]float64 // 0..1 per author
Seen map[string]bool
NotInterestedTopics map[string]bool
NotInterestedAuthors map[string]bool
MoreInterestedTopics map[string]bool
// Languages is the set of languages the viewer reads; empty disables
// language matching.
Languages map[string]bool
// SearchInterests are topics derived from the viewer's own recent
// searches (see MapQuery); they join Interests in the topic term.
SearchInterests []string
// TrendingTopics is the network-wide search heat per topic in [0,1],
// aggregated by the caller from all users' search queries.
TrendingTopics map[string]float64
}
UserContext holds the viewer's signals; zero values mean "no signal".