← back to Open Seo
src/client/features/ai-search/brandLookupFiltering.ts
98 lines
import { parseTerms } from "@/client/features/keywords/utils";
import type { BrandLookupResult } from "@/types/schemas/ai-search";
import type {
QueriesFilterValues,
TopPagesFilterValues,
} from "./brandLookupFilterTypes";
function passesNumericFilter(
value: number | null | undefined,
min: string,
max: string,
): boolean {
if (value == null) return true;
const minN = Number(min);
if (min && !Number.isNaN(minN) && value < minN) return false;
const maxN = Number(max);
if (max && !Number.isNaN(maxN) && value > maxN) return false;
return true;
}
function passesTextFilter(
haystack: string,
includeTerms: string[],
excludeTerms: string[],
): boolean {
const lower = haystack.toLowerCase();
if (
includeTerms.length > 0 &&
!includeTerms.some((term) => lower.includes(term))
) {
return false;
}
if (excludeTerms.some((term) => lower.includes(term))) {
return false;
}
return true;
}
export function filterTopPages(
rows: BrandLookupResult["topPages"],
filters: TopPagesFilterValues,
): BrandLookupResult["topPages"] {
const includeTerms = parseTerms(filters.include);
const excludeTerms = parseTerms(filters.exclude);
return rows.filter((row) => {
const textFields = [
row.url,
row.domain,
...row.keywords.map((keyword) => keyword.question),
]
.filter((v): v is string => Boolean(v))
.join(" ");
if (!passesTextFilter(textFields, includeTerms, excludeTerms)) return false;
if (filters.platform && row.platform !== filters.platform) return false;
if (
!passesNumericFilter(
row.mentions,
filters.minMentions,
filters.maxMentions,
)
) {
return false;
}
return true;
});
}
export function filterQueries(
rows: BrandLookupResult["topQueries"],
filters: QueriesFilterValues,
): BrandLookupResult["topQueries"] {
const includeTerms = parseTerms(filters.include);
const excludeTerms = parseTerms(filters.exclude);
return rows.filter((row) => {
const textFields = [row.question, ...row.brandsMentioned].join(" ");
if (!passesTextFilter(textFields, includeTerms, excludeTerms)) return false;
if (filters.platform && row.platform !== filters.platform) return false;
if (
!passesNumericFilter(
row.aiSearchVolume,
filters.minVolume,
filters.maxVolume,
)
) {
return false;
}
return true;
});
}
export function countActiveFilters(values: Record<string, string>): number {
return Object.values(values).filter((v) => v.trim() !== "").length;
}