mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-23 15:36:33 +00:00
feat(places): remember the Amap id a place was found by
The provider produced amap_poi_id, but nothing stored it, so a saved Amap place could never be reopened against Amap. The column now travels with google_place_id and osm_id through the shared contract, the place insert and update, duplicate matching, the assignment projections, the trip copy and the three place MCP tools.
This commit is contained in:
@@ -60,7 +60,7 @@ export class AssignmentsService {
|
||||
COALESCE(da.assignment_time, p.place_time) as place_time,
|
||||
COALESCE(da.assignment_end_time, p.end_time) as end_time,
|
||||
p.duration_minutes, p.notes as place_notes,
|
||||
p.image_url, p.transport_mode, p.google_place_id, p.google_ftid, p.osm_id, p.website, p.phone, p.stop_type, p.fill_percent,
|
||||
p.image_url, p.transport_mode, p.google_place_id, p.google_ftid, p.osm_id, p.amap_poi_id, p.website, p.phone, p.stop_type, p.fill_percent,
|
||||
c.name as category_name, c.color as category_color, c.icon as category_icon
|
||||
FROM day_assignments da
|
||||
JOIN places p ON da.place_id = p.id
|
||||
@@ -96,7 +96,7 @@ export class AssignmentsService {
|
||||
COALESCE(da.assignment_time, p.place_time) as place_time,
|
||||
COALESCE(da.assignment_end_time, p.end_time) as end_time,
|
||||
p.duration_minutes, p.notes as place_notes,
|
||||
p.image_url, p.transport_mode, p.google_place_id, p.google_ftid, p.osm_id, p.website, p.phone, p.stop_type, p.fill_percent,
|
||||
p.image_url, p.transport_mode, p.google_place_id, p.google_ftid, p.osm_id, p.amap_poi_id, p.website, p.phone, p.stop_type, p.fill_percent,
|
||||
c.name as category_name, c.color as category_color, c.icon as category_icon
|
||||
FROM day_assignments da
|
||||
JOIN places p ON da.place_id = p.id
|
||||
|
||||
@@ -42,6 +42,7 @@ export function formatAssignmentWithPlace(a: AssignmentRow, tags: Partial<Tag>[]
|
||||
google_place_id: a.google_place_id,
|
||||
google_ftid: a.google_ftid,
|
||||
osm_id: a.osm_id,
|
||||
amap_poi_id: a.amap_poi_id,
|
||||
website: a.website,
|
||||
phone: a.phone,
|
||||
// The rail marks a fuel stop as one; without it here every stop would need
|
||||
|
||||
@@ -91,7 +91,7 @@ export class DaysService {
|
||||
COALESCE(da.assignment_time, p.place_time) as place_time,
|
||||
COALESCE(da.assignment_end_time, p.end_time) as end_time,
|
||||
p.duration_minutes, p.notes as place_notes,
|
||||
p.image_url, p.transport_mode, p.google_place_id, p.google_ftid, p.osm_id, p.website, p.phone, p.stop_type, p.fill_percent,
|
||||
p.image_url, p.transport_mode, p.google_place_id, p.google_ftid, p.osm_id, p.amap_poi_id, p.website, p.phone, p.stop_type, p.fill_percent,
|
||||
c.name as category_name, c.color as category_color, c.icon as category_icon
|
||||
FROM day_assignments da
|
||||
JOIN places p ON da.place_id = p.id
|
||||
@@ -134,6 +134,7 @@ export class DaysService {
|
||||
google_place_id: a.google_place_id,
|
||||
google_ftid: a.google_ftid,
|
||||
osm_id: a.osm_id,
|
||||
amap_poi_id: a.amap_poi_id,
|
||||
website: a.website,
|
||||
phone: a.phone,
|
||||
// Hand-built here rather than through `formatAssignmentWithPlace`, so every
|
||||
@@ -172,7 +173,7 @@ export class DaysService {
|
||||
COALESCE(da.assignment_time, p.place_time) as place_time,
|
||||
COALESCE(da.assignment_end_time, p.end_time) as end_time,
|
||||
p.duration_minutes, p.notes as place_notes,
|
||||
p.image_url, p.transport_mode, p.google_place_id, p.google_ftid, p.osm_id, p.website, p.phone, p.stop_type, p.fill_percent,
|
||||
p.image_url, p.transport_mode, p.google_place_id, p.google_ftid, p.osm_id, p.amap_poi_id, p.website, p.phone, p.stop_type, p.fill_percent,
|
||||
c.name as category_name, c.color as category_color, c.icon as category_icon
|
||||
FROM day_assignments da
|
||||
JOIN places p ON da.place_id = p.id
|
||||
|
||||
@@ -126,7 +126,7 @@ export async function reclaimPhotoCache(cache: PlacePhotoCacheService, googlePla
|
||||
export interface DedupSet {
|
||||
names: Set<string>;
|
||||
coords: Array<{ lat: number; lng: number }>;
|
||||
/** Provider ids (google_place_id, google_ftid, osm_id) of the places already in the trip. */
|
||||
/** Provider ids (google_place_id, google_ftid, osm_id, amap_poi_id) of the places already in the trip. */
|
||||
externalIds: Set<string>;
|
||||
}
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ export class PlacesMcp {
|
||||
|
||||
@Tool({
|
||||
name: 'create_place',
|
||||
description: 'Add a new place/POI to a trip. Set google_place_id, google_ftid, or osm_id (from search_place) so the app can show opening hours, ratings, and direct Google Maps links. Set price + currency to record the cost so it shows on the item.',
|
||||
description: 'Add a new place/POI to a trip. Set google_place_id, google_ftid, osm_id or amap_poi_id (from search_place) so the app can show opening hours, ratings, and direct Google Maps links. Set price + currency to record the cost so it shows on the item.',
|
||||
inputSchema: {
|
||||
tripId: z.number().int().positive(),
|
||||
name: z.string().min(1).max(200),
|
||||
@@ -73,6 +73,7 @@ export class PlacesMcp {
|
||||
google_place_id: z.string().optional().describe('Google Place ID from search_place — enables opening hours display'),
|
||||
google_ftid: z.string().optional().describe('Google Maps feature ID from search_place — enables direct Google Maps links'),
|
||||
osm_id: z.string().optional().describe('OpenStreetMap ID from search_place (e.g. "way:12345") — enables opening hours if no Google ID'),
|
||||
amap_poi_id: z.string().optional().describe('Amap (高德地图) POI ID from search_place, amap:-prefixed, enables opening hours on an install using Amap'),
|
||||
notes: z.string().max(2000).optional(),
|
||||
website: placeWebsiteSchema.optional(),
|
||||
phone: z.string().max(50).optional(),
|
||||
@@ -85,9 +86,9 @@ export class PlacesMcp {
|
||||
access: { group: 'places', mode: 'write' },
|
||||
})
|
||||
async createPlace(
|
||||
{ tripId, name, description, lat, lng, address, category_id, google_place_id, google_ftid, osm_id, notes, website, phone, image_url, price, currency, stop_type }: {
|
||||
{ tripId, name, description, lat, lng, address, category_id, google_place_id, google_ftid, osm_id, amap_poi_id, notes, website, phone, image_url, price, currency, stop_type }: {
|
||||
tripId: number; name: string; description?: string; lat?: number; lng?: number; address?: string;
|
||||
category_id?: number; google_place_id?: string; google_ftid?: string; osm_id?: string;
|
||||
category_id?: number; google_place_id?: string; google_ftid?: string; osm_id?: string; amap_poi_id?: string;
|
||||
notes?: string; website?: string; phone?: string; image_url?: string; price?: number; currency?: string;
|
||||
stop_type?: RoadtripStopType;
|
||||
},
|
||||
@@ -96,7 +97,7 @@ export class PlacesMcp {
|
||||
if (this.auth.isDemoUser(ctx.userId)) return demoDenied();
|
||||
if (!this.db.canAccessTrip(tripId, ctx.userId)) return noAccess();
|
||||
if (!this.guards.hasTripPermission('place_edit', tripId, ctx.userId)) return permissionDenied();
|
||||
const place = this.places.create(String(tripId), { name, description, lat, lng, address, category_id, google_place_id, google_ftid, osm_id, notes, website, phone, image_url, price, currency, stop_type });
|
||||
const place = this.places.create(String(tripId), { name, description, lat, lng, address, category_id, google_place_id, google_ftid, osm_id, amap_poi_id, notes, website, phone, image_url, price, currency, stop_type });
|
||||
this.guards.safeBroadcast(tripId, 'place:created', { place });
|
||||
return ok({ place });
|
||||
}
|
||||
@@ -116,6 +117,7 @@ export class PlacesMcp {
|
||||
google_place_id: z.string().optional().describe('Google Place ID from search_place — enables opening hours display'),
|
||||
google_ftid: z.string().optional().describe('Google Maps feature ID from search_place — enables direct Google Maps links'),
|
||||
osm_id: z.string().optional().describe('OpenStreetMap ID from search_place (e.g. "way:12345")'),
|
||||
amap_poi_id: z.string().optional().describe('Amap (高德地图) POI ID from search_place, amap:-prefixed'),
|
||||
place_notes: z.string().max(2000).optional().describe('Notes for the place'),
|
||||
website: placeWebsiteSchema.optional(),
|
||||
phone: z.string().max(50).optional(),
|
||||
@@ -129,9 +131,9 @@ export class PlacesMcp {
|
||||
access: { group: 'places', mode: 'write' },
|
||||
})
|
||||
async createAndAssignPlace(
|
||||
{ tripId, dayId, name, description, lat, lng, address, category_id, google_place_id, google_ftid, osm_id, place_notes, website, phone, image_url, assignment_notes, price, currency, stop_type }: {
|
||||
{ tripId, dayId, name, description, lat, lng, address, category_id, google_place_id, google_ftid, osm_id, amap_poi_id, place_notes, website, phone, image_url, assignment_notes, price, currency, stop_type }: {
|
||||
tripId: number; dayId: number; name: string; description?: string; lat?: number; lng?: number; address?: string;
|
||||
category_id?: number; google_place_id?: string; google_ftid?: string; osm_id?: string;
|
||||
category_id?: number; google_place_id?: string; google_ftid?: string; osm_id?: string; amap_poi_id?: string;
|
||||
place_notes?: string; website?: string; phone?: string; image_url?: string; assignment_notes?: string;
|
||||
price?: number; currency?: string; stop_type?: RoadtripStopType;
|
||||
},
|
||||
@@ -143,7 +145,7 @@ export class PlacesMcp {
|
||||
if (!this.assignments.dayExists(dayId, tripId)) return { content: [{ type: 'text' as const, text: 'Day not found.' }], isError: true };
|
||||
try {
|
||||
const result = this.db.transaction(() => {
|
||||
const place = this.places.create(String(tripId), { name, description, lat, lng, address, category_id, google_place_id, google_ftid, osm_id, notes: place_notes, website, phone, image_url, price, currency, stop_type });
|
||||
const place = this.places.create(String(tripId), { name, description, lat, lng, address, category_id, google_place_id, google_ftid, osm_id, amap_poi_id, notes: place_notes, website, phone, image_url, price, currency, stop_type });
|
||||
const assignment = this.assignments.createAssignment(dayId, place.id, assignment_notes ?? null);
|
||||
return { place, assignment };
|
||||
});
|
||||
@@ -181,6 +183,7 @@ export class PlacesMcp {
|
||||
osm_id: z.string().optional().describe('OpenStreetMap ID (e.g. "way:12345")'),
|
||||
google_place_id: z.string().optional().describe('Google Place ID (e.g. "ChIJd8BlQ2BZwokRAFUEcm_qrcA")'),
|
||||
google_ftid: z.string().optional().describe('Google Maps feature ID (e.g. "0x89c259b7abdd4769:0x103aaf1c8bf8a050")'),
|
||||
amap_poi_id: z.string().optional().describe('Amap (高德地图) POI ID, amap:-prefixed (e.g. "amap:B000A83M61")'),
|
||||
stop_type: roadtripStopTypeSchema.nullable().optional().describe('What kind of stop on a drive this is: fuel, charging, rest_area, campsite, restaurant, sights or hotel. Pass null to turn a service stop back into an ordinary place.'),
|
||||
fill_percent: z.number().int().min(1).max(100).nullable().optional().describe('How full THIS stop fills the tank, 1-100. A motorway rapid charger is worth about 80 %, the one at the hotel 100 %. Pass null to follow whatever the traveller set as their default fill.'),
|
||||
},
|
||||
@@ -188,13 +191,13 @@ export class PlacesMcp {
|
||||
access: { group: 'places', mode: 'write' },
|
||||
})
|
||||
async updatePlace(
|
||||
{ tripId, placeId, name, description, lat, lng, address, category_id, price, currency, place_time, end_time, duration_minutes, notes, website, phone, image_url, transport_mode, osm_id, google_place_id, google_ftid, stop_type, fill_percent }: {
|
||||
{ tripId, placeId, name, description, lat, lng, address, category_id, price, currency, place_time, end_time, duration_minutes, notes, website, phone, image_url, transport_mode, osm_id, google_place_id, google_ftid, amap_poi_id, stop_type, fill_percent }: {
|
||||
tripId: number; placeId: number; name?: string; description?: string; lat?: number; lng?: number;
|
||||
address?: string; category_id?: number; price?: number; currency?: string; place_time?: string | null;
|
||||
end_time?: string | null; duration_minutes?: number | null; notes?: string; website?: string; phone?: string;
|
||||
image_url?: string | null;
|
||||
transport_mode?: 'walking' | 'driving' | 'cycling' | 'transit' | 'flight'; osm_id?: string;
|
||||
google_place_id?: string; google_ftid?: string; stop_type?: RoadtripStopType | null;
|
||||
google_place_id?: string; google_ftid?: string; amap_poi_id?: string; stop_type?: RoadtripStopType | null;
|
||||
fill_percent?: number | null;
|
||||
},
|
||||
ctx: McpContext,
|
||||
@@ -202,7 +205,7 @@ export class PlacesMcp {
|
||||
if (this.auth.isDemoUser(ctx.userId)) return demoDenied();
|
||||
if (!this.db.canAccessTrip(tripId, ctx.userId)) return noAccess();
|
||||
if (!this.guards.hasTripPermission('place_edit', tripId, ctx.userId)) return permissionDenied();
|
||||
const place = await this.places.update(String(tripId), String(placeId), { name, description, lat, lng, address, category_id, price, currency, place_time, end_time, duration_minutes, notes, website, phone, image_url, transport_mode, osm_id, google_place_id, google_ftid, stop_type, fill_percent });
|
||||
const place = await this.places.update(String(tripId), String(placeId), { name, description, lat, lng, address, category_id, price, currency, place_time, end_time, duration_minutes, notes, website, phone, image_url, transport_mode, osm_id, google_place_id, google_ftid, amap_poi_id, stop_type, fill_percent });
|
||||
if (!place) return { content: [{ type: 'text' as const, text: 'Place not found.' }], isError: true };
|
||||
this.guards.safeBroadcast(tripId, 'place:updated', { place });
|
||||
return ok({ place });
|
||||
|
||||
@@ -67,7 +67,7 @@ export interface PlaceCreateInput {
|
||||
category_id?: number; price?: number; currency?: string;
|
||||
place_time?: string; end_time?: string;
|
||||
duration_minutes?: number; notes?: string; image_url?: string;
|
||||
google_place_id?: string; google_ftid?: string; osm_id?: string; website?: string; phone?: string;
|
||||
google_place_id?: string; google_ftid?: string; osm_id?: string; amap_poi_id?: string; website?: string; phone?: string;
|
||||
/** What kind of stop this is on a drive (fuel, charging, rest_area, campsite); null for an ordinary place. */
|
||||
stop_type?: string | null;
|
||||
/** How full THIS stop fills the tank, 1-100; null to follow the traveller's own setting. */
|
||||
@@ -81,7 +81,7 @@ export interface PlaceUpdateInput {
|
||||
category_id?: number; price?: number; currency?: string;
|
||||
place_time?: string; end_time?: string;
|
||||
duration_minutes?: number; notes?: string; image_url?: string;
|
||||
google_place_id?: string; google_ftid?: string; osm_id?: string; website?: string; phone?: string;
|
||||
google_place_id?: string; google_ftid?: string; osm_id?: string; amap_poi_id?: string; website?: string; phone?: string;
|
||||
/** What kind of stop this is on a drive (fuel, charging, rest_area, campsite); null for an ordinary place. */
|
||||
stop_type?: string | null;
|
||||
/** How full THIS stop fills the tank, 1-100; null to follow the traveller's own setting. */
|
||||
@@ -232,16 +232,16 @@ export class PlacesService {
|
||||
const {
|
||||
name, description, lat, lng, address, category_id, price, currency,
|
||||
place_time, end_time,
|
||||
duration_minutes, notes, image_url, google_place_id, google_ftid, osm_id, website, phone,
|
||||
duration_minutes, notes, image_url, google_place_id, google_ftid, osm_id, amap_poi_id, website, phone,
|
||||
transport_mode, route_geometry, route_color, stop_type, fill_percent, tags = [],
|
||||
} = body;
|
||||
|
||||
const result = this.dbs.run(`
|
||||
INSERT INTO places (trip_id, name, description, lat, lng, address, category_id, price, currency,
|
||||
place_time, end_time,
|
||||
duration_minutes, notes, image_url, google_place_id, google_ftid, osm_id, website, phone, transport_mode,
|
||||
duration_minutes, notes, image_url, google_place_id, google_ftid, osm_id, amap_poi_id, website, phone, transport_mode,
|
||||
route_geometry, route_color, stop_type, fill_percent)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`,
|
||||
// lat/lng/price/duration_minutes use an explicit undefined check, not `||`:
|
||||
// 0 is a legitimate value for all four (Null Island, a free entry, a
|
||||
@@ -255,7 +255,7 @@ export class PlacesService {
|
||||
tripId, name, description || null, lat ?? null, lng ?? null, address || null,
|
||||
category_id || null, price ?? null, currency || null,
|
||||
place_time || null, end_time || null, duration_minutes ?? 60, notes || null, image_url || null,
|
||||
google_place_id || null, google_ftid || null, osm_id || null, website || null, phone || null, transport_mode || 'walking',
|
||||
google_place_id || null, google_ftid || null, osm_id || null, amap_poi_id || null, website || null, phone || null, transport_mode || 'walking',
|
||||
route_geometry || null, route_color || null, stop_type || null,
|
||||
// `?? null` rather than `|| null`, the same reason lat/lng have it: the column is a
|
||||
// percentage and the falsy check would be a silent floor.
|
||||
@@ -323,7 +323,7 @@ export class PlacesService {
|
||||
const {
|
||||
name, description, lat, lng, address, category_id, price, currency,
|
||||
place_time, end_time,
|
||||
duration_minutes, notes, image_url, google_place_id, google_ftid, osm_id, website, phone,
|
||||
duration_minutes, notes, image_url, google_place_id, google_ftid, osm_id, amap_poi_id, website, phone,
|
||||
transport_mode, route_color, stop_type, fill_percent, tags,
|
||||
} = body;
|
||||
|
||||
@@ -345,6 +345,7 @@ export class PlacesService {
|
||||
google_place_id = ?,
|
||||
google_ftid = ?,
|
||||
osm_id = ?,
|
||||
amap_poi_id = ?,
|
||||
website = ?,
|
||||
phone = ?,
|
||||
transport_mode = COALESCE(?, transport_mode),
|
||||
@@ -372,6 +373,7 @@ export class PlacesService {
|
||||
google_place_id !== undefined ? google_place_id : existingPlace.google_place_id,
|
||||
google_ftid !== undefined ? google_ftid : existingPlace.google_ftid,
|
||||
osm_id !== undefined ? osm_id : existingPlace.osm_id,
|
||||
amap_poi_id !== undefined ? amap_poi_id : existingPlace.amap_poi_id,
|
||||
website !== undefined ? website : existingPlace.website,
|
||||
phone !== undefined ? phone : existingPlace.phone,
|
||||
transport_mode || null,
|
||||
@@ -518,9 +520,9 @@ export class PlacesService {
|
||||
private buildDedupSet(tripId: string): DedupSet {
|
||||
const rows = this.dbs.all<{
|
||||
name: string | null; lat: number | null; lng: number | null;
|
||||
google_place_id: string | null; google_ftid: string | null; osm_id: string | null;
|
||||
google_place_id: string | null; google_ftid: string | null; osm_id: string | null; amap_poi_id: string | null;
|
||||
}>(
|
||||
'SELECT name, lat, lng, google_place_id, google_ftid, osm_id FROM places WHERE trip_id = ?', tripId,
|
||||
'SELECT name, lat, lng, google_place_id, google_ftid, osm_id, amap_poi_id FROM places WHERE trip_id = ?', tripId,
|
||||
);
|
||||
const names = new Set<string>();
|
||||
const coords: Array<{ lat: number; lng: number }> = [];
|
||||
@@ -585,10 +587,10 @@ export class PlacesService {
|
||||
if (strategy.by === 'externalId') {
|
||||
hit = this.dbs.get<{ id: number; google_ftid: string | null }>(`
|
||||
SELECT id, google_ftid FROM places
|
||||
WHERE trip_id = ? AND (google_place_id = ? OR google_ftid = ? OR osm_id = ?)
|
||||
WHERE trip_id = ? AND (google_place_id = ? OR google_ftid = ? OR osm_id = ? OR amap_poi_id = ?)
|
||||
ORDER BY id ASC
|
||||
LIMIT 1
|
||||
`, tripId, strategy.id, strategy.id, strategy.id);
|
||||
`, tripId, strategy.id, strategy.id, strategy.id, strategy.id);
|
||||
} else if (strategy.by === 'name') {
|
||||
hit = this.dbs.get<{ id: number; google_ftid: string | null }>(`
|
||||
SELECT id, google_ftid FROM places
|
||||
|
||||
@@ -552,15 +552,15 @@ export class TripsService {
|
||||
INSERT INTO places (trip_id, name, description, lat, lng, address, category_id, price, currency,
|
||||
reservation_status, reservation_notes, reservation_datetime, place_time, end_time,
|
||||
duration_minutes, notes, image_url, google_place_id, google_ftid, website, phone, transport_mode, osm_id,
|
||||
route_geometry, route_color, stop_type)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
amap_poi_id, route_geometry, route_color, stop_type)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`);
|
||||
for (const p of oldPlaces) {
|
||||
const r = insertPlace.run(newTripId, p.name, p.description, p.lat, p.lng, p.address, p.category_id,
|
||||
p.price, p.currency, p.reservation_status, p.reservation_notes, p.reservation_datetime,
|
||||
p.place_time, p.end_time, p.duration_minutes, p.notes, p.image_url, p.google_place_id,
|
||||
p.google_ftid, p.website, p.phone, p.transport_mode, p.osm_id, p.route_geometry, p.route_color,
|
||||
p.stop_type);
|
||||
p.google_ftid, p.website, p.phone, p.transport_mode, p.osm_id, p.amap_poi_id, p.route_geometry,
|
||||
p.route_color, p.stop_type);
|
||||
placeMap.set(p.id, r.lastInsertRowid);
|
||||
}
|
||||
|
||||
|
||||
@@ -75,6 +75,7 @@ export interface Place {
|
||||
google_place_id?: string | null;
|
||||
google_ftid?: string | null;
|
||||
osm_id?: string | null;
|
||||
amap_poi_id?: string | null;
|
||||
route_geometry?: string | null;
|
||||
route_color?: string | null;
|
||||
website?: string | null;
|
||||
@@ -350,6 +351,7 @@ export interface AssignmentRow extends DayAssignment {
|
||||
google_place_id: string | null;
|
||||
google_ftid: string | null;
|
||||
osm_id: string | null;
|
||||
amap_poi_id: string | null;
|
||||
website: string | null;
|
||||
phone: string | null;
|
||||
stop_type: string | null;
|
||||
|
||||
@@ -35,6 +35,7 @@ export interface PlaceMatchCandidate {
|
||||
google_place_id?: string | null;
|
||||
google_ftid?: string | null;
|
||||
osm_id?: string | null;
|
||||
amap_poi_id?: string | null;
|
||||
}
|
||||
|
||||
/** One way to look for an existing place, to be tried in order. */
|
||||
@@ -51,7 +52,7 @@ export function normalizePlaceName(name: string | null | undefined): string | nu
|
||||
|
||||
/** The provider ids a candidate carries, trimmed, in provider order, blanks dropped. */
|
||||
export function externalIdsOf(candidate: PlaceMatchCandidate): string[] {
|
||||
return [candidate.google_place_id, candidate.google_ftid, candidate.osm_id]
|
||||
return [candidate.google_place_id, candidate.google_ftid, candidate.osm_id, candidate.amap_poi_id]
|
||||
.filter((id): id is string => typeof id === 'string' && id.trim() !== '')
|
||||
.map((id) => id.trim());
|
||||
}
|
||||
|
||||
@@ -135,6 +135,9 @@ export const placeSchema = z.object({
|
||||
google_place_id: z.string().nullable().optional(),
|
||||
google_ftid: z.string().nullable().optional(),
|
||||
osm_id: z.string().nullable().optional(),
|
||||
// The Amap (高德) POI id a place was found by, `amap:`-prefixed. null for every
|
||||
// place that came from another provider.
|
||||
amap_poi_id: z.string().nullable().optional(),
|
||||
route_geometry: z.string().nullable().optional(),
|
||||
// Manual track colour (#776). null = inherit the category colour like before.
|
||||
route_color: hexColorSchema.nullable().optional(),
|
||||
@@ -199,6 +202,7 @@ export const assignmentPlaceSchema = z.object({
|
||||
// Carried on the embedded place so the day-plan thumbnail can auto-fetch an
|
||||
// OSM photo the same way the sidebar/inspector do (#1136 follow-up).
|
||||
osm_id: z.string().nullable().optional(),
|
||||
amap_poi_id: z.string().nullable().optional(),
|
||||
website: z.string().nullable().optional(),
|
||||
phone: z.string().nullable().optional(),
|
||||
// Carried through so the road-trip rail can mark a fuel stop as one without a
|
||||
|
||||
Reference in New Issue
Block a user