mirror of
https://github.com/MarginaliaSearch/MarginaliaSearch.git
synced 2025-10-06 17:32:39 +02:00
Compare commits
18 Commits
deploy-004
...
deploy-005
Author | SHA1 | Date | |
---|---|---|---|
|
b245cc9f38 | ||
|
6614d05bdf | ||
|
55aeb03c4a | ||
|
faa589962f | ||
|
c7edd6b39f | ||
|
79da622e3b | ||
|
3da8337ba6 | ||
|
a32d230f0a | ||
|
3772bfd387 | ||
|
02a7900d1a | ||
|
a1fb92468f | ||
|
b7f0a2a98e | ||
|
5fb76b2e79 | ||
|
ad8c97f342 | ||
|
dc1b6373eb | ||
|
983d6d067c | ||
|
a84a06975c | ||
|
d2864c13ec |
@@ -103,11 +103,11 @@ public class DbDomainQueries {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<EdgeDomain> otherSubdomains(EdgeDomain domain, int cnt) {
|
public List<DomainWithNode> otherSubdomains(EdgeDomain domain, int cnt) {
|
||||||
List<EdgeDomain> ret = new ArrayList<>();
|
List<DomainWithNode> ret = new ArrayList<>();
|
||||||
|
|
||||||
try (var conn = dataSource.getConnection();
|
try (var conn = dataSource.getConnection();
|
||||||
var stmt = conn.prepareStatement("SELECT DOMAIN_NAME FROM EC_DOMAIN WHERE DOMAIN_TOP = ? LIMIT ?")) {
|
var stmt = conn.prepareStatement("SELECT DOMAIN_NAME, NODE_AFFINITY FROM EC_DOMAIN WHERE DOMAIN_TOP = ? LIMIT ?")) {
|
||||||
stmt.setString(1, domain.topDomain);
|
stmt.setString(1, domain.topDomain);
|
||||||
stmt.setInt(2, cnt);
|
stmt.setInt(2, cnt);
|
||||||
|
|
||||||
@@ -118,7 +118,7 @@ public class DbDomainQueries {
|
|||||||
if (sibling.equals(domain))
|
if (sibling.equals(domain))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
ret.add(sibling);
|
ret.add(new DomainWithNode(sibling, rs.getInt(2)));
|
||||||
}
|
}
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
logger.error("Failed to get domain neighbors");
|
logger.error("Failed to get domain neighbors");
|
||||||
@@ -126,4 +126,10 @@ public class DbDomainQueries {
|
|||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public record DomainWithNode (EdgeDomain domain, int nodeAffinity) {
|
||||||
|
public boolean isIndexed() {
|
||||||
|
return nodeAffinity > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -83,6 +83,11 @@ public class QueryParams {
|
|||||||
if (path.endsWith("StoryView.py")) { // folklore.org is neat
|
if (path.endsWith("StoryView.py")) { // folklore.org is neat
|
||||||
return param.startsWith("project=") || param.startsWith("story=");
|
return param.startsWith("project=") || param.startsWith("story=");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// www.perseus.tufts.edu:
|
||||||
|
if (param.startsWith("collection=")) return true;
|
||||||
|
if (param.startsWith("doc=")) return true;
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -89,7 +89,7 @@ public class DatabaseModule extends AbstractModule {
|
|||||||
config.addDataSourceProperty("prepStmtCacheSize", "250");
|
config.addDataSourceProperty("prepStmtCacheSize", "250");
|
||||||
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
|
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
|
||||||
|
|
||||||
config.setMaximumPoolSize(5);
|
config.setMaximumPoolSize(Integer.getInteger("db.poolSize", 5));
|
||||||
config.setMinimumIdle(2);
|
config.setMinimumIdle(2);
|
||||||
|
|
||||||
config.setMaxLifetime(Duration.ofMinutes(9).toMillis());
|
config.setMaxLifetime(Duration.ofMinutes(9).toMillis());
|
||||||
|
@@ -29,6 +29,7 @@ dependencies {
|
|||||||
implementation libs.jsoup
|
implementation libs.jsoup
|
||||||
implementation project(':third-party:rssreader')
|
implementation project(':third-party:rssreader')
|
||||||
implementation libs.opencsv
|
implementation libs.opencsv
|
||||||
|
implementation libs.slop
|
||||||
implementation libs.sqlite
|
implementation libs.sqlite
|
||||||
implementation libs.bundles.slf4j
|
implementation libs.bundles.slf4j
|
||||||
implementation libs.commons.lang3
|
implementation libs.commons.lang3
|
||||||
|
@@ -15,7 +15,9 @@ import java.util.Map;
|
|||||||
|
|
||||||
/** Client for local browserless.io API */
|
/** Client for local browserless.io API */
|
||||||
public class BrowserlessClient implements AutoCloseable {
|
public class BrowserlessClient implements AutoCloseable {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(BrowserlessClient.class);
|
private static final Logger logger = LoggerFactory.getLogger(BrowserlessClient.class);
|
||||||
|
private static final String BROWSERLESS_TOKEN = System.getProperty("live-capture.browserless-token", "BROWSERLESS_TOKEN");
|
||||||
|
|
||||||
private final HttpClient httpClient = HttpClient.newBuilder()
|
private final HttpClient httpClient = HttpClient.newBuilder()
|
||||||
.version(HttpClient.Version.HTTP_1_1)
|
.version(HttpClient.Version.HTTP_1_1)
|
||||||
@@ -36,7 +38,7 @@ public class BrowserlessClient implements AutoCloseable {
|
|||||||
);
|
);
|
||||||
|
|
||||||
var request = HttpRequest.newBuilder()
|
var request = HttpRequest.newBuilder()
|
||||||
.uri(browserlessURI.resolve("/content"))
|
.uri(browserlessURI.resolve("/content?token="+BROWSERLESS_TOKEN))
|
||||||
.method("POST", HttpRequest.BodyPublishers.ofString(
|
.method("POST", HttpRequest.BodyPublishers.ofString(
|
||||||
gson.toJson(requestData)
|
gson.toJson(requestData)
|
||||||
))
|
))
|
||||||
@@ -63,7 +65,7 @@ public class BrowserlessClient implements AutoCloseable {
|
|||||||
);
|
);
|
||||||
|
|
||||||
var request = HttpRequest.newBuilder()
|
var request = HttpRequest.newBuilder()
|
||||||
.uri(browserlessURI.resolve("/screenshot"))
|
.uri(browserlessURI.resolve("/screenshot?token="+BROWSERLESS_TOKEN))
|
||||||
.method("POST", HttpRequest.BodyPublishers.ofString(
|
.method("POST", HttpRequest.BodyPublishers.ofString(
|
||||||
gson.toJson(requestData)
|
gson.toJson(requestData)
|
||||||
))
|
))
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
package nu.marginalia.rss.model;
|
package nu.marginalia.rss.model;
|
||||||
|
|
||||||
import com.apptasticsoftware.rssreader.Item;
|
import nu.marginalia.rss.svc.SimpleFeedParser;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jsoup.Jsoup;
|
import org.jsoup.Jsoup;
|
||||||
@@ -18,37 +18,33 @@ public record FeedItem(String title,
|
|||||||
public static final int MAX_DESC_LENGTH = 255;
|
public static final int MAX_DESC_LENGTH = 255;
|
||||||
public static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
|
public static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
|
||||||
|
|
||||||
public static FeedItem fromItem(Item item, boolean keepFragment) {
|
public static FeedItem fromItem(SimpleFeedParser.ItemData item, boolean keepFragment) {
|
||||||
String title = item.getTitle().orElse("");
|
String title = item.title();
|
||||||
String date = getItemDate(item);
|
String date = getItemDate(item);
|
||||||
String description = getItemDescription(item);
|
String description = getItemDescription(item);
|
||||||
String url;
|
String url;
|
||||||
|
|
||||||
if (keepFragment || item.getLink().isEmpty()) {
|
if (keepFragment) {
|
||||||
url = item.getLink().orElse("");
|
url = item.url();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
try {
|
try {
|
||||||
String link = item.getLink().get();
|
String link = item.url();
|
||||||
var linkUri = new URI(link);
|
var linkUri = new URI(link);
|
||||||
var cleanUri = new URI(linkUri.getScheme(), linkUri.getAuthority(), linkUri.getPath(), linkUri.getQuery(), null);
|
var cleanUri = new URI(linkUri.getScheme(), linkUri.getAuthority(), linkUri.getPath(), linkUri.getQuery(), null);
|
||||||
url = cleanUri.toString();
|
url = cleanUri.toString();
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
// fallback to original link if we can't clean it, this is not a very important step
|
// fallback to original link if we can't clean it, this is not a very important step
|
||||||
url = item.getLink().get();
|
url = item.url();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new FeedItem(title, date, description, url);
|
return new FeedItem(title, date, description, url);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getItemDescription(Item item) {
|
private static String getItemDescription(SimpleFeedParser.ItemData item) {
|
||||||
Optional<String> description = item.getDescription();
|
String rawDescription = item.description();
|
||||||
if (description.isEmpty())
|
|
||||||
return "";
|
|
||||||
|
|
||||||
String rawDescription = description.get();
|
|
||||||
if (rawDescription.indexOf('<') >= 0) {
|
if (rawDescription.indexOf('<') >= 0) {
|
||||||
rawDescription = Jsoup.parseBodyFragment(rawDescription).text();
|
rawDescription = Jsoup.parseBodyFragment(rawDescription).text();
|
||||||
}
|
}
|
||||||
@@ -58,15 +54,18 @@ public record FeedItem(String title,
|
|||||||
|
|
||||||
// e.g. http://fabiensanglard.net/rss.xml does dates like this: 1 Apr 2021 00:00:00 +0000
|
// e.g. http://fabiensanglard.net/rss.xml does dates like this: 1 Apr 2021 00:00:00 +0000
|
||||||
private static final DateTimeFormatter extraFormatter = DateTimeFormatter.ofPattern("d MMM yyyy HH:mm:ss Z");
|
private static final DateTimeFormatter extraFormatter = DateTimeFormatter.ofPattern("d MMM yyyy HH:mm:ss Z");
|
||||||
private static String getItemDate(Item item) {
|
private static String getItemDate(SimpleFeedParser.ItemData item) {
|
||||||
Optional<ZonedDateTime> zonedDateTime = Optional.empty();
|
Optional<ZonedDateTime> zonedDateTime = Optional.empty();
|
||||||
try {
|
try {
|
||||||
zonedDateTime = item.getPubDateZonedDateTime();
|
zonedDateTime = item.getPubDateZonedDateTime();
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
zonedDateTime = item.getPubDate()
|
try {
|
||||||
.map(extraFormatter::parse)
|
zonedDateTime = Optional.of(ZonedDateTime.from(extraFormatter.parse(item.pubDate())));
|
||||||
.map(ZonedDateTime::from);
|
}
|
||||||
|
catch (Exception e2) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return zonedDateTime.map(date -> date.format(DATE_FORMAT)).orElse("");
|
return zonedDateTime.map(date -> date.format(DATE_FORMAT)).orElse("");
|
||||||
|
@@ -1,7 +1,5 @@
|
|||||||
package nu.marginalia.rss.svc;
|
package nu.marginalia.rss.svc;
|
||||||
|
|
||||||
import com.apptasticsoftware.rssreader.Item;
|
|
||||||
import com.apptasticsoftware.rssreader.RssReader;
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.opencsv.CSVReader;
|
import com.opencsv.CSVReader;
|
||||||
import nu.marginalia.WmsaHome;
|
import nu.marginalia.WmsaHome;
|
||||||
@@ -20,7 +18,6 @@ import nu.marginalia.storage.FileStorageService;
|
|||||||
import nu.marginalia.storage.model.FileStorage;
|
import nu.marginalia.storage.model.FileStorage;
|
||||||
import nu.marginalia.storage.model.FileStorageType;
|
import nu.marginalia.storage.model.FileStorageType;
|
||||||
import nu.marginalia.util.SimpleBlockingThreadPool;
|
import nu.marginalia.util.SimpleBlockingThreadPool;
|
||||||
import org.apache.commons.io.input.BOMInputStream;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -32,7 +29,6 @@ import java.net.URISyntaxException;
|
|||||||
import java.net.http.HttpClient;
|
import java.net.http.HttpClient;
|
||||||
import java.net.http.HttpRequest;
|
import java.net.http.HttpRequest;
|
||||||
import java.net.http.HttpResponse;
|
import java.net.http.HttpResponse;
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.time.*;
|
import java.time.*;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
@@ -48,8 +44,6 @@ public class FeedFetcherService {
|
|||||||
private static final int MAX_FEED_ITEMS = 10;
|
private static final int MAX_FEED_ITEMS = 10;
|
||||||
private static final Logger logger = LoggerFactory.getLogger(FeedFetcherService.class);
|
private static final Logger logger = LoggerFactory.getLogger(FeedFetcherService.class);
|
||||||
|
|
||||||
private final RssReader rssReader = new RssReader();
|
|
||||||
|
|
||||||
private final FeedDb feedDb;
|
private final FeedDb feedDb;
|
||||||
private final FileStorageService fileStorageService;
|
private final FileStorageService fileStorageService;
|
||||||
private final NodeConfigurationService nodeConfigurationService;
|
private final NodeConfigurationService nodeConfigurationService;
|
||||||
@@ -72,17 +66,6 @@ public class FeedFetcherService {
|
|||||||
this.nodeConfigurationService = nodeConfigurationService;
|
this.nodeConfigurationService = nodeConfigurationService;
|
||||||
this.serviceHeartbeat = serviceHeartbeat;
|
this.serviceHeartbeat = serviceHeartbeat;
|
||||||
this.executorClient = executorClient;
|
this.executorClient = executorClient;
|
||||||
|
|
||||||
|
|
||||||
// Add support for some alternate date tags for atom
|
|
||||||
rssReader.addItemExtension("issued", this::setDateFallback);
|
|
||||||
rssReader.addItemExtension("created", this::setDateFallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setDateFallback(Item item, String value) {
|
|
||||||
if (item.getPubDate().isEmpty()) {
|
|
||||||
item.setPubDate(value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum UpdateMode {
|
public enum UpdateMode {
|
||||||
@@ -96,6 +79,7 @@ public class FeedFetcherService {
|
|||||||
throw new IllegalStateException("Already updating feeds, refusing to start another update");
|
throw new IllegalStateException("Already updating feeds, refusing to start another update");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
try (FeedDbWriter writer = feedDb.createWriter();
|
try (FeedDbWriter writer = feedDb.createWriter();
|
||||||
HttpClient client = HttpClient.newBuilder()
|
HttpClient client = HttpClient.newBuilder()
|
||||||
.connectTimeout(Duration.ofSeconds(15))
|
.connectTimeout(Duration.ofSeconds(15))
|
||||||
@@ -103,6 +87,7 @@ public class FeedFetcherService {
|
|||||||
.followRedirects(HttpClient.Redirect.NORMAL)
|
.followRedirects(HttpClient.Redirect.NORMAL)
|
||||||
.version(HttpClient.Version.HTTP_2)
|
.version(HttpClient.Version.HTTP_2)
|
||||||
.build();
|
.build();
|
||||||
|
FeedJournal feedJournal = FeedJournal.create();
|
||||||
var heartbeat = serviceHeartbeat.createServiceAdHocTaskHeartbeat("Update Rss Feeds")
|
var heartbeat = serviceHeartbeat.createServiceAdHocTaskHeartbeat("Update Rss Feeds")
|
||||||
) {
|
) {
|
||||||
updating = true;
|
updating = true;
|
||||||
@@ -155,6 +140,8 @@ public class FeedFetcherService {
|
|||||||
case FetchResult.Success(String value, String etag) -> {
|
case FetchResult.Success(String value, String etag) -> {
|
||||||
writer.saveEtag(feed.domain(), etag);
|
writer.saveEtag(feed.domain(), etag);
|
||||||
writer.saveFeed(parseFeed(value, feed));
|
writer.saveFeed(parseFeed(value, feed));
|
||||||
|
|
||||||
|
feedJournal.record(feed.feedUrl(), value);
|
||||||
}
|
}
|
||||||
case FetchResult.NotModified() -> {
|
case FetchResult.NotModified() -> {
|
||||||
writer.saveEtag(feed.domain(), ifNoneMatchTag);
|
writer.saveEtag(feed.domain(), ifNoneMatchTag);
|
||||||
@@ -367,12 +354,7 @@ public class FeedFetcherService {
|
|||||||
|
|
||||||
public FeedItems parseFeed(String feedData, FeedDefinition definition) {
|
public FeedItems parseFeed(String feedData, FeedDefinition definition) {
|
||||||
try {
|
try {
|
||||||
feedData = sanitizeEntities(feedData);
|
List<SimpleFeedParser.ItemData> rawItems = SimpleFeedParser.parse(feedData);
|
||||||
|
|
||||||
List<Item> rawItems = rssReader.read(
|
|
||||||
// Massage the data to maximize the possibility of the flaky XML parser consuming it
|
|
||||||
new BOMInputStream(new ByteArrayInputStream(feedData.trim().getBytes(StandardCharsets.UTF_8)), false)
|
|
||||||
).toList();
|
|
||||||
|
|
||||||
boolean keepUriFragment = rawItems.size() < 2 || areFragmentsDisparate(rawItems);
|
boolean keepUriFragment = rawItems.size() < 2 || areFragmentsDisparate(rawItems);
|
||||||
|
|
||||||
@@ -395,33 +377,6 @@ public class FeedFetcherService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Map<String, String> HTML_ENTITIES = Map.of(
|
|
||||||
"»", "»",
|
|
||||||
"«", "«",
|
|
||||||
"—", "--",
|
|
||||||
"–", "-",
|
|
||||||
"’", "'",
|
|
||||||
"‘", "'",
|
|
||||||
""", "\"",
|
|
||||||
" ", ""
|
|
||||||
);
|
|
||||||
|
|
||||||
/** The XML parser will blow up if you insert HTML entities in the feed XML,
|
|
||||||
* which is unfortunately relatively common. Replace them as far as is possible
|
|
||||||
* with their corresponding characters
|
|
||||||
*/
|
|
||||||
static String sanitizeEntities(String feedData) {
|
|
||||||
String result = feedData;
|
|
||||||
for (Map.Entry<String, String> entry : HTML_ENTITIES.entrySet()) {
|
|
||||||
result = result.replace(entry.getKey(), entry.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle lone ampersands not part of a recognized XML entity
|
|
||||||
result = result.replaceAll("&(?!(amp|lt|gt|apos|quot);)", "&");
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Decide whether to keep URI fragments in the feed items.
|
/** Decide whether to keep URI fragments in the feed items.
|
||||||
* <p></p>
|
* <p></p>
|
||||||
* We keep fragments if there are multiple different fragments in the items.
|
* We keep fragments if there are multiple different fragments in the items.
|
||||||
@@ -429,16 +384,16 @@ public class FeedFetcherService {
|
|||||||
* @param items The items to check
|
* @param items The items to check
|
||||||
* @return True if we should keep the fragments, false otherwise
|
* @return True if we should keep the fragments, false otherwise
|
||||||
*/
|
*/
|
||||||
private boolean areFragmentsDisparate(List<Item> items) {
|
private boolean areFragmentsDisparate(List<SimpleFeedParser.ItemData> items) {
|
||||||
Set<String> seenFragments = new HashSet<>();
|
Set<String> seenFragments = new HashSet<>();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (var item : items) {
|
for (var item : items) {
|
||||||
if (item.getLink().isEmpty()) {
|
if (item.url().isBlank()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var link = item.getLink().get();
|
var link = item.url();
|
||||||
if (!link.contains("#")) {
|
if (!link.contains("#")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,76 @@
|
|||||||
|
package nu.marginalia.rss.svc;
|
||||||
|
|
||||||
|
import nu.marginalia.WmsaHome;
|
||||||
|
import nu.marginalia.slop.SlopTable;
|
||||||
|
import nu.marginalia.slop.column.string.StringColumn;
|
||||||
|
import nu.marginalia.slop.desc.StorageType;
|
||||||
|
import org.apache.commons.io.FileUtils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
|
/** Utility for recording fetched feeds to a journal, useful in debugging feed parser issues.
|
||||||
|
*/
|
||||||
|
public interface FeedJournal extends AutoCloseable {
|
||||||
|
StringColumn urlColumn = new StringColumn("url");
|
||||||
|
StringColumn contentsColumn = new StringColumn("contents", StandardCharsets.UTF_8, StorageType.ZSTD);
|
||||||
|
|
||||||
|
void record(String url, String contents) throws IOException;
|
||||||
|
void close() throws IOException;
|
||||||
|
|
||||||
|
|
||||||
|
static FeedJournal create() throws IOException {
|
||||||
|
if (Boolean.getBoolean("feedFetcher.persistJournal")) {
|
||||||
|
Path journalPath = WmsaHome.getDataPath().resolve("feed-journal");
|
||||||
|
if (Files.isDirectory(journalPath)) {
|
||||||
|
FileUtils.deleteDirectory(journalPath.toFile());
|
||||||
|
}
|
||||||
|
Files.createDirectories(journalPath);
|
||||||
|
return new RecordingFeedJournal(journalPath);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return new NoOpFeedJournal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NoOpFeedJournal implements FeedJournal {
|
||||||
|
@Override
|
||||||
|
public void record(String url, String contents) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RecordingFeedJournal extends SlopTable implements FeedJournal {
|
||||||
|
|
||||||
|
private final StringColumn.Writer urlWriter;
|
||||||
|
private final StringColumn.Writer contentsWriter;
|
||||||
|
|
||||||
|
public RecordingFeedJournal(Path path) throws IOException {
|
||||||
|
super(path, SlopTable.getNumPages(path, FeedJournal.urlColumn));
|
||||||
|
|
||||||
|
urlWriter = urlColumn.create(this);
|
||||||
|
contentsWriter = contentsColumn.create(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void record(String url, String contents) throws IOException {
|
||||||
|
urlWriter.put(url);
|
||||||
|
contentsWriter.put(contents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void replay(Path journalPath, BiConsumer<String, String> urlAndContent) throws IOException {
|
||||||
|
try (SlopTable table = new SlopTable(journalPath)) {
|
||||||
|
final StringColumn.Reader urlReader = urlColumn.open(table);
|
||||||
|
final StringColumn.Reader contentsReader = contentsColumn.open(table);
|
||||||
|
|
||||||
|
while (urlReader.hasRemaining()) {
|
||||||
|
urlAndContent.accept(urlReader.get(), contentsReader.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,94 @@
|
|||||||
|
package nu.marginalia.rss.svc;
|
||||||
|
|
||||||
|
import com.apptasticsoftware.rssreader.DateTimeParser;
|
||||||
|
import com.apptasticsoftware.rssreader.util.Default;
|
||||||
|
import org.jsoup.Jsoup;
|
||||||
|
import org.jsoup.parser.Parser;
|
||||||
|
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public class SimpleFeedParser {
|
||||||
|
|
||||||
|
private static final DateTimeParser dateTimeParser = Default.getDateTimeParser();
|
||||||
|
|
||||||
|
public record ItemData (
|
||||||
|
String title,
|
||||||
|
String description,
|
||||||
|
String url,
|
||||||
|
String pubDate
|
||||||
|
) {
|
||||||
|
public boolean isWellFormed() {
|
||||||
|
return title != null && !title.isBlank() &&
|
||||||
|
description != null && !description.isBlank() &&
|
||||||
|
url != null && !url.isBlank() &&
|
||||||
|
pubDate != null && !pubDate.isBlank();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<ZonedDateTime> getPubDateZonedDateTime() {
|
||||||
|
try {
|
||||||
|
return Optional.ofNullable(dateTimeParser.parse(pubDate()));
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<ItemData> parse(String content) {
|
||||||
|
var doc = Jsoup.parse(content, Parser.xmlParser());
|
||||||
|
List<ItemData> ret = new ArrayList<>();
|
||||||
|
|
||||||
|
doc.select("item, entry").forEach(element -> {
|
||||||
|
String link = "";
|
||||||
|
String title = "";
|
||||||
|
String description = "";
|
||||||
|
String pubDate = "";
|
||||||
|
|
||||||
|
for (String attr : List.of("title", "dc:title")) {
|
||||||
|
if (!title.isBlank())
|
||||||
|
break;
|
||||||
|
var tag = element.getElementsByTag(attr).first();
|
||||||
|
if (tag != null) {
|
||||||
|
title = tag.text();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String attr : List.of("title", "summary", "content", "description", "dc:description")) {
|
||||||
|
if (!description.isBlank())
|
||||||
|
break;
|
||||||
|
var tag = element.getElementsByTag(attr).first();
|
||||||
|
if (tag != null) {
|
||||||
|
description = tag.text();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String attr : List.of("pubDate", "published", "updated", "issued", "created", "dc:date")) {
|
||||||
|
if (!pubDate.isBlank())
|
||||||
|
break;
|
||||||
|
var tag = element.getElementsByTag(attr).first();
|
||||||
|
if (tag != null) {
|
||||||
|
pubDate = tag.text();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String attr : List.of("link", "url")) {
|
||||||
|
if (!link.isBlank())
|
||||||
|
break;
|
||||||
|
var tag = element.getElementsByTag(attr).first();
|
||||||
|
if (tag != null) {
|
||||||
|
link = tag.text();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.add(new ItemData(title, description, link, pubDate));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -2,16 +2,21 @@ package nu.marginalia.livecapture;
|
|||||||
|
|
||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.Tag;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.testcontainers.containers.GenericContainer;
|
import org.testcontainers.containers.GenericContainer;
|
||||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||||
import org.testcontainers.utility.DockerImageName;
|
import org.testcontainers.utility.DockerImageName;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
@Testcontainers
|
@Testcontainers
|
||||||
|
@Tag("slow")
|
||||||
public class BrowserlessClientTest {
|
public class BrowserlessClientTest {
|
||||||
static GenericContainer<?> container = new GenericContainer<>(DockerImageName.parse("browserless/chrome")).withExposedPorts(3000);
|
static GenericContainer<?> container = new GenericContainer<>(DockerImageName.parse("browserless/chrome"))
|
||||||
|
.withEnv(Map.of("TOKEN", "BROWSERLESS_TOKEN"))
|
||||||
|
.withExposedPorts(3000);
|
||||||
|
|
||||||
@BeforeAll
|
@BeforeAll
|
||||||
public static void setup() {
|
public static void setup() {
|
||||||
|
@@ -1,50 +0,0 @@
|
|||||||
package nu.marginalia.rss.svc;
|
|
||||||
|
|
||||||
import com.apptasticsoftware.rssreader.Item;
|
|
||||||
import com.apptasticsoftware.rssreader.RssReader;
|
|
||||||
import org.junit.jupiter.api.Assertions;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
public class TestXmlSanitization {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testPreservedEntities() {
|
|
||||||
Assertions.assertEquals("&", FeedFetcherService.sanitizeEntities("&"));
|
|
||||||
Assertions.assertEquals("<", FeedFetcherService.sanitizeEntities("<"));
|
|
||||||
Assertions.assertEquals(">", FeedFetcherService.sanitizeEntities(">"));
|
|
||||||
Assertions.assertEquals("'", FeedFetcherService.sanitizeEntities("'"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testNlnetTitleTag() {
|
|
||||||
// The NLnet atom feed puts HTML tags in the entry/title tags, which breaks the vanilla RssReader code
|
|
||||||
|
|
||||||
// Verify we're able to consume and strip out the HTML tags
|
|
||||||
RssReader r = new RssReader();
|
|
||||||
|
|
||||||
List<Item> items = r.read(ClassLoader.getSystemResourceAsStream("nlnet.atom")).toList();
|
|
||||||
|
|
||||||
Assertions.assertEquals(1, items.size());
|
|
||||||
for (var item : items) {
|
|
||||||
Assertions.assertEquals(Optional.of("50 Free and Open Source Projects Selected for NGI Zero grants"), item.getTitle());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testStrayAmpersand() {
|
|
||||||
Assertions.assertEquals("Bed & Breakfast", FeedFetcherService.sanitizeEntities("Bed & Breakfast"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testTranslatedHtmlEntity() {
|
|
||||||
Assertions.assertEquals("Foo -- Bar", FeedFetcherService.sanitizeEntities("Foo — Bar"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testTranslatedHtmlEntityQuot() {
|
|
||||||
Assertions.assertEquals("\"Bob\"", FeedFetcherService.sanitizeEntities(""Bob""));
|
|
||||||
}
|
|
||||||
}
|
|
@@ -2,9 +2,6 @@ package nu.marginalia.api.searchquery;
|
|||||||
|
|
||||||
import nu.marginalia.api.searchquery.model.query.SearchPhraseConstraint;
|
import nu.marginalia.api.searchquery.model.query.SearchPhraseConstraint;
|
||||||
import nu.marginalia.api.searchquery.model.query.SearchQuery;
|
import nu.marginalia.api.searchquery.model.query.SearchQuery;
|
||||||
import nu.marginalia.api.searchquery.model.results.Bm25Parameters;
|
|
||||||
import nu.marginalia.api.searchquery.model.results.ResultRankingParameters;
|
|
||||||
import nu.marginalia.index.query.limit.QueryLimits;
|
|
||||||
import nu.marginalia.index.query.limit.SpecificationLimit;
|
import nu.marginalia.index.query.limit.SpecificationLimit;
|
||||||
import nu.marginalia.index.query.limit.SpecificationLimitType;
|
import nu.marginalia.index.query.limit.SpecificationLimitType;
|
||||||
|
|
||||||
@@ -27,37 +24,19 @@ public class IndexProtobufCodec {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static QueryLimits convertQueryLimits(RpcQueryLimits queryLimits) {
|
|
||||||
return new QueryLimits(
|
|
||||||
queryLimits.getResultsByDomain(),
|
|
||||||
queryLimits.getResultsTotal(),
|
|
||||||
queryLimits.getTimeoutMs(),
|
|
||||||
queryLimits.getFetchSize()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static RpcQueryLimits convertQueryLimits(QueryLimits queryLimits) {
|
|
||||||
return RpcQueryLimits.newBuilder()
|
|
||||||
.setResultsByDomain(queryLimits.resultsByDomain())
|
|
||||||
.setResultsTotal(queryLimits.resultsTotal())
|
|
||||||
.setTimeoutMs(queryLimits.timeoutMs())
|
|
||||||
.setFetchSize(queryLimits.fetchSize())
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static SearchQuery convertRpcQuery(RpcQuery query) {
|
public static SearchQuery convertRpcQuery(RpcQuery query) {
|
||||||
List<SearchPhraseConstraint> phraeConstraints = new ArrayList<>();
|
List<SearchPhraseConstraint> phraseConstraints = new ArrayList<>();
|
||||||
|
|
||||||
for (int j = 0; j < query.getPhrasesCount(); j++) {
|
for (int j = 0; j < query.getPhrasesCount(); j++) {
|
||||||
var coh = query.getPhrases(j);
|
var coh = query.getPhrases(j);
|
||||||
if (coh.getType() == RpcPhrases.TYPE.OPTIONAL) {
|
if (coh.getType() == RpcPhrases.TYPE.OPTIONAL) {
|
||||||
phraeConstraints.add(new SearchPhraseConstraint.Optional(List.copyOf(coh.getTermsList())));
|
phraseConstraints.add(new SearchPhraseConstraint.Optional(List.copyOf(coh.getTermsList())));
|
||||||
}
|
}
|
||||||
else if (coh.getType() == RpcPhrases.TYPE.MANDATORY) {
|
else if (coh.getType() == RpcPhrases.TYPE.MANDATORY) {
|
||||||
phraeConstraints.add(new SearchPhraseConstraint.Mandatory(List.copyOf(coh.getTermsList())));
|
phraseConstraints.add(new SearchPhraseConstraint.Mandatory(List.copyOf(coh.getTermsList())));
|
||||||
}
|
}
|
||||||
else if (coh.getType() == RpcPhrases.TYPE.FULL) {
|
else if (coh.getType() == RpcPhrases.TYPE.FULL) {
|
||||||
phraeConstraints.add(new SearchPhraseConstraint.Full(List.copyOf(coh.getTermsList())));
|
phraseConstraints.add(new SearchPhraseConstraint.Full(List.copyOf(coh.getTermsList())));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
throw new IllegalArgumentException("Unknown phrase constraint type: " + coh.getType());
|
throw new IllegalArgumentException("Unknown phrase constraint type: " + coh.getType());
|
||||||
@@ -70,7 +49,7 @@ public class IndexProtobufCodec {
|
|||||||
query.getExcludeList(),
|
query.getExcludeList(),
|
||||||
query.getAdviceList(),
|
query.getAdviceList(),
|
||||||
query.getPriorityList(),
|
query.getPriorityList(),
|
||||||
phraeConstraints
|
phraseConstraints
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,60 +82,4 @@ public class IndexProtobufCodec {
|
|||||||
return subqueryBuilder.build();
|
return subqueryBuilder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ResultRankingParameters convertRankingParameterss(RpcResultRankingParameters params) {
|
|
||||||
if (params == null)
|
|
||||||
return ResultRankingParameters.sensibleDefaults();
|
|
||||||
|
|
||||||
return new ResultRankingParameters(
|
|
||||||
new Bm25Parameters(params.getBm25K(), params.getBm25B()),
|
|
||||||
params.getShortDocumentThreshold(),
|
|
||||||
params.getShortDocumentPenalty(),
|
|
||||||
params.getDomainRankBonus(),
|
|
||||||
params.getQualityPenalty(),
|
|
||||||
params.getShortSentenceThreshold(),
|
|
||||||
params.getShortSentencePenalty(),
|
|
||||||
params.getBm25Weight(),
|
|
||||||
params.getTcfFirstPositionWeight(),
|
|
||||||
params.getTcfVerbatimWeight(),
|
|
||||||
params.getTcfProximityWeight(),
|
|
||||||
ResultRankingParameters.TemporalBias.valueOf(params.getTemporalBias().getBias().name()),
|
|
||||||
params.getTemporalBiasWeight(),
|
|
||||||
params.getExportDebugData()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static RpcResultRankingParameters convertRankingParameterss(ResultRankingParameters rankingParams,
|
|
||||||
RpcTemporalBias temporalBias)
|
|
||||||
{
|
|
||||||
if (rankingParams == null) {
|
|
||||||
rankingParams = ResultRankingParameters.sensibleDefaults();
|
|
||||||
}
|
|
||||||
|
|
||||||
var builder = RpcResultRankingParameters.newBuilder()
|
|
||||||
.setBm25B(rankingParams.bm25Params.b())
|
|
||||||
.setBm25K(rankingParams.bm25Params.k())
|
|
||||||
.setShortDocumentThreshold(rankingParams.shortDocumentThreshold)
|
|
||||||
.setShortDocumentPenalty(rankingParams.shortDocumentPenalty)
|
|
||||||
.setDomainRankBonus(rankingParams.domainRankBonus)
|
|
||||||
.setQualityPenalty(rankingParams.qualityPenalty)
|
|
||||||
.setShortSentenceThreshold(rankingParams.shortSentenceThreshold)
|
|
||||||
.setShortSentencePenalty(rankingParams.shortSentencePenalty)
|
|
||||||
.setBm25Weight(rankingParams.bm25Weight)
|
|
||||||
.setTcfFirstPositionWeight(rankingParams.tcfFirstPosition)
|
|
||||||
.setTcfProximityWeight(rankingParams.tcfProximity)
|
|
||||||
.setTcfVerbatimWeight(rankingParams.tcfVerbatim)
|
|
||||||
.setTemporalBiasWeight(rankingParams.temporalBiasWeight)
|
|
||||||
.setExportDebugData(rankingParams.exportDebugData);
|
|
||||||
|
|
||||||
if (temporalBias != null && temporalBias.getBias() != RpcTemporalBias.Bias.NONE) {
|
|
||||||
builder.setTemporalBias(temporalBias);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
builder.setTemporalBias(RpcTemporalBias.newBuilder()
|
|
||||||
.setBias(RpcTemporalBias.Bias.valueOf(rankingParams.temporalBias.name())));
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -5,7 +5,7 @@ import nu.marginalia.api.searchquery.model.query.QueryParams;
|
|||||||
import nu.marginalia.api.searchquery.model.query.QueryResponse;
|
import nu.marginalia.api.searchquery.model.query.QueryResponse;
|
||||||
import nu.marginalia.api.searchquery.model.query.SearchSpecification;
|
import nu.marginalia.api.searchquery.model.query.SearchSpecification;
|
||||||
import nu.marginalia.api.searchquery.model.results.DecoratedSearchResultItem;
|
import nu.marginalia.api.searchquery.model.results.DecoratedSearchResultItem;
|
||||||
import nu.marginalia.api.searchquery.model.results.ResultRankingParameters;
|
import nu.marginalia.api.searchquery.model.results.PrototypeRankingParameters;
|
||||||
import nu.marginalia.api.searchquery.model.results.SearchResultItem;
|
import nu.marginalia.api.searchquery.model.results.SearchResultItem;
|
||||||
import nu.marginalia.api.searchquery.model.results.SearchResultKeywordScore;
|
import nu.marginalia.api.searchquery.model.results.SearchResultKeywordScore;
|
||||||
import nu.marginalia.api.searchquery.model.results.debug.DebugFactor;
|
import nu.marginalia.api.searchquery.model.results.debug.DebugFactor;
|
||||||
@@ -37,7 +37,7 @@ public class QueryProtobufCodec {
|
|||||||
builder.setSize(IndexProtobufCodec.convertSpecLimit(query.specs.size));
|
builder.setSize(IndexProtobufCodec.convertSpecLimit(query.specs.size));
|
||||||
builder.setRank(IndexProtobufCodec.convertSpecLimit(query.specs.rank));
|
builder.setRank(IndexProtobufCodec.convertSpecLimit(query.specs.rank));
|
||||||
|
|
||||||
builder.setQueryLimits(IndexProtobufCodec.convertQueryLimits(query.specs.queryLimits));
|
builder.setQueryLimits(query.specs.queryLimits);
|
||||||
|
|
||||||
// Query strategy may be overridden by the query, but if not, use the one from the request
|
// Query strategy may be overridden by the query, but if not, use the one from the request
|
||||||
if (query.specs.queryStrategy != null && query.specs.queryStrategy != QueryStrategy.AUTO)
|
if (query.specs.queryStrategy != null && query.specs.queryStrategy != QueryStrategy.AUTO)
|
||||||
@@ -45,9 +45,27 @@ public class QueryProtobufCodec {
|
|||||||
else
|
else
|
||||||
builder.setQueryStrategy(request.getQueryStrategy());
|
builder.setQueryStrategy(request.getQueryStrategy());
|
||||||
|
|
||||||
if (query.specs.rankingParams != null) {
|
if (request.getTemporalBias().getBias() != RpcTemporalBias.Bias.NONE) {
|
||||||
builder.setParameters(IndexProtobufCodec.convertRankingParameterss(query.specs.rankingParams, request.getTemporalBias()));
|
if (query.specs.rankingParams != null) {
|
||||||
|
builder.setParameters(
|
||||||
|
RpcResultRankingParameters.newBuilder(query.specs.rankingParams)
|
||||||
|
.setTemporalBias(request.getTemporalBias())
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
builder.setParameters(
|
||||||
|
RpcResultRankingParameters.newBuilder(PrototypeRankingParameters.sensibleDefaults())
|
||||||
|
.setTemporalBias(request.getTemporalBias())
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (query.specs.rankingParams != null) {
|
||||||
|
builder.setParameters(query.specs.rankingParams);
|
||||||
}
|
}
|
||||||
|
// else {
|
||||||
|
// if we have no ranking params, we don't need to set them, the client check and use the default values
|
||||||
|
// so we don't need to send this huge object over the wire
|
||||||
|
// }
|
||||||
|
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
@@ -65,18 +83,13 @@ public class QueryProtobufCodec {
|
|||||||
builder.setSize(IndexProtobufCodec.convertSpecLimit(query.specs.size));
|
builder.setSize(IndexProtobufCodec.convertSpecLimit(query.specs.size));
|
||||||
builder.setRank(IndexProtobufCodec.convertSpecLimit(query.specs.rank));
|
builder.setRank(IndexProtobufCodec.convertSpecLimit(query.specs.rank));
|
||||||
|
|
||||||
builder.setQueryLimits(IndexProtobufCodec.convertQueryLimits(query.specs.queryLimits));
|
builder.setQueryLimits(query.specs.queryLimits);
|
||||||
|
|
||||||
// Query strategy may be overridden by the query, but if not, use the one from the request
|
// Query strategy may be overridden by the query, but if not, use the one from the request
|
||||||
builder.setQueryStrategy(query.specs.queryStrategy.name());
|
builder.setQueryStrategy(query.specs.queryStrategy.name());
|
||||||
|
|
||||||
if (query.specs.rankingParams != null) {
|
if (query.specs.rankingParams != null) {
|
||||||
builder.setParameters(IndexProtobufCodec.convertRankingParameterss(
|
builder.setParameters(query.specs.rankingParams);
|
||||||
query.specs.rankingParams,
|
|
||||||
RpcTemporalBias.newBuilder().setBias(
|
|
||||||
RpcTemporalBias.Bias.NONE)
|
|
||||||
.build())
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder.build();
|
return builder.build();
|
||||||
@@ -95,10 +108,10 @@ public class QueryProtobufCodec {
|
|||||||
IndexProtobufCodec.convertSpecLimit(request.getSize()),
|
IndexProtobufCodec.convertSpecLimit(request.getSize()),
|
||||||
IndexProtobufCodec.convertSpecLimit(request.getRank()),
|
IndexProtobufCodec.convertSpecLimit(request.getRank()),
|
||||||
request.getDomainIdsList(),
|
request.getDomainIdsList(),
|
||||||
IndexProtobufCodec.convertQueryLimits(request.getQueryLimits()),
|
request.getQueryLimits(),
|
||||||
request.getSearchSetIdentifier(),
|
request.getSearchSetIdentifier(),
|
||||||
QueryStrategy.valueOf(request.getQueryStrategy()),
|
QueryStrategy.valueOf(request.getQueryStrategy()),
|
||||||
ResultRankingParameters.TemporalBias.valueOf(request.getTemporalBias().getBias().name()),
|
RpcTemporalBias.Bias.valueOf(request.getTemporalBias().getBias().name()),
|
||||||
request.getPagination().getPage()
|
request.getPagination().getPage()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -294,9 +307,9 @@ public class QueryProtobufCodec {
|
|||||||
IndexProtobufCodec.convertSpecLimit(specs.getYear()),
|
IndexProtobufCodec.convertSpecLimit(specs.getYear()),
|
||||||
IndexProtobufCodec.convertSpecLimit(specs.getSize()),
|
IndexProtobufCodec.convertSpecLimit(specs.getSize()),
|
||||||
IndexProtobufCodec.convertSpecLimit(specs.getRank()),
|
IndexProtobufCodec.convertSpecLimit(specs.getRank()),
|
||||||
IndexProtobufCodec.convertQueryLimits(specs.getQueryLimits()),
|
specs.getQueryLimits(),
|
||||||
QueryStrategy.valueOf(specs.getQueryStrategy()),
|
QueryStrategy.valueOf(specs.getQueryStrategy()),
|
||||||
IndexProtobufCodec.convertRankingParameterss(specs.getParameters())
|
specs.hasParameters() ? specs.getParameters() : null
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,7 +320,7 @@ public class QueryProtobufCodec {
|
|||||||
.addAllTacitExcludes(params.tacitExcludes())
|
.addAllTacitExcludes(params.tacitExcludes())
|
||||||
.addAllTacitPriority(params.tacitPriority())
|
.addAllTacitPriority(params.tacitPriority())
|
||||||
.setHumanQuery(params.humanQuery())
|
.setHumanQuery(params.humanQuery())
|
||||||
.setQueryLimits(IndexProtobufCodec.convertQueryLimits(params.limits()))
|
.setQueryLimits(params.limits())
|
||||||
.setQuality(IndexProtobufCodec.convertSpecLimit(params.quality()))
|
.setQuality(IndexProtobufCodec.convertSpecLimit(params.quality()))
|
||||||
.setYear(IndexProtobufCodec.convertSpecLimit(params.year()))
|
.setYear(IndexProtobufCodec.convertSpecLimit(params.year()))
|
||||||
.setSize(IndexProtobufCodec.convertSpecLimit(params.size()))
|
.setSize(IndexProtobufCodec.convertSpecLimit(params.size()))
|
||||||
@@ -319,7 +332,7 @@ public class QueryProtobufCodec {
|
|||||||
.build())
|
.build())
|
||||||
.setPagination(RpcQsQueryPagination.newBuilder()
|
.setPagination(RpcQsQueryPagination.newBuilder()
|
||||||
.setPage(params.page())
|
.setPage(params.page())
|
||||||
.setPageSize(Math.min(100, params.limits().resultsTotal()))
|
.setPageSize(Math.min(100, params.limits().getResultsTotal()))
|
||||||
.build());
|
.build());
|
||||||
|
|
||||||
if (params.nearDomain() != null)
|
if (params.nearDomain() != null)
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
package nu.marginalia.api.searchquery.model.query;
|
package nu.marginalia.api.searchquery.model.query;
|
||||||
|
|
||||||
import nu.marginalia.api.searchquery.model.results.ResultRankingParameters;
|
import nu.marginalia.api.searchquery.RpcQueryLimits;
|
||||||
import nu.marginalia.index.query.limit.QueryLimits;
|
import nu.marginalia.api.searchquery.RpcTemporalBias;
|
||||||
import nu.marginalia.index.query.limit.QueryStrategy;
|
import nu.marginalia.index.query.limit.QueryStrategy;
|
||||||
import nu.marginalia.index.query.limit.SpecificationLimit;
|
import nu.marginalia.index.query.limit.SpecificationLimit;
|
||||||
|
|
||||||
@@ -21,14 +21,14 @@ public record QueryParams(
|
|||||||
SpecificationLimit size,
|
SpecificationLimit size,
|
||||||
SpecificationLimit rank,
|
SpecificationLimit rank,
|
||||||
List<Integer> domainIds,
|
List<Integer> domainIds,
|
||||||
QueryLimits limits,
|
RpcQueryLimits limits,
|
||||||
String identifier,
|
String identifier,
|
||||||
QueryStrategy queryStrategy,
|
QueryStrategy queryStrategy,
|
||||||
ResultRankingParameters.TemporalBias temporalBias,
|
RpcTemporalBias.Bias temporalBias,
|
||||||
int page
|
int page
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
public QueryParams(String query, QueryLimits limits, String identifier) {
|
public QueryParams(String query, RpcQueryLimits limits, String identifier) {
|
||||||
this(query, null,
|
this(query, null,
|
||||||
List.of(),
|
List.of(),
|
||||||
List.of(),
|
List.of(),
|
||||||
@@ -42,7 +42,7 @@ public record QueryParams(
|
|||||||
limits,
|
limits,
|
||||||
identifier,
|
identifier,
|
||||||
QueryStrategy.AUTO,
|
QueryStrategy.AUTO,
|
||||||
ResultRankingParameters.TemporalBias.NONE,
|
RpcTemporalBias.Bias.NONE,
|
||||||
1 // page
|
1 // page
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -1,10 +1,11 @@
|
|||||||
package nu.marginalia.api.searchquery.model.query;
|
package nu.marginalia.api.searchquery.model.query;
|
||||||
|
|
||||||
import nu.marginalia.api.searchquery.model.results.ResultRankingParameters;
|
import nu.marginalia.api.searchquery.RpcQueryLimits;
|
||||||
import nu.marginalia.index.query.limit.QueryLimits;
|
import nu.marginalia.api.searchquery.RpcResultRankingParameters;
|
||||||
import nu.marginalia.index.query.limit.QueryStrategy;
|
import nu.marginalia.index.query.limit.QueryStrategy;
|
||||||
import nu.marginalia.index.query.limit.SpecificationLimit;
|
import nu.marginalia.index.query.limit.SpecificationLimit;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class SearchSpecification {
|
public class SearchSpecification {
|
||||||
@@ -24,11 +25,12 @@ public class SearchSpecification {
|
|||||||
public SpecificationLimit size;
|
public SpecificationLimit size;
|
||||||
public SpecificationLimit rank;
|
public SpecificationLimit rank;
|
||||||
|
|
||||||
public final QueryLimits queryLimits;
|
public final RpcQueryLimits queryLimits;
|
||||||
|
|
||||||
public final QueryStrategy queryStrategy;
|
public final QueryStrategy queryStrategy;
|
||||||
|
|
||||||
public final ResultRankingParameters rankingParams;
|
@Nullable
|
||||||
|
public final RpcResultRankingParameters rankingParams;
|
||||||
|
|
||||||
public SearchSpecification(SearchQuery query,
|
public SearchSpecification(SearchQuery query,
|
||||||
List<Integer> domains,
|
List<Integer> domains,
|
||||||
@@ -38,9 +40,9 @@ public class SearchSpecification {
|
|||||||
SpecificationLimit year,
|
SpecificationLimit year,
|
||||||
SpecificationLimit size,
|
SpecificationLimit size,
|
||||||
SpecificationLimit rank,
|
SpecificationLimit rank,
|
||||||
QueryLimits queryLimits,
|
RpcQueryLimits queryLimits,
|
||||||
QueryStrategy queryStrategy,
|
QueryStrategy queryStrategy,
|
||||||
ResultRankingParameters rankingParams)
|
@Nullable RpcResultRankingParameters rankingParams)
|
||||||
{
|
{
|
||||||
this.query = query;
|
this.query = query;
|
||||||
this.domains = domains;
|
this.domains = domains;
|
||||||
@@ -91,7 +93,7 @@ public class SearchSpecification {
|
|||||||
return this.rank;
|
return this.rank;
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryLimits getQueryLimits() {
|
public RpcQueryLimits getQueryLimits() {
|
||||||
return this.queryLimits;
|
return this.queryLimits;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,7 +101,7 @@ public class SearchSpecification {
|
|||||||
return this.queryStrategy;
|
return this.queryStrategy;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResultRankingParameters getRankingParams() {
|
public RpcResultRankingParameters getRankingParams() {
|
||||||
return this.rankingParams;
|
return this.rankingParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,9 +122,9 @@ public class SearchSpecification {
|
|||||||
private boolean size$set;
|
private boolean size$set;
|
||||||
private SpecificationLimit rank$value;
|
private SpecificationLimit rank$value;
|
||||||
private boolean rank$set;
|
private boolean rank$set;
|
||||||
private QueryLimits queryLimits;
|
private RpcQueryLimits queryLimits;
|
||||||
private QueryStrategy queryStrategy;
|
private QueryStrategy queryStrategy;
|
||||||
private ResultRankingParameters rankingParams;
|
private RpcResultRankingParameters rankingParams;
|
||||||
|
|
||||||
SearchSpecificationBuilder() {
|
SearchSpecificationBuilder() {
|
||||||
}
|
}
|
||||||
@@ -171,7 +173,7 @@ public class SearchSpecification {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SearchSpecificationBuilder queryLimits(QueryLimits queryLimits) {
|
public SearchSpecificationBuilder queryLimits(RpcQueryLimits queryLimits) {
|
||||||
this.queryLimits = queryLimits;
|
this.queryLimits = queryLimits;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@@ -181,7 +183,7 @@ public class SearchSpecification {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SearchSpecificationBuilder rankingParams(ResultRankingParameters rankingParams) {
|
public SearchSpecificationBuilder rankingParams(RpcResultRankingParameters rankingParams) {
|
||||||
this.rankingParams = rankingParams;
|
this.rankingParams = rankingParams;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,33 @@
|
|||||||
|
package nu.marginalia.api.searchquery.model.results;
|
||||||
|
|
||||||
|
import nu.marginalia.api.searchquery.RpcResultRankingParameters;
|
||||||
|
import nu.marginalia.api.searchquery.RpcTemporalBias;
|
||||||
|
|
||||||
|
public class PrototypeRankingParameters {
|
||||||
|
|
||||||
|
/** These are the default ranking parameters that are used when no parameters are specified. */
|
||||||
|
|
||||||
|
private static final RpcResultRankingParameters _sensibleDefaults = RpcResultRankingParameters.newBuilder()
|
||||||
|
.setBm25B(0.5)
|
||||||
|
.setBm25K(1.2)
|
||||||
|
.setShortDocumentThreshold(2000)
|
||||||
|
.setShortDocumentPenalty(2.)
|
||||||
|
.setDomainRankBonus(1 / 100.)
|
||||||
|
.setQualityPenalty(1 / 15.)
|
||||||
|
.setShortSentenceThreshold(2)
|
||||||
|
.setShortSentencePenalty(5)
|
||||||
|
.setBm25Weight(1.)
|
||||||
|
.setTcfVerbatimWeight(1.)
|
||||||
|
.setTcfProximityWeight(1.)
|
||||||
|
.setTcfFirstPositionWeight(5)
|
||||||
|
.setTemporalBias(RpcTemporalBias.newBuilder().setBias(RpcTemporalBias.Bias.NONE))
|
||||||
|
.setTemporalBiasWeight(5.0)
|
||||||
|
.setExportDebugData(false)
|
||||||
|
.setDisablePenalties(false)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
public static RpcResultRankingParameters sensibleDefaults() {
|
||||||
|
return _sensibleDefaults;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -1,12 +1,13 @@
|
|||||||
package nu.marginalia.api.searchquery.model.results;
|
package nu.marginalia.api.searchquery.model.results;
|
||||||
|
|
||||||
|
import nu.marginalia.api.searchquery.RpcResultRankingParameters;
|
||||||
import nu.marginalia.api.searchquery.model.compiled.CqDataInt;
|
import nu.marginalia.api.searchquery.model.compiled.CqDataInt;
|
||||||
|
|
||||||
import java.util.BitSet;
|
import java.util.BitSet;
|
||||||
|
|
||||||
public class ResultRankingContext {
|
public class ResultRankingContext {
|
||||||
private final int docCount;
|
private final int docCount;
|
||||||
public final ResultRankingParameters params;
|
public final RpcResultRankingParameters params;
|
||||||
|
|
||||||
|
|
||||||
public final BitSet regularMask;
|
public final BitSet regularMask;
|
||||||
@@ -21,7 +22,7 @@ public class ResultRankingContext {
|
|||||||
public final CqDataInt priorityCounts;
|
public final CqDataInt priorityCounts;
|
||||||
|
|
||||||
public ResultRankingContext(int docCount,
|
public ResultRankingContext(int docCount,
|
||||||
ResultRankingParameters params,
|
RpcResultRankingParameters params,
|
||||||
BitSet ngramsMask,
|
BitSet ngramsMask,
|
||||||
BitSet regularMask,
|
BitSet regularMask,
|
||||||
CqDataInt fullCounts,
|
CqDataInt fullCounts,
|
||||||
|
@@ -1,278 +0,0 @@
|
|||||||
package nu.marginalia.api.searchquery.model.results;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public class ResultRankingParameters {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tuning for BM25 when applied to full document matches
|
|
||||||
*/
|
|
||||||
public final Bm25Parameters bm25Params;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Documents below this length are penalized
|
|
||||||
*/
|
|
||||||
public int shortDocumentThreshold;
|
|
||||||
|
|
||||||
public double shortDocumentPenalty;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Scaling factor associated with domain rank (unscaled rank value is 0-255; high is good)
|
|
||||||
*/
|
|
||||||
public double domainRankBonus;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Scaling factor associated with document quality (unscaled rank value is 0-15; high is bad)
|
|
||||||
*/
|
|
||||||
public double qualityPenalty;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Average sentence length values below this threshold are penalized, range [0-4), 2 or 3 is probably what you want
|
|
||||||
*/
|
|
||||||
public int shortSentenceThreshold;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Magnitude of penalty for documents with low average sentence length
|
|
||||||
*/
|
|
||||||
public double shortSentencePenalty;
|
|
||||||
|
|
||||||
public double bm25Weight;
|
|
||||||
public double tcfFirstPosition;
|
|
||||||
public double tcfVerbatim;
|
|
||||||
public double tcfProximity;
|
|
||||||
|
|
||||||
public TemporalBias temporalBias;
|
|
||||||
public double temporalBiasWeight;
|
|
||||||
|
|
||||||
public boolean exportDebugData;
|
|
||||||
|
|
||||||
public ResultRankingParameters(Bm25Parameters bm25Params, int shortDocumentThreshold, double shortDocumentPenalty, double domainRankBonus, double qualityPenalty, int shortSentenceThreshold, double shortSentencePenalty, double bm25Weight, double tcfFirstPosition, double tcfVerbatim, double tcfProximity, TemporalBias temporalBias, double temporalBiasWeight, boolean exportDebugData) {
|
|
||||||
this.bm25Params = bm25Params;
|
|
||||||
this.shortDocumentThreshold = shortDocumentThreshold;
|
|
||||||
this.shortDocumentPenalty = shortDocumentPenalty;
|
|
||||||
this.domainRankBonus = domainRankBonus;
|
|
||||||
this.qualityPenalty = qualityPenalty;
|
|
||||||
this.shortSentenceThreshold = shortSentenceThreshold;
|
|
||||||
this.shortSentencePenalty = shortSentencePenalty;
|
|
||||||
this.bm25Weight = bm25Weight;
|
|
||||||
this.tcfFirstPosition = tcfFirstPosition;
|
|
||||||
this.tcfVerbatim = tcfVerbatim;
|
|
||||||
this.tcfProximity = tcfProximity;
|
|
||||||
this.temporalBias = temporalBias;
|
|
||||||
this.temporalBiasWeight = temporalBiasWeight;
|
|
||||||
this.exportDebugData = exportDebugData;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ResultRankingParameters sensibleDefaults() {
|
|
||||||
return builder()
|
|
||||||
.bm25Params(new Bm25Parameters(1.2, 0.5))
|
|
||||||
.shortDocumentThreshold(2000)
|
|
||||||
.shortDocumentPenalty(2.)
|
|
||||||
.domainRankBonus(1 / 100.)
|
|
||||||
.qualityPenalty(1 / 15.)
|
|
||||||
.shortSentenceThreshold(2)
|
|
||||||
.shortSentencePenalty(5)
|
|
||||||
.bm25Weight(1.)
|
|
||||||
.tcfVerbatim(1.)
|
|
||||||
.tcfProximity(1.)
|
|
||||||
.tcfFirstPosition(5)
|
|
||||||
.temporalBias(TemporalBias.NONE)
|
|
||||||
.temporalBiasWeight(5.0)
|
|
||||||
.exportDebugData(false)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ResultRankingParametersBuilder builder() {
|
|
||||||
return new ResultRankingParametersBuilder();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Bm25Parameters getBm25Params() {
|
|
||||||
return this.bm25Params;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getShortDocumentThreshold() {
|
|
||||||
return this.shortDocumentThreshold;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getShortDocumentPenalty() {
|
|
||||||
return this.shortDocumentPenalty;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getDomainRankBonus() {
|
|
||||||
return this.domainRankBonus;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getQualityPenalty() {
|
|
||||||
return this.qualityPenalty;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getShortSentenceThreshold() {
|
|
||||||
return this.shortSentenceThreshold;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getShortSentencePenalty() {
|
|
||||||
return this.shortSentencePenalty;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getBm25Weight() {
|
|
||||||
return this.bm25Weight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getTcfFirstPosition() {
|
|
||||||
return this.tcfFirstPosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getTcfVerbatim() {
|
|
||||||
return this.tcfVerbatim;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getTcfProximity() {
|
|
||||||
return this.tcfProximity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TemporalBias getTemporalBias() {
|
|
||||||
return this.temporalBias;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getTemporalBiasWeight() {
|
|
||||||
return this.temporalBiasWeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isExportDebugData() {
|
|
||||||
return this.exportDebugData;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (!(o instanceof ResultRankingParameters that)) return false;
|
|
||||||
|
|
||||||
return shortDocumentThreshold == that.shortDocumentThreshold && Double.compare(shortDocumentPenalty, that.shortDocumentPenalty) == 0 && Double.compare(domainRankBonus, that.domainRankBonus) == 0 && Double.compare(qualityPenalty, that.qualityPenalty) == 0 && shortSentenceThreshold == that.shortSentenceThreshold && Double.compare(shortSentencePenalty, that.shortSentencePenalty) == 0 && Double.compare(bm25Weight, that.bm25Weight) == 0 && Double.compare(tcfFirstPosition, that.tcfFirstPosition) == 0 && Double.compare(tcfVerbatim, that.tcfVerbatim) == 0 && Double.compare(tcfProximity, that.tcfProximity) == 0 && Double.compare(temporalBiasWeight, that.temporalBiasWeight) == 0 && exportDebugData == that.exportDebugData && Objects.equals(bm25Params, that.bm25Params) && temporalBias == that.temporalBias;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
int result = Objects.hashCode(bm25Params);
|
|
||||||
result = 31 * result + shortDocumentThreshold;
|
|
||||||
result = 31 * result + Double.hashCode(shortDocumentPenalty);
|
|
||||||
result = 31 * result + Double.hashCode(domainRankBonus);
|
|
||||||
result = 31 * result + Double.hashCode(qualityPenalty);
|
|
||||||
result = 31 * result + shortSentenceThreshold;
|
|
||||||
result = 31 * result + Double.hashCode(shortSentencePenalty);
|
|
||||||
result = 31 * result + Double.hashCode(bm25Weight);
|
|
||||||
result = 31 * result + Double.hashCode(tcfFirstPosition);
|
|
||||||
result = 31 * result + Double.hashCode(tcfVerbatim);
|
|
||||||
result = 31 * result + Double.hashCode(tcfProximity);
|
|
||||||
result = 31 * result + Objects.hashCode(temporalBias);
|
|
||||||
result = 31 * result + Double.hashCode(temporalBiasWeight);
|
|
||||||
result = 31 * result + Boolean.hashCode(exportDebugData);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toString() {
|
|
||||||
return "ResultRankingParameters(bm25Params=" + this.getBm25Params() + ", shortDocumentThreshold=" + this.getShortDocumentThreshold() + ", shortDocumentPenalty=" + this.getShortDocumentPenalty() + ", domainRankBonus=" + this.getDomainRankBonus() + ", qualityPenalty=" + this.getQualityPenalty() + ", shortSentenceThreshold=" + this.getShortSentenceThreshold() + ", shortSentencePenalty=" + this.getShortSentencePenalty() + ", bm25Weight=" + this.getBm25Weight() + ", tcfFirstPosition=" + this.getTcfFirstPosition() + ", tcfVerbatim=" + this.getTcfVerbatim() + ", tcfProximity=" + this.getTcfProximity() + ", temporalBias=" + this.getTemporalBias() + ", temporalBiasWeight=" + this.getTemporalBiasWeight() + ", exportDebugData=" + this.isExportDebugData() + ")";
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum TemporalBias {
|
|
||||||
RECENT, OLD, NONE
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class ResultRankingParametersBuilder {
|
|
||||||
private Bm25Parameters bm25Params;
|
|
||||||
private int shortDocumentThreshold;
|
|
||||||
private double shortDocumentPenalty;
|
|
||||||
private double domainRankBonus;
|
|
||||||
private double qualityPenalty;
|
|
||||||
private int shortSentenceThreshold;
|
|
||||||
private double shortSentencePenalty;
|
|
||||||
private double bm25Weight;
|
|
||||||
private double tcfFirstPosition;
|
|
||||||
private double tcfVerbatim;
|
|
||||||
private double tcfProximity;
|
|
||||||
private TemporalBias temporalBias;
|
|
||||||
private double temporalBiasWeight;
|
|
||||||
private boolean exportDebugData;
|
|
||||||
|
|
||||||
ResultRankingParametersBuilder() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public ResultRankingParametersBuilder bm25Params(Bm25Parameters bm25Params) {
|
|
||||||
this.bm25Params = bm25Params;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ResultRankingParametersBuilder shortDocumentThreshold(int shortDocumentThreshold) {
|
|
||||||
this.shortDocumentThreshold = shortDocumentThreshold;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ResultRankingParametersBuilder shortDocumentPenalty(double shortDocumentPenalty) {
|
|
||||||
this.shortDocumentPenalty = shortDocumentPenalty;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ResultRankingParametersBuilder domainRankBonus(double domainRankBonus) {
|
|
||||||
this.domainRankBonus = domainRankBonus;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ResultRankingParametersBuilder qualityPenalty(double qualityPenalty) {
|
|
||||||
this.qualityPenalty = qualityPenalty;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ResultRankingParametersBuilder shortSentenceThreshold(int shortSentenceThreshold) {
|
|
||||||
this.shortSentenceThreshold = shortSentenceThreshold;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ResultRankingParametersBuilder shortSentencePenalty(double shortSentencePenalty) {
|
|
||||||
this.shortSentencePenalty = shortSentencePenalty;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ResultRankingParametersBuilder bm25Weight(double bm25Weight) {
|
|
||||||
this.bm25Weight = bm25Weight;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ResultRankingParametersBuilder tcfFirstPosition(double tcfFirstPosition) {
|
|
||||||
this.tcfFirstPosition = tcfFirstPosition;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ResultRankingParametersBuilder tcfVerbatim(double tcfVerbatim) {
|
|
||||||
this.tcfVerbatim = tcfVerbatim;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ResultRankingParametersBuilder tcfProximity(double tcfProximity) {
|
|
||||||
this.tcfProximity = tcfProximity;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ResultRankingParametersBuilder temporalBias(TemporalBias temporalBias) {
|
|
||||||
this.temporalBias = temporalBias;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ResultRankingParametersBuilder temporalBiasWeight(double temporalBiasWeight) {
|
|
||||||
this.temporalBiasWeight = temporalBiasWeight;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ResultRankingParametersBuilder exportDebugData(boolean exportDebugData) {
|
|
||||||
this.exportDebugData = exportDebugData;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ResultRankingParameters build() {
|
|
||||||
return new ResultRankingParameters(this.bm25Params, this.shortDocumentThreshold, this.shortDocumentPenalty, this.domainRankBonus, this.qualityPenalty, this.shortSentenceThreshold, this.shortSentencePenalty, this.bm25Weight, this.tcfFirstPosition, this.tcfVerbatim, this.tcfProximity, this.temporalBias, this.temporalBiasWeight, this.exportDebugData);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toString() {
|
|
||||||
return "ResultRankingParameters.ResultRankingParametersBuilder(bm25Params=" + this.bm25Params + ", shortDocumentThreshold=" + this.shortDocumentThreshold + ", shortDocumentPenalty=" + this.shortDocumentPenalty + ", domainRankBonus=" + this.domainRankBonus + ", qualityPenalty=" + this.qualityPenalty + ", shortSentenceThreshold=" + this.shortSentenceThreshold + ", shortSentencePenalty=" + this.shortSentencePenalty + ", bm25Weight=" + this.bm25Weight + ", tcfFirstPosition=" + this.tcfFirstPosition + ", tcfVerbatim=" + this.tcfVerbatim + ", tcfProximity=" + this.tcfProximity + ", temporalBias=" + this.temporalBias + ", temporalBiasWeight=" + this.temporalBiasWeight + ", exportDebugData=" + this.exportDebugData + ")";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -162,6 +162,7 @@ message RpcResultRankingParameters {
|
|||||||
double temporalBiasWeight = 17;
|
double temporalBiasWeight = 17;
|
||||||
|
|
||||||
bool exportDebugData = 18;
|
bool exportDebugData = 18;
|
||||||
|
bool disablePenalties = 19;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -3,8 +3,6 @@ package nu.marginalia.index.client;
|
|||||||
import nu.marginalia.api.searchquery.IndexProtobufCodec;
|
import nu.marginalia.api.searchquery.IndexProtobufCodec;
|
||||||
import nu.marginalia.api.searchquery.model.query.SearchPhraseConstraint;
|
import nu.marginalia.api.searchquery.model.query.SearchPhraseConstraint;
|
||||||
import nu.marginalia.api.searchquery.model.query.SearchQuery;
|
import nu.marginalia.api.searchquery.model.query.SearchQuery;
|
||||||
import nu.marginalia.api.searchquery.model.results.ResultRankingParameters;
|
|
||||||
import nu.marginalia.index.query.limit.QueryLimits;
|
|
||||||
import nu.marginalia.index.query.limit.SpecificationLimit;
|
import nu.marginalia.index.query.limit.SpecificationLimit;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
@@ -22,18 +20,6 @@ class IndexProtobufCodecTest {
|
|||||||
verifyIsIdentityTransformation(SpecificationLimit.lessThan(1), l -> IndexProtobufCodec.convertSpecLimit(IndexProtobufCodec.convertSpecLimit(l)));
|
verifyIsIdentityTransformation(SpecificationLimit.lessThan(1), l -> IndexProtobufCodec.convertSpecLimit(IndexProtobufCodec.convertSpecLimit(l)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRankingParameters() {
|
|
||||||
verifyIsIdentityTransformation(ResultRankingParameters.sensibleDefaults(),
|
|
||||||
p -> IndexProtobufCodec.convertRankingParameterss(IndexProtobufCodec.convertRankingParameterss(p, null)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testQueryLimits() {
|
|
||||||
verifyIsIdentityTransformation(new QueryLimits(1,2,3,4),
|
|
||||||
l -> IndexProtobufCodec.convertQueryLimits(IndexProtobufCodec.convertQueryLimits(l))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@Test
|
@Test
|
||||||
public void testSubqery() {
|
public void testSubqery() {
|
||||||
verifyIsIdentityTransformation(new SearchQuery(
|
verifyIsIdentityTransformation(new SearchQuery(
|
||||||
|
@@ -2,8 +2,9 @@ package nu.marginalia.functions.searchquery;
|
|||||||
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.Singleton;
|
import com.google.inject.Singleton;
|
||||||
|
import nu.marginalia.api.searchquery.RpcQueryLimits;
|
||||||
|
import nu.marginalia.api.searchquery.RpcResultRankingParameters;
|
||||||
import nu.marginalia.api.searchquery.model.query.*;
|
import nu.marginalia.api.searchquery.model.query.*;
|
||||||
import nu.marginalia.api.searchquery.model.results.ResultRankingParameters;
|
|
||||||
import nu.marginalia.functions.searchquery.query_parser.QueryExpansion;
|
import nu.marginalia.functions.searchquery.query_parser.QueryExpansion;
|
||||||
import nu.marginalia.functions.searchquery.query_parser.QueryParser;
|
import nu.marginalia.functions.searchquery.query_parser.QueryParser;
|
||||||
import nu.marginalia.functions.searchquery.query_parser.token.QueryToken;
|
import nu.marginalia.functions.searchquery.query_parser.token.QueryToken;
|
||||||
@@ -36,7 +37,7 @@ public class QueryFactory {
|
|||||||
|
|
||||||
|
|
||||||
public ProcessedQuery createQuery(QueryParams params,
|
public ProcessedQuery createQuery(QueryParams params,
|
||||||
@Nullable ResultRankingParameters rankingParams) {
|
@Nullable RpcResultRankingParameters rankingParams) {
|
||||||
final var query = params.humanQuery();
|
final var query = params.humanQuery();
|
||||||
|
|
||||||
if (query.length() > 1000) {
|
if (query.length() > 1000) {
|
||||||
@@ -132,7 +133,9 @@ public class QueryFactory {
|
|||||||
var limits = params.limits();
|
var limits = params.limits();
|
||||||
// Disable limits on number of results per domain if we're searching with a site:-type term
|
// Disable limits on number of results per domain if we're searching with a site:-type term
|
||||||
if (domain != null) {
|
if (domain != null) {
|
||||||
limits = limits.forSingleDomain();
|
limits = RpcQueryLimits.newBuilder(limits)
|
||||||
|
.setResultsByDomain(limits.getResultsTotal())
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
var expansion = queryExpansion.expandQuery(queryBuilder.searchTermsInclude);
|
var expansion = queryExpansion.expandQuery(queryBuilder.searchTermsInclude);
|
||||||
|
@@ -9,7 +9,7 @@ import nu.marginalia.api.searchquery.*;
|
|||||||
import nu.marginalia.api.searchquery.model.query.ProcessedQuery;
|
import nu.marginalia.api.searchquery.model.query.ProcessedQuery;
|
||||||
import nu.marginalia.api.searchquery.model.query.QueryParams;
|
import nu.marginalia.api.searchquery.model.query.QueryParams;
|
||||||
import nu.marginalia.api.searchquery.model.results.DecoratedSearchResultItem;
|
import nu.marginalia.api.searchquery.model.results.DecoratedSearchResultItem;
|
||||||
import nu.marginalia.api.searchquery.model.results.ResultRankingParameters;
|
import nu.marginalia.api.searchquery.model.results.PrototypeRankingParameters;
|
||||||
import nu.marginalia.index.api.IndexClient;
|
import nu.marginalia.index.api.IndexClient;
|
||||||
import nu.marginalia.service.server.DiscoverableService;
|
import nu.marginalia.service.server.DiscoverableService;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -55,7 +55,7 @@ public class QueryGRPCService
|
|||||||
.time(() -> {
|
.time(() -> {
|
||||||
|
|
||||||
var params = QueryProtobufCodec.convertRequest(request);
|
var params = QueryProtobufCodec.convertRequest(request);
|
||||||
var query = queryFactory.createQuery(params, ResultRankingParameters.sensibleDefaults());
|
var query = queryFactory.createQuery(params, PrototypeRankingParameters.sensibleDefaults());
|
||||||
|
|
||||||
var indexRequest = QueryProtobufCodec.convertQuery(request, query);
|
var indexRequest = QueryProtobufCodec.convertQuery(request, query);
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@ public class QueryGRPCService
|
|||||||
String originalQuery,
|
String originalQuery,
|
||||||
QueryParams params,
|
QueryParams params,
|
||||||
IndexClient.Pagination pagination,
|
IndexClient.Pagination pagination,
|
||||||
ResultRankingParameters rankingParameters) {
|
RpcResultRankingParameters rankingParameters) {
|
||||||
|
|
||||||
var query = queryFactory.createQuery(params, rankingParameters);
|
var query = queryFactory.createQuery(params, rankingParameters);
|
||||||
IndexClient.AggregateQueryResponse response = indexClient.executeQueries(QueryProtobufCodec.convertQuery(originalQuery, query), pagination);
|
IndexClient.AggregateQueryResponse response = indexClient.executeQueries(QueryProtobufCodec.convertQuery(originalQuery, query), pagination);
|
||||||
|
@@ -1,12 +1,12 @@
|
|||||||
package nu.marginalia.query.svc;
|
package nu.marginalia.query.svc;
|
||||||
|
|
||||||
import nu.marginalia.WmsaHome;
|
import nu.marginalia.WmsaHome;
|
||||||
|
import nu.marginalia.api.searchquery.RpcQueryLimits;
|
||||||
|
import nu.marginalia.api.searchquery.RpcTemporalBias;
|
||||||
import nu.marginalia.api.searchquery.model.query.QueryParams;
|
import nu.marginalia.api.searchquery.model.query.QueryParams;
|
||||||
import nu.marginalia.api.searchquery.model.query.SearchSpecification;
|
import nu.marginalia.api.searchquery.model.query.SearchSpecification;
|
||||||
import nu.marginalia.api.searchquery.model.results.ResultRankingParameters;
|
|
||||||
import nu.marginalia.functions.searchquery.QueryFactory;
|
import nu.marginalia.functions.searchquery.QueryFactory;
|
||||||
import nu.marginalia.functions.searchquery.query_parser.QueryExpansion;
|
import nu.marginalia.functions.searchquery.query_parser.QueryExpansion;
|
||||||
import nu.marginalia.index.query.limit.QueryLimits;
|
|
||||||
import nu.marginalia.index.query.limit.QueryStrategy;
|
import nu.marginalia.index.query.limit.QueryStrategy;
|
||||||
import nu.marginalia.index.query.limit.SpecificationLimit;
|
import nu.marginalia.index.query.limit.SpecificationLimit;
|
||||||
import nu.marginalia.index.query.limit.SpecificationLimitType;
|
import nu.marginalia.index.query.limit.SpecificationLimitType;
|
||||||
@@ -49,10 +49,15 @@ public class QueryFactoryTest {
|
|||||||
SpecificationLimit.none(),
|
SpecificationLimit.none(),
|
||||||
SpecificationLimit.none(),
|
SpecificationLimit.none(),
|
||||||
null,
|
null,
|
||||||
new QueryLimits(100, 100, 100, 100),
|
RpcQueryLimits.newBuilder()
|
||||||
|
.setResultsTotal(100)
|
||||||
|
.setResultsByDomain(100)
|
||||||
|
.setTimeoutMs(100)
|
||||||
|
.setFetchSize(100)
|
||||||
|
.build(),
|
||||||
"NONE",
|
"NONE",
|
||||||
QueryStrategy.AUTO,
|
QueryStrategy.AUTO,
|
||||||
ResultRankingParameters.TemporalBias.NONE,
|
RpcTemporalBias.Bias.NONE,
|
||||||
0), null).specs;
|
0), null).specs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -10,12 +10,12 @@ import it.unimi.dsi.fastutil.longs.LongArrayList;
|
|||||||
import nu.marginalia.api.searchquery.IndexApiGrpc;
|
import nu.marginalia.api.searchquery.IndexApiGrpc;
|
||||||
import nu.marginalia.api.searchquery.RpcDecoratedResultItem;
|
import nu.marginalia.api.searchquery.RpcDecoratedResultItem;
|
||||||
import nu.marginalia.api.searchquery.RpcIndexQuery;
|
import nu.marginalia.api.searchquery.RpcIndexQuery;
|
||||||
|
import nu.marginalia.api.searchquery.RpcResultRankingParameters;
|
||||||
import nu.marginalia.api.searchquery.model.compiled.CompiledQuery;
|
import nu.marginalia.api.searchquery.model.compiled.CompiledQuery;
|
||||||
import nu.marginalia.api.searchquery.model.compiled.CompiledQueryLong;
|
import nu.marginalia.api.searchquery.model.compiled.CompiledQueryLong;
|
||||||
import nu.marginalia.api.searchquery.model.compiled.CqDataInt;
|
import nu.marginalia.api.searchquery.model.compiled.CqDataInt;
|
||||||
import nu.marginalia.api.searchquery.model.query.SearchSpecification;
|
import nu.marginalia.api.searchquery.model.query.SearchSpecification;
|
||||||
import nu.marginalia.api.searchquery.model.results.ResultRankingContext;
|
import nu.marginalia.api.searchquery.model.results.ResultRankingContext;
|
||||||
import nu.marginalia.api.searchquery.model.results.ResultRankingParameters;
|
|
||||||
import nu.marginalia.array.page.LongQueryBuffer;
|
import nu.marginalia.array.page.LongQueryBuffer;
|
||||||
import nu.marginalia.index.index.StatefulIndex;
|
import nu.marginalia.index.index.StatefulIndex;
|
||||||
import nu.marginalia.index.model.SearchParameters;
|
import nu.marginalia.index.model.SearchParameters;
|
||||||
@@ -211,7 +211,7 @@ public class IndexGrpcService
|
|||||||
/** This class is responsible for ranking the results and adding the best results to the
|
/** This class is responsible for ranking the results and adding the best results to the
|
||||||
* resultHeap, which depending on the state of the indexLookup threads may or may not block
|
* resultHeap, which depending on the state of the indexLookup threads may or may not block
|
||||||
*/
|
*/
|
||||||
private ResultRankingContext createRankingContext(ResultRankingParameters rankingParams,
|
private ResultRankingContext createRankingContext(RpcResultRankingParameters rankingParams,
|
||||||
CompiledQuery<String> compiledQuery,
|
CompiledQuery<String> compiledQuery,
|
||||||
CompiledQueryLong compiledQueryIds)
|
CompiledQueryLong compiledQueryIds)
|
||||||
{
|
{
|
||||||
|
@@ -2,12 +2,13 @@ package nu.marginalia.index.model;
|
|||||||
|
|
||||||
import nu.marginalia.api.searchquery.IndexProtobufCodec;
|
import nu.marginalia.api.searchquery.IndexProtobufCodec;
|
||||||
import nu.marginalia.api.searchquery.RpcIndexQuery;
|
import nu.marginalia.api.searchquery.RpcIndexQuery;
|
||||||
|
import nu.marginalia.api.searchquery.RpcResultRankingParameters;
|
||||||
import nu.marginalia.api.searchquery.model.compiled.CompiledQuery;
|
import nu.marginalia.api.searchquery.model.compiled.CompiledQuery;
|
||||||
import nu.marginalia.api.searchquery.model.compiled.CompiledQueryLong;
|
import nu.marginalia.api.searchquery.model.compiled.CompiledQueryLong;
|
||||||
import nu.marginalia.api.searchquery.model.compiled.CompiledQueryParser;
|
import nu.marginalia.api.searchquery.model.compiled.CompiledQueryParser;
|
||||||
import nu.marginalia.api.searchquery.model.query.SearchSpecification;
|
|
||||||
import nu.marginalia.api.searchquery.model.query.SearchQuery;
|
import nu.marginalia.api.searchquery.model.query.SearchQuery;
|
||||||
import nu.marginalia.api.searchquery.model.results.ResultRankingParameters;
|
import nu.marginalia.api.searchquery.model.query.SearchSpecification;
|
||||||
|
import nu.marginalia.api.searchquery.model.results.PrototypeRankingParameters;
|
||||||
import nu.marginalia.index.query.IndexSearchBudget;
|
import nu.marginalia.index.query.IndexSearchBudget;
|
||||||
import nu.marginalia.index.query.limit.QueryStrategy;
|
import nu.marginalia.index.query.limit.QueryStrategy;
|
||||||
import nu.marginalia.index.searchset.SearchSet;
|
import nu.marginalia.index.searchset.SearchSet;
|
||||||
@@ -23,7 +24,7 @@ public class SearchParameters {
|
|||||||
public final IndexSearchBudget budget;
|
public final IndexSearchBudget budget;
|
||||||
public final SearchQuery query;
|
public final SearchQuery query;
|
||||||
public final QueryParams queryParams;
|
public final QueryParams queryParams;
|
||||||
public final ResultRankingParameters rankingParams;
|
public final RpcResultRankingParameters rankingParams;
|
||||||
|
|
||||||
public final int limitByDomain;
|
public final int limitByDomain;
|
||||||
public final int limitTotal;
|
public final int limitTotal;
|
||||||
@@ -41,11 +42,11 @@ public class SearchParameters {
|
|||||||
public SearchParameters(SearchSpecification specsSet, SearchSet searchSet) {
|
public SearchParameters(SearchSpecification specsSet, SearchSet searchSet) {
|
||||||
var limits = specsSet.queryLimits;
|
var limits = specsSet.queryLimits;
|
||||||
|
|
||||||
this.fetchSize = limits.fetchSize();
|
this.fetchSize = limits.getFetchSize();
|
||||||
this.budget = new IndexSearchBudget(limits.timeoutMs());
|
this.budget = new IndexSearchBudget(limits.getTimeoutMs());
|
||||||
this.query = specsSet.query;
|
this.query = specsSet.query;
|
||||||
this.limitByDomain = limits.resultsByDomain();
|
this.limitByDomain = limits.getResultsByDomain();
|
||||||
this.limitTotal = limits.resultsTotal();
|
this.limitTotal = limits.getResultsTotal();
|
||||||
|
|
||||||
queryParams = new QueryParams(
|
queryParams = new QueryParams(
|
||||||
specsSet.quality,
|
specsSet.quality,
|
||||||
@@ -62,17 +63,17 @@ public class SearchParameters {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public SearchParameters(RpcIndexQuery request, SearchSet searchSet) {
|
public SearchParameters(RpcIndexQuery request, SearchSet searchSet) {
|
||||||
var limits = IndexProtobufCodec.convertQueryLimits(request.getQueryLimits());
|
var limits = request.getQueryLimits();
|
||||||
|
|
||||||
this.fetchSize = limits.fetchSize();
|
this.fetchSize = limits.getFetchSize();
|
||||||
|
|
||||||
// The time budget is halved because this is the point when we start to
|
// The time budget is halved because this is the point when we start to
|
||||||
// wrap up the search and return the results.
|
// wrap up the search and return the results.
|
||||||
this.budget = new IndexSearchBudget(limits.timeoutMs() / 2);
|
this.budget = new IndexSearchBudget(limits.getTimeoutMs() / 2);
|
||||||
this.query = IndexProtobufCodec.convertRpcQuery(request.getQuery());
|
this.query = IndexProtobufCodec.convertRpcQuery(request.getQuery());
|
||||||
|
|
||||||
this.limitByDomain = limits.resultsByDomain();
|
this.limitByDomain = limits.getResultsByDomain();
|
||||||
this.limitTotal = limits.resultsTotal();
|
this.limitTotal = limits.getResultsTotal();
|
||||||
|
|
||||||
queryParams = new QueryParams(
|
queryParams = new QueryParams(
|
||||||
convertSpecLimit(request.getQuality()),
|
convertSpecLimit(request.getQuality()),
|
||||||
@@ -85,7 +86,7 @@ public class SearchParameters {
|
|||||||
compiledQuery = CompiledQueryParser.parse(this.query.compiledQuery);
|
compiledQuery = CompiledQueryParser.parse(this.query.compiledQuery);
|
||||||
compiledQueryIds = compiledQuery.mapToLong(SearchTermsUtil::getWordId);
|
compiledQueryIds = compiledQuery.mapToLong(SearchTermsUtil::getWordId);
|
||||||
|
|
||||||
rankingParams = IndexProtobufCodec.convertRankingParameterss(request.getParameters());
|
rankingParams = request.hasParameters() ? request.getParameters() : PrototypeRankingParameters.sensibleDefaults();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -2,7 +2,6 @@ package nu.marginalia.index.results;
|
|||||||
|
|
||||||
import nu.marginalia.api.searchquery.model.compiled.CqDataInt;
|
import nu.marginalia.api.searchquery.model.compiled.CqDataInt;
|
||||||
import nu.marginalia.api.searchquery.model.compiled.CqExpression;
|
import nu.marginalia.api.searchquery.model.compiled.CqExpression;
|
||||||
import nu.marginalia.api.searchquery.model.results.Bm25Parameters;
|
|
||||||
import nu.marginalia.api.searchquery.model.results.ResultRankingContext;
|
import nu.marginalia.api.searchquery.model.results.ResultRankingContext;
|
||||||
|
|
||||||
import java.util.BitSet;
|
import java.util.BitSet;
|
||||||
@@ -24,14 +23,14 @@ public class Bm25GraphVisitor implements CqExpression.DoubleVisitor {
|
|||||||
|
|
||||||
private final BitSet mask;
|
private final BitSet mask;
|
||||||
|
|
||||||
public Bm25GraphVisitor(Bm25Parameters bm25Parameters,
|
public Bm25GraphVisitor(double k1, double b,
|
||||||
float[] counts,
|
float[] counts,
|
||||||
int length,
|
int length,
|
||||||
ResultRankingContext ctx) {
|
ResultRankingContext ctx) {
|
||||||
this.length = length;
|
this.length = length;
|
||||||
|
|
||||||
this.k1 = bm25Parameters.k();
|
this.k1 = k1;
|
||||||
this.b = bm25Parameters.b();
|
this.b = b;
|
||||||
|
|
||||||
this.docCount = ctx.termFreqDocCount();
|
this.docCount = ctx.termFreqDocCount();
|
||||||
this.counts = counts;
|
this.counts = counts;
|
||||||
|
@@ -156,7 +156,7 @@ public class IndexResultRankingService {
|
|||||||
// for the selected results, as this would be comically expensive to do for all the results we
|
// for the selected results, as this would be comically expensive to do for all the results we
|
||||||
// discard along the way
|
// discard along the way
|
||||||
|
|
||||||
if (params.rankingParams.exportDebugData) {
|
if (params.rankingParams.getExportDebugData()) {
|
||||||
var combinedIdsList = new LongArrayList(resultsList.size());
|
var combinedIdsList = new LongArrayList(resultsList.size());
|
||||||
for (var item : resultsList) {
|
for (var item : resultsList) {
|
||||||
combinedIdsList.add(item.combinedId);
|
combinedIdsList.add(item.combinedId);
|
||||||
|
@@ -2,10 +2,11 @@ package nu.marginalia.index.results;
|
|||||||
|
|
||||||
import it.unimi.dsi.fastutil.ints.IntIterator;
|
import it.unimi.dsi.fastutil.ints.IntIterator;
|
||||||
import it.unimi.dsi.fastutil.ints.IntList;
|
import it.unimi.dsi.fastutil.ints.IntList;
|
||||||
|
import nu.marginalia.api.searchquery.RpcResultRankingParameters;
|
||||||
|
import nu.marginalia.api.searchquery.RpcTemporalBias;
|
||||||
import nu.marginalia.api.searchquery.model.compiled.CompiledQuery;
|
import nu.marginalia.api.searchquery.model.compiled.CompiledQuery;
|
||||||
import nu.marginalia.api.searchquery.model.compiled.CompiledQueryLong;
|
import nu.marginalia.api.searchquery.model.compiled.CompiledQueryLong;
|
||||||
import nu.marginalia.api.searchquery.model.results.ResultRankingContext;
|
import nu.marginalia.api.searchquery.model.results.ResultRankingContext;
|
||||||
import nu.marginalia.api.searchquery.model.results.ResultRankingParameters;
|
|
||||||
import nu.marginalia.api.searchquery.model.results.SearchResultItem;
|
import nu.marginalia.api.searchquery.model.results.SearchResultItem;
|
||||||
import nu.marginalia.api.searchquery.model.results.debug.DebugRankingFactors;
|
import nu.marginalia.api.searchquery.model.results.debug.DebugRankingFactors;
|
||||||
import nu.marginalia.index.forward.spans.DocumentSpans;
|
import nu.marginalia.index.forward.spans.DocumentSpans;
|
||||||
@@ -116,14 +117,14 @@ public class IndexResultScoreCalculator {
|
|||||||
|
|
||||||
float proximitiyFac = getProximitiyFac(decodedPositions, searchTerms.phraseConstraints, verbatimMatches, unorderedMatches, spans);
|
float proximitiyFac = getProximitiyFac(decodedPositions, searchTerms.phraseConstraints, verbatimMatches, unorderedMatches, spans);
|
||||||
|
|
||||||
double score_firstPosition = params.tcfFirstPosition * (1.0 / Math.sqrt(unorderedMatches.firstPosition));
|
double score_firstPosition = params.getTcfFirstPositionWeight() * (1.0 / Math.sqrt(unorderedMatches.firstPosition));
|
||||||
double score_verbatim = params.tcfVerbatim * verbatimMatches.getScore();
|
double score_verbatim = params.getTcfVerbatimWeight() * verbatimMatches.getScore();
|
||||||
double score_proximity = params.tcfProximity * proximitiyFac;
|
double score_proximity = params.getTcfProximityWeight() * proximitiyFac;
|
||||||
double score_bM25 = params.bm25Weight
|
double score_bM25 = params.getBm25Weight()
|
||||||
* wordFlagsQuery.root.visit(new Bm25GraphVisitor(params.bm25Params, unorderedMatches.getWeightedCounts(), docSize, rankingContext))
|
* wordFlagsQuery.root.visit(new Bm25GraphVisitor(params.getBm25K(), params.getBm25B(), unorderedMatches.getWeightedCounts(), docSize, rankingContext))
|
||||||
/ (Math.sqrt(unorderedMatches.searchableKeywordCount + 1));
|
/ (Math.sqrt(unorderedMatches.searchableKeywordCount + 1));
|
||||||
double score_bFlags = params.bm25Weight
|
double score_bFlags = params.getBm25Weight()
|
||||||
* wordFlagsQuery.root.visit(new TermFlagsGraphVisitor(params.bm25Params, wordFlagsQuery.data, unorderedMatches.getWeightedCounts(), rankingContext))
|
* wordFlagsQuery.root.visit(new TermFlagsGraphVisitor(params.getBm25K(), wordFlagsQuery.data, unorderedMatches.getWeightedCounts(), rankingContext))
|
||||||
/ (Math.sqrt(unorderedMatches.searchableKeywordCount + 1));
|
/ (Math.sqrt(unorderedMatches.searchableKeywordCount + 1));
|
||||||
|
|
||||||
double score = normalize(
|
double score = normalize(
|
||||||
@@ -245,9 +246,13 @@ public class IndexResultScoreCalculator {
|
|||||||
private double calculateDocumentBonus(long documentMetadata,
|
private double calculateDocumentBonus(long documentMetadata,
|
||||||
int features,
|
int features,
|
||||||
int length,
|
int length,
|
||||||
ResultRankingParameters rankingParams,
|
RpcResultRankingParameters rankingParams,
|
||||||
@Nullable DebugRankingFactors debugRankingFactors) {
|
@Nullable DebugRankingFactors debugRankingFactors) {
|
||||||
|
|
||||||
|
if (rankingParams.getDisablePenalties()) {
|
||||||
|
return 0.;
|
||||||
|
}
|
||||||
|
|
||||||
int rank = DocumentMetadata.decodeRank(documentMetadata);
|
int rank = DocumentMetadata.decodeRank(documentMetadata);
|
||||||
int asl = DocumentMetadata.decodeAvgSentenceLength(documentMetadata);
|
int asl = DocumentMetadata.decodeAvgSentenceLength(documentMetadata);
|
||||||
int quality = DocumentMetadata.decodeQuality(documentMetadata);
|
int quality = DocumentMetadata.decodeQuality(documentMetadata);
|
||||||
@@ -256,18 +261,18 @@ public class IndexResultScoreCalculator {
|
|||||||
int topology = DocumentMetadata.decodeTopology(documentMetadata);
|
int topology = DocumentMetadata.decodeTopology(documentMetadata);
|
||||||
int year = DocumentMetadata.decodeYear(documentMetadata);
|
int year = DocumentMetadata.decodeYear(documentMetadata);
|
||||||
|
|
||||||
double averageSentenceLengthPenalty = (asl >= rankingParams.shortSentenceThreshold ? 0 : -rankingParams.shortSentencePenalty);
|
double averageSentenceLengthPenalty = (asl >= rankingParams.getShortSentenceThreshold() ? 0 : -rankingParams.getShortSentencePenalty());
|
||||||
|
|
||||||
final double qualityPenalty = calculateQualityPenalty(size, quality, rankingParams);
|
final double qualityPenalty = calculateQualityPenalty(size, quality, rankingParams);
|
||||||
final double rankingBonus = (255. - rank) * rankingParams.domainRankBonus;
|
final double rankingBonus = (255. - rank) * rankingParams.getDomainRankBonus();
|
||||||
final double topologyBonus = Math.log(1 + topology);
|
final double topologyBonus = Math.log(1 + topology);
|
||||||
final double documentLengthPenalty = length > rankingParams.shortDocumentThreshold ? 0 : -rankingParams.shortDocumentPenalty;
|
final double documentLengthPenalty = length > rankingParams.getShortDocumentThreshold() ? 0 : -rankingParams.getShortDocumentPenalty();
|
||||||
final double temporalBias;
|
final double temporalBias;
|
||||||
|
|
||||||
if (rankingParams.temporalBias == ResultRankingParameters.TemporalBias.RECENT) {
|
if (rankingParams.getTemporalBias().getBias() == RpcTemporalBias.Bias.RECENT) {
|
||||||
temporalBias = - Math.abs(year - PubDate.MAX_YEAR) * rankingParams.temporalBiasWeight;
|
temporalBias = - Math.abs(year - PubDate.MAX_YEAR) * rankingParams.getTemporalBiasWeight();
|
||||||
} else if (rankingParams.temporalBias == ResultRankingParameters.TemporalBias.OLD) {
|
} else if (rankingParams.getTemporalBias().getBias() == RpcTemporalBias.Bias.OLD) {
|
||||||
temporalBias = - Math.abs(year - PubDate.MIN_YEAR) * rankingParams.temporalBiasWeight;
|
temporalBias = - Math.abs(year - PubDate.MIN_YEAR) * rankingParams.getTemporalBiasWeight();
|
||||||
} else {
|
} else {
|
||||||
temporalBias = 0;
|
temporalBias = 0;
|
||||||
}
|
}
|
||||||
@@ -506,14 +511,14 @@ public class IndexResultScoreCalculator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private double calculateQualityPenalty(int size, int quality, ResultRankingParameters rankingParams) {
|
private double calculateQualityPenalty(int size, int quality, RpcResultRankingParameters rankingParams) {
|
||||||
if (size < 400) {
|
if (size < 400) {
|
||||||
if (quality < 5)
|
if (quality < 5)
|
||||||
return 0;
|
return 0;
|
||||||
return -quality * rankingParams.qualityPenalty;
|
return -quality * rankingParams.getQualityPenalty();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return -quality * rankingParams.qualityPenalty * 20;
|
return -quality * rankingParams.getQualityPenalty() * 20;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -3,7 +3,6 @@ package nu.marginalia.index.results;
|
|||||||
import nu.marginalia.api.searchquery.model.compiled.CqDataInt;
|
import nu.marginalia.api.searchquery.model.compiled.CqDataInt;
|
||||||
import nu.marginalia.api.searchquery.model.compiled.CqDataLong;
|
import nu.marginalia.api.searchquery.model.compiled.CqDataLong;
|
||||||
import nu.marginalia.api.searchquery.model.compiled.CqExpression;
|
import nu.marginalia.api.searchquery.model.compiled.CqExpression;
|
||||||
import nu.marginalia.api.searchquery.model.results.Bm25Parameters;
|
|
||||||
import nu.marginalia.api.searchquery.model.results.ResultRankingContext;
|
import nu.marginalia.api.searchquery.model.results.ResultRankingContext;
|
||||||
import nu.marginalia.model.idx.WordFlags;
|
import nu.marginalia.model.idx.WordFlags;
|
||||||
|
|
||||||
@@ -15,15 +14,14 @@ public class TermFlagsGraphVisitor implements CqExpression.DoubleVisitor {
|
|||||||
private final CqDataLong wordMetaData;
|
private final CqDataLong wordMetaData;
|
||||||
private final CqDataInt frequencies;
|
private final CqDataInt frequencies;
|
||||||
private final float[] counts;
|
private final float[] counts;
|
||||||
private final Bm25Parameters bm25Parameters;
|
private final double k1;
|
||||||
|
|
||||||
private final int docCount;
|
private final int docCount;
|
||||||
|
|
||||||
public TermFlagsGraphVisitor(Bm25Parameters bm25Parameters,
|
public TermFlagsGraphVisitor(double k1,
|
||||||
CqDataLong wordMetaData,
|
CqDataLong wordMetaData,
|
||||||
float[] counts,
|
float[] counts,
|
||||||
ResultRankingContext ctx) {
|
ResultRankingContext ctx) {
|
||||||
this.bm25Parameters = bm25Parameters;
|
this.k1 = k1;
|
||||||
this.counts = counts;
|
this.counts = counts;
|
||||||
this.docCount = ctx.termFreqDocCount();
|
this.docCount = ctx.termFreqDocCount();
|
||||||
this.wordMetaData = wordMetaData;
|
this.wordMetaData = wordMetaData;
|
||||||
@@ -55,7 +53,7 @@ public class TermFlagsGraphVisitor implements CqExpression.DoubleVisitor {
|
|||||||
int freq = frequencies.get(idx);
|
int freq = frequencies.get(idx);
|
||||||
|
|
||||||
// note we override b to zero for priority terms as they are independent of document length
|
// note we override b to zero for priority terms as they are independent of document length
|
||||||
return invFreq(docCount, freq) * f(bm25Parameters.k(), 0, count, 0);
|
return invFreq(docCount, freq) * f(k1, 0, count, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private double evaluatePriorityScore(int idx) {
|
private double evaluatePriorityScore(int idx) {
|
||||||
|
@@ -1,7 +0,0 @@
|
|||||||
package nu.marginalia.index.query.limit;
|
|
||||||
|
|
||||||
public record QueryLimits(int resultsByDomain, int resultsTotal, int timeoutMs, int fetchSize) {
|
|
||||||
public QueryLimits forSingleDomain() {
|
|
||||||
return new QueryLimits(resultsTotal, resultsTotal, timeoutMs, fetchSize);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -4,10 +4,11 @@ import com.google.inject.Guice;
|
|||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import nu.marginalia.IndexLocations;
|
import nu.marginalia.IndexLocations;
|
||||||
import nu.marginalia.api.searchquery.RpcDecoratedResultItem;
|
import nu.marginalia.api.searchquery.RpcDecoratedResultItem;
|
||||||
|
import nu.marginalia.api.searchquery.RpcQueryLimits;
|
||||||
import nu.marginalia.api.searchquery.model.query.SearchPhraseConstraint;
|
import nu.marginalia.api.searchquery.model.query.SearchPhraseConstraint;
|
||||||
import nu.marginalia.api.searchquery.model.query.SearchQuery;
|
import nu.marginalia.api.searchquery.model.query.SearchQuery;
|
||||||
import nu.marginalia.api.searchquery.model.query.SearchSpecification;
|
import nu.marginalia.api.searchquery.model.query.SearchSpecification;
|
||||||
import nu.marginalia.api.searchquery.model.results.ResultRankingParameters;
|
import nu.marginalia.api.searchquery.model.results.PrototypeRankingParameters;
|
||||||
import nu.marginalia.index.construction.DocIdRewriter;
|
import nu.marginalia.index.construction.DocIdRewriter;
|
||||||
import nu.marginalia.index.construction.full.FullIndexConstructor;
|
import nu.marginalia.index.construction.full.FullIndexConstructor;
|
||||||
import nu.marginalia.index.construction.prio.PrioIndexConstructor;
|
import nu.marginalia.index.construction.prio.PrioIndexConstructor;
|
||||||
@@ -17,7 +18,6 @@ import nu.marginalia.index.forward.construction.ForwardIndexConverter;
|
|||||||
import nu.marginalia.index.index.StatefulIndex;
|
import nu.marginalia.index.index.StatefulIndex;
|
||||||
import nu.marginalia.index.journal.IndexJournal;
|
import nu.marginalia.index.journal.IndexJournal;
|
||||||
import nu.marginalia.index.journal.IndexJournalSlopWriter;
|
import nu.marginalia.index.journal.IndexJournalSlopWriter;
|
||||||
import nu.marginalia.index.query.limit.QueryLimits;
|
|
||||||
import nu.marginalia.index.query.limit.QueryStrategy;
|
import nu.marginalia.index.query.limit.QueryStrategy;
|
||||||
import nu.marginalia.index.query.limit.SpecificationLimit;
|
import nu.marginalia.index.query.limit.SpecificationLimit;
|
||||||
import nu.marginalia.linkdb.docs.DocumentDbReader;
|
import nu.marginalia.linkdb.docs.DocumentDbReader;
|
||||||
@@ -115,9 +115,16 @@ public class IndexQueryServiceIntegrationSmokeTest {
|
|||||||
|
|
||||||
var rsp = queryService.justQuery(
|
var rsp = queryService.justQuery(
|
||||||
SearchSpecification.builder()
|
SearchSpecification.builder()
|
||||||
.queryLimits(new QueryLimits(10, 10, Integer.MAX_VALUE, 4000))
|
.queryLimits(
|
||||||
|
RpcQueryLimits.newBuilder()
|
||||||
|
.setResultsByDomain(10)
|
||||||
|
.setResultsTotal(10)
|
||||||
|
.setTimeoutMs(Integer.MAX_VALUE)
|
||||||
|
.setFetchSize(4000)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
.queryStrategy(QueryStrategy.SENTENCE)
|
.queryStrategy(QueryStrategy.SENTENCE)
|
||||||
.rankingParams(ResultRankingParameters.sensibleDefaults())
|
.rankingParams(PrototypeRankingParameters.sensibleDefaults())
|
||||||
.domains(new ArrayList<>())
|
.domains(new ArrayList<>())
|
||||||
.searchSetIdentifier("NONE")
|
.searchSetIdentifier("NONE")
|
||||||
.query(
|
.query(
|
||||||
@@ -171,9 +178,16 @@ public class IndexQueryServiceIntegrationSmokeTest {
|
|||||||
|
|
||||||
var rsp = queryService.justQuery(
|
var rsp = queryService.justQuery(
|
||||||
SearchSpecification.builder()
|
SearchSpecification.builder()
|
||||||
.queryLimits(new QueryLimits(10, 10, Integer.MAX_VALUE, 4000))
|
.queryLimits(
|
||||||
|
RpcQueryLimits.newBuilder()
|
||||||
|
.setResultsByDomain(10)
|
||||||
|
.setResultsTotal(10)
|
||||||
|
.setTimeoutMs(Integer.MAX_VALUE)
|
||||||
|
.setFetchSize(4000)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
.queryStrategy(QueryStrategy.SENTENCE)
|
.queryStrategy(QueryStrategy.SENTENCE)
|
||||||
.rankingParams(ResultRankingParameters.sensibleDefaults())
|
.rankingParams(PrototypeRankingParameters.sensibleDefaults())
|
||||||
.domains(new ArrayList<>())
|
.domains(new ArrayList<>())
|
||||||
.searchSetIdentifier("NONE")
|
.searchSetIdentifier("NONE")
|
||||||
.query(
|
.query(
|
||||||
@@ -225,8 +239,15 @@ public class IndexQueryServiceIntegrationSmokeTest {
|
|||||||
|
|
||||||
var rsp = queryService.justQuery(
|
var rsp = queryService.justQuery(
|
||||||
SearchSpecification.builder()
|
SearchSpecification.builder()
|
||||||
.queryLimits(new QueryLimits(10, 10, Integer.MAX_VALUE, 4000))
|
.queryLimits(
|
||||||
.rankingParams(ResultRankingParameters.sensibleDefaults())
|
RpcQueryLimits.newBuilder()
|
||||||
|
.setResultsByDomain(10)
|
||||||
|
.setResultsTotal(10)
|
||||||
|
.setTimeoutMs(Integer.MAX_VALUE)
|
||||||
|
.setFetchSize(4000)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.rankingParams(PrototypeRankingParameters.sensibleDefaults())
|
||||||
.queryStrategy(QueryStrategy.SENTENCE)
|
.queryStrategy(QueryStrategy.SENTENCE)
|
||||||
.domains(List.of(2))
|
.domains(List.of(2))
|
||||||
.query(
|
.query(
|
||||||
@@ -282,11 +303,18 @@ public class IndexQueryServiceIntegrationSmokeTest {
|
|||||||
|
|
||||||
var rsp = queryService.justQuery(
|
var rsp = queryService.justQuery(
|
||||||
SearchSpecification.builder()
|
SearchSpecification.builder()
|
||||||
.queryLimits(new QueryLimits(10, 10, Integer.MAX_VALUE, 4000))
|
.queryLimits(
|
||||||
|
RpcQueryLimits.newBuilder()
|
||||||
|
.setResultsByDomain(10)
|
||||||
|
.setResultsTotal(10)
|
||||||
|
.setTimeoutMs(Integer.MAX_VALUE)
|
||||||
|
.setFetchSize(4000)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
.year(SpecificationLimit.equals(1998))
|
.year(SpecificationLimit.equals(1998))
|
||||||
.queryStrategy(QueryStrategy.SENTENCE)
|
.queryStrategy(QueryStrategy.SENTENCE)
|
||||||
.searchSetIdentifier("NONE")
|
.searchSetIdentifier("NONE")
|
||||||
.rankingParams(ResultRankingParameters.sensibleDefaults())
|
.rankingParams(PrototypeRankingParameters.sensibleDefaults())
|
||||||
.query(
|
.query(
|
||||||
SearchQuery.builder()
|
SearchQuery.builder()
|
||||||
.compiledQuery("4")
|
.compiledQuery("4")
|
||||||
|
@@ -4,10 +4,11 @@ import com.google.inject.Guice;
|
|||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import it.unimi.dsi.fastutil.ints.IntList;
|
import it.unimi.dsi.fastutil.ints.IntList;
|
||||||
import nu.marginalia.IndexLocations;
|
import nu.marginalia.IndexLocations;
|
||||||
|
import nu.marginalia.api.searchquery.RpcQueryLimits;
|
||||||
import nu.marginalia.api.searchquery.model.query.SearchPhraseConstraint;
|
import nu.marginalia.api.searchquery.model.query.SearchPhraseConstraint;
|
||||||
import nu.marginalia.api.searchquery.model.query.SearchQuery;
|
import nu.marginalia.api.searchquery.model.query.SearchQuery;
|
||||||
import nu.marginalia.api.searchquery.model.query.SearchSpecification;
|
import nu.marginalia.api.searchquery.model.query.SearchSpecification;
|
||||||
import nu.marginalia.api.searchquery.model.results.ResultRankingParameters;
|
import nu.marginalia.api.searchquery.model.results.PrototypeRankingParameters;
|
||||||
import nu.marginalia.hash.MurmurHash3_128;
|
import nu.marginalia.hash.MurmurHash3_128;
|
||||||
import nu.marginalia.index.construction.DocIdRewriter;
|
import nu.marginalia.index.construction.DocIdRewriter;
|
||||||
import nu.marginalia.index.construction.full.FullIndexConstructor;
|
import nu.marginalia.index.construction.full.FullIndexConstructor;
|
||||||
@@ -18,7 +19,6 @@ import nu.marginalia.index.forward.construction.ForwardIndexConverter;
|
|||||||
import nu.marginalia.index.index.StatefulIndex;
|
import nu.marginalia.index.index.StatefulIndex;
|
||||||
import nu.marginalia.index.journal.IndexJournal;
|
import nu.marginalia.index.journal.IndexJournal;
|
||||||
import nu.marginalia.index.journal.IndexJournalSlopWriter;
|
import nu.marginalia.index.journal.IndexJournalSlopWriter;
|
||||||
import nu.marginalia.index.query.limit.QueryLimits;
|
|
||||||
import nu.marginalia.index.query.limit.QueryStrategy;
|
import nu.marginalia.index.query.limit.QueryStrategy;
|
||||||
import nu.marginalia.index.query.limit.SpecificationLimit;
|
import nu.marginalia.index.query.limit.SpecificationLimit;
|
||||||
import nu.marginalia.linkdb.docs.DocumentDbReader;
|
import nu.marginalia.linkdb.docs.DocumentDbReader;
|
||||||
@@ -389,13 +389,20 @@ public class IndexQueryServiceIntegrationTest {
|
|||||||
SearchSpecification basicQuery(Function<SearchSpecification.SearchSpecificationBuilder, SearchSpecification.SearchSpecificationBuilder> mutator)
|
SearchSpecification basicQuery(Function<SearchSpecification.SearchSpecificationBuilder, SearchSpecification.SearchSpecificationBuilder> mutator)
|
||||||
{
|
{
|
||||||
var builder = SearchSpecification.builder()
|
var builder = SearchSpecification.builder()
|
||||||
.queryLimits(new QueryLimits(10, 10, Integer.MAX_VALUE, 4000))
|
.queryLimits(
|
||||||
|
RpcQueryLimits.newBuilder()
|
||||||
|
.setResultsByDomain(10)
|
||||||
|
.setResultsTotal(10)
|
||||||
|
.setTimeoutMs(Integer.MAX_VALUE)
|
||||||
|
.setFetchSize(4000)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
.queryStrategy(QueryStrategy.SENTENCE)
|
.queryStrategy(QueryStrategy.SENTENCE)
|
||||||
.year(SpecificationLimit.none())
|
.year(SpecificationLimit.none())
|
||||||
.quality(SpecificationLimit.none())
|
.quality(SpecificationLimit.none())
|
||||||
.size(SpecificationLimit.none())
|
.size(SpecificationLimit.none())
|
||||||
.rank(SpecificationLimit.none())
|
.rank(SpecificationLimit.none())
|
||||||
.rankingParams(ResultRankingParameters.sensibleDefaults())
|
.rankingParams(PrototypeRankingParameters.sensibleDefaults())
|
||||||
.domains(new ArrayList<>())
|
.domains(new ArrayList<>())
|
||||||
.searchSetIdentifier("NONE");
|
.searchSetIdentifier("NONE");
|
||||||
|
|
||||||
|
@@ -44,6 +44,7 @@ dependencies {
|
|||||||
implementation libs.bundles.jetty
|
implementation libs.bundles.jetty
|
||||||
implementation libs.opencsv
|
implementation libs.opencsv
|
||||||
implementation libs.trove
|
implementation libs.trove
|
||||||
|
implementation libs.protobuf
|
||||||
implementation libs.fastutil
|
implementation libs.fastutil
|
||||||
implementation libs.bundles.gson
|
implementation libs.bundles.gson
|
||||||
implementation libs.bundles.mariadb
|
implementation libs.bundles.mariadb
|
||||||
|
@@ -6,10 +6,10 @@ import nu.marginalia.api.model.ApiSearchResult;
|
|||||||
import nu.marginalia.api.model.ApiSearchResultQueryDetails;
|
import nu.marginalia.api.model.ApiSearchResultQueryDetails;
|
||||||
import nu.marginalia.api.model.ApiSearchResults;
|
import nu.marginalia.api.model.ApiSearchResults;
|
||||||
import nu.marginalia.api.searchquery.QueryClient;
|
import nu.marginalia.api.searchquery.QueryClient;
|
||||||
|
import nu.marginalia.api.searchquery.RpcQueryLimits;
|
||||||
import nu.marginalia.api.searchquery.model.query.QueryParams;
|
import nu.marginalia.api.searchquery.model.query.QueryParams;
|
||||||
import nu.marginalia.api.searchquery.model.query.SearchSetIdentifier;
|
import nu.marginalia.api.searchquery.model.query.SearchSetIdentifier;
|
||||||
import nu.marginalia.api.searchquery.model.results.DecoratedSearchResultItem;
|
import nu.marginalia.api.searchquery.model.results.DecoratedSearchResultItem;
|
||||||
import nu.marginalia.index.query.limit.QueryLimits;
|
|
||||||
import nu.marginalia.model.idx.WordFlags;
|
import nu.marginalia.model.idx.WordFlags;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -47,11 +47,12 @@ public class ApiSearchOperator {
|
|||||||
|
|
||||||
return new QueryParams(
|
return new QueryParams(
|
||||||
query,
|
query,
|
||||||
new QueryLimits(
|
RpcQueryLimits.newBuilder()
|
||||||
2,
|
.setResultsByDomain(2)
|
||||||
Math.min(100, count),
|
.setResultsTotal(Math.min(100, count))
|
||||||
150,
|
.setTimeoutMs(150)
|
||||||
8192),
|
.setFetchSize(8192)
|
||||||
|
.build(),
|
||||||
searchSet.name());
|
searchSet.name());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -5,11 +5,11 @@ import com.google.inject.Singleton;
|
|||||||
import nu.marginalia.WebsiteUrl;
|
import nu.marginalia.WebsiteUrl;
|
||||||
import nu.marginalia.api.math.MathClient;
|
import nu.marginalia.api.math.MathClient;
|
||||||
import nu.marginalia.api.searchquery.QueryClient;
|
import nu.marginalia.api.searchquery.QueryClient;
|
||||||
|
import nu.marginalia.api.searchquery.RpcQueryLimits;
|
||||||
import nu.marginalia.api.searchquery.model.query.QueryResponse;
|
import nu.marginalia.api.searchquery.model.query.QueryResponse;
|
||||||
import nu.marginalia.api.searchquery.model.results.DecoratedSearchResultItem;
|
import nu.marginalia.api.searchquery.model.results.DecoratedSearchResultItem;
|
||||||
import nu.marginalia.bbpc.BrailleBlockPunchCards;
|
import nu.marginalia.bbpc.BrailleBlockPunchCards;
|
||||||
import nu.marginalia.db.DbDomainQueries;
|
import nu.marginalia.db.DbDomainQueries;
|
||||||
import nu.marginalia.index.query.limit.QueryLimits;
|
|
||||||
import nu.marginalia.model.EdgeDomain;
|
import nu.marginalia.model.EdgeDomain;
|
||||||
import nu.marginalia.model.EdgeUrl;
|
import nu.marginalia.model.EdgeUrl;
|
||||||
import nu.marginalia.model.crawl.DomainIndexingState;
|
import nu.marginalia.model.crawl.DomainIndexingState;
|
||||||
@@ -155,15 +155,15 @@ public class SearchOperator {
|
|||||||
|
|
||||||
|
|
||||||
public List<UrlDetails> getResultsFromQuery(QueryResponse queryResponse) {
|
public List<UrlDetails> getResultsFromQuery(QueryResponse queryResponse) {
|
||||||
final QueryLimits limits = queryResponse.specs().queryLimits;
|
final RpcQueryLimits limits = queryResponse.specs().queryLimits;
|
||||||
final UrlDeduplicator deduplicator = new UrlDeduplicator(limits.resultsByDomain());
|
final UrlDeduplicator deduplicator = new UrlDeduplicator(limits.getResultsByDomain());
|
||||||
|
|
||||||
// Update the query count (this is what you see on the front page)
|
// Update the query count (this is what you see on the front page)
|
||||||
searchVisitorCount.registerQuery();
|
searchVisitorCount.registerQuery();
|
||||||
|
|
||||||
return queryResponse.results().stream()
|
return queryResponse.results().stream()
|
||||||
.filter(deduplicator::shouldRetain)
|
.filter(deduplicator::shouldRetain)
|
||||||
.limit(limits.resultsTotal())
|
.limit(limits.getResultsTotal())
|
||||||
.map(SearchOperator::createDetails)
|
.map(SearchOperator::createDetails)
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
package nu.marginalia.search;
|
package nu.marginalia.search;
|
||||||
|
|
||||||
|
import nu.marginalia.api.searchquery.RpcQueryLimits;
|
||||||
|
import nu.marginalia.api.searchquery.RpcTemporalBias;
|
||||||
import nu.marginalia.api.searchquery.model.query.QueryParams;
|
import nu.marginalia.api.searchquery.model.query.QueryParams;
|
||||||
import nu.marginalia.api.searchquery.model.query.SearchQuery;
|
import nu.marginalia.api.searchquery.model.query.SearchQuery;
|
||||||
import nu.marginalia.api.searchquery.model.query.SearchSetIdentifier;
|
import nu.marginalia.api.searchquery.model.query.SearchSetIdentifier;
|
||||||
import nu.marginalia.api.searchquery.model.results.ResultRankingParameters;
|
|
||||||
import nu.marginalia.index.query.limit.QueryLimits;
|
|
||||||
import nu.marginalia.index.query.limit.QueryStrategy;
|
import nu.marginalia.index.query.limit.QueryStrategy;
|
||||||
import nu.marginalia.index.query.limit.SpecificationLimit;
|
import nu.marginalia.index.query.limit.SpecificationLimit;
|
||||||
import nu.marginalia.search.command.SearchParameters;
|
import nu.marginalia.search.command.SearchParameters;
|
||||||
@@ -12,6 +12,21 @@ import nu.marginalia.search.command.SearchParameters;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class SearchQueryParamFactory {
|
public class SearchQueryParamFactory {
|
||||||
|
static final RpcQueryLimits defaultLimits = RpcQueryLimits.newBuilder()
|
||||||
|
.setResultsTotal(100)
|
||||||
|
.setResultsByDomain(5)
|
||||||
|
.setTimeoutMs(200)
|
||||||
|
.setFetchSize(8192)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
|
||||||
|
static final RpcQueryLimits shallowLimit = RpcQueryLimits.newBuilder()
|
||||||
|
.setResultsTotal(100)
|
||||||
|
.setResultsByDomain(100)
|
||||||
|
.setTimeoutMs(100)
|
||||||
|
.setFetchSize(512)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
|
||||||
public QueryParams forRegularSearch(SearchParameters userParams) {
|
public QueryParams forRegularSearch(SearchParameters userParams) {
|
||||||
SearchQuery prototype = new SearchQuery();
|
SearchQuery prototype = new SearchQuery();
|
||||||
@@ -33,7 +48,7 @@ public class SearchQueryParamFactory {
|
|||||||
profile.getSizeLimit(),
|
profile.getSizeLimit(),
|
||||||
SpecificationLimit.none(),
|
SpecificationLimit.none(),
|
||||||
List.of(),
|
List.of(),
|
||||||
new QueryLimits(5, 100, 200, 8192),
|
defaultLimits,
|
||||||
profile.searchSetIdentifier.name(),
|
profile.searchSetIdentifier.name(),
|
||||||
userParams.strategy(),
|
userParams.strategy(),
|
||||||
userParams.temporalBias(),
|
userParams.temporalBias(),
|
||||||
@@ -54,10 +69,15 @@ public class SearchQueryParamFactory {
|
|||||||
SpecificationLimit.none(),
|
SpecificationLimit.none(),
|
||||||
SpecificationLimit.none(),
|
SpecificationLimit.none(),
|
||||||
List.of(domainId),
|
List.of(domainId),
|
||||||
new QueryLimits(count, count, 100, 512),
|
RpcQueryLimits.newBuilder()
|
||||||
|
.setResultsTotal(count)
|
||||||
|
.setResultsByDomain(count)
|
||||||
|
.setTimeoutMs(100)
|
||||||
|
.setFetchSize(512)
|
||||||
|
.build(),
|
||||||
SearchSetIdentifier.NONE.name(),
|
SearchSetIdentifier.NONE.name(),
|
||||||
QueryStrategy.AUTO,
|
QueryStrategy.AUTO,
|
||||||
ResultRankingParameters.TemporalBias.NONE,
|
RpcTemporalBias.Bias.NONE,
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -74,10 +94,10 @@ public class SearchQueryParamFactory {
|
|||||||
SpecificationLimit.none(),
|
SpecificationLimit.none(),
|
||||||
SpecificationLimit.none(),
|
SpecificationLimit.none(),
|
||||||
List.of(),
|
List.of(),
|
||||||
new QueryLimits(100, 100, 100, 512),
|
shallowLimit,
|
||||||
SearchSetIdentifier.NONE.name(),
|
SearchSetIdentifier.NONE.name(),
|
||||||
QueryStrategy.AUTO,
|
QueryStrategy.AUTO,
|
||||||
ResultRankingParameters.TemporalBias.NONE,
|
RpcTemporalBias.Bias.NONE,
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -94,10 +114,10 @@ public class SearchQueryParamFactory {
|
|||||||
SpecificationLimit.none(),
|
SpecificationLimit.none(),
|
||||||
SpecificationLimit.none(),
|
SpecificationLimit.none(),
|
||||||
List.of(),
|
List.of(),
|
||||||
new QueryLimits(100, 100, 100, 512),
|
shallowLimit,
|
||||||
SearchSetIdentifier.NONE.name(),
|
SearchSetIdentifier.NONE.name(),
|
||||||
QueryStrategy.AUTO,
|
QueryStrategy.AUTO,
|
||||||
ResultRankingParameters.TemporalBias.NONE,
|
RpcTemporalBias.Bias.NONE,
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
package nu.marginalia.search.command;
|
package nu.marginalia.search.command;
|
||||||
|
|
||||||
import nu.marginalia.WebsiteUrl;
|
import nu.marginalia.WebsiteUrl;
|
||||||
import nu.marginalia.api.searchquery.model.results.ResultRankingParameters;
|
import nu.marginalia.api.searchquery.RpcTemporalBias;
|
||||||
import nu.marginalia.index.query.limit.QueryStrategy;
|
import nu.marginalia.index.query.limit.QueryStrategy;
|
||||||
import nu.marginalia.index.query.limit.SpecificationLimit;
|
import nu.marginalia.index.query.limit.SpecificationLimit;
|
||||||
import nu.marginalia.search.model.SearchProfile;
|
import nu.marginalia.search.model.SearchProfile;
|
||||||
@@ -78,15 +78,15 @@ public record SearchParameters(String query,
|
|||||||
return baseUrl.withPath(path);
|
return baseUrl.withPath(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResultRankingParameters.TemporalBias temporalBias() {
|
public RpcTemporalBias.Bias temporalBias() {
|
||||||
if (recent == RECENT) {
|
if (recent == RECENT) {
|
||||||
return ResultRankingParameters.TemporalBias.RECENT;
|
return RpcTemporalBias.Bias.RECENT;
|
||||||
}
|
}
|
||||||
else if (profile == SearchProfile.VINTAGE) {
|
else if (profile == SearchProfile.VINTAGE) {
|
||||||
return ResultRankingParameters.TemporalBias.OLD;
|
return RpcTemporalBias.Bias.OLD;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ResultRankingParameters.TemporalBias.NONE;
|
return RpcTemporalBias.Bias.NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryStrategy strategy() {
|
public QueryStrategy strategy() {
|
||||||
|
@@ -2,14 +2,13 @@ package nu.marginalia.search;
|
|||||||
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.Singleton;
|
import com.google.inject.Singleton;
|
||||||
import nu.marginalia.WebsiteUrl;
|
|
||||||
import nu.marginalia.api.math.MathClient;
|
import nu.marginalia.api.math.MathClient;
|
||||||
import nu.marginalia.api.searchquery.QueryClient;
|
import nu.marginalia.api.searchquery.QueryClient;
|
||||||
|
import nu.marginalia.api.searchquery.RpcQueryLimits;
|
||||||
import nu.marginalia.api.searchquery.model.query.QueryResponse;
|
import nu.marginalia.api.searchquery.model.query.QueryResponse;
|
||||||
import nu.marginalia.api.searchquery.model.results.DecoratedSearchResultItem;
|
import nu.marginalia.api.searchquery.model.results.DecoratedSearchResultItem;
|
||||||
import nu.marginalia.bbpc.BrailleBlockPunchCards;
|
import nu.marginalia.bbpc.BrailleBlockPunchCards;
|
||||||
import nu.marginalia.db.DbDomainQueries;
|
import nu.marginalia.db.DbDomainQueries;
|
||||||
import nu.marginalia.index.query.limit.QueryLimits;
|
|
||||||
import nu.marginalia.model.EdgeDomain;
|
import nu.marginalia.model.EdgeDomain;
|
||||||
import nu.marginalia.model.EdgeUrl;
|
import nu.marginalia.model.EdgeUrl;
|
||||||
import nu.marginalia.model.crawl.DomainIndexingState;
|
import nu.marginalia.model.crawl.DomainIndexingState;
|
||||||
@@ -47,7 +46,6 @@ public class SearchOperator {
|
|||||||
private final DbDomainQueries domainQueries;
|
private final DbDomainQueries domainQueries;
|
||||||
private final QueryClient queryClient;
|
private final QueryClient queryClient;
|
||||||
private final SearchQueryParamFactory paramFactory;
|
private final SearchQueryParamFactory paramFactory;
|
||||||
private final WebsiteUrl websiteUrl;
|
|
||||||
private final SearchUnitConversionService searchUnitConversionService;
|
private final SearchUnitConversionService searchUnitConversionService;
|
||||||
private final SearchQueryCountService searchVisitorCount;
|
private final SearchQueryCountService searchVisitorCount;
|
||||||
|
|
||||||
@@ -57,7 +55,6 @@ public class SearchOperator {
|
|||||||
DbDomainQueries domainQueries,
|
DbDomainQueries domainQueries,
|
||||||
QueryClient queryClient,
|
QueryClient queryClient,
|
||||||
SearchQueryParamFactory paramFactory,
|
SearchQueryParamFactory paramFactory,
|
||||||
WebsiteUrl websiteUrl,
|
|
||||||
SearchUnitConversionService searchUnitConversionService,
|
SearchUnitConversionService searchUnitConversionService,
|
||||||
SearchQueryCountService searchVisitorCount
|
SearchQueryCountService searchVisitorCount
|
||||||
)
|
)
|
||||||
@@ -67,7 +64,6 @@ public class SearchOperator {
|
|||||||
this.domainQueries = domainQueries;
|
this.domainQueries = domainQueries;
|
||||||
this.queryClient = queryClient;
|
this.queryClient = queryClient;
|
||||||
this.paramFactory = paramFactory;
|
this.paramFactory = paramFactory;
|
||||||
this.websiteUrl = websiteUrl;
|
|
||||||
this.searchUnitConversionService = searchUnitConversionService;
|
this.searchUnitConversionService = searchUnitConversionService;
|
||||||
this.searchVisitorCount = searchVisitorCount;
|
this.searchVisitorCount = searchVisitorCount;
|
||||||
}
|
}
|
||||||
@@ -154,8 +150,8 @@ public class SearchOperator {
|
|||||||
|
|
||||||
|
|
||||||
public SimpleSearchResults getResultsFromQuery(QueryResponse queryResponse) {
|
public SimpleSearchResults getResultsFromQuery(QueryResponse queryResponse) {
|
||||||
final QueryLimits limits = queryResponse.specs().queryLimits;
|
final RpcQueryLimits limits = queryResponse.specs().queryLimits;
|
||||||
final UrlDeduplicator deduplicator = new UrlDeduplicator(limits.resultsByDomain());
|
final UrlDeduplicator deduplicator = new UrlDeduplicator(limits.getResultsByDomain());
|
||||||
|
|
||||||
// Update the query count (this is what you see on the front page)
|
// Update the query count (this is what you see on the front page)
|
||||||
searchVisitorCount.registerQuery();
|
searchVisitorCount.registerQuery();
|
||||||
@@ -164,7 +160,7 @@ public class SearchOperator {
|
|||||||
.sorted(this::retentionSortOrder) // Sort in an order that makes us more likely to discard the "bad" duplicates
|
.sorted(this::retentionSortOrder) // Sort in an order that makes us more likely to discard the "bad" duplicates
|
||||||
.filter(deduplicator::shouldRetain)
|
.filter(deduplicator::shouldRetain)
|
||||||
.sorted() // Return to the presentation sort order before limiting so we don't throw out good results over schema and "ip-ness"
|
.sorted() // Return to the presentation sort order before limiting so we don't throw out good results over schema and "ip-ness"
|
||||||
.limit(limits.resultsTotal())
|
.limit(limits.getResultsTotal())
|
||||||
.map(SearchOperator::createDetails)
|
.map(SearchOperator::createDetails)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
package nu.marginalia.search;
|
package nu.marginalia.search;
|
||||||
|
|
||||||
|
import nu.marginalia.api.searchquery.RpcQueryLimits;
|
||||||
|
import nu.marginalia.api.searchquery.RpcTemporalBias;
|
||||||
import nu.marginalia.api.searchquery.model.query.QueryParams;
|
import nu.marginalia.api.searchquery.model.query.QueryParams;
|
||||||
import nu.marginalia.api.searchquery.model.query.SearchQuery;
|
import nu.marginalia.api.searchquery.model.query.SearchQuery;
|
||||||
import nu.marginalia.api.searchquery.model.query.SearchSetIdentifier;
|
import nu.marginalia.api.searchquery.model.query.SearchSetIdentifier;
|
||||||
import nu.marginalia.api.searchquery.model.results.ResultRankingParameters;
|
|
||||||
import nu.marginalia.index.query.limit.QueryLimits;
|
|
||||||
import nu.marginalia.index.query.limit.QueryStrategy;
|
import nu.marginalia.index.query.limit.QueryStrategy;
|
||||||
import nu.marginalia.index.query.limit.SpecificationLimit;
|
import nu.marginalia.index.query.limit.SpecificationLimit;
|
||||||
import nu.marginalia.search.command.SearchParameters;
|
import nu.marginalia.search.command.SearchParameters;
|
||||||
@@ -13,6 +13,22 @@ import java.util.List;
|
|||||||
|
|
||||||
public class SearchQueryParamFactory {
|
public class SearchQueryParamFactory {
|
||||||
|
|
||||||
|
|
||||||
|
static final RpcQueryLimits defaultLimits = RpcQueryLimits.newBuilder()
|
||||||
|
.setResultsTotal(100)
|
||||||
|
.setResultsByDomain(5)
|
||||||
|
.setTimeoutMs(200)
|
||||||
|
.setFetchSize(8192)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
|
||||||
|
static final RpcQueryLimits shallowLimit = RpcQueryLimits.newBuilder()
|
||||||
|
.setResultsTotal(100)
|
||||||
|
.setResultsByDomain(100)
|
||||||
|
.setTimeoutMs(100)
|
||||||
|
.setFetchSize(512)
|
||||||
|
.build();
|
||||||
|
|
||||||
public QueryParams forRegularSearch(SearchParameters userParams) {
|
public QueryParams forRegularSearch(SearchParameters userParams) {
|
||||||
SearchQuery prototype = new SearchQuery();
|
SearchQuery prototype = new SearchQuery();
|
||||||
var profile = userParams.profile();
|
var profile = userParams.profile();
|
||||||
@@ -29,11 +45,11 @@ public class SearchQueryParamFactory {
|
|||||||
prototype.searchTermsPriority,
|
prototype.searchTermsPriority,
|
||||||
prototype.searchTermsAdvice,
|
prototype.searchTermsAdvice,
|
||||||
profile.getQualityLimit(),
|
profile.getQualityLimit(),
|
||||||
profile.getYearLimit(),
|
userParams.yearLimit(),
|
||||||
profile.getSizeLimit(),
|
profile.getSizeLimit(),
|
||||||
SpecificationLimit.none(),
|
SpecificationLimit.none(),
|
||||||
List.of(),
|
List.of(),
|
||||||
new QueryLimits(5, 100, 200, 8192),
|
defaultLimits,
|
||||||
profile.searchSetIdentifier.name(),
|
profile.searchSetIdentifier.name(),
|
||||||
userParams.strategy(),
|
userParams.strategy(),
|
||||||
userParams.temporalBias(),
|
userParams.temporalBias(),
|
||||||
@@ -54,10 +70,15 @@ public class SearchQueryParamFactory {
|
|||||||
SpecificationLimit.none(),
|
SpecificationLimit.none(),
|
||||||
SpecificationLimit.none(),
|
SpecificationLimit.none(),
|
||||||
List.of(domainId),
|
List.of(domainId),
|
||||||
new QueryLimits(count, count, 100, 512),
|
RpcQueryLimits.newBuilder()
|
||||||
|
.setResultsTotal(count)
|
||||||
|
.setResultsByDomain(count)
|
||||||
|
.setTimeoutMs(100)
|
||||||
|
.setFetchSize(512)
|
||||||
|
.build(),
|
||||||
SearchSetIdentifier.NONE.name(),
|
SearchSetIdentifier.NONE.name(),
|
||||||
QueryStrategy.AUTO,
|
QueryStrategy.AUTO,
|
||||||
ResultRankingParameters.TemporalBias.NONE,
|
RpcTemporalBias.Bias.NONE,
|
||||||
page
|
page
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -74,10 +95,10 @@ public class SearchQueryParamFactory {
|
|||||||
SpecificationLimit.none(),
|
SpecificationLimit.none(),
|
||||||
SpecificationLimit.none(),
|
SpecificationLimit.none(),
|
||||||
List.of(),
|
List.of(),
|
||||||
new QueryLimits(100, 100, 100, 512),
|
shallowLimit,
|
||||||
SearchSetIdentifier.NONE.name(),
|
SearchSetIdentifier.NONE.name(),
|
||||||
QueryStrategy.AUTO,
|
QueryStrategy.AUTO,
|
||||||
ResultRankingParameters.TemporalBias.NONE,
|
RpcTemporalBias.Bias.NONE,
|
||||||
page
|
page
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -94,10 +115,10 @@ public class SearchQueryParamFactory {
|
|||||||
SpecificationLimit.none(),
|
SpecificationLimit.none(),
|
||||||
SpecificationLimit.none(),
|
SpecificationLimit.none(),
|
||||||
List.of(),
|
List.of(),
|
||||||
new QueryLimits(100, 100, 100, 512),
|
shallowLimit,
|
||||||
SearchSetIdentifier.NONE.name(),
|
SearchSetIdentifier.NONE.name(),
|
||||||
QueryStrategy.AUTO,
|
QueryStrategy.AUTO,
|
||||||
ResultRankingParameters.TemporalBias.NONE,
|
RpcTemporalBias.Bias.NONE,
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -1,15 +1,14 @@
|
|||||||
package nu.marginalia.search;
|
package nu.marginalia.search;
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
|
import io.jooby.Context;
|
||||||
import io.jooby.Jooby;
|
import io.jooby.Jooby;
|
||||||
import io.prometheus.client.Counter;
|
import io.prometheus.client.Counter;
|
||||||
import io.prometheus.client.Histogram;
|
import io.prometheus.client.Histogram;
|
||||||
import nu.marginalia.WebsiteUrl;
|
|
||||||
import nu.marginalia.search.svc.*;
|
import nu.marginalia.search.svc.*;
|
||||||
import nu.marginalia.service.discovery.property.ServicePartition;
|
import nu.marginalia.service.discovery.property.ServicePartition;
|
||||||
import nu.marginalia.service.server.BaseServiceParams;
|
import nu.marginalia.service.server.BaseServiceParams;
|
||||||
import nu.marginalia.service.server.JoobyService;
|
import nu.marginalia.service.server.JoobyService;
|
||||||
import nu.marginalia.service.server.StaticResources;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -34,8 +33,6 @@ public class SearchService extends JoobyService {
|
|||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public SearchService(BaseServiceParams params,
|
public SearchService(BaseServiceParams params,
|
||||||
WebsiteUrl websiteUrl,
|
|
||||||
StaticResources staticResources,
|
|
||||||
SearchFrontPageService frontPageService,
|
SearchFrontPageService frontPageService,
|
||||||
SearchAddToCrawlQueueService addToCrawlQueueService,
|
SearchAddToCrawlQueueService addToCrawlQueueService,
|
||||||
SearchSiteSubscriptionService siteSubscriptionService,
|
SearchSiteSubscriptionService siteSubscriptionService,
|
||||||
@@ -62,7 +59,25 @@ public class SearchService extends JoobyService {
|
|||||||
public void startJooby(Jooby jooby) {
|
public void startJooby(Jooby jooby) {
|
||||||
super.startJooby(jooby);
|
super.startJooby(jooby);
|
||||||
|
|
||||||
|
final String startTimeAttribute = "start-time";
|
||||||
|
|
||||||
jooby.get("/export-opml", siteSubscriptionService::exportOpml);
|
jooby.get("/export-opml", siteSubscriptionService::exportOpml);
|
||||||
|
jooby.before((Context ctx) -> {
|
||||||
|
ctx.setAttribute(startTimeAttribute, System.nanoTime());
|
||||||
|
});
|
||||||
|
|
||||||
|
jooby.after((Context ctx, Object result, Throwable failure) -> {
|
||||||
|
if (failure != null) {
|
||||||
|
wmsa_search_service_error_count.labels(ctx.getRoute().getPattern(), ctx.getMethod()).inc();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Long startTime = ctx.getAttribute(startTimeAttribute);
|
||||||
|
if (startTime != null) {
|
||||||
|
wmsa_search_service_request_time.labels(ctx.getRoute().getPattern(), ctx.getMethod())
|
||||||
|
.observe((System.nanoTime() - startTime) / 1e9);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
package nu.marginalia.search.command;
|
package nu.marginalia.search.command;
|
||||||
|
|
||||||
import nu.marginalia.WebsiteUrl;
|
import nu.marginalia.WebsiteUrl;
|
||||||
import nu.marginalia.api.searchquery.model.results.ResultRankingParameters;
|
import nu.marginalia.api.searchquery.RpcTemporalBias;
|
||||||
import nu.marginalia.index.query.limit.QueryStrategy;
|
import nu.marginalia.index.query.limit.QueryStrategy;
|
||||||
import nu.marginalia.index.query.limit.SpecificationLimit;
|
import nu.marginalia.index.query.limit.SpecificationLimit;
|
||||||
import nu.marginalia.model.EdgeDomain;
|
import nu.marginalia.model.EdgeDomain;
|
||||||
@@ -98,15 +98,15 @@ public record SearchParameters(WebsiteUrl url,
|
|||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResultRankingParameters.TemporalBias temporalBias() {
|
public RpcTemporalBias.Bias temporalBias() {
|
||||||
if (recent == RECENT) {
|
if (recent == RECENT) {
|
||||||
return ResultRankingParameters.TemporalBias.RECENT;
|
return RpcTemporalBias.Bias.RECENT;
|
||||||
}
|
}
|
||||||
else if (profile == SearchProfile.VINTAGE) {
|
else if (profile == SearchProfile.VINTAGE) {
|
||||||
return ResultRankingParameters.TemporalBias.OLD;
|
return RpcTemporalBias.Bias.OLD;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ResultRankingParameters.TemporalBias.NONE;
|
return RpcTemporalBias.Bias.NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryStrategy strategy() {
|
public QueryStrategy strategy() {
|
||||||
|
@@ -47,18 +47,23 @@ public class SearchAddToCrawlQueueService {
|
|||||||
return new MapModelAndView("redirect.jte", Map.of("url", "/site/"+domainName));
|
return new MapModelAndView("redirect.jte", Map.of("url", "/site/"+domainName));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addToCrawlQueue(int id) throws SQLException {
|
/** Mark a domain for crawling by setting node affinity to zero,
|
||||||
|
* unless it is already marked for crawling, then node affinity should
|
||||||
|
* be left unchanged.
|
||||||
|
* */
|
||||||
|
void addToCrawlQueue(int domainId) throws SQLException {
|
||||||
try (var conn = dataSource.getConnection();
|
try (var conn = dataSource.getConnection();
|
||||||
var stmt = conn.prepareStatement("""
|
var stmt = conn.prepareStatement("""
|
||||||
INSERT IGNORE INTO CRAWL_QUEUE(DOMAIN_NAME, SOURCE)
|
UPDATE EC_DOMAIN
|
||||||
SELECT DOMAIN_NAME, "user" FROM EC_DOMAIN WHERE ID=?
|
SET WMSA_prod.EC_DOMAIN.NODE_AFFINITY = 0
|
||||||
|
WHERE ID=? AND WMSA_prod.EC_DOMAIN.NODE_AFFINITY < 0
|
||||||
""")) {
|
""")) {
|
||||||
stmt.setInt(1, id);
|
stmt.setInt(1, domainId);
|
||||||
stmt.executeUpdate();
|
stmt.executeUpdate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getDomainName(int id) {
|
String getDomainName(int id) {
|
||||||
var domain = domainQueries.getDomain(id);
|
var domain = domainQueries.getDomain(id);
|
||||||
if (domain.isEmpty())
|
if (domain.isEmpty())
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
|
@@ -37,7 +37,7 @@ public class SearchQueryService {
|
|||||||
@QueryParam String profile,
|
@QueryParam String profile,
|
||||||
@QueryParam String js,
|
@QueryParam String js,
|
||||||
@QueryParam String recent,
|
@QueryParam String recent,
|
||||||
@QueryParam String title,
|
@QueryParam String searchTitle,
|
||||||
@QueryParam String adtech,
|
@QueryParam String adtech,
|
||||||
@QueryParam Integer page
|
@QueryParam Integer page
|
||||||
) {
|
) {
|
||||||
@@ -47,7 +47,7 @@ public class SearchQueryService {
|
|||||||
SearchProfile.getSearchProfile(profile),
|
SearchProfile.getSearchProfile(profile),
|
||||||
SearchJsParameter.parse(js),
|
SearchJsParameter.parse(js),
|
||||||
SearchRecentParameter.parse(recent),
|
SearchRecentParameter.parse(recent),
|
||||||
SearchTitleParameter.parse(title),
|
SearchTitleParameter.parse(searchTitle),
|
||||||
SearchAdtechParameter.parse(adtech),
|
SearchAdtechParameter.parse(adtech),
|
||||||
false,
|
false,
|
||||||
Objects.requireNonNullElse(page,1));
|
Objects.requireNonNullElse(page,1));
|
||||||
|
@@ -26,6 +26,8 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
@@ -69,6 +71,8 @@ public class SearchSiteInfoService {
|
|||||||
this.searchSiteSubscriptions = searchSiteSubscriptions;
|
this.searchSiteSubscriptions = searchSiteSubscriptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private volatile SiteOverviewModel model = new SiteOverviewModel(List.of(), Instant.EPOCH);
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("/site")
|
@Path("/site")
|
||||||
public ModelAndView<?> handleOverview(@QueryParam String domain) {
|
public ModelAndView<?> handleOverview(@QueryParam String domain) {
|
||||||
@@ -77,6 +81,27 @@ public class SearchSiteInfoService {
|
|||||||
return new MapModelAndView("redirect.jte", Map.of("url", "/site/"+domain));
|
return new MapModelAndView("redirect.jte", Map.of("url", "/site/"+domain));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (model.age().compareTo(Duration.ofMinutes(15)) > 0) {
|
||||||
|
updateModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new MapModelAndView("siteinfo/start.jte",
|
||||||
|
Map.of("navbar", NavbarModel.SITEINFO,
|
||||||
|
"model", model));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Update the model if it is older than 15 minutes.
|
||||||
|
* This query is expensive and should not be run too often,
|
||||||
|
* and the data doesn't change that often either.
|
||||||
|
* <p></p>
|
||||||
|
* This method is synchronized to avoid multiple threads updating the model at the same time.
|
||||||
|
*/
|
||||||
|
private synchronized void updateModel() {
|
||||||
|
var currentModel = model;
|
||||||
|
if (currentModel.age().compareTo(Duration.ofMinutes(15)) < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
List<SiteOverviewModel.DiscoveredDomain> domains = new ArrayList<>();
|
List<SiteOverviewModel.DiscoveredDomain> domains = new ArrayList<>();
|
||||||
|
|
||||||
try (var conn = dataSource.getConnection();
|
try (var conn = dataSource.getConnection();
|
||||||
@@ -91,13 +116,20 @@ public class SearchSiteInfoService {
|
|||||||
throw new RuntimeException();
|
throw new RuntimeException();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new MapModelAndView("siteinfo/start.jte",
|
model = new SiteOverviewModel(domains);
|
||||||
Map.of("navbar", NavbarModel.SITEINFO,
|
|
||||||
"model", new SiteOverviewModel(domains)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public record SiteOverviewModel(List<DiscoveredDomain> domains) {
|
public record SiteOverviewModel(List<DiscoveredDomain> domains, Instant captureTime) {
|
||||||
|
|
||||||
|
public SiteOverviewModel(List<DiscoveredDomain> domains) {
|
||||||
|
this(domains, Instant.now());
|
||||||
|
}
|
||||||
|
|
||||||
public record DiscoveredDomain(String name, String timestamp) {}
|
public record DiscoveredDomain(String name, String timestamp) {}
|
||||||
|
|
||||||
|
public Duration age() {
|
||||||
|
return Duration.between(captureTime, Instant.now());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@@ -352,7 +384,7 @@ public class SearchSiteInfoService {
|
|||||||
|
|
||||||
public record SiteInfoWithContext(String domain,
|
public record SiteInfoWithContext(String domain,
|
||||||
boolean isSubscribed,
|
boolean isSubscribed,
|
||||||
List<EdgeDomain> siblingDomains,
|
List<DbDomainQueries.DomainWithNode> siblingDomains,
|
||||||
int domainId,
|
int domainId,
|
||||||
String siteUrl,
|
String siteUrl,
|
||||||
boolean hasScreenshot,
|
boolean hasScreenshot,
|
||||||
|
@@ -2,13 +2,24 @@
|
|||||||
|
|
||||||
This service handles search traffic and is the service
|
This service handles search traffic and is the service
|
||||||
you're most directly interacting with when visiting
|
you're most directly interacting with when visiting
|
||||||
[search.marginalia.nu](https://search.marginalia.nu).
|
[marginalia-search.com](https://marginalia-search.com).
|
||||||
|
|
||||||
It interprets a "human" query and translates it into a
|
It interprets a "human" query and translates it into a
|
||||||
request that gets passed into to the index service, which finds
|
request that gets passed into to the index service, which finds
|
||||||
related documents, which this service then ranks and returns
|
related documents, which this service then ranks and returns
|
||||||
to the user.
|
to the user.
|
||||||
|
|
||||||
|
The UI is built using [JTE templates](https://jte.gg/syntax/) and the [Jooby framework](https://jooby.io), primarily using
|
||||||
|
its MVC facilities.
|
||||||
|
|
||||||
|
When developing, it's possible to set up a mock version of the UI by running
|
||||||
|
the gradle command
|
||||||
|
|
||||||
|
```$ ./gradlew paperDoll -i```
|
||||||
|
|
||||||
|
The UI will be available at http://localhost:9999/, and has hot reloading of JTE classes
|
||||||
|
and static resources.
|
||||||
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
@@ -34,12 +34,12 @@
|
|||||||
<div class="max-w-7xl mx-auto flex flex-col space-y-4 fill-w">
|
<div class="max-w-7xl mx-auto flex flex-col space-y-4 fill-w">
|
||||||
<div class="border dark:border-gray-600 dark:bg-gray-800 bg-white rounded p-2 m-4 ">
|
<div class="border dark:border-gray-600 dark:bg-gray-800 bg-white rounded p-2 m-4 ">
|
||||||
<div class="text-slate-700 dark:text-white text-sm p-4">
|
<div class="text-slate-700 dark:text-white text-sm p-4">
|
||||||
<div class="fas fa-wrench mr-1 text-margeblue dark:text-slate-200"></div>
|
<div class="fas fa-gift mr-1 text-margeblue dark:text-slate-200"></div>
|
||||||
This is the new design and home of Marginalia Search. Migration to the new domain <pre class="inline text-red-800 dark:text-red-100">marginalia-search.com</pre> is currently <em>in progress</em>,
|
This is the new design and home of Marginalia Search.
|
||||||
so mind that some things may be a bit broken for a day or two. <a href="https://about.marginalia-search.com/article/redesign/" class="underline text-liteblue dark:text-blue-200">Read more</a>.
|
You can about what this entails <a href="https://about.marginalia-search.com/article/redesign/" class="underline text-liteblue dark:text-blue-200">here</a>.
|
||||||
<p class="my-4"></p>
|
<p class="my-4"></p>
|
||||||
If you have any issues or feedback regarding this change, please email
|
The old version of Marginalia Search remains available at
|
||||||
<a href="mailto:contact@marginalia-search.com" class="underline text-liteblue dark:text-blue-200">contact@marginalia-search.com</a>.
|
<a href="https://old-search.marginalia.nu/" class="underline text-liteblue dark:text-blue-200">https://old-search.marginalia.nu/</a>.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mx-auto flex flex-col sm:flex-row my-4 sm:space-x-2 space-y-2 sm:space-y-0 w-full md:w-auto px-2">
|
<div class="mx-auto flex flex-col sm:flex-row my-4 sm:space-x-2 space-y-2 sm:space-y-0 w-full md:w-auto px-2">
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
@import nu.marginalia.db.DbDomainQueries
|
||||||
@import nu.marginalia.model.EdgeDomain
|
@import nu.marginalia.model.EdgeDomain
|
||||||
@import nu.marginalia.search.svc.SearchSiteInfoService
|
@import nu.marginalia.search.svc.SearchSiteInfoService
|
||||||
@import nu.marginalia.search.svc.SearchSiteInfoService.*
|
@import nu.marginalia.search.svc.SearchSiteInfoService.*
|
||||||
@@ -80,31 +81,6 @@
|
|||||||
|
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
|
||||||
@if (!siteInfo.siblingDomains().isEmpty())
|
|
||||||
<div class="mx-3 flex place-items-baseline space-x-2 p-2 bg-gray-100 dark:bg-gray-600 rounded">
|
|
||||||
<i class="fas fa-globe"></i>
|
|
||||||
<span>Related Subdomains</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-600 mx-4">
|
|
||||||
<thead>
|
|
||||||
<tr class="bg-gray-50 dark:bg-gray-700">
|
|
||||||
<th scope="col" class="px-2 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-100 uppercase tracking-wider">Domain Name</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-600 text-xs">
|
|
||||||
@for (EdgeDomain sibling : siteInfo.siblingDomains())
|
|
||||||
<tr>
|
|
||||||
<td class="px-3 py-6 md:py-3 whitespace-nowrap">
|
|
||||||
<a class="text-liteblue dark:text-blue-200" href="/site/${sibling.toString()}">${sibling.toString()}</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
@endfor
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
@if (siteInfo.domainInformation().isUnknownDomain())
|
@if (siteInfo.domainInformation().isUnknownDomain())
|
||||||
<div class="mx-3 flex place-items-baseline space-x-2 p-2 bg-gray-100 dark:bg-gray-600 rounded">
|
<div class="mx-3 flex place-items-baseline space-x-2 p-2 bg-gray-100 dark:bg-gray-600 rounded">
|
||||||
<i class="fa-regular fa-circle-question"></i>
|
<i class="fa-regular fa-circle-question"></i>
|
||||||
@@ -173,6 +149,36 @@
|
|||||||
</form>
|
</form>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
|
||||||
|
@if (!siteInfo.siblingDomains().isEmpty())
|
||||||
|
<div class="mx-3 flex place-items-baseline space-x-2 p-2 bg-gray-100 dark:bg-gray-600 rounded">
|
||||||
|
<i class="fas fa-globe"></i>
|
||||||
|
<span>Related Subdomains</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-600 mx-4">
|
||||||
|
<thead>
|
||||||
|
<tr class="bg-gray-50 dark:bg-gray-700">
|
||||||
|
<th scope="col" class="px-2 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-100 uppercase tracking-wider">Domain Name</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-600 text-xs">
|
||||||
|
@for (DbDomainQueries.DomainWithNode sibling : siteInfo.siblingDomains())
|
||||||
|
<tr>
|
||||||
|
<td class="px-3 py-6 md:py-3 whitespace-nowrap">
|
||||||
|
<a class="text-liteblue dark:text-blue-200" href="/site/${sibling.domain().toString()}">${sibling.domain().toString()}</a>
|
||||||
|
|
||||||
|
@if (!sibling.isIndexed())
|
||||||
|
<i class="ml-1 fa-regular fa-question-circle text-gray-400 dark:text-gray-600 text-xs" title="Not indexed"></i>
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endfor
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
|
||||||
@if (siteInfo.isKnown())
|
@if (siteInfo.isKnown())
|
||||||
<div class="mx-3 flex place-items-baseline space-x-2 p-2 bg-gray-100 dark:bg-gray-600 rounded">
|
<div class="mx-3 flex place-items-baseline space-x-2 p-2 bg-gray-100 dark:bg-gray-600 rounded">
|
||||||
<i class="fas fa-chart-simple"></i>
|
<i class="fas fa-chart-simple"></i>
|
||||||
|
@@ -6,6 +6,7 @@ import nu.marginalia.api.domains.model.SimilarDomain;
|
|||||||
import nu.marginalia.api.searchquery.model.results.SearchResultItem;
|
import nu.marginalia.api.searchquery.model.results.SearchResultItem;
|
||||||
import nu.marginalia.browse.model.BrowseResult;
|
import nu.marginalia.browse.model.BrowseResult;
|
||||||
import nu.marginalia.browse.model.BrowseResultSet;
|
import nu.marginalia.browse.model.BrowseResultSet;
|
||||||
|
import nu.marginalia.db.DbDomainQueries;
|
||||||
import nu.marginalia.model.EdgeDomain;
|
import nu.marginalia.model.EdgeDomain;
|
||||||
import nu.marginalia.model.EdgeUrl;
|
import nu.marginalia.model.EdgeUrl;
|
||||||
import nu.marginalia.model.crawl.DomainIndexingState;
|
import nu.marginalia.model.crawl.DomainIndexingState;
|
||||||
@@ -132,8 +133,9 @@ public class MockedSearchResults {
|
|||||||
return new SearchSiteInfoService.SiteInfoWithContext(
|
return new SearchSiteInfoService.SiteInfoWithContext(
|
||||||
"www.example.com",
|
"www.example.com",
|
||||||
false,
|
false,
|
||||||
List.of(new EdgeDomain("example.com"),
|
List.of(
|
||||||
new EdgeDomain("about.example.com")
|
new DbDomainQueries.DomainWithNode(new EdgeDomain("example.com"), 1),
|
||||||
|
new DbDomainQueries.DomainWithNode(new EdgeDomain("example.com"), 0)
|
||||||
),
|
),
|
||||||
14,
|
14,
|
||||||
"https://www.example.com",
|
"https://www.example.com",
|
||||||
|
@@ -0,0 +1,85 @@
|
|||||||
|
package nu.marginalia.search.svc;
|
||||||
|
|
||||||
|
import com.zaxxer.hikari.HikariConfig;
|
||||||
|
import com.zaxxer.hikari.HikariDataSource;
|
||||||
|
import nu.marginalia.db.DbDomainQueries;
|
||||||
|
import nu.marginalia.model.EdgeDomain;
|
||||||
|
import nu.marginalia.test.TestMigrationLoader;
|
||||||
|
import org.junit.jupiter.api.*;
|
||||||
|
import org.testcontainers.containers.MariaDBContainer;
|
||||||
|
import org.testcontainers.junit.jupiter.Container;
|
||||||
|
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
@Tag("slow")
|
||||||
|
@Testcontainers
|
||||||
|
class SearchAddToCrawlQueueServiceTest {
|
||||||
|
@Container
|
||||||
|
static MariaDBContainer<?> mariaDBContainer = new MariaDBContainer<>("mariadb")
|
||||||
|
.withDatabaseName("WMSA_prod")
|
||||||
|
.withUsername("wmsa")
|
||||||
|
.withPassword("wmsa")
|
||||||
|
.withNetworkAliases("mariadb");
|
||||||
|
|
||||||
|
static HikariDataSource dataSource;
|
||||||
|
|
||||||
|
private DbDomainQueries domainQueries;
|
||||||
|
private SearchAddToCrawlQueueService addToCrawlQueueService;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setUp() throws SQLException {
|
||||||
|
try (var conn = dataSource.getConnection();
|
||||||
|
var stmt = conn.createStatement()) {
|
||||||
|
stmt.executeQuery("DELETE FROM EC_DOMAIN"); // Wipe any old state from other test runs
|
||||||
|
|
||||||
|
stmt.executeQuery("INSERT INTO EC_DOMAIN (DOMAIN_NAME, DOMAIN_TOP, NODE_AFFINITY) VALUES ('known.example.com', 'example.com', -1)");
|
||||||
|
stmt.executeQuery("INSERT INTO EC_DOMAIN (DOMAIN_NAME, DOMAIN_TOP, NODE_AFFINITY) VALUES ('added.example.com', 'example.com', 0)");
|
||||||
|
stmt.executeQuery("INSERT INTO EC_DOMAIN (DOMAIN_NAME, DOMAIN_TOP, NODE_AFFINITY) VALUES ('indexed.example.com', 'example.com', 1)");
|
||||||
|
}
|
||||||
|
|
||||||
|
domainQueries = new DbDomainQueries(dataSource);
|
||||||
|
addToCrawlQueueService = new SearchAddToCrawlQueueService(domainQueries, dataSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
public static void setUpAll() {
|
||||||
|
HikariConfig config = new HikariConfig();
|
||||||
|
config.setJdbcUrl(mariaDBContainer.getJdbcUrl());
|
||||||
|
config.setUsername("wmsa");
|
||||||
|
config.setPassword("wmsa");
|
||||||
|
|
||||||
|
dataSource = new HikariDataSource(config);
|
||||||
|
TestMigrationLoader.flywayMigration(dataSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getNodeAffinity(String domainName) throws SQLException {
|
||||||
|
try (var conn = dataSource.getConnection();
|
||||||
|
var stmt = conn.prepareStatement("SELECT NODE_AFFINITY FROM EC_DOMAIN WHERE DOMAIN_NAME=?"))
|
||||||
|
{
|
||||||
|
stmt.setString(1, domainName);
|
||||||
|
var rsp = stmt.executeQuery();
|
||||||
|
if (rsp.next()) {
|
||||||
|
return rsp.getInt(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void addToCrawlQueue() throws SQLException {
|
||||||
|
int knownId = domainQueries.getDomainId(new EdgeDomain("known.example.com"));
|
||||||
|
int addedId = domainQueries.getDomainId(new EdgeDomain("added.example.com"));
|
||||||
|
int indexedId = domainQueries.getDomainId(new EdgeDomain("indexed.example.com"));
|
||||||
|
|
||||||
|
addToCrawlQueueService.addToCrawlQueue(knownId);
|
||||||
|
addToCrawlQueueService.addToCrawlQueue(addedId);
|
||||||
|
addToCrawlQueueService.addToCrawlQueue(indexedId);
|
||||||
|
|
||||||
|
Assertions.assertEquals(0, getNodeAffinity("known.example.com"));
|
||||||
|
Assertions.assertEquals(0, getNodeAffinity("added.example.com"));
|
||||||
|
Assertions.assertEquals(1, getNodeAffinity("indexed.example.com"));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -55,6 +55,7 @@ dependencies {
|
|||||||
|
|
||||||
implementation libs.duckdb
|
implementation libs.duckdb
|
||||||
implementation libs.jsoup
|
implementation libs.jsoup
|
||||||
|
implementation libs.protobuf
|
||||||
|
|
||||||
implementation libs.trove
|
implementation libs.trove
|
||||||
implementation dependencies.create(libs.spark.get()) {
|
implementation dependencies.create(libs.spark.get()) {
|
||||||
|
@@ -2,11 +2,10 @@ package nu.marginalia.control.app.svc;
|
|||||||
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import nu.marginalia.api.searchquery.QueryClient;
|
import nu.marginalia.api.searchquery.QueryClient;
|
||||||
|
import nu.marginalia.api.searchquery.RpcQueryLimits;
|
||||||
import nu.marginalia.api.searchquery.model.query.QueryParams;
|
import nu.marginalia.api.searchquery.model.query.QueryParams;
|
||||||
import nu.marginalia.control.ControlRendererFactory;
|
import nu.marginalia.control.ControlRendererFactory;
|
||||||
import nu.marginalia.index.query.limit.QueryLimits;
|
|
||||||
import nu.marginalia.model.EdgeUrl;
|
import nu.marginalia.model.EdgeUrl;
|
||||||
import nu.marginalia.nodecfg.NodeConfigurationService;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import spark.Request;
|
import spark.Request;
|
||||||
@@ -22,18 +21,16 @@ public class SearchToBanService {
|
|||||||
private final ControlRendererFactory rendererFactory;
|
private final ControlRendererFactory rendererFactory;
|
||||||
private final QueryClient queryClient;
|
private final QueryClient queryClient;
|
||||||
private final Logger logger = LoggerFactory.getLogger(getClass());
|
private final Logger logger = LoggerFactory.getLogger(getClass());
|
||||||
private final NodeConfigurationService nodeConfigurationService;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public SearchToBanService(ControlBlacklistService blacklistService,
|
public SearchToBanService(ControlBlacklistService blacklistService,
|
||||||
ControlRendererFactory rendererFactory,
|
ControlRendererFactory rendererFactory,
|
||||||
QueryClient queryClient, NodeConfigurationService nodeConfigurationService)
|
QueryClient queryClient)
|
||||||
{
|
{
|
||||||
|
|
||||||
this.blacklistService = blacklistService;
|
this.blacklistService = blacklistService;
|
||||||
this.rendererFactory = rendererFactory;
|
this.rendererFactory = rendererFactory;
|
||||||
this.queryClient = queryClient;
|
this.queryClient = queryClient;
|
||||||
this.nodeConfigurationService = nodeConfigurationService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void register() throws IOException {
|
public void register() throws IOException {
|
||||||
@@ -76,7 +73,14 @@ public class SearchToBanService {
|
|||||||
|
|
||||||
private Object executeQuery(String query) {
|
private Object executeQuery(String query) {
|
||||||
return queryClient.search(new QueryParams(
|
return queryClient.search(new QueryParams(
|
||||||
query, new QueryLimits(2, 200, 250, 8192),
|
query,
|
||||||
|
RpcQueryLimits.newBuilder()
|
||||||
|
.setResultsTotal(100)
|
||||||
|
.setResultsByDomain(2)
|
||||||
|
.setTimeoutMs(200)
|
||||||
|
.setFetchSize(8192)
|
||||||
|
.build()
|
||||||
|
,
|
||||||
"NONE"
|
"NONE"
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@@ -3,12 +3,13 @@ package nu.marginalia.query;
|
|||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
|
import nu.marginalia.api.searchquery.RpcQueryLimits;
|
||||||
|
import nu.marginalia.api.searchquery.RpcResultRankingParameters;
|
||||||
|
import nu.marginalia.api.searchquery.RpcTemporalBias;
|
||||||
import nu.marginalia.api.searchquery.model.query.QueryParams;
|
import nu.marginalia.api.searchquery.model.query.QueryParams;
|
||||||
import nu.marginalia.api.searchquery.model.results.Bm25Parameters;
|
import nu.marginalia.api.searchquery.model.results.PrototypeRankingParameters;
|
||||||
import nu.marginalia.api.searchquery.model.results.ResultRankingParameters;
|
|
||||||
import nu.marginalia.functions.searchquery.QueryGRPCService;
|
import nu.marginalia.functions.searchquery.QueryGRPCService;
|
||||||
import nu.marginalia.index.api.IndexClient;
|
import nu.marginalia.index.api.IndexClient;
|
||||||
import nu.marginalia.index.query.limit.QueryLimits;
|
|
||||||
import nu.marginalia.model.gson.GsonFactory;
|
import nu.marginalia.model.gson.GsonFactory;
|
||||||
import nu.marginalia.renderer.MustacheRenderer;
|
import nu.marginalia.renderer.MustacheRenderer;
|
||||||
import nu.marginalia.renderer.RendererFactory;
|
import nu.marginalia.renderer.RendererFactory;
|
||||||
@@ -53,9 +54,14 @@ public class QueryBasicInterface {
|
|||||||
int domainCount = parseInt(requireNonNullElse(request.queryParams("domainCount"), "5"));
|
int domainCount = parseInt(requireNonNullElse(request.queryParams("domainCount"), "5"));
|
||||||
String set = requireNonNullElse(request.queryParams("set"), "");
|
String set = requireNonNullElse(request.queryParams("set"), "");
|
||||||
|
|
||||||
var params = new QueryParams(queryString, new QueryLimits(
|
var params = new QueryParams(queryString,
|
||||||
domainCount, min(100, count * 10), 250, 8192
|
RpcQueryLimits.newBuilder()
|
||||||
), set);
|
.setResultsByDomain(domainCount)
|
||||||
|
.setResultsTotal(min(100, count * 10))
|
||||||
|
.setTimeoutMs(250)
|
||||||
|
.setFetchSize(8192)
|
||||||
|
.build()
|
||||||
|
, set);
|
||||||
|
|
||||||
var pagination = new IndexClient.Pagination(page, count);
|
var pagination = new IndexClient.Pagination(page, count);
|
||||||
|
|
||||||
@@ -63,7 +69,7 @@ public class QueryBasicInterface {
|
|||||||
queryString,
|
queryString,
|
||||||
params,
|
params,
|
||||||
pagination,
|
pagination,
|
||||||
ResultRankingParameters.sensibleDefaults()
|
PrototypeRankingParameters.sensibleDefaults()
|
||||||
);
|
);
|
||||||
|
|
||||||
var results = detailedDirectResult.result();
|
var results = detailedDirectResult.result();
|
||||||
@@ -92,7 +98,7 @@ public class QueryBasicInterface {
|
|||||||
String queryString = request.queryParams("q");
|
String queryString = request.queryParams("q");
|
||||||
if (queryString == null) {
|
if (queryString == null) {
|
||||||
// Show the default query form if no query is given
|
// Show the default query form if no query is given
|
||||||
return qdebugRenderer.render(Map.of("rankingParams", ResultRankingParameters.sensibleDefaults())
|
return qdebugRenderer.render(Map.of("rankingParams", PrototypeRankingParameters.sensibleDefaults())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,9 +107,14 @@ public class QueryBasicInterface {
|
|||||||
int domainCount = parseInt(requireNonNullElse(request.queryParams("domainCount"), "5"));
|
int domainCount = parseInt(requireNonNullElse(request.queryParams("domainCount"), "5"));
|
||||||
String set = requireNonNullElse(request.queryParams("set"), "");
|
String set = requireNonNullElse(request.queryParams("set"), "");
|
||||||
|
|
||||||
var queryParams = new QueryParams(queryString, new QueryLimits(
|
var queryParams = new QueryParams(queryString,
|
||||||
domainCount, min(100, count * 10), 250, 8192
|
RpcQueryLimits.newBuilder()
|
||||||
), set);
|
.setResultsByDomain(domainCount)
|
||||||
|
.setResultsTotal(min(100, count * 10))
|
||||||
|
.setTimeoutMs(250)
|
||||||
|
.setFetchSize(8192)
|
||||||
|
.build(),
|
||||||
|
set);
|
||||||
|
|
||||||
var pagination = new IndexClient.Pagination(page, count);
|
var pagination = new IndexClient.Pagination(page, count);
|
||||||
|
|
||||||
@@ -126,27 +137,28 @@ public class QueryBasicInterface {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ResultRankingParameters debugRankingParamsFromRequest(Request request) {
|
private RpcResultRankingParameters debugRankingParamsFromRequest(Request request) {
|
||||||
var sensibleDefaults = ResultRankingParameters.sensibleDefaults();
|
var sensibleDefaults = PrototypeRankingParameters.sensibleDefaults();
|
||||||
|
|
||||||
return ResultRankingParameters.builder()
|
var bias = RpcTemporalBias.Bias.valueOf(stringFromRequest(request, "temporalBias", "NONE"));
|
||||||
.domainRankBonus(doubleFromRequest(request, "domainRankBonus", sensibleDefaults.domainRankBonus))
|
|
||||||
.qualityPenalty(doubleFromRequest(request, "qualityPenalty", sensibleDefaults.qualityPenalty))
|
return RpcResultRankingParameters.newBuilder()
|
||||||
.shortDocumentThreshold(intFromRequest(request, "shortDocumentThreshold", sensibleDefaults.shortDocumentThreshold))
|
.setDomainRankBonus(doubleFromRequest(request, "domainRankBonus", sensibleDefaults.getDomainRankBonus()))
|
||||||
.shortDocumentPenalty(doubleFromRequest(request, "shortDocumentPenalty", sensibleDefaults.shortDocumentPenalty))
|
.setQualityPenalty(doubleFromRequest(request, "qualityPenalty", sensibleDefaults.getQualityPenalty()))
|
||||||
.tcfFirstPosition(doubleFromRequest(request, "tcfFirstPosition", sensibleDefaults.tcfFirstPosition))
|
.setShortDocumentThreshold(intFromRequest(request, "shortDocumentThreshold", sensibleDefaults.getShortDocumentThreshold()))
|
||||||
.tcfVerbatim(doubleFromRequest(request, "tcfVerbatim", sensibleDefaults.tcfVerbatim))
|
.setShortDocumentPenalty(doubleFromRequest(request, "shortDocumentPenalty", sensibleDefaults.getShortDocumentPenalty()))
|
||||||
.tcfProximity(doubleFromRequest(request, "tcfProximity", sensibleDefaults.tcfProximity))
|
.setTcfFirstPositionWeight(doubleFromRequest(request, "tcfFirstPositionWeight", sensibleDefaults.getTcfFirstPositionWeight()))
|
||||||
.bm25Params(new Bm25Parameters(
|
.setTcfVerbatimWeight(doubleFromRequest(request, "tcfVerbatimWeight", sensibleDefaults.getTcfVerbatimWeight()))
|
||||||
doubleFromRequest(request, "bm25.k1", sensibleDefaults.bm25Params.k()),
|
.setTcfProximityWeight(doubleFromRequest(request, "tcfProximityWeight", sensibleDefaults.getTcfProximityWeight()))
|
||||||
doubleFromRequest(request, "bm25.b", sensibleDefaults.bm25Params.b())
|
.setBm25B(doubleFromRequest(request, "bm25b", sensibleDefaults.getBm25B()))
|
||||||
))
|
.setBm25K(doubleFromRequest(request, "bm25k", sensibleDefaults.getBm25K()))
|
||||||
.temporalBias(ResultRankingParameters.TemporalBias.valueOf(stringFromRequest(request, "temporalBias", sensibleDefaults.temporalBias.toString())))
|
.setTemporalBias(RpcTemporalBias.newBuilder().setBias(bias).build())
|
||||||
.temporalBiasWeight(doubleFromRequest(request, "temporalBiasWeight", sensibleDefaults.temporalBiasWeight))
|
.setTemporalBiasWeight(doubleFromRequest(request, "temporalBiasWeight", sensibleDefaults.getTemporalBiasWeight()))
|
||||||
.shortSentenceThreshold(intFromRequest(request, "shortSentenceThreshold", sensibleDefaults.shortSentenceThreshold))
|
.setShortSentenceThreshold(intFromRequest(request, "shortSentenceThreshold", sensibleDefaults.getShortSentenceThreshold()))
|
||||||
.shortSentencePenalty(doubleFromRequest(request, "shortSentencePenalty", sensibleDefaults.shortSentencePenalty))
|
.setShortSentencePenalty(doubleFromRequest(request, "shortSentencePenalty", sensibleDefaults.getShortSentencePenalty()))
|
||||||
.bm25Weight(doubleFromRequest(request, "bm25Weight", sensibleDefaults.bm25Weight))
|
.setBm25Weight(doubleFromRequest(request, "bm25Weight", sensibleDefaults.getBm25Weight()))
|
||||||
.exportDebugData(true)
|
.setDisablePenalties(boolFromRequest(request, "disablePenalties", sensibleDefaults.getDisablePenalties()))
|
||||||
|
.setExportDebugData(true)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,6 +166,13 @@ public class QueryBasicInterface {
|
|||||||
return Strings.isNullOrEmpty(request.queryParams(param)) ? defaultValue : Double.parseDouble(request.queryParams(param));
|
return Strings.isNullOrEmpty(request.queryParams(param)) ? defaultValue : Double.parseDouble(request.queryParams(param));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean boolFromRequest(Request request, String param, boolean defaultValue) {
|
||||||
|
if (param == null)
|
||||||
|
return defaultValue;
|
||||||
|
|
||||||
|
return Strings.isNullOrEmpty(request.queryParams(param)) ? defaultValue : Boolean.parseBoolean(request.queryParams(param));
|
||||||
|
}
|
||||||
|
|
||||||
int intFromRequest(Request request, String param, int defaultValue) {
|
int intFromRequest(Request request, String param, int defaultValue) {
|
||||||
return Strings.isNullOrEmpty(request.queryParams(param)) ? defaultValue : parseInt(request.queryParams(param));
|
return Strings.isNullOrEmpty(request.queryParams(param)) ? defaultValue : parseInt(request.queryParams(param));
|
||||||
}
|
}
|
||||||
|
@@ -31,20 +31,20 @@
|
|||||||
<div class="col-sm-2"><input type="text" class="form-control" id="shortDocumentPenalty" name="shortDocumentPenalty" value="{{shortDocumentPenalty}}"></div>
|
<div class="col-sm-2"><input type="text" class="form-control" id="shortDocumentPenalty" name="shortDocumentPenalty" value="{{shortDocumentPenalty}}"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row my-2">
|
<div class="row my-2">
|
||||||
<div class="col-sm-2"><label for="tcfFirstPosition">TCF First Position Weight</label></div>
|
<div class="col-sm-2"><label for="tcfFirstPositionWeight">TCF First Position Weight</label></div>
|
||||||
<div class="col-sm-2"><input type="text" class="form-control" id="tcfFirstPosition" name="tcfFirstPosition" value="{{tcfFirstPosition}}"></div>
|
<div class="col-sm-2"><input type="text" class="form-control" id="tcfFirstPositionWeight" name="tcfFirstPositionWeight" value="{{tcfFirstPositionWeight}}"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row my-2">
|
<div class="row my-2">
|
||||||
<div class="col-sm-2"><label for="tcfVerbatim">TCF Verbatim</label></div>
|
<div class="col-sm-2"><label for="tcfVerbatimWeight">TCF Verbatim</label></div>
|
||||||
<div class="col-sm-2"><input type="text" class="form-control" id="tcfVerbatim" name="tcfVerbatim" value="{{tcfVerbatim}}"></div>
|
<div class="col-sm-2"><input type="text" class="form-control" id="tcfVerbatimWeight" name="tcfVerbatimWeight" value="{{tcfVerbatimWeight}}"></div>
|
||||||
<div class="col-sm-2"><label for="tcfProximity">TCF Proximity</label></div>
|
<div class="col-sm-2"><label for="tcfProximityWeight">TCF Proximity</label></div>
|
||||||
<div class="col-sm-2"><input type="text" class="form-control" id="tcfProximity" name="tcfProximity" value="{{tcfProximity}}"></div>
|
<div class="col-sm-2"><input type="text" class="form-control" id="tcfProximityWeight" name="tcfProximityWeight" value="{{tcfProximityWeight}}"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row my-2">
|
<div class="row my-2">
|
||||||
<div class="col-sm-2"><label for="bm25.k1">BM25 K1</label></div>
|
<div class="col-sm-2"><label for="bm25k">BM25 K1</label></div>
|
||||||
<div class="col-sm-2"><input type="text" class="form-control" id="bm25.k1" name="bm25.k1" value="{{bm25Params.k}}"></div>
|
<div class="col-sm-2"><input type="text" class="form-control" id="bm25k" name="bm25k" value="{{bm25K}}"></div>
|
||||||
<div class="col-sm-2"><label for="bm25.b">BM25 B</label></div>
|
<div class="col-sm-2"><label for="bm25b">BM25 B</label></div>
|
||||||
<div class="col-sm-2"><input type="text" class="form-control" id="bm25.b" name="bm25.b" value="{{bm25Params.b}}"></div>
|
<div class="col-sm-2"><input type="text" class="form-control" id="bm25b" name="bm25b" value="{{bm25B}}"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row my-2">
|
<div class="row my-2">
|
||||||
<div class="col-sm-2"><label for="temporalBias">Temporal Bias</label></div>
|
<div class="col-sm-2"><label for="temporalBias">Temporal Bias</label></div>
|
||||||
@@ -67,6 +67,14 @@
|
|||||||
<div class="row my-2">
|
<div class="row my-2">
|
||||||
<div class="col-sm-2"><label for="bm25FullWeight">BM25 Weight</label></div>
|
<div class="col-sm-2"><label for="bm25FullWeight">BM25 Weight</label></div>
|
||||||
<div class="col-sm-2"><input type="text" class="form-control" id="bm25Weight" name="bm25Weight" value="{{bm25Weight}}"></div>
|
<div class="col-sm-2"><input type="text" class="form-control" id="bm25Weight" name="bm25Weight" value="{{bm25Weight}}"></div>
|
||||||
|
|
||||||
|
<div class="col-sm-2"><label for="disablePenalties">Disable Penalties</label></div>
|
||||||
|
<div class="col-sm-2">
|
||||||
|
<select class="form-select" id="disablePenalties" name="disablePenalties">
|
||||||
|
<option value="FALSE" {{#unless disablePenalties}}selected{{/unless}}>FALSE</option>
|
||||||
|
<option value="TRUE" {{#if disablePenalties}}selected{{/if}}>TRUE</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{/with}}
|
{{/with}}
|
||||||
|
@@ -5,7 +5,8 @@ import com.google.inject.Inject;
|
|||||||
import nu.marginalia.api.searchquery.QueryProtobufCodec;
|
import nu.marginalia.api.searchquery.QueryProtobufCodec;
|
||||||
import nu.marginalia.api.searchquery.RpcQsQuery;
|
import nu.marginalia.api.searchquery.RpcQsQuery;
|
||||||
import nu.marginalia.api.searchquery.RpcQueryLimits;
|
import nu.marginalia.api.searchquery.RpcQueryLimits;
|
||||||
import nu.marginalia.api.searchquery.model.results.ResultRankingParameters;
|
import nu.marginalia.api.searchquery.RpcResultRankingParameters;
|
||||||
|
import nu.marginalia.api.searchquery.model.results.PrototypeRankingParameters;
|
||||||
import nu.marginalia.converting.processor.DomainProcessor;
|
import nu.marginalia.converting.processor.DomainProcessor;
|
||||||
import nu.marginalia.converting.writer.ConverterBatchWriter;
|
import nu.marginalia.converting.writer.ConverterBatchWriter;
|
||||||
import nu.marginalia.crawl.fetcher.ContentTags;
|
import nu.marginalia.crawl.fetcher.ContentTags;
|
||||||
@@ -211,8 +212,7 @@ public class IntegrationTest {
|
|||||||
|
|
||||||
var params = QueryProtobufCodec.convertRequest(request);
|
var params = QueryProtobufCodec.convertRequest(request);
|
||||||
|
|
||||||
var p = ResultRankingParameters.sensibleDefaults();
|
var p = RpcResultRankingParameters.newBuilder(PrototypeRankingParameters.sensibleDefaults()).setExportDebugData(true).build();
|
||||||
p.exportDebugData = true;
|
|
||||||
var query = queryFactory.createQuery(params, p);
|
var query = queryFactory.createQuery(params, p);
|
||||||
|
|
||||||
|
|
||||||
|
@@ -28,7 +28,7 @@ import java.util.concurrent.TimeUnit;
|
|||||||
public class ScreenshotCaptureToolMain {
|
public class ScreenshotCaptureToolMain {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(ScreenshotCaptureToolMain.class);
|
private static final Logger logger = LoggerFactory.getLogger(ScreenshotCaptureToolMain.class);
|
||||||
|
private static final String BROWSERLESS_TOKEN = System.getenv("live-capture.browserless-token");
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
DatabaseModule databaseModule = new DatabaseModule(false);
|
DatabaseModule databaseModule = new DatabaseModule(false);
|
||||||
var ds = databaseModule.provideConnection();
|
var ds = databaseModule.provideConnection();
|
||||||
@@ -107,7 +107,7 @@ public class ScreenshotCaptureToolMain {
|
|||||||
);
|
);
|
||||||
|
|
||||||
var request = HttpRequest.newBuilder()
|
var request = HttpRequest.newBuilder()
|
||||||
.uri(new URI("http://browserless:3000/screenshot"))
|
.uri(new URI("http://browserless:3000/screenshot?token=" + BROWSERLESS_TOKEN))
|
||||||
.method("POST", HttpRequest.BodyPublishers.ofString(
|
.method("POST", HttpRequest.BodyPublishers.ofString(
|
||||||
gson.toJson(requestData)
|
gson.toJson(requestData)
|
||||||
))
|
))
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
## This is a token file for automatic deployment
|
## This is a token file for automatic deployment
|
||||||
|
|
||||||
|
2025-01-08: Deploy executor.
|
||||||
2025-01-07: Deploy executor.
|
2025-01-07: Deploy executor.
|
Reference in New Issue
Block a user