Commit 43030677 authored by Lorenzo Trujillo Rojas's avatar Lorenzo Trujillo Rojas
Browse files

Merge branch 'main' into 'main'

Creacion de rutas

See merge request ltrpro/pueblosmagicosconia!72
parents d0065118 41f1bd03
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
import { ApiProperty } from '@nestjs/swagger';

export class CreateRouteReq {
  @ApiProperty({ name: 'start', type: Number })
  start: number;

  @ApiProperty({ name: 'end', type: Number })
  end: number;
}
+9 −1
Original line number Diff line number Diff line
@@ -2,6 +2,11 @@ import { Town } from 'src/town/entities/town.entity';
import { TravelPlace } from 'src/travel-place/entities/travel-place.entity';
import { User } from 'src/user/entities/user.entity';
import { PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn, Entity, OneToMany } from 'typeorm';
enum RouteStatus {
  PENDING = 'pending',
  ACCEPTED = 'accepted',
  REJECTED = 'rejected',
}

@Entity()
export class Route {
@@ -16,11 +21,14 @@ export class Route {
  @ManyToOne(() => Town, (town) => town.townId, { nullable: false })
  town: Town;

  @OneToMany(() => TravelPlace, (travelPlace) => travelPlace.route)
  @OneToMany(() => TravelPlace, (travelPlace) => travelPlace.route, { eager: true })
  travelPlace: TravelPlace[];

  @Column({ nullable: false })
  startDate: Date;
  @Column({ nullable: false })
  endDate: Date;

  @Column({ nullable: false, default: RouteStatus.PENDING })
  status: RouteStatus;
}
+27 −4
Original line number Diff line number Diff line
import { Controller, Get, Param, UseGuards, Req, Query } from '@nestjs/common';
import { Controller, Param, UseGuards, Req, Query, Body, Post, Get } from '@nestjs/common';
import { RouteService } from './route.service';
import { ApiBearerAuth, ApiParam, ApiQuery, ApiTags } from '@nestjs/swagger';
import { ApiBearerAuth, ApiBody, ApiParam, ApiQuery, ApiTags } from '@nestjs/swagger';
import { LANGUAGES } from 'src/shared/enum/languages.enum';
import { AuthUserGuard } from 'src/auth/user/authUser.guard';
import { CustomUserRequest } from 'src/auth/user/interface/customUserReq';
import { CreateRouteReq } from './dto/create-route-req';

@Controller('route')
@ApiTags('route')
export class RouteController {
  constructor(private readonly routeService: RouteService) {}

  @Post('recommend/:idTown')
  @ApiQuery({ name: 'lang', type: String })
  @ApiParam({ name: 'idTown', type: Number })
  @ApiBody({ type: CreateRouteReq })
  @ApiBearerAuth('jwt')
  @UseGuards(AuthUserGuard)
  async recommendRoute(
    @Req() req: CustomUserRequest,
    @Query('lang') lang: string,
    @Param('idTown') idTown: number,
    @Body() createRouteReq: CreateRouteReq,
  ) {
    const { email } = req.user;
    return await this.routeService.recommend(
      idTown,
      email,
      lang as LANGUAGES,
      createRouteReq.start,
      createRouteReq.end,
    );
  }

  @Get('recommend/:idTown')
  @ApiQuery({ name: 'lang', type: String })
  @ApiParam({ name: 'idTown', type: Number })
  @ApiBearerAuth('jwt')
  @UseGuards(AuthUserGuard)
  async recommendRoute(@Req() req: CustomUserRequest, @Query('lang') lang: string, @Param('idTown') idTown: number) {
  async recommendRouteGet(@Req() req: CustomUserRequest, @Query('lang') lang: string, @Param('idTown') idTown: number) {
    const { email } = req.user;
    return await this.routeService.recommend(idTown, email, lang as LANGUAGES);
    return await this.routeService.getRouteAndPlacesByUser(email, lang as LANGUAGES, idTown);
  }
}
+40 −23
Original line number Diff line number Diff line
@@ -12,6 +12,8 @@ import { VisitedService } from 'src/visited/visited.service';
import { GetPlaceDto } from 'src/place/dto/get-place.dto';
import { Visited } from 'src/visited/entities/visited.entity';
import { RecommendPlace } from './dto/recommend-route.dto';
import { CreateRouteDto } from './dto/create-route.dto';
import { CreateTravelPlaceDto } from 'src/travel-place/dto/create-travel-place.dto';

@Injectable()
export class RouteService {
@@ -24,22 +26,11 @@ export class RouteService {
    private readonly placeService: PlaceService,
    private readonly visitedService: VisitedService,
  ) {}
  private async createRoute(email: string, idTown: number, idPlace: number, startDate: Date, endDate: Date) {
    const user: User = await this.userRepository.findOneBy({ email });
    const town: Town = await this.townRepository.findOneBy({ townId: idTown });
    // const idRoute =  await this.routeRepository.save({ user, town, startDate, endDate });
    const idRoute = (await this.routeRepository.save({ user, town, startDate, endDate })).idRoute;
    await this.travelPlaceService.create({
      idRoute,
      idPlace,
      startDate: new Date(),
      endDate: new Date(),
      done: false,
    });
  }

  async recommend(idTown: number, email: string, language: LANGUAGES) {
  async recommend(idTown: number, email: string, language: LANGUAGES, start, end) {
    // Obtener los visitados y los candidatos
    const town: Town = await this.townRepository.findOneBy({ townId: idTown });
    const user: User = await this.userRepository.findOneBy({ email });
    const placesNotVisited: GetPlaceDto[] = await this.placeService.findPlacesNotVisitedByUser(email, language, idTown);
    const visited: Visited[] = await this.visitedService.getVisitedByUser(email);
    const placesMapped: RecommendPlace[] = placesNotVisited.map((place) => {
@@ -62,16 +53,42 @@ export class RouteService {
      };
    });

    const system = new RecommendationsSystem();
    const chosen: [number, number][] = system.recommend(visitedMapped, placesMapped);
    const placesChooen: GetPlaceDto[] = [];
    for (const [index] of chosen) {
      placesChooen.push(await this.placeService.findOneAndTradAndAvailable(index, LANGUAGES.EN));
    }
    const system = new RecommendationsSystem(visitedMapped, placesMapped, start, end);
    const chosen: RecommendPlace[] = system.recommend();

    const startDate: Date = new Date();
    startDate.setHours(start);
    const endDate = new Date();
    endDate.setHours(end);

    for (const place of placesChooen) {
      await this.createRoute(email, idTown, place.idPlace, new Date(), new Date());
    const createRouteDto: CreateRouteDto = { startDate, endDate, town, user };
    const idRoute = (await this.routeRepository.save(createRouteDto)).idRoute;

    for (const curRecommended of chosen) {
      const place = await this.placeService.findOneAndTradAndAvailable(curRecommended.idPlace, language);
      const endDate = new Date();
      endDate.setHours(curRecommended.closeAt);
      const startDate = new Date();
      startDate.setHours(curRecommended.openAt);

      const createTravelPlace: CreateTravelPlaceDto = {
        ...place,
        done: false,
        idRoute,
        endDate,
        startDate,
      };

      await this.travelPlaceService.create(createTravelPlace);
    }
  }
    return placesChooen;

  async getRouteAndPlacesByUser(email: string, language: LANGUAGES, idTown: number) {
    const res = await this.routeRepository.findOne({
      relations: ['travelPlace'],
      where: { user: { email }, town: { townId: idTown } },
    });

    return res;
  }
}
+64 −9
Original line number Diff line number Diff line
import { RecommendPlace } from '../dto/recommend-route.dto';
import { DataFrame, Series } from './math';
import { customSort } from './sort';
import { customSort, sortByClose } from './sort';

export class RecommendationsSystem {
  getCategories(places: RecommendPlace[]): number[] {
  private visited: RecommendPlace[] = [];
  private candidates: RecommendPlace[] = [];
  private start: number = 0;
  private end: number = 0;

  constructor(visited: RecommendPlace[], candidates: RecommendPlace[], start: number, end: number) {
    this.visited = visited;
    this.candidates = candidates;
    this.start = start;
    this.end = end;
  }

  private getCategories(places: RecommendPlace[]): number[] {
    const categories = new Set<number>();
    for (const place of places) {
      for (const category of place.categories) {
@@ -13,7 +25,7 @@ export class RecommendationsSystem {
    return Array.from(categories);
  }

  oneHotEncode(categories: number[], placesToEncode: RecommendPlace[]): DataFrame {
  private oneHotEncode(categories: number[], placesToEncode: RecommendPlace[]): DataFrame {
    const data: DataFrame = {};
    for (const category of categories) {
      data[category] = [];
@@ -24,7 +36,7 @@ export class RecommendationsSystem {
    return data;
  }

  rankVisited(visited: RecommendPlace[]): Series {
  private rankVisited(visited: RecommendPlace[]): Series {
    const visitedCategories = this.getCategories(visited);
    const visitedEncoded = this.oneHotEncode(visitedCategories, visited);
    const grades = visited.map((place) => place.rating);
@@ -44,7 +56,7 @@ export class RecommendationsSystem {
    return result;
  }

  rankCandidates(candidates: RecommendPlace[], visited: Series): [number, number][] {
  private rankCandidates(candidates: RecommendPlace[], visited: Series): [number, number][] {
    const visitedCategories = Object.keys(visited).map(Number);
    const candidatesEncoded = this.oneHotEncode(visitedCategories, candidates);

@@ -62,9 +74,52 @@ export class RecommendationsSystem {
    return ranked;
  }

  recommend(visited: RecommendPlace[], candidates: RecommendPlace[]) {
    const visitedRanking = this.rankVisited(visited);
    const candidatesRanked = this.rankCandidates(candidates, visitedRanking);
    return candidatesRanked;
  takeTopNValid(recommendations: [number, number][]): RecommendPlace[] {
    const validRecommendations: RecommendPlace[] = [];

    // Filtrar los lugares que existen en candidates
    for (const [id] of recommendations) {
      const place = this.candidates.find((candidate) => candidate.idPlace === id);
      if (place) {
        validRecommendations.push(place);
      }
    }

    validRecommendations.sort(sortByClose);

    const chosen: RecommendPlace[] = [];
    let currentTime = this.start; // Tiempo inicial de la ruta

    for (const place of validRecommendations) {
      // Verificar si el lugar está abierto y si se puede acomodar dentro del horario
      const maxStart = Math.max(currentTime, place.openAt);
      const minEnd = Math.min((currentTime + 2) % 24, place.closeAt);

      if (minEnd - 2 >= maxStart) {
        // Verificar si aún hay tiempo suficiente en el rango total de la ruta
        if ((currentTime + 2) % 24 <= this.end) {
          // Actualizar el lugar con el nuevo horario dentro de la ruta
          place.openAt = Math.max(currentTime, place.openAt);
          place.closeAt = (place.openAt + 2) % 24;

          chosen.push(place);

          // Actualizar la hora actual para el siguiente lugar
          currentTime = (currentTime + 2) % 24;
        } else {
          // Si ya no hay tiempo suficiente para otro lugar, terminamos el bucle
          break;
        }
      }
    }

    return chosen;
  }

  recommend(): RecommendPlace[] {
    const visitedRanking = this.rankVisited(this.visited);
    const candidatesRanked = this.rankCandidates(this.candidates, visitedRanking);
    const chosen = this.takeTopNValid(candidatesRanked);
    return chosen;
  }
}
Loading