Skip to content

Commit

Permalink
retouch merge, compartmentalize classes
Browse files Browse the repository at this point in the history
  • Loading branch information
MaxAPCS committed Apr 1, 2024
1 parent f8ae0c0 commit 8e7914f
Show file tree
Hide file tree
Showing 10 changed files with 198 additions and 178 deletions.
85 changes: 50 additions & 35 deletions lib/interfaces/bluealliance.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@ import 'package:stock/stock.dart';
import '../main.dart' show prefs;
import 'localstore.dart';

final qualificationMatchInfoPattern = RegExp(r'^(?<level>qm)(?<index>\d+)$');
final finalsMatchInfoPattern = RegExp(r'^(?<level>qf|sf|f)(?<finalnum>\d{1,2})m(?<index>\d+)$');

enum MatchLevel {
qualification(compLevel: "qm"),
quarterfinals(compLevel: "qf"),
Expand All @@ -21,37 +18,54 @@ enum MatchLevel {
static fromCompLevel(String s) => MatchLevel.values.firstWhere((type) => type.compLevel == s);
}

typedef MatchInfo = ({MatchLevel level, int? finalnum, int index});
MatchInfo? parseMatchInfo(String? s) {
if (s == null || s.isEmpty) return null;
RegExpMatch? match =
qualificationMatchInfoPattern.firstMatch(s) ?? finalsMatchInfoPattern.firstMatch(s);
if (match == null) return null;
return (
level: MatchLevel.fromCompLevel(match.namedGroup("level")!),
finalnum: match.groupNames.contains("finalnum")
? int.tryParse(match.namedGroup("finalnum") ?? "")
: null,
index: int.parse(match.namedGroup("index")!)
);
}
class MatchInfo implements Comparable {
static final _qualificationPattern = RegExp(r'^(?<level>qm)(?<index>\d+)$');
static final _finalsPattern = RegExp(r'^(?<level>qf|sf|f)(?<finalnum>\d{1,2})m(?<index>\d+)$');

final MatchLevel level;
final int? finalnum;
final int index;
MatchInfo({required this.level, this.finalnum, required this.index});
factory MatchInfo.fromString(String s) {
RegExpMatch? match = _qualificationPattern.firstMatch(s) ?? _finalsPattern.firstMatch(s);
return MatchInfo(
level: MatchLevel.fromCompLevel(match!.namedGroup("level")!),
finalnum: match.groupNames.contains("finalnum")
? int.tryParse(match.namedGroup("finalnum") ?? "")
: null,
index: int.parse(match.namedGroup("index")!));
}

String stringifyMatchInfo(MatchInfo m) =>
"${m.level.compLevel}${m.finalnum != null ? '${m.finalnum}m' : ''}${m.index}";
@override
String toString() => "${level.compLevel}${finalnum != null ? '${finalnum}m' : ''}$index";

int compareMatchInfo(MatchInfo a, MatchInfo b) => a.level != b.level
? b.level.index - a.level.index
: a.finalnum != null && b.finalnum != null && a.finalnum != b.finalnum
? b.finalnum! - a.finalnum!
: b.index - a.index;
@override
int compareTo(b) => level != b.level
? b.level.index - level.index
: finalnum != null && b.finalnum != null && finalnum != b.finalnum
? b.finalnum! - finalnum!
: b.index - index;

bool stringMatchIsLevel(String s, MatchLevel l) => s.startsWith(l.compLevel);
@override
int get hashCode => Object.hash(level, finalnum, index);

typedef TBAInfo = ({int season, String? event, String? match});
String stringifyTBAInfo(TBAInfo t) =>
t.season.toString() +
(t.event != null ? t.event! : "") +
(t.match != null ? "_${t.match!}" : "");
@override
bool operator ==(Object other) =>
other is MatchInfo &&
other.level == level &&
other.finalnum == finalnum &&
other.index == index;
}

class TBAInfo {
final int season;
final String? event, match;
TBAInfo({required this.season, this.event, this.match});

@override
String toString() =>
season.toString() + (event != null ? event! : "") + (match != null ? "_${match!}" : "");
}

typedef OPRData = ({double? opr, double? dpr, double? ccwms});

Expand Down Expand Up @@ -101,7 +115,7 @@ class BlueAlliance {
}).catchError((_) => false);
}

static final stockSoT = LocalSourceOfTruth("tba");
static final stockSoT = LocalSourceOfTruth<TBAInfo>("tba");
static final stock = Stock<TBAInfo, Map<String, String>>(
sourceOfTruth: stockSoT.mapTo<Map<String, String>>(
(p) => p.map((k, v) => MapEntry(k, v.toString())), (p) => p),
Expand Down Expand Up @@ -129,7 +143,8 @@ class BlueAlliance {
for (MapEntry<String, dynamic> alliance
in Map<String, dynamic>.from(data['alliances']).entries) {
for (MapEntry<int, String> team in List<String>.from(alliance.value['team_keys'])
.followedBy(List<String>.from(alliance.value['surrogate_team_keys']))
.followedBy(List<String>.from(alliance.value[
'surrogate_team_keys'])) // surrogate additions- i have no idea if they work this way
.toList(growable: false)
.asMap()
.entries) {
Expand Down Expand Up @@ -162,10 +177,10 @@ class BlueAlliance {
}
String matchkey = (matchdata['key'] as String).split("_").last;
matches[matchkey] = matchdata['key'];
await stockSoT.write((season: season, event: event, match: matchkey), o);
await stockSoT.write(TBAInfo(season: season, event: event, match: matchkey), o);
}
await stockSoT.write((season: season, event: event, match: null), matches);
await stockSoT.write((season: season, event: event, match: "*"), pitTeams);
await stockSoT.write(TBAInfo(season: season, event: event), matches);
await stockSoT.write(TBAInfo(season: season, event: event, match: "*"), pitTeams);
}

static final _oprStockSoT =
Expand Down
16 changes: 16 additions & 0 deletions lib/interfaces/geoplugin.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import 'dart:convert';

import 'package:http/http.dart' show Client;

typedef Coordinates = ({double lat, double long});

class GeoPlugin {
static final _client = Client();
static final _url = Uri.http('geoplugin.net', '/json.gp');

static Future<Coordinates> get() => _client
.get(_url)
.then((resp) => Map<String, dynamic>.from(jsonDecode(resp.body)))
.then((data) =>
(lat: data['geoplugin_latitude'] as double, long: data['geoplugin_longitude'] as double));
}
18 changes: 9 additions & 9 deletions lib/interfaces/localstore.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import 'package:localstore/localstore.dart';
import 'package:stock/stock.dart';

import '../pages/matchscout.dart' show MatchScoutInfoSerialized;
import 'bluealliance.dart';

class LocalStoreInterface {
static final _db = Localstore.instance;
Expand All @@ -31,15 +30,16 @@ class LocalStoreInterface {
_db.collection("scout").get().then((value) => value?.keys.toSet() ?? {});
}

class LocalSourceOfTruth implements SourceOfTruth<TBAInfo, Map<String, dynamic>> {
class LocalSourceOfTruth<K> implements SourceOfTruth<K, Map<String, dynamic>> {
final CollectionRef collection;
final StreamController<({String key, Map<String, dynamic>? value})> _stream =
StreamController.broadcast();
LocalSourceOfTruth(String key) : collection = LocalStoreInterface._db.collection(key);
LocalSourceOfTruth(String collection)
: collection = LocalStoreInterface._db.collection(collection);

@override
Future<void> delete(TBAInfo key) {
String s = stringifyTBAInfo(key);
Future<void> delete(K key) {
String s = key.toString();
_stream.add((key: s, value: null));
return collection.doc(s).delete();
}
Expand All @@ -53,15 +53,15 @@ class LocalSourceOfTruth implements SourceOfTruth<TBAInfo, Map<String, dynamic>>
}).then((_) => collection.delete());

@override
Stream<Map<String, dynamic>?> reader(TBAInfo key) async* {
String s = stringifyTBAInfo(key);
Stream<Map<String, dynamic>?> reader(K key) async* {
String s = key.toString();
yield* collection.doc(s).get().asStream();
yield* _stream.stream.where((e) => e.key == s).map((e) => e.value);
}

@override
Future<void> write(TBAInfo key, Map<String, dynamic>? value) {
String s = stringifyTBAInfo(key);
Future<void> write(K key, Map<String, dynamic>? value) {
String s = key.toString();
_stream.add((key: s, value: value));
return collection.doc(s).set(value ?? {});
}
Expand Down
41 changes: 21 additions & 20 deletions lib/interfaces/supabase.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import 'package:supabase_flutter/supabase_flutter.dart';

import '../pages/configuration.dart';
import '../pages/matchscout.dart' hide MatchScoutPage;
import 'bluealliance.dart' show MatchInfo, parseMatchInfo;
import 'bluealliance.dart' show MatchInfo;

typedef AggInfo = ({int season, String? event, String? team});

Expand Down Expand Up @@ -123,36 +123,37 @@ class SupabaseInterface {
.map((k, v) => MapEntry(k, Map<String, dynamic>.from(v)));
if (key.event != null && key.team != null) {
return LinkedHashMap<MatchInfo, Map<String, num>>.of(
response.map((k, v) => MapEntry(parseMatchInfo(k)!, Map.from(v[key.team]))));
response.map((k, v) => MapEntry(MatchInfo.fromString(k), Map.from(v[key.team]))));
// match: {scoretype: aggregated_count}
}
if (key.team != null) {
return LinkedHashMap.fromEntries(response.entries
.map((evententry) => Map<String, dynamic>.from(evententry.value).map(
(matchstring, matchscores) => MapEntry(
(event: evententry.key, match: parseMatchInfo(matchstring)!),
(event: evententry.key, match: MatchInfo.fromString(matchstring)),
Map<String, num>.from(matchscores))))
.expand((e) => e.entries));
}
throw UnimplementedError("No aggregate for that combination");
}));

static final eventAggregateStock = Stock<({int season, String event}), LinkedHashMap<String, double>>(sourceOfTruth: CachedSourceOfTruth(),fetcher: Fetcher.ofFuture((key) =>
Supabase.instance.client.functions
.invoke("event_aggregator?season=${key.season}&event=${key.event}")
.then((resp) => resp.status >= 400
? throw Exception("HTTP Error ${resp.status}")
: LinkedHashMap.fromEntries((Map<String, double?>.from(resp.data)
..removeWhere((key, value) => value == null))
.cast<String, double>()
.entries
.toList()
..sort((a, b) => a.value == b.value
? 0
: a.value > b.value
? -1
: 1)))
));

static final eventAggregateStock =
Stock<({int season, String event}), LinkedHashMap<String, double>>(
sourceOfTruth: CachedSourceOfTruth(),
fetcher: Fetcher.ofFuture((key) => Supabase.instance.client.functions
.invoke("event_aggregator?season=${key.season}&event=${key.event}")
.then((resp) => resp.status >= 400
? throw Exception("HTTP Error ${resp.status}")
: LinkedHashMap.fromEntries((Map<String, double?>.from(resp.data)
..removeWhere((key, value) => value == null))
.cast<String, double>()
.entries
.toList()
..sort((a, b) => a.value == b.value
? 0
: a.value > b.value
? -1
: 1)))));

static final distinctStock = Stock<
AggInfo,
Expand Down
19 changes: 11 additions & 8 deletions lib/pages/admin/matchinsight.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,18 @@ class MatchInsightPage extends StatelessWidget {
@override
Widget build(BuildContext context) => FutureBuilder(
future: BlueAlliance.stock
.get((season: Configuration.instance.season, event: Configuration.event!, match: null))
.get(TBAInfo(season: Configuration.instance.season, event: Configuration.event!))
.then((eventMatches) => Future.wait(eventMatches.keys.map((matchCode) => BlueAlliance.stock
.get((season: Configuration.instance.season, event: Configuration.event!, match: matchCode)).then(
(matchTeams) => matchTeams.keys.any(
(teamKey) => teamKey.startsWith(UserMetadata.instance.team!.toString()))
? MapEntry(matchCode, matchTeams)
: null))))
.then((teamMatches) =>
LinkedHashMap.fromEntries(teamMatches.whereType<MapEntry<String, Map<String, String>>>())),
.get(TBAInfo(
season: Configuration.instance.season,
event: Configuration.event!,
match: matchCode))
.then((matchTeams) => matchTeams.keys
.any((teamKey) => teamKey.startsWith(UserMetadata.instance.team!.toString()))
? MapEntry(matchCode, matchTeams)
: null))))
.then(
(teamMatches) => LinkedHashMap.fromEntries(teamMatches.whereType<MapEntry<String, Map<String, String>>>())),
builder: (context, snapshot) => !snapshot.hasData
? snapshot.hasError
? Column(mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [
Expand Down
15 changes: 7 additions & 8 deletions lib/pages/admin/statgraph.dart
Original file line number Diff line number Diff line change
Expand Up @@ -370,15 +370,15 @@ class TeamAtEventGraph extends StatelessWidget {
future: Future.wait([
SupabaseInterface.matchAggregateStock.get((season: season, event: event, team: team)),
BlueAlliance.stock
.get((season: season, event: event, match: null))
.get(TBAInfo(season: season, event: event))
.then((eventMatches) => Future.wait(eventMatches.keys.map((match) => BlueAlliance.stock
.get((season: season, event: event, match: match)).then(
(teams) => teams.keys.contains(team) ? match : null))))
.get(TBAInfo(season: season, event: event, match: match))
.then((teams) => teams.keys.contains(team) ? match : null))))
.then((unparsedEventMatches) => unparsedEventMatches
.where((match) => match != null)
.map((match) => parseMatchInfo(match)!)
.whereType<String>()
.map((match) => MatchInfo.fromString(match))
.toList()
..sort((a, b) => compareMatchInfo(b, a)))
..sort((b, a) => a.compareTo(b)))
]).then((results) => (
data: results[0] as LinkedHashMap<MatchInfo, Map<String, num>>,
ordinalMatches: results[1] as List<MatchInfo>
Expand Down Expand Up @@ -412,8 +412,7 @@ class TeamAtEventGraph extends StatelessWidget {
}
return SideTitleWidget(
axisSide: AxisSide.bottom,
child:
Text(stringifyMatchInfo(snapshot.data!.ordinalMatches[n])));
child: Text(snapshot.data!.ordinalMatches[n].toString()));
})),
leftTitles: const AxisTitles(
axisNameWidget: Text("Score"),
Expand Down
32 changes: 18 additions & 14 deletions lib/pages/configuration.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import 'package:birdseye/utils.dart';
import 'package:carousel_slider/carousel_slider.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

import '../interfaces/bluealliance.dart';
import '../interfaces/supabase.dart';
import '../main.dart' show prefs;
import '../utils.dart';

class ConfigurationPage extends StatelessWidget {
final _eventCarouselController = CarouselController();
Expand All @@ -32,10 +32,12 @@ class ConfigurationPage extends StatelessWidget {
])
: const LinearProgressIndicator();
}
int index = snapshot.data!
.indexWhere((element) => element == Configuration.instance.season);
if (Configuration.instance.season < 0 || index < 0) {
index = snapshot.data!.length - 1;
int maybeSeason = Configuration.instance.season < 0
? DateTime.now().year
: Configuration.instance.season;
int index = snapshot.data!.indexWhere((element) => element == maybeSeason);
if (index < 0) index = snapshot.data!.length - 1;
if (snapshot.data![index] != Configuration.instance.season) {
WidgetsBinding.instance.addPostFrameCallback(
(_) => Configuration.instance.season = snapshot.data![index]);
}
Expand Down Expand Up @@ -64,8 +66,8 @@ class ConfigurationPage extends StatelessWidget {
builder: (context, child) => Configuration.instance.season < 0
? const Center(child: CircularProgressIndicator())
: FutureBuilder(
future: BlueAlliance.stock.get(
(season: Configuration.instance.season, event: null, match: null)),
future: BlueAlliance.stock
.get(TBAInfo(season: Configuration.instance.season)),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return snapshot.hasError
Expand Down Expand Up @@ -93,12 +95,13 @@ class ConfigurationPage extends StatelessWidget {
const Text("No Events Found")
]);
}
int index = 0;
if (Configuration.event == null) {
String maybeEvent = Configuration.event ??
entries[entries.length ~/ 2].key; // TODO autofill event goes here
int index =
entries.indexWhere((element) => element.key == maybeEvent);
if (index < 0) index = snapshot.data!.length ~/ 2;
if (entries[index].key != Configuration.event) {
Configuration.event = entries[index].key;
} else {
index = snapshot.data!.keys.toList().indexOf(Configuration.event!);
if (index < 0) index = (entries.length / 2).round();
}
return ListenableBuilder(
listenable: _carouselProgress,
Expand Down Expand Up @@ -179,6 +182,7 @@ class Configuration extends ChangeNotifier {
Future<bool> get isValid async =>
season >= 0 &&
event != null &&
await BlueAlliance.stock.get((season: season, event: null, match: null)).then(
(value) => value.containsKey(event));
await BlueAlliance.stock
.get(TBAInfo(season: season))
.then((value) => value.containsKey(event));
}
Loading

0 comments on commit 8e7914f

Please sign in to comment.