IT55ASWSZHHHW7MZ6MWVFZFZZNA3WKFBXQLNAFY7PYGHYQUJOZIAC AJKCAY3ODMTFBO6GXWXWQSWNFIH3H7JNBRFVW5H6ZSWA7SZGY3FAC B7W4Q522DLB6DKH2TFDOCTSZFZLFTOLCT6CCZEOC3V3UUMSAOOFAC N7TZV5WJKLYKSG6SIVAID4ZCC76YHZU5U5WWQGG6TFKAV6CE3Y6AC BZWC6XMOUXEPOI7P65MREJZ45TSQRFMMR2SQ7LML7RMCKUL57VHAC B6QIBRKFDFZKHHHT6F6M2NST2CYXNK4FCCYL3JOIAYDZYX3FQLRAC Q4BYGYNX3UZXX47EMX2GWX6M6ICSMIMCLQ7HVOYS4HB4RHYI4NEQC 7QS7R2ORWCZMTCE3SD5PJ5ZL27N3RS7YCWBHXH3JDPTHDQ3KBAMQC GGREOYIRZE2GH62X7YXQA7IUSSB25ENLQ5OKVJYWPNI462DP37FAC #[Object]impl TeamStats {async fn team(&self) -> i32 {self.team}async fn zone_id(&self) -> i32 {self.zone_id}async fn bosses_killed(&self) -> i32 {self.bosses_killed}async fn raid_nights(&self, ctx: &Context<'_>) -> Result<Vec<RaidNight>> {use crate::schema::raid_nights::dsl as rn;use crate::schema::team_stats::dsl as ts;let Data { conpool, .. } = ctx.data()?;Ok(rn::raid_nights.inner_join(ts::team_stats).filter(ts::team.eq(self.team).and(ts::zone_id.eq(self.zone_id))).select((rn::ts,rn::day_of_week,rn::start_time,rn::end_time,rn::observed_instances,)).load(&*conpool.get()?)?)}}#[derive(Queryable, Insertable, SimpleObject)]pub struct RaidNight {#[graphql(skip)]pub ts: i32,pub day_of_week: i32,pub start_time: NaiveTime,pub end_time: NaiveTime,pub observed_instances: i32,}
fn daytime_interval(dur: Duration, interval: u32) -> NaiveTime {let hour = (dur.num_hours() - 24 * dur.num_days()) as u32;let min = (dur.num_minutes() - 60 * dur.num_hours()) as u32;let min = if min % interval > interval / 2 {min + (interval - min % interval)} else {min - (min % interval)};
pub fn team_analysis(con: &PgConnection, team_id: i32, zone_id: i32) -> Result<()> {
let (hour, min) = if min == 60 {(hour + 1, 0)} else {(hour, min)};let sec = 0;NaiveTime::from_hms(hour, min, sec)}// Returns a duration representing the time from the 00:00:00 on Sundayfn weekpoint(dt: DateTime<Utc>) -> Duration {Duration::days(dt.weekday().num_days_from_sunday() as i64)+ Duration::hours(dt.hour() as i64)+ Duration::seconds(dt.second() as i64)}struct RNData {log_start: DateTime<Utc>,start_time: Duration,end_time: Duration,}fn into_raid_night(data: &mut Vec<RNData>) -> (u32, NaiveTime, NaiveTime, usize) {data.sort_by_key(|d| d.end_time);let med_end_time = data[data.len() / 2].end_time.clone();data.sort_by_key(|d| d.start_time);let med_start = &data[data.len() / 2];let res = (med_start.log_start.weekday().num_days_from_sunday(),daytime_interval(med_start.start_time, 30),daytime_interval(med_end_time, 30),data.len(),);data.truncate(0);res}fn raid_night_analysis(con: &PgConnection,team_id: i32,zone_id: i32,difficulty: i32,stat_id: i32,) -> Result<()> {use crate::schema::fights::dsl as fights;use crate::schema::logs::dsl as logs;use crate::schema::raid_nights::dsl as rn;use crate::schema::teams::dsl as teams;use diesel::sql_types::Int4;let mut time_info: Vec<(DateTime<Utc>, i32, i32, String)> = logs::logs.inner_join(fights::fights).inner_join(teams::teams).filter(teams::id.eq(team_id).and(logs::zone_id.eq(zone_id)).and(fights::difficulty.eq(difficulty)),).select((logs::start_time,sql::<Int4>("min(fights.start_time)"),sql::<Int4>("max(fights.end_time)"),logs::code,)).group_by(logs::iid).load(con)?;time_info.sort_by_key(|(log, fight, _, _)| weekpoint(*log + Duration::milliseconds(*fight as i64)));let mut data = vec![];let mut days = vec![];// the idea here is that we walk through the sorted list, pluggingfor (log_start_time, fight_start, fight_end, code) in time_info {let start_time = weekpoint(log_start_time + Duration::milliseconds(fight_start as i64));let end_time = weekpoint(log_start_time + Duration::milliseconds(fight_end as i64));info!("found log {} from {} - {} (original: {})",code, start_time, end_time, log_start_time);if !data.is_empty() {let RNData {start_time: prev_start_time,..} = data.last().unwrap();let diff = start_time - *prev_start_time;debug!("comparing {:?} to {:?} ({:?} diff)",start_time, prev_start_time, diff);if diff >= Duration::hours(6) {// new partition---this resets the data arraydays.push(into_raid_night(&mut data));}}data.push(RNData {log_start: log_start_time,start_time,end_time,});}days.push(into_raid_night(&mut data));con.transaction(|| {diesel::delete(rn::raid_nights.filter(rn::ts.eq(stat_id))).execute(con)?;diesel::insert_into(rn::raid_nights).values(days.into_iter().map(|(day, start_time, end_time, count)| RaidNight {ts: stat_id,day_of_week: day as i32,start_time,end_time,observed_instances: count as i32,}).collect::<Vec<_>>(),).execute(con)?;Ok(())})}pub fn team_analysis(con: &PgConnection,team_id: i32,zone_id: i32,difficulty: i32,) -> Result<()> {
export function formatRaidTime(day_of_week: number, utc_time: string): Result<{ time: string; day: string; }, 'invalid data'> {const date = new Date(`January ${3 + day_of_week}, 2021, ${utc_time} GMT+00:00`);if (date) {return Ok({time: new Intl.DateTimeFormat('en-US', { hour: 'numeric', minute: 'numeric', timeZoneName: 'short' }).format(date),day: new Intl.DateTimeFormat('en-US', { weekday: 'short' }).format(date),});} else {return Err('invalid data' as const);}}
}interface RaidNight {dayOfWeek: number;startTime: string;endTime: string;observedInstances: number;}function TeamOverview(props: { team: number; zone: number }) {const { data } = useQuery(TEAM_STATS_QUERY, { variables: props })console.log(data);if(data && data.teamStats) {const maxObserved = Math.max.apply(null, data.teamStats.raidNights.map((rn: RaidNight) => rn.observedInstances));const threshold = Math.ceil(maxObserved / 2);return (<section><legend className="text-2xl">Team Overview</legend><dl><dt>Bosses Killed:</dt><dd>{data.teamStats.bossesKilled}</dd><dt>Raid Times:</dt><dd><table><tbody>{data.teamStats.raidNights.filter((rn: RaidNight) => rn.observedInstances >= threshold).map((rn: RaidNight) => {const { day } = formatRaidTime(rn.dayOfWeek, rn.startTime).unwrap();return (<tr key={rn.dayOfWeek}><td>{day}</td><td>{formatRaidTime(rn.dayOfWeek, rn.startTime).unwrap().time}</td><td>{formatRaidTime(rn.dayOfWeek, rn.endTime).unwrap().time}</td></tr>);})}</tbody></table></dd></dl></section>);} else {return null;}