mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
feat(places): descriptions from the place's own website
Overture has no descriptions, and the encyclopaedias only describe the places that have an article, which is a fraction of a percent. What Overture does have is the address of the page the description is on, for 43 percent of places, and those pages publish a summary in JSON-LD or og:description precisely so machines can read it. The API fetches each page once and keeps the summary; every instance reads it from there. TREK never opens a business's website itself, which would mean one request per instance per place against small servers. In the ladder it sits below the encyclopaedias and above Google: an article about this exact place beats the operator's own copy, but for a restaurant or a shop the operator's summary is the only one that exists. Credited by host and linked back, with no licence claimed.
This commit is contained in:
@@ -35,6 +35,13 @@ vi.mock('../../../src/utils/ssrfGuard', () => ({
|
||||
|
||||
vi.mock('../../../src/config', () => ({ JWT_SECRET: 'test-secret', ENCRYPTION_KEY: '0'.repeat(64) }));
|
||||
|
||||
const { mockTrekPlacesById } = vi.hoisted(() => ({
|
||||
mockTrekPlacesById: vi.fn(async (_gers: string): Promise<unknown> => null),
|
||||
}));
|
||||
vi.mock('../../../src/nest/maps/trek-places.client', () => ({
|
||||
trekPlacesById: mockTrekPlacesById,
|
||||
}));
|
||||
|
||||
import { db } from '../../../src/db/database';
|
||||
import { DatabaseService } from '../../../src/nest/database/database.service';
|
||||
import { PlaceEnrichmentService } from '../../../src/nest/place-enrichment/place-enrichment.service';
|
||||
@@ -81,6 +88,8 @@ beforeEach(() => {
|
||||
mockDbGet.mockReset();
|
||||
mockDbGet.mockReturnValue(undefined);
|
||||
mockDbRun.mockReset();
|
||||
mockTrekPlacesById.mockReset();
|
||||
mockTrekPlacesById.mockResolvedValue(null);
|
||||
});
|
||||
|
||||
describe('identity resolution', () => {
|
||||
@@ -339,3 +348,70 @@ describe('filling Google gaps from the free sources', () => {
|
||||
});
|
||||
})
|
||||
|
||||
|
||||
describe("the description on the place's own website", () => {
|
||||
// The ordinary case, and the one nothing covered before: a restaurant has no
|
||||
// encyclopaedia article and never will, but it does publish a summary of
|
||||
// itself in JSON-LD for machines to read. 43 percent of Overture places carry
|
||||
// a website; a fraction of a percent carry a wiki tag.
|
||||
const GERS_REQ = {
|
||||
lat: 54.0879,
|
||||
lng: 12.1408,
|
||||
name: "L'Osteria",
|
||||
placeId: 'gers:abc-123',
|
||||
lang: 'de',
|
||||
};
|
||||
|
||||
it('ENRICH-090: quotes the site and credits it by URL', async () => {
|
||||
mockTrekPlacesById.mockResolvedValue({
|
||||
description: { text: 'Pizza in Rostock, seit 2015.', sourceUrl: 'https://losteria.net/rostock' },
|
||||
});
|
||||
|
||||
const out = await make(mapsStub()).enrich(1, GERS_REQ);
|
||||
|
||||
expect(mockTrekPlacesById).toHaveBeenCalledWith('abc-123');
|
||||
expect(out.description).toEqual({
|
||||
text: 'Pizza in Rostock, seit 2015.',
|
||||
source: 'website',
|
||||
sourceUrl: 'https://losteria.net/rostock',
|
||||
// No licence claim: the operator published a summary for machines, they
|
||||
// did not grant terms. Naming and linking the source is what is owed.
|
||||
license: null,
|
||||
});
|
||||
});
|
||||
|
||||
it('ENRICH-091: leaves the encyclopaedias ahead of it', async () => {
|
||||
// An article about this exact place, written by someone with no stake in
|
||||
// it, beats the operator's own copy. The website only fills the gap the
|
||||
// encyclopaedias leave, which is almost every business.
|
||||
mockTrekPlacesById.mockResolvedValue({ description: { text: 'Unser Restaurant.', sourceUrl: 'https://x.de' } });
|
||||
const maps = mapsStub({
|
||||
details: vi.fn(async () => ({ place: { source: 'openstreetmap', wikipedia: 'de:Irgendwas' } })),
|
||||
fetchWikiExtract: vi.fn(async () => EXTRACT),
|
||||
});
|
||||
|
||||
const out = await make(maps).enrich(1, GERS_REQ);
|
||||
|
||||
expect(out.description?.source).toBe('wikipedia');
|
||||
});
|
||||
|
||||
it('ENRICH-092: asks only for places that came from the API', async () => {
|
||||
await make(mapsStub()).enrich(1, { ...GERS_REQ, placeId: 'ChIJsomethinggoogle' });
|
||||
expect(mockTrekPlacesById).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('ENRICH-093: a failing API costs the place nothing else', async () => {
|
||||
mockTrekPlacesById.mockRejectedValue(new Error('places api down'));
|
||||
const maps = mapsStub({
|
||||
details: vi.fn(async () => ({ place: { source: 'openstreetmap', name: "L'Osteria" } })),
|
||||
});
|
||||
|
||||
await expect(make(maps).enrich(1, GERS_REQ)).resolves.toMatchObject({ description: null });
|
||||
});
|
||||
|
||||
it('ENRICH-094: ignores an empty or whitespace-only summary', async () => {
|
||||
mockTrekPlacesById.mockResolvedValue({ description: { text: ' ', sourceUrl: 'https://x.de' } });
|
||||
const out = await make(mapsStub()).enrich(1, GERS_REQ);
|
||||
expect(out.description).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user