Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .circleci/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@
pip >= 20
wheel >= 0.36
setuptools >= 50

# Temporarily include SRComp development branch
git+https://github.com/PeterJCLaw/srcomp@9dfcea54f073101da73e8fb62c8e4b108c118f1b # match-release
38 changes: 33 additions & 5 deletions docs/endpoints.rst
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,13 @@ the current time.
"time": "..."
}

The ``delay`` value is the amount of delay in seconds currently active.
Note that this value is only useful during match periods (it will otherwise
be ``0``).
The ``delay`` value is the amount of committed delay in seconds currently
active. This does not account for matches which have not yet been released but
which have passed their release threshold. Delays from belated match releases
will appear only when the match is eventually released (and the corresponding
delay is committed into the state).
Note that this value is only useful during match periods (it will otherwise be
``0``).

The ``matches`` key is a list of the matches which are currently being
played, as measured by the current time falling between the start and end
Expand All @@ -159,7 +163,13 @@ being shepherded for, as measured by the current time falling between the
earliest shepherding signal value and time when staging closes. They are
presented in the same format as the `/matches`_ endpoint uses.

The ``time`` key is the current time on the server.
Each of ``matches``, ``staging_matches``, ``shepherding_matches`` accounts for
the match releasing mechanism. In the case of a match not being released "on
time" then it and subsequent matches are held back and will remain in their
corresponding keys as if time had stopped at the release threshold.

The ``time`` key is the current time on the server. This value progresses
regardless of match holds.

/state
------
Expand Down Expand Up @@ -261,12 +271,14 @@ limits start from the last match and work backwards.
.. code-block:: json

{
"last_released": "...",
"last_scored": "...",
"matches": [
{
"arena": "...",
"display_name": "Match ...",
"num": "...",
"is_released": "bool",
"scores": {
"game": {
"...": "...",
Expand Down Expand Up @@ -301,6 +313,9 @@ limits start from the last match and work backwards.
"end": "...",
"start": "..."
},
"operations": {
"release_threshold": "...",
},
"slot": {
"end": "...",
"start": "..."
Expand All @@ -319,7 +334,8 @@ limits start from the last match and work backwards.
]
}

``last_scored`` contains the same value as in the following endpoint.
``last_released`` contains the same value as in its endpoint below.
``last_scored`` contains the same value as in its endpoint below.
Any dates are in ISO 8601 format.

Only one of the ``league`` or ``normalised`` sub-keys of ``scores`` will be
Expand All @@ -333,6 +349,18 @@ The staging deadline is available in ``times.staging.closes`` while the
``times.staging.signal_shepherds`` value is when shepherds should start looking
for teams although this isn't a strict value.

/matches/last_released
----------------------

.. code-block:: json

{
"last_released": "..."
}

``last_released`` contains the highest match number which has been released,
but may be ``null`` if no matches have yet been released.

/matches/last_scored
--------------------

Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
author="Student Robotics Competition Software SIG",
author_email="srobo-devel@googlegroups.com",
install_requires=[
# TODO(PR): bump srcomp version once we know what version will include match-release
'sr.comp >=1.5, <2',
'Flask >=2.2',
'Werkzeug >= 2, <4',
Expand Down
2 changes: 1 addition & 1 deletion sr/comp/http/json_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def default(self, obj: object) -> Any:
return obj.value
elif isinstance(obj, Match):
comp: SRComp = g.comp_man.get_comp()
return match_json_info(comp, obj)
return match_json_info(comp, obj, comp.schedule.datetime_now)
elif isinstance(obj, datetime.datetime):
return http_date(obj.utctimetuple())
elif isinstance(obj, datetime.date):
Expand Down
31 changes: 20 additions & 11 deletions sr/comp/http/query_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from league_ranker import LeaguePoints, RankedPosition

from sr.comp.comp import SRComp
from sr.comp.match_operations import MatchState
from sr.comp.match_period import Match, MatchType
from sr.comp.types import ArenaName, GamePoints, MatchNumber, ShepherdName, TLA

Expand All @@ -34,6 +35,12 @@ class Times(TypedDict):
end: str


class OpsTimes(TypedDict):
# TODO: I don't like this name here. It fits for consistency with its other
# uses, however it doesn't fit so well alongside e.g: start/end, opens/closes.
release_threshold: str


class StagingTimes(TypedDict):
opens: str
closes: str
Expand All @@ -43,6 +50,7 @@ class StagingTimes(TypedDict):

class MatchTimings(TypedDict):
slot: Times
operations: OpsTimes
game: Times
staging: StagingTimes

Expand All @@ -53,6 +61,7 @@ class _MatchInfo(TypedDict):
arena: ArenaName
teams: list[TLA | None]
type: str # noqa:A003
is_released: bool
times: MatchTimings


Expand All @@ -63,7 +72,7 @@ class MatchInfo(_MatchInfo, total=False):
TParseable = TypeVar('TParseable', int, str, datetime.datetime)


def match_json_info(comp: SRComp, match: Match) -> MatchInfo:
def match_json_info(comp: SRComp, match: Match, when: datetime.datetime) -> MatchInfo:
"""
Get match JSON information.

Expand All @@ -73,13 +82,16 @@ def match_json_info(comp: SRComp, match: Match) -> MatchInfo:
A competition instance.
match : sr.comp.match_periods.Match
A match.
when : datetime.datetime
The current time.

Returns
-------
dict
A :class:`dict` containing JSON suitable output.
"""
match_slot_lengths = comp.schedule.match_slot_lengths
arena_times = comp.operations.get_arena_times(match)
state = comp.operations.get_match_state(match, when)
staging_times = comp.schedule.get_staging_times(match)

info = MatchInfo({
Expand All @@ -88,21 +100,18 @@ def match_json_info(comp: SRComp, match: Match) -> MatchInfo:
'arena': match.arena,
'teams': match.teams,
'type': match.type.value,
'is_released': state == MatchState.RELEASED,
'times': {
'slot': {
'start': match.start_time.isoformat(),
'end': match.end_time.isoformat(),
},
'operations': {
'release_threshold': arena_times.release_threshold.isoformat(),
},
'game': {
'start': (
match.start_time +
match_slot_lengths['pre']
).isoformat(),
'end': (
match.start_time +
match_slot_lengths['pre'] +
match_slot_lengths['match']
).isoformat(),
'start': arena_times.start.isoformat(),
'end': arena_times.end.isoformat(),
},
'staging': {
'opens': staging_times['opens'].isoformat(),
Expand Down
54 changes: 26 additions & 28 deletions sr/comp/http/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,12 @@ def config() -> Response:
return jsonify(config=get_config_dict(comp))


@app.route("/matches/last_released")
def last_released_match() -> Response:
comp: SRComp = g.comp_man.get_comp()
return jsonify(last_released=comp.operations.last_released_match)


@app.route("/matches/last_scored")
def last_scored_match() -> Response:
comp: SRComp = g.comp_man.get_comp()
Expand All @@ -272,10 +278,11 @@ def last_scored_match() -> Response:
@app.route("/matches")
def matches() -> Response:
comp: SRComp = g.comp_man.get_comp()
now = comp.schedule.datetime_now
matches: list[MatchInfo] = []
for slots in comp.schedule.matches:
matches.extend(
match_json_info(comp, match)
match_json_info(comp, match, now)
for match in slots.values()
)

Expand Down Expand Up @@ -338,7 +345,11 @@ def parse_date(string: str) -> datetime.datetime:
else:
raise AssertionError("Limit isn't a number?")

return jsonify(matches=matches, last_scored=comp.scores.last_scored_match)
return jsonify(
matches=matches,
last_released=comp.operations.last_released_match,
last_scored=comp.scores.last_scored_match,
)


@app.route("/periods")
Expand Down Expand Up @@ -372,36 +383,23 @@ def current_state() -> Response:
delay = comp.schedule.delay_at(time)
delay_seconds = int(delay.total_seconds())

matches = [
match_json_info(comp, x)
for x in comp.schedule.matches_at(time)
]

staging_matches = []
shepherding_matches = []
for slot in comp.schedule.matches:
for match in slot.values():
staging_times = comp.schedule.get_staging_times(match)

if time > staging_times['closes']:
# Already done staging
continue

if staging_times['opens'] <= time:
staging_matches.append(match_json_info(comp, match))

signal_shepherds = staging_times['signal_shepherds']
if signal_shepherds:
first_signal = min(signal_shepherds.values())
if first_signal <= time:
shepherding_matches.append(match_json_info(comp, match))
current_matches = comp.operations.get_matches_at(time)

return jsonify(
delay=delay_seconds,
time=time.isoformat(),
matches=matches,
staging_matches=staging_matches,
shepherding_matches=shepherding_matches,
matches=[
match_json_info(comp, x, time)
for x in current_matches.matches
],
staging_matches=[
match_json_info(comp, x, time)
for x in current_matches.staging_matches
],
shepherding_matches=[
match_json_info(comp, x, time)
for x in current_matches.shepherding_matches
],
)


Expand Down
Loading