mirror of
https://github.com/profullstack/logicsrc.git
synced 2026-08-14 14:57:28 +00:00
Add feed discovery plugin
This commit is contained in:
parent
e47616bf9d
commit
5cfeea6b57
37 changed files with 2432 additions and 5 deletions
95
plugins/feed-discovery/src/scoring.ts
Normal file
95
plugins/feed-discovery/src/scoring.ts
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
import type { DiscoveredFeed, FeedDiscoveryQuery } from "./types.js";
|
||||
|
||||
const PROVIDER_SCORES: Record<string, number> = {
|
||||
"manual-curated": 1,
|
||||
podcastindex: 0.9,
|
||||
"itunes-podcast": 0.85,
|
||||
"opml-directory": 0.75,
|
||||
"web-feed-probe": 0.7,
|
||||
rsshub: 0.7,
|
||||
reddit: 0.65,
|
||||
github: 0.65,
|
||||
youtube: 0.65,
|
||||
unknown: 0.3
|
||||
};
|
||||
|
||||
export function scoreFeed(feed: DiscoveredFeed, query: FeedDiscoveryQuery): DiscoveredFeed {
|
||||
const keywordScore = feed.keywordScore ?? calculateKeywordScore(feed, query.q);
|
||||
const freshnessScore = feed.freshnessScore ?? calculateFreshnessScore(feed.lastPublishedAt);
|
||||
const providerScore = feed.providerScore ?? PROVIDER_SCORES[feed.provider] ?? PROVIDER_SCORES.unknown;
|
||||
const validationScore = feed.validationScore ?? (feed.isValid === false ? 0.2 : 1);
|
||||
const score = keywordScore * 0.4 + freshnessScore * 0.25 + providerScore * 0.2 + validationScore * 0.15;
|
||||
|
||||
return {
|
||||
...feed,
|
||||
score: round(score),
|
||||
confidence: round(Math.max(feed.confidence, score * validationScore)),
|
||||
freshnessScore: round(freshnessScore),
|
||||
keywordScore: round(keywordScore),
|
||||
providerScore: round(providerScore),
|
||||
validationScore: round(validationScore)
|
||||
};
|
||||
}
|
||||
|
||||
export function calculateKeywordScore(feed: DiscoveredFeed, query: string) {
|
||||
const terms = normalizeTerms(query);
|
||||
if (terms.length === 0) {
|
||||
return 0.1;
|
||||
}
|
||||
|
||||
const weightedText = [
|
||||
[feed.title, 0.35],
|
||||
[feed.description, 0.2],
|
||||
[feed.homepageUrl, 0.1],
|
||||
[feed.feedUrl, 0.05],
|
||||
[feed.tags.join(" "), 0.15],
|
||||
[feed.sampleItems?.map((item) => item.title).join(" "), 0.15]
|
||||
] as const;
|
||||
|
||||
let score = 0;
|
||||
for (const [value, weight] of weightedText) {
|
||||
const text = String(value ?? "").toLowerCase();
|
||||
if (!text) {
|
||||
continue;
|
||||
}
|
||||
const matches = terms.filter((term) => text.includes(term)).length;
|
||||
score += weight * (matches / terms.length);
|
||||
}
|
||||
|
||||
return Math.min(1, Math.max(0.05, score));
|
||||
}
|
||||
|
||||
export function calculateFreshnessScore(lastPublishedAt?: string) {
|
||||
if (!lastPublishedAt) {
|
||||
return 0.1;
|
||||
}
|
||||
const ageDays = (Date.now() - Date.parse(lastPublishedAt)) / 86_400_000;
|
||||
if (!Number.isFinite(ageDays) || ageDays < 0) {
|
||||
return 0.1;
|
||||
}
|
||||
if (ageDays <= 7) {
|
||||
return 1;
|
||||
}
|
||||
if (ageDays <= 30) {
|
||||
return 0.8;
|
||||
}
|
||||
if (ageDays <= 90) {
|
||||
return 0.6;
|
||||
}
|
||||
if (ageDays <= 365) {
|
||||
return 0.3;
|
||||
}
|
||||
return 0.1;
|
||||
}
|
||||
|
||||
function normalizeTerms(query: string) {
|
||||
return query
|
||||
.toLowerCase()
|
||||
.split(/[^a-z0-9]+/)
|
||||
.map((term) => term.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function round(value: number) {
|
||||
return Math.round(value * 1000) / 1000;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue