From 731bcf50c98d7ed38233212398c36988ce4c12df Mon Sep 17 00:00:00 2001 From: dmnisson Date: Sun, 11 Nov 2018 17:55:55 -0800 Subject: [PATCH] #2 and #22 removed registration page and added geolocation package and web api for logistic regression analysis --- .../Data/DriverTrackerIdentityDbContext.cs | 2 +- .../Areas/Identity/IdentityHostingStartup.cs | 21 +++- .../Identity/Pages/Account/Login.cshtml.cs | 102 ++++++++++++++++++ .../Identity/Pages/Account/Logout.cshtml.cs | 43 ++++++++ .../Controllers/AnalysisApiController.cs | 81 ++++++++++++++ .../Controllers/DriversApiController.cs | 2 +- .../Controllers/DriversController.cs | 12 +-- DriverTracker/Domain/DriverStatistics.cs | 5 + DriverTracker/Domain/FarePrediction.cs | 71 ++++++++++++ .../Domain/LogisticFarePredictionResult.cs | 14 +++ DriverTracker/DriverTracker.db | Bin 131072 -> 131072 bytes ...107035929_CreateIdentitySchema.Designer.cs | 2 +- ...erTrackerIdentityDbContextModelSnapshot.cs | 2 +- DriverTracker/Startup.cs | 5 +- 14 files changed, 348 insertions(+), 14 deletions(-) create mode 100644 DriverTracker/Areas/Identity/Pages/Account/Login.cshtml.cs create mode 100644 DriverTracker/Areas/Identity/Pages/Account/Logout.cshtml.cs create mode 100644 DriverTracker/Controllers/AnalysisApiController.cs create mode 100644 DriverTracker/Domain/FarePrediction.cs create mode 100644 DriverTracker/Domain/LogisticFarePredictionResult.cs diff --git a/DriverTracker/Areas/Identity/Data/DriverTrackerIdentityDbContext.cs b/DriverTracker/Areas/Identity/Data/DriverTrackerIdentityDbContext.cs index 57a552a..7e8f02a 100644 --- a/DriverTracker/Areas/Identity/Data/DriverTrackerIdentityDbContext.cs +++ b/DriverTracker/Areas/Identity/Data/DriverTrackerIdentityDbContext.cs @@ -6,7 +6,7 @@ using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; -namespace DriverTracker.Areas.Identity.Data +namespace DriverTracker.Data { public class DriverTrackerIdentityDbContext : IdentityDbContext { diff --git a/DriverTracker/Areas/Identity/IdentityHostingStartup.cs b/DriverTracker/Areas/Identity/IdentityHostingStartup.cs index 8ad45e4..63bf3b7 100644 --- a/DriverTracker/Areas/Identity/IdentityHostingStartup.cs +++ b/DriverTracker/Areas/Identity/IdentityHostingStartup.cs @@ -1,11 +1,13 @@ using System; -using DriverTracker.Areas.Identity.Data; +using DriverTracker.Data; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity.UI; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Identity.UI.Services; [assembly: HostingStartup(typeof(DriverTracker.Areas.Identity.IdentityHostingStartup))] namespace DriverTracker.Areas.Identity @@ -20,7 +22,22 @@ public void Configure(IWebHostBuilder builder) services.AddIdentity() .AddEntityFrameworkStores() - .AddDefaultUI().AddDefaultTokenProviders(); + .AddDefaultTokenProviders(); + + services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1) + .AddRazorPagesOptions(options => + { + options.AllowAreas = true; + options.Conventions.AuthorizeAreaFolder("Identity", "/Account/Manage"); + options.Conventions.AuthorizeAreaFolder("Identity", "/Account/Logout"); + }); + + services.ConfigureApplicationCookie(options => + { + options.LoginPath = $"/Identity/Account/Login"; + options.LogoutPath = $"/Identity/Account/Logout"; + options.AccessDeniedPath = $"/Identity/Account/AccessDenied"; + }); }); } diff --git a/DriverTracker/Areas/Identity/Pages/Account/Login.cshtml.cs b/DriverTracker/Areas/Identity/Pages/Account/Login.cshtml.cs new file mode 100644 index 0000000..700a5b1 --- /dev/null +++ b/DriverTracker/Areas/Identity/Pages/Account/Login.cshtml.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.Extensions.Logging; + +namespace DriverTracker.Areas.Identity.Pages.Account +{ + [AllowAnonymous] + public class LoginModel : PageModel + { + private readonly SignInManager _signInManager; + private readonly ILogger _logger; + + public LoginModel(SignInManager signInManager, ILogger logger) + { + _signInManager = signInManager; + _logger = logger; + } + + [BindProperty] + public InputModel Input { get; set; } + + public IList ExternalLogins { get; set; } + + public string ReturnUrl { get; set; } + + [TempData] + public string ErrorMessage { get; set; } + + public class InputModel + { + [Required] + [EmailAddress] + public string Email { get; set; } + + [Required] + [DataType(DataType.Password)] + public string Password { get; set; } + + [Display(Name = "Remember me?")] + public bool RememberMe { get; set; } + } + + public async Task OnGetAsync(string returnUrl = null) + { + if (!string.IsNullOrEmpty(ErrorMessage)) + { + ModelState.AddModelError(string.Empty, ErrorMessage); + } + + returnUrl = returnUrl ?? Url.Content("~/"); + + // Clear the existing external cookie to ensure a clean login process + await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme); + + ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList(); + + ReturnUrl = returnUrl; + } + + public async Task OnPostAsync(string returnUrl = null) + { + returnUrl = returnUrl ?? Url.Content("~/"); + + if (ModelState.IsValid) + { + // This doesn't count login failures towards account lockout + // To enable password failures to trigger account lockout, set lockoutOnFailure: true + var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: true); + if (result.Succeeded) + { + _logger.LogInformation("User logged in."); + return LocalRedirect(returnUrl); + } + if (result.RequiresTwoFactor) + { + return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, RememberMe = Input.RememberMe }); + } + if (result.IsLockedOut) + { + _logger.LogWarning("User account locked out."); + return RedirectToPage("./Lockout"); + } + else + { + ModelState.AddModelError(string.Empty, "Invalid login attempt."); + return Page(); + } + } + + // If we got this far, something failed, redisplay form + return Page(); + } + } +} diff --git a/DriverTracker/Areas/Identity/Pages/Account/Logout.cshtml.cs b/DriverTracker/Areas/Identity/Pages/Account/Logout.cshtml.cs new file mode 100644 index 0000000..54f2e6a --- /dev/null +++ b/DriverTracker/Areas/Identity/Pages/Account/Logout.cshtml.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.Extensions.Logging; + +namespace DriverTracker.Areas.Identity.Pages.Account +{ + [AllowAnonymous] + public class LogoutModel : PageModel + { + private readonly SignInManager _signInManager; + private readonly ILogger _logger; + + public LogoutModel(SignInManager signInManager, ILogger logger) + { + _signInManager = signInManager; + _logger = logger; + } + + public void OnGet() + { + } + + public async Task OnPost(string returnUrl = null) + { + await _signInManager.SignOutAsync(); + _logger.LogInformation("User logged out."); + if (returnUrl != null) + { + return LocalRedirect(returnUrl); + } + else + { + return Page(); + } + } + } +} \ No newline at end of file diff --git a/DriverTracker/Controllers/AnalysisApiController.cs b/DriverTracker/Controllers/AnalysisApiController.cs new file mode 100644 index 0000000..da3c185 --- /dev/null +++ b/DriverTracker/Controllers/AnalysisApiController.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Authorization; +using Accord.Statistics.Models.Regression; + +using DriverTracker.Models; +using DriverTracker.Domain; + +namespace DriverTracker.Controllers +{ + [Authorize(Roles = "Admin,Analyst")] + [Route("api/[controller]")] + public class AnalysisApiController : Controller + { + private readonly MvcDriverContext _context; + private readonly DriverStatistics _driverStatistics; + + public AnalysisApiController(MvcDriverContext context) { + _context = context; + _driverStatistics = new DriverStatistics(context); + } + + // GET: api/analysisapi + [HttpGet] + public IEnumerable Get() + { + List driverStatisticResults = new List(); + + IEnumerable drivers = _context.Drivers.AsEnumerable(); + + + foreach (Driver driver in drivers) { + _driverStatistics.ComputeDriverStatistics(driver.DriverID); + driverStatisticResults.Add(_driverStatistics.GetDriverStatisticResults(driver.DriverID)); + } + + return driverStatisticResults; + } + + // GET api/analysisapi/5 + [HttpGet("{id}")] + public DriverStatisticResults Get(int id) + { + _driverStatistics.ComputeDriverStatistics(id); + return _driverStatistics.GetDriverStatisticResults(id); + } + + // GET api/analysisapi/logistic/5 + [HttpGet("logistic/{id}")] + public LogisticFarePredictionResult GetLogistic(int id) + { + FarePrediction farePrediction = new FarePrediction(_context, id); + DateTime fromDateTime = DateTime.Now.AddMonths(-12); + DateTime toDateTime = DateTime.Now; + farePrediction.LearnFromDates(fromDateTime, toDateTime); + + LogisticFarePredictionResult result = new LogisticFarePredictionResult(); + result.DriverID = id; + result.FromDateTime = fromDateTime; + result.ToDateTime = toDateTime; + result.RegressionResult = farePrediction.GetRegressionModel(); + + return result; + } + + // GET api/analysisapi/multipickupprob/5/6/12/13.7/ + [HttpGet("multipickupprob/{id}/{delay}/{duration}/{fare}/")] + public double[] GetMultiPickupProb(int id, double delay, double duration, double fare) { + FarePrediction farePrediction = new FarePrediction(_context, id); + DateTime fromDateTime = DateTime.Now.AddMonths(-12); + DateTime toDateTime = DateTime.Now; + farePrediction.LearnFromDates(fromDateTime, toDateTime); + + LogisticRegression regression = farePrediction.GetRegressionModel(); + return regression?.Probabilities(new double[] { delay, duration, fare }); + } + } +} diff --git a/DriverTracker/Controllers/DriversApiController.cs b/DriverTracker/Controllers/DriversApiController.cs index c43c2ea..a526c97 100644 --- a/DriverTracker/Controllers/DriversApiController.cs +++ b/DriverTracker/Controllers/DriversApiController.cs @@ -11,7 +11,7 @@ namespace DriverTracker.Controllers { - [Authorize] + [Authorize(Roles = "Admin, Analyst, Driver")] [Route("api/[controller]")] public class DriversApiController : ControllerBase { diff --git a/DriverTracker/Controllers/DriversController.cs b/DriverTracker/Controllers/DriversController.cs index de72082..fd18f62 100644 --- a/DriverTracker/Controllers/DriversController.cs +++ b/DriverTracker/Controllers/DriversController.cs @@ -24,7 +24,7 @@ public DriversController(MvcDriverContext context) } // GET: Drivers - [Authorize] + [Authorize(Roles = "Admin,Driver")] public async Task Index() { _driverStatisticsService.ComputeCompanyStatistics(); @@ -61,7 +61,7 @@ public async Task Index() } // GET: Drivers/Details/5 - [Authorize] + [Authorize(Roles = "Admin,Driver")] public async Task Details(int? id) { _driverStatisticsService.ComputeDriverStatistics(id.Value); @@ -104,7 +104,7 @@ public async Task Details(int? id) } // GET: Drivers/Create - [Authorize] + [Authorize(Roles = "Admin,Driver")] public IActionResult Create() { return View(); @@ -113,7 +113,7 @@ public IActionResult Create() // POST: Drivers/Create // To protect from overposting attacks, please enable the specific properties you want to bind to, for // more details see http://go.microsoft.com/fwlink/?LinkId=317598. - [Authorize] + [Authorize(Roles = "Admin,Driver")] [HttpPost] [ValidateAntiForgeryToken] public async Task Create([Bind("DriverID,UserID,Name,LicenseNumber")] Driver driver) @@ -128,7 +128,7 @@ public async Task Create([Bind("DriverID,UserID,Name,LicenseNumbe } // GET: Drivers/Edit/5 - [Authorize] + [Authorize(Roles = "Admin,Driver")] public async Task Edit(int? id) { if (id == null) @@ -149,7 +149,7 @@ public async Task Edit(int? id) // more details see http://go.microsoft.com/fwlink/?LinkId=317598. [HttpPost] [ValidateAntiForgeryToken] - [Authorize] + [Authorize(Roles = "Admin,Driver")] public async Task Edit(int id, [Bind("DriverID,UserID,Name,LicenseNumber")] Driver driver) { if (id != driver.DriverID) diff --git a/DriverTracker/Domain/DriverStatistics.cs b/DriverTracker/Domain/DriverStatistics.cs index 192c64f..dc857af 100644 --- a/DriverTracker/Domain/DriverStatistics.cs +++ b/DriverTracker/Domain/DriverStatistics.cs @@ -139,5 +139,10 @@ public decimal NetProfit { return netProfit; } } + + public DriverStatisticResults GetDriverStatisticResults(int id) + { + return driverStats[id]; + } } } diff --git a/DriverTracker/Domain/FarePrediction.cs b/DriverTracker/Domain/FarePrediction.cs new file mode 100644 index 0000000..29af857 --- /dev/null +++ b/DriverTracker/Domain/FarePrediction.cs @@ -0,0 +1,71 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; +using Accord.Statistics.Analysis; +using Accord.Statistics.Models.Regression; + +using DriverTracker.Models; + +namespace DriverTracker.Domain +{ + public class FarePrediction + { + private readonly LogisticRegressionAnalysis _logisticRegressionAnalysis; + private LogisticRegression _logisticRegression; + private readonly MvcDriverContext _context; + private readonly int _DriverID; + + public FarePrediction(MvcDriverContext context, int DriverID) { + _logisticRegressionAnalysis = new LogisticRegressionAnalysis(); + _context = context; + _DriverID = DriverID; + + _logisticRegressionAnalysis.Inputs = new string[] { "delay", "legDuration", "fare" }; + _logisticRegressionAnalysis.Output = "multiplePickups"; + } + + /* Learn from legs with specified request times in a given date range */ + public async void LearnFromDates(DateTime from, DateTime to) { + List legs = await _context.Legs.Where(leg => leg.DriverID == _DriverID + && leg.StartTime.CompareTo(from) >= 0 + && leg.StartTime.CompareTo(to) < 0 + && leg.PickupRequestTime.HasValue) + .ToListAsync(); + + double[][] trainingInputs = legs.Select(leg => + { + return new double[] + { + leg.StartTime.Subtract(leg.PickupRequestTime.Value).TotalMinutes, + leg.ArrivalTime.Subtract(leg.StartTime).TotalMinutes, + decimal.ToDouble(leg.Fare) + }; + }).ToArray(); + + /*{ + // delays in minutes + legs.Select(leg => leg.StartTime.Subtract(leg.PickupRequestTime.Value).TotalMinutes) + .ToArray(), + + // leg duration in minutes + legs.Select(leg => leg.ArrivalTime.Subtract(leg.StartTime).TotalMinutes) + .ToArray(), + + // fare collected in local currency units + legs.Select(leg => decimal.ToDouble(leg.Fare)).ToArray() + };*/ + + double[] trainingOutputs = + legs.Select(leg => leg.NumOfPassengersPickedUp > 1 ? 1.0 : 0.0).ToArray(); + + _logisticRegression = _logisticRegressionAnalysis.Learn(trainingInputs, trainingOutputs); + } + + /* Get the regression result */ + public LogisticRegression GetRegressionModel() { + return _logisticRegression; + } + } +} diff --git a/DriverTracker/Domain/LogisticFarePredictionResult.cs b/DriverTracker/Domain/LogisticFarePredictionResult.cs new file mode 100644 index 0000000..c44da4c --- /dev/null +++ b/DriverTracker/Domain/LogisticFarePredictionResult.cs @@ -0,0 +1,14 @@ +using System; + +using Accord.Statistics.Models.Regression; + +namespace DriverTracker.Domain +{ + public class LogisticFarePredictionResult + { + public int DriverID { get; set; } + public DateTime FromDateTime { get; set; } + public DateTime ToDateTime { get; set; } + public LogisticRegression RegressionResult { get; set; } + } +} diff --git a/DriverTracker/DriverTracker.db b/DriverTracker/DriverTracker.db index afeee658ce0d7db91b335f9ba68bf5a04fcb3ccf..41b6bc7e3024485671b49e17ee830619e9d59250 100644 GIT binary patch delta 239 zcmZo@;Am*zm>|t4GEv5vQDkGn5_=9N{#OkApZQ;H7BslW&&kfjEXwJVnqIv5v%jDM z8zX-{1AqQzL4j2M`bIuRUItEgV`Fb)QBh7#Q9~nR69vPZ1!a1b`s31c3Y> Pl0XE51hBURumSu}Aqo^g diff --git a/DriverTracker/Migrations/DriverTrackerIdentityDb/20181107035929_CreateIdentitySchema.Designer.cs b/DriverTracker/Migrations/DriverTrackerIdentityDb/20181107035929_CreateIdentitySchema.Designer.cs index 25858da..58d1049 100644 --- a/DriverTracker/Migrations/DriverTrackerIdentityDb/20181107035929_CreateIdentitySchema.Designer.cs +++ b/DriverTracker/Migrations/DriverTrackerIdentityDb/20181107035929_CreateIdentitySchema.Designer.cs @@ -1,6 +1,6 @@ // using System; -using DriverTracker.Areas.Identity.Data; +using DriverTracker.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; diff --git a/DriverTracker/Migrations/DriverTrackerIdentityDb/DriverTrackerIdentityDbContextModelSnapshot.cs b/DriverTracker/Migrations/DriverTrackerIdentityDb/DriverTrackerIdentityDbContextModelSnapshot.cs index ef4fe3a..8bac5f4 100644 --- a/DriverTracker/Migrations/DriverTrackerIdentityDb/DriverTrackerIdentityDbContextModelSnapshot.cs +++ b/DriverTracker/Migrations/DriverTrackerIdentityDb/DriverTrackerIdentityDbContextModelSnapshot.cs @@ -1,6 +1,6 @@ // using System; -using DriverTracker.Areas.Identity.Data; +using DriverTracker.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; diff --git a/DriverTracker/Startup.cs b/DriverTracker/Startup.cs index 57b5b93..e1754be 100644 --- a/DriverTracker/Startup.cs +++ b/DriverTracker/Startup.cs @@ -14,7 +14,7 @@ using Microsoft.EntityFrameworkCore; using DriverTracker.Models; -using DriverTracker.Areas.Identity.Data; +using DriverTracker.Data; namespace DriverTracker { @@ -42,7 +42,8 @@ public void ConfigureServices(IServiceCollection services) services.AddDbContext(options => options.UseSqlite("Data Source=DriverTracker.db")); services.AddDbContext(options => - options.UseSqlite("Data Source=DriverTracker.db")); + options.UseSqlite("Data Source=DriverTracker.db")); + } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.