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 Sunday
fn 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, plugging
for (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 array
days.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;
}