Repository navigation
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #859 +/- ##
==========================================
+ Coverage 92.74% 92.95% +0.21%
==========================================
Files 126 128 +2
Lines 8640 8934 +294
==========================================
+ Hits 8013 8305 +292
- Misses 627 629 +2
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…urations Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… optional columns Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…e count) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ith track/leg routing Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…nputs Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…roupers Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…y track share Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ields Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ber-days, merge reset summaries Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…es, CI-visible leg tests - get_patrol_observations / patrol_segments_from_patrols_df store the two details objects as JSON strings so the frames write to parquet - leg frame patrol_serial_number is a string, matching the track frame group key - new add_patrol_type_display_names task (+ example-return parquet); leg fixtures carry string serial numbers - pause/missing-leader skip runs before the patrol type lookup - mock-only leg tests move out of io-marked modules so CI runs them Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…name task; seed side effects Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The design doc and plan live in Claude Docs; ignore the local copies. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…t; apportion idempotency guard - add_leg_names_to_tracks(df, legs): copies patrol_team_name / patrol_member_names / patrol_asset_names from the leg frame onto a track frame by patrol_segment_id, "None" where the column or the leg is missing. Names are resolved once (on the legs) because mock-io keys fixtures per task function, so the same io task cannot serve both frames in a mock run. - join_summary_tables(tables: list[AnyDataFrame]): reduce left to right so a groupbykey pair with one side missing still renders. - apportion_legs_by_spatial_index skips a spatial grouper whose level is already on the leg frame (second pass with an overlapping grouper set). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…patrol_segment_id Observations recorded before the leg schema carry no leg id, so no track share can be computed; every leg then lands whole in the "None" spatial group instead of raising KeyError (found by the patrol-effort-table before/after regression on the packaged 2015 fixtures). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…litter, patrol-type display copy by slug Three contracts for the patrol-effort-plot chart workflow: - EncounterRateMetric gains ActiveDurationMetric, ElapsedDurationMetric, LegCountMetric and MemberDaysMetric (title order kept), so the chart config, figures and metrics tasks accept the four leg presets. Consumers' positional uiSchema oneOf lists grow 13 -> 17. - get_chart_mode_category_groupers(config) -> UserDefinedGroupers: the Category breakdown as a one-element ValueGrouper list, so explode_for_groupers can explode a member/asset category before the chart splits series. - add_patrol_type_display_from_legs(df, legs): copies the display names resolved on the leg frame onto tracks/events by slug, keeping the slug in patrol_type__value; unknown slugs and empty legs keep the slug. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Off by default (the table keeps its "None" row). On, legs whose track matches no spatial feature — every leg when the fixes carry no leg id, i.e. no patrol_schemas flag — are dropped instead of forming a "None" group. The chart workflow binds it so a flag-off site's spatial breakdown does not gain a zero-valued "None" series. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A summary side with no selected metrics (select_leg_metrics over a track-only metric list) carries only the group keys; in the outer join a key present on that side alone became an all-blank row (a leg whose start bucket has no track row, or the "None" spatial view at a flag-off site). Such a table has nothing to contribute, so it is skipped; when every table is key-only the first is kept so an all-key run still renders. Found by patrol-encounter-rate-stats' before/after comparison. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
9d50ee5 to
d3d5647
Compare
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
atmorling
left a comment
There was a problem hiding this comment.
A couple of questions to sanity check (the patrol type slug assumption + the io tags) but otherwise LGTM
| return cast(EventGDF, patrol_events) | ||
|
|
||
|
|
||
| @register(tags=["io"]) |
There was a problem hiding this comment.
This doesn't need an io tag does it? As in this task itself doesn't interact with the ER API
There was a problem hiding this comment.
The io tag here also enables a mock data parquet with several patrol legs. We could also replace the existing parquet but the existing tests will be affected. What would you recommend?
There was a problem hiding this comment.
I'd like to avoid io tagging tasks that don't need it if possible. Updating the existing mocks seems reasonable - we'll have to redo snapshots on some workflows but that seems worth doing if it means our mock data is changing to reflect the evolution of patrols in the ER API
There was a problem hiding this comment.
☝️ Happy for this to be follow up tech-debt
| return cast(AnyDataFrame, legs) | ||
|
|
||
|
|
||
| @register(tags=["io"]) |
| def add_patrol_type_display_from_legs( | ||
| df: Annotated[ | ||
| AnyDataFrame, | ||
| Field(description="Track or event frame whose `patrol_type` holds the type's value (slug).", exclude=True), | ||
| ], | ||
| legs: Annotated[ | ||
| AnyDataFrame, | ||
| Field(description="Leg frame with `patrol_type` as the display name and the slug in `patrol_type__value`."), | ||
| ], | ||
| ) -> Annotated[ | ||
| AnyDataFrame, | ||
| Field(description="The frame with `patrol_type` as the display name and the slug in `patrol_type__value`."), | ||
| ]: |
There was a problem hiding this comment.
Just a sanity check on the assumption that patrol_type is a slug and not the UUID? IIUC the client returns patrol_type as a UUID
ecoscope/ecoscope/io/earthranger.py
Lines 1199 to 1203 in a8f701d
There was a problem hiding this comment.
Good point. fixed inline
| if isinstance(groupers, AllGrouper): | ||
| return legs | ||
|
|
||
| out = legs |
There was a problem hiding this comment.
Do we definitely want to rename here?
| merged = out.assign(_pos=range(len(out))).merge(shares, on="patrol_segment_id", how="left") | ||
| merged.index = out.index[merged.pop("_pos")] | ||
| if drop_unplaced: | ||
| merged = merged[merged["share"].notna()] | ||
| out = merged | ||
| out["share"] = out["share"].fillna(1.0) | ||
| out[level] = out[level].astype(object).where(out[level].notna(), "None") |
There was a problem hiding this comment.
Is this safe against leg/out being an empty df?
…-typed inputs - patrol_segments_from_patrols_df returns a typed empty frame (LEG_COLUMNS), takes open_leg_end so in-progress legs are counted when not truncating, and counts member-days in the time range's timezone - apportion_legs_by_spatial_index passes an empty frame through and spreads the patrol-level elapsed seconds over the whole patrol's track share - add_patrol_type_display_from_legs reads the slug from patrol_type__value, which the track frame already carries next to the type UUID in patrol_type - explode_for_groupers splits member-days for patrol_members as well as the names - get_patrol_teams tolerates a config without a teams key - get_patrol_observations includes inactive patrol types and no longer fails on a leg whose type is unknown - StatSummaryParam.blank_if_all_nan replaces the leg_member_days special case Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…t-in-tasks Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…tead of an out alias Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
atmorling
left a comment
There was a problem hiding this comment.
Thanks @Yun-Wu - can we make a tech-debt issue to follow up on the io tags? We probably want to land wildlife-dynamics/wt#217 which will help keep the mock data used by workflow x bleeding into workflow y
Summary
Makes the new EarthRanger patrol leg fields (team, members, assets, details, pauses) available to patrol metrics, without changing any existing metric at a site that lacks the
patrol_schemasflag.get_patrol_observationscarriespatrol_segment_idand, when present,patrol_team,patrol_members,patrol_assets,patrol_segment_details,patrol_type_details(details as JSON strings); pause legs are skipped.include_pausesis never sent.get_patrol_segments_from_patrols_df(+ combined-params variant) build one row per non-pause leg with clippedleg_active_seconds,patrol_elapsed_seconds(first leg per patrol) andleg_member_days.select_track_metrics/select_leg_metricsroute the user's list to the trajectory and leg frames andjoin_summary_tablesjoins the two summaries, so existing presets and defaults are untouched.add_patrol_team_names(via the patrol configuration route),add_patrol_roster_names(subjects lookup,include_inactive),add_patrol_type_display_names; the name columns always exist, filled"None"when a site lacks the flag.explode_for_groupersexplodes list-valued grouper columns at run time with full credit per member (member-days split per member);apportion_legs_by_spatial_indexsplits leg metrics across spatial features by share of track length.scripts/seed_patrol_legs_mep_dev.pyseeds mep-dev (idempotent, non-destructive) and a live test reads it back; example-return parquets for the five new IO tasks.built-in-tasks.md.Design doc, including the workflow wiring contract the follow-up workflow PRs implement: https://claude.ai/artifact/CaEvhS5ZPwJbHApTCXde5F
Review fixes
patrol_segments_from_patrols_dfreturns a typed empty frame, ends open legs at the window end even without truncation, and counts member-days in the time range's timezoneapportion_legs_by_spatial_indexpasses empty frames through and spreads the patrol-level elapsed seconds by the whole patrol's track shareadd_patrol_type_display_from_legsreads the slug frompatrol_type__value(the track frame holds the type UUID inpatrol_type)explode_for_grouperssplits member-days forpatrol_memberstoo;get_patrol_teamstolerates a config withoutteams;get_patrol_observationsincludes inactive types and survives an unknown typeStatSummaryParam.blank_if_all_nanreplaces theleg_member_daysspecial caseTest plan
pytest tests/platform -m "not io and not smart_io": 1081 passed (statsmodels-dependent trend tests excluded locally).mypy ecoscope: clean.pytest tests/platform/tasks/test_io_earthranger.py -k live_patrol_legs -m ioagainst mep-dev passes (3 legs, 9 h active, 8 h / 2 h elapsed, resolved names).Closes #858
🤖 Generated with Claude Code