Skip to content
Permalink
Browse files

Add monthly stats

  • Loading branch information...
pboyd committed Oct 16, 2019
1 parent a766775 commit 7e3653a4eeb45d4c94c530e657f843e337da0267
@@ -121,7 +121,7 @@ func makeSchema(db *sql.DB) (graphql.Schema, error) {
}

dailyFlightStats := &graphql.Field{
Type: dailyFlightStats,
Type: gqlFlightStatsByDate,
Args: graphql.FieldConfigArgument{
"origin": &graphql.ArgumentConfig{
Type: graphql.String,
@@ -135,6 +135,21 @@ func makeSchema(db *sql.DB) (graphql.Schema, error) {
Resolve: resolveDailyFlightStats(db),
}

monthlyFlightStats := &graphql.Field{
Type: gqlFlightStatsByDate,
Args: graphql.FieldConfigArgument{
"origin": &graphql.ArgumentConfig{
Type: graphql.String,
Description: "airport IATA code (e.g. LAX)",
},
"destination": &graphql.ArgumentConfig{
Type: graphql.String,
Description: "airport IATA code (e.g. LAX)",
},
},
Resolve: resolveMonthlyFlightStats(db),
}

return graphql.NewSchema(
graphql.SchemaConfig{
Query: graphql.NewObject(
@@ -145,6 +160,7 @@ func makeSchema(db *sql.DB) (graphql.Schema, error) {
"airportList": airportList,
"flightStatsByAirline": flightStatsByAirline,
"dailyFlightStats": dailyFlightStats,
"monthlyFlightStats": monthlyFlightStats,
},
},
),
@@ -90,19 +90,21 @@ func resolveFlightStatsByAirline(db *sql.DB) graphql.FieldResolveFn {
)
}

type dailyStatsAirline struct {
type flightStatsByDate struct {
Airline string
Days []*flightStatsDay
Rows []*flightStatsByDateRow
}

type flightStatsDay struct {
type flightStatsByDateRow struct {
Date time.Time
Flights int
Delays int
OnTimePercentage float64
}

var dailyFlightStatsRow = graphql.NewObject(
var gqlFlightStatsByDateRow = graphql.NewObject(
graphql.ObjectConfig{
Name: "dailyFlightStatsDay",
Name: "flightStatsByDateRow",
Fields: graphql.Fields{
"date": &graphql.Field{Type: graphql.DateTime},
"flights": &graphql.Field{Type: graphql.Int},
@@ -112,13 +114,13 @@ var dailyFlightStatsRow = graphql.NewObject(
},
)

var dailyFlightStats = graphql.NewList(
var gqlFlightStatsByDate = graphql.NewList(
graphql.NewObject(
graphql.ObjectConfig{
Name: "dailyFlightStats",
Name: "flightStatsByDate",
Fields: graphql.Fields{
"airline": &graphql.Field{Type: graphql.String},
"days": &graphql.Field{Type: graphql.NewList(dailyFlightStatsRow)},
"rows": &graphql.Field{Type: graphql.NewList(gqlFlightStatsByDateRow)},
},
},
),
@@ -154,32 +156,34 @@ func resolveDailyFlightStats(db *sql.DB) graphql.FieldResolveFn {
}
defer rows.Close()

statsMap := map[string][]*flightStatsDay{}
statsMap := map[string][]*flightStatsByDateRow{}

for rows.Next() {
var (
airline string
row flightStatsDay
flights, delays int
airline string
row flightStatsByDateRow
)

err := rows.Scan(&row.Date, &airline, &flights, &delays)
err := rows.Scan(&row.Date, &airline, &row.Flights, &row.Delays)
if err != nil {
return nil, err
}

row.OnTimePercentage = (1.0 - float64(delays)/float64(flights)) * 100
row.OnTimePercentage = (1.0 - float64(row.Delays)/float64(row.Flights)) * 100

if statsMap[airline] == nil {
statsMap[airline] = []*flightStatsDay{}
statsMap[airline] = []*flightStatsByDateRow{}
}

statsMap[airline] = append(statsMap[airline], &row)
}

stats := make([]dailyStatsAirline, 0, len(statsMap))
for airline, days := range statsMap {
stats = append(stats, dailyStatsAirline{Airline: airline, Days: days})
stats := make([]flightStatsByDate, 0, len(statsMap))
for airline, rows := range statsMap {
stats = append(stats, flightStatsByDate{
Airline: airline,
Rows: rows,
})
}

sort.Slice(stats, func(i, j int) bool {
@@ -190,3 +194,78 @@ func resolveDailyFlightStats(db *sql.DB) graphql.FieldResolveFn {
},
)
}

func resolveMonthlyFlightStats(db *sql.DB) graphql.FieldResolveFn {
return graphQLMetrics("monthly_flight_stats",
func(p graphql.ResolveParams) (interface{}, error) {
origin, _ := p.Args["origin"].(string)
origin = strings.ToUpper(origin)

dest, _ := p.Args["destination"].(string)
dest = strings.ToUpper(dest)

if !isAirportCode(origin) || !isAirportCode(dest) {
return nil, nil
}

rows, err := db.QueryContext(p.Context,
`SELECT
YEAR(date) AS year,
MONTH(date) AS month,
carriers.name,
SUM(total_flights),
SUM(IF(delayed_flights IS NULL, 0, delayed_flights)) AS delay_flights_not_null
FROM
flights_day
INNER JOIN carriers ON carrier=carriers.code
WHERE origin=? AND destination=? GROUP BY year, month, carriers.name`,
origin, dest)
if err != nil {
return nil, err
}
defer rows.Close()

statsMap := map[string][]*flightStatsByDateRow{}

for rows.Next() {
var (
airline string
row flightStatsByDateRow
year, month int
)

err := rows.Scan(&year, &month, &airline, &row.Flights, &row.Delays)
if err != nil {
return nil, err
}

row.Date = time.Date(year, time.Month(month), 1, 0, 0, 0, 0, time.UTC)
row.OnTimePercentage = (1.0 - float64(row.Delays)/float64(row.Flights)) * 100

if statsMap[airline] == nil {
statsMap[airline] = []*flightStatsByDateRow{}
}

statsMap[airline] = append(statsMap[airline], &row)
}

stats := make([]flightStatsByDate, 0, len(statsMap))
for airline, rows := range statsMap {
stats = append(stats, flightStatsByDate{
Airline: airline,
Rows: rows,
})
}

sort.Slice(stats, func(i, j int) bool {
return stats[i].Airline < stats[j].Airline
})

return stats, nil
},
)
}

/*
SELECT YEAR(date) AS year, MONTH(date) AS month, carriers.name, SUM(total_flights), SUM(IF(delayed_flights IS NULL, 0, delayed_flights)) AS delay_flights_not_null FROM flights_day INNER JOIN carriers ON carrier=carriers.code WHERE origin='RDU' AND destination='LAX' GROUP BY year, month, carriers.name;
*/
@@ -7,7 +7,8 @@ import (

type FlightStatsStore interface {
FlightStatsByAirline(ctx context.Context, origin, destination string) ([]*FlightStats, error)
DailyFlightStats(ctx context.Context, origin, destination string) (map[string][]*FlightStatsDay, error)
DailyFlightStats(ctx context.Context, origin, destination string) (map[string][]*FlightStatsByDateRow, error)
MonthlyFlightStats(ctx context.Context, origin, destination string) (map[string][]*FlightStatsByDateRow, error)
}

type FlightStats struct {
@@ -21,13 +22,13 @@ func (fs *FlightStats) OnTimePercentage() float64 {
return calcOnTimePercentage(fs.TotalFlights, fs.TotalDelays)
}

type FlightStatsDay struct {
type FlightStatsByDateRow struct {
Date time.Time
Flights int
Delays int
}

func (fs *FlightStatsDay) OnTimePercentage() float64 {
func (fs *FlightStatsByDateRow) OnTimePercentage() float64 {
return calcOnTimePercentage(fs.Flights, fs.Delays)
}

@@ -8,21 +8,33 @@ import (
"github.com/pboyd/flightranker-backend/backendb/app"
)

var airlineFlightStatsType = graphql.NewObject(
var flightStatsByDateRow = graphql.NewObject(
graphql.ObjectConfig{
Name: "airlineFlightStats",
Name: "flightStatsByDateRow",
Fields: graphql.Fields{
"airline": &graphql.Field{Type: graphql.String},
"totalFlights": &graphql.Field{Type: graphql.Int},
"date": &graphql.Field{Type: graphql.DateTime},
"flights": &graphql.Field{Type: graphql.Int},
"delays": &graphql.Field{Type: graphql.Int},
"onTimePercentage": &graphql.Field{Type: graphql.Float, Resolve: resolveOnTimePercentage},
"lastFlight": &graphql.Field{Type: graphql.DateTime},
},
},
)

func (p *Processor) flightStatsByAirlineQuery() *graphql.Field {
var gqlFlightStatsByDate = graphql.NewList(
graphql.NewObject(
graphql.ObjectConfig{
Name: "flightStatsByDate",
Fields: graphql.Fields{
"airline": &graphql.Field{Type: graphql.String},
"rows": &graphql.Field{Type: graphql.NewList(flightStatsByDateRow)},
},
},
),
)

func (p *Processor) dailyFlightStatsQuery() *graphql.Field {
return &graphql.Field{
Type: graphql.NewList(airlineFlightStatsType),
Type: gqlFlightStatsByDate,
Args: graphql.FieldConfigArgument{
"origin": &graphql.ArgumentConfig{
Type: graphql.String,
@@ -33,11 +45,16 @@ func (p *Processor) flightStatsByAirlineQuery() *graphql.Field {
Description: "airport IATA code (e.g. LAX)",
},
},
Resolve: instrumentResolver("flightstats_by_airline", p.resolveFlightStatsByAirlineQuery),
Resolve: instrumentResolver("daily_flight_stats", p.resolveDailyFlightStats),
}
}

func (p *Processor) resolveFlightStatsByAirlineQuery(params graphql.ResolveParams) (interface{}, error) {
type flightStatsByDate struct {
Airline string
Rows []*app.FlightStatsByDateRow
}

func (p *Processor) resolveDailyFlightStats(params graphql.ResolveParams) (interface{}, error) {
origin, _ := params.Args["origin"].(string)
origin = strings.ToUpper(origin)

@@ -48,45 +65,26 @@ func (p *Processor) resolveFlightStatsByAirlineQuery(params graphql.ResolveParam
return nil, nil
}

return p.config.FlightStatsStore.FlightStatsByAirline(params.Context, origin, dest)
}

func resolveOnTimePercentage(params graphql.ResolveParams) (interface{}, error) {
stats, ok := params.Source.(app.OnTimeStat)
if !ok {
return 0, nil
statsMap, err := p.config.FlightStatsStore.DailyFlightStats(params.Context, origin, dest)
if err != nil {
return nil, err
}

return stats.OnTimePercentage(), nil
}
stats := make([]flightStatsByDate, 0, len(statsMap))
for airline, rows := range statsMap {
stats = append(stats, flightStatsByDate{Airline: airline, Rows: rows})
}

var dailyFlightStatsRow = graphql.NewObject(
graphql.ObjectConfig{
Name: "dailyFlightStatsDay",
Fields: graphql.Fields{
"date": &graphql.Field{Type: graphql.DateTime},
"flights": &graphql.Field{Type: graphql.Int},
"delays": &graphql.Field{Type: graphql.Int},
"onTimePercentage": &graphql.Field{Type: graphql.Float, Resolve: resolveOnTimePercentage},
},
},
)
sort.Slice(stats, func(i, j int) bool {
return stats[i].Airline < stats[j].Airline
})

var dailyFlightStats = graphql.NewList(
graphql.NewObject(
graphql.ObjectConfig{
Name: "dailyFlightStats",
Fields: graphql.Fields{
"airline": &graphql.Field{Type: graphql.String},
"days": &graphql.Field{Type: graphql.NewList(dailyFlightStatsRow)},
},
},
),
)
return stats, nil
}

func (p *Processor) dailyFlightStatsQuery() *graphql.Field {
func (p *Processor) monthlyFlightStatsQuery() *graphql.Field {
return &graphql.Field{
Type: dailyFlightStats,
Type: gqlFlightStatsByDate,
Args: graphql.FieldConfigArgument{
"origin": &graphql.ArgumentConfig{
Type: graphql.String,
@@ -97,16 +95,11 @@ func (p *Processor) dailyFlightStatsQuery() *graphql.Field {
Description: "airport IATA code (e.g. LAX)",
},
},
Resolve: instrumentResolver("daily_flight_stats", p.resolveDailyFlightStats),
Resolve: instrumentResolver("monthly_flight_stats", p.resolveMonthlyFlightStats),
}
}

type dailyStatsAirline struct {
Airline string
Days []*app.FlightStatsDay
}

func (p *Processor) resolveDailyFlightStats(params graphql.ResolveParams) (interface{}, error) {
func (p *Processor) resolveMonthlyFlightStats(params graphql.ResolveParams) (interface{}, error) {
origin, _ := params.Args["origin"].(string)
origin = strings.ToUpper(origin)

@@ -117,14 +110,14 @@ func (p *Processor) resolveDailyFlightStats(params graphql.ResolveParams) (inter
return nil, nil
}

statsMap, err := p.config.FlightStatsStore.DailyFlightStats(params.Context, origin, dest)
statsMap, err := p.config.FlightStatsStore.MonthlyFlightStats(params.Context, origin, dest)
if err != nil {
return nil, err
}

stats := make([]dailyStatsAirline, 0, len(statsMap))
for airline, days := range statsMap {
stats = append(stats, dailyStatsAirline{Airline: airline, Days: days})
stats := make([]flightStatsByDate, 0, len(statsMap))
for airline, rows := range statsMap {
stats = append(stats, flightStatsByDate{Airline: airline, Rows: rows})
}

sort.Slice(stats, func(i, j int) bool {

0 comments on commit 7e3653a

Please sign in to comment.
You can’t perform that action at this time.