From ec83d1fc1ab033825a42b6f026e6afb03ee47cb6 Mon Sep 17 00:00:00 2001 From: Lorenzo Trujillo Date: Wed, 28 Aug 2024 14:35:43 -0600 Subject: [PATCH 1/5] Creacion de entidades para representar un dataframe y una serie --- backend/src/route/utils/math.ts | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 backend/src/route/utils/math.ts diff --git a/backend/src/route/utils/math.ts b/backend/src/route/utils/math.ts new file mode 100644 index 00000000..f6b24547 --- /dev/null +++ b/backend/src/route/utils/math.ts @@ -0,0 +1,7 @@ +export interface DataFrame { + [key: number]: number[]; +} + +export interface Series { + [index: number]: number; +} -- GitLab From 9d04cd7c53015e60a89ee7bcb5903f1e3ec2ba7e Mon Sep 17 00:00:00 2001 From: Lorenzo Trujillo Date: Wed, 28 Aug 2024 14:36:41 -0600 Subject: [PATCH 2/5] =?UTF-8?q?Se=20cre=C3=B3=20la=20representaci=C3=B3n?= =?UTF-8?q?=20de=20un=20place=20necesario=20para=20hacer=20una=20recomenda?= =?UTF-8?q?cion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/route/dto/recommend-route.dto.ts | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 backend/src/route/dto/recommend-route.dto.ts diff --git a/backend/src/route/dto/recommend-route.dto.ts b/backend/src/route/dto/recommend-route.dto.ts new file mode 100644 index 00000000..66e99e40 --- /dev/null +++ b/backend/src/route/dto/recommend-route.dto.ts @@ -0,0 +1,7 @@ +export interface RecommendPlace { + idPlace: number; + openAt: number; + closeAt: number; + categories: number[]; + grade: number; +} -- GitLab From 6f60e75131ac6872506e66d6f2ad4aa477caee06 Mon Sep 17 00:00:00 2001 From: Lorenzo Trujillo Date: Wed, 28 Aug 2024 14:37:20 -0600 Subject: [PATCH 3/5] =?UTF-8?q?Se=20cre=C3=B3=20un=20algoritmo=20para=20re?= =?UTF-8?q?comendaci=C3=B3n=20de=20lugares?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/route/utils/recommendations.ts | 70 ++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 backend/src/route/utils/recommendations.ts diff --git a/backend/src/route/utils/recommendations.ts b/backend/src/route/utils/recommendations.ts new file mode 100644 index 00000000..5835dcec --- /dev/null +++ b/backend/src/route/utils/recommendations.ts @@ -0,0 +1,70 @@ +import { RecommendPlace } from '../dto/recommend-route.dto'; +import { DataFrame, Series } from './math'; +import { customSort } from './sort'; + +export class RecommendationsSystem { + getCategories(places: RecommendPlace[]): number[] { + const categories = new Set(); + for (const place of places) { + for (const category of place.categories) { + categories.add(category); + } + } + return Array.from(categories); + } + + oneHotEncode(categories: number[], placesToEncode: RecommendPlace[]): DataFrame { + const data: DataFrame = {}; + for (const category of categories) { + data[category] = []; + for (const place of placesToEncode) { + data[category].push(place.categories.includes(category) ? 1 : 0); + } + } + return data; + } + + rankVisited(visited: RecommendPlace[]): Series { + const visitedCategories = this.getCategories(visited); + const visitedEncoded = this.oneHotEncode(visitedCategories, visited); + const grades = visited.map((place) => place.grade); + + const dataframe: DataFrame = visitedEncoded; + + const result: Series = {}; + for (const category in dataframe) { + result[category] = dataframe[category].reduce((sum, value, index) => sum + value * grades[index], 0); + } + + const sumResult = Object.values(result).reduce((a, b) => a + b, 0); + for (const category in result) { + result[category] /= sumResult; + } + + return result; + } + + rankCandidates(candidates: RecommendPlace[], visited: Series): [number, number][] { + const visitedCategories = Object.keys(visited).map(Number); + const candidatesEncoded = this.oneHotEncode(visitedCategories, candidates); + + const dataframe: DataFrame = candidatesEncoded; + + const result: number[] = candidates.map((_, i) => { + return visitedCategories.reduce((sum, category) => { + return sum + dataframe[category][i] * visited[category]; + }, 0); + }); + + let ranked: [number, number][] = candidates.map((place, i) => [place.idPlace, result[i]]); + ranked.sort(customSort); + + return ranked; + } + + recommend(visited: RecommendPlace[], candidates: RecommendPlace[]) { + const visitedRanking = this.rankVisited(visited); + const candidatesRanked = this.rankCandidates(candidates, visitedRanking); + return candidatesRanked; + } +} -- GitLab From bbba415f8c589abcac01692fcf580bd3ffeea0d5 Mon Sep 17 00:00:00 2001 From: Lorenzo Trujillo Date: Wed, 28 Aug 2024 14:38:02 -0600 Subject: [PATCH 4/5] algoritmo de ordenamiento personalizado para pares --- backend/src/route/utils/sort.ts | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 backend/src/route/utils/sort.ts diff --git a/backend/src/route/utils/sort.ts b/backend/src/route/utils/sort.ts new file mode 100644 index 00000000..4da8984a --- /dev/null +++ b/backend/src/route/utils/sort.ts @@ -0,0 +1,3 @@ +export function customSort(a: [number, number], b: [number, number]): number { + return b[1] - a[1]; +} -- GitLab From 604806071dd5e09a6b864f1efadec0da911a8e93 Mon Sep 17 00:00:00 2001 From: Lorenzo Trujillo Date: Wed, 28 Aug 2024 14:38:33 -0600 Subject: [PATCH 5/5] =?UTF-8?q?se=20agreg=C3=B3=20un=20endpoint=20para=20h?= =?UTF-8?q?acer=20recomendaciones?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/package-lock.json | 36 ++++++++++++++++++++++++++- backend/src/route/route.controller.ts | 9 +++++-- backend/src/route/route.service.ts | 8 ++++++ 3 files changed, 50 insertions(+), 3 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index 30e373bf..07cd3119 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -18,6 +18,7 @@ "@nestjs/serve-static": "^4.0.2", "@nestjs/swagger": "^7.3.0", "@nestjs/typeorm": "^10.0.2", + "axios": "^1.7.5", "bcrypt": "^5.1.1", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", @@ -3373,6 +3374,16 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, + "node_modules/axios": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.5.tgz", + "integrity": "sha512-fZu86yCo+svH3uqJ/yTdQ0QHpQu5oL+/QE+QPSv6BZSkDAoky9vytxp7u5qk83OJFS3kEBcesWni9WTZAv3tSw==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/babel-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", @@ -5321,6 +5332,25 @@ "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/foreground-child": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", @@ -5390,7 +5420,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -8288,6 +8317,11 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", diff --git a/backend/src/route/route.controller.ts b/backend/src/route/route.controller.ts index fc7d8394..0d8c512f 100644 --- a/backend/src/route/route.controller.ts +++ b/backend/src/route/route.controller.ts @@ -4,8 +4,8 @@ import { CreateRouteDto } from './dto/create-route.dto'; import { UpdateRouteDto } from './dto/update-route.dto'; import { ApiConsumes, ApiTags } from '@nestjs/swagger'; -@Controller('Route') -@ApiTags('testing') +@Controller('route') +@ApiTags('routes') export class RouteController { constructor(private readonly routeService: RouteService) {} @@ -35,4 +35,9 @@ export class RouteController { remove(@Param('id') id: string) { return this.routeService.remove(+id); } + + @Get('recommend') + recommendRoute() { + return this.routeService.recommend(); + } } diff --git a/backend/src/route/route.service.ts b/backend/src/route/route.service.ts index 51c2f121..c5da153b 100644 --- a/backend/src/route/route.service.ts +++ b/backend/src/route/route.service.ts @@ -8,6 +8,7 @@ import { User } from 'src/user/entities/user.entity'; import { Town } from 'src/town/entities/town.entity'; import { TravelPlaceService } from 'src/travel-place/travel-place.service'; import { TravelPlace } from 'src/travel-place/entities/travel-place.entity'; +import { RecommendationsSystem } from './utils/recommendations'; @Injectable() export class RouteService { @@ -52,4 +53,11 @@ export class RouteService { remove(id: number) { return `This action removes a #${id} route`; } + + recommend() { + // Obtener los visitados y los candidatos + const system = new RecommendationsSystem(); + // system.recommend(visited, candidates); + } + } -- GitLab