@@ -299,30 +299,54 @@ def get_total_distance(self, distance_unit=None):
299299 return _distance_in (raw_distance , self .get_effective_odometer_unit (), distance_unit )
300300 return raw_distance
301301
302- def get_average_consumption (self , consumption_unit = None , volume_unit = 'L' ):
303- """Calculate average fuel consumption between the first and last
304- full-tank fill-ups.
305-
306- Sums every litre poured between those two anchors so partial fills
307- in the middle are counted (issue #169). Returns None when any log
308- in the range is flagged ``is_missed`` — we have no way to make the
309- figure honest in that case.
302+ def _valid_consumption_segments (self ):
303+ """Collect (distance, fuel) spans usable for the consumption average.
304+
305+ Each span runs between consecutive full-tank fill-ups, counting every
306+ litre poured within it so partial fills are included (issue #169).
307+ A span containing a log flagged ``is_missed`` is discarded — there is
308+ no way to make that span honest — but spans either side of it remain
309+ usable, so one missed fill-up doesn't invalidate the whole history
310+ (issue #251).
311+
312+ Returns ``None`` when there are fewer than two full-tank anchors,
313+ otherwise a (possibly empty) list of ``(distance, fuel)`` tuples.
310314 """
311315 full_logs = self .fuel_logs .filter_by (is_full_tank = True ).order_by (FuelLog .odometer ).all ()
312316 if len (full_logs ) < 2 :
313317 return None
314318
315- first_odo = full_logs [0 ].odometer
316- last_odo = full_logs [- 1 ].odometer
317319 range_logs = self .fuel_logs .filter (
318- FuelLog .odometer > first_odo ,
319- FuelLog .odometer <= last_odo ,
320- ).all ()
321- if any (log .is_missed for log in range_logs ):
320+ FuelLog .odometer > full_logs [0 ].odometer ,
321+ FuelLog .odometer <= full_logs [- 1 ].odometer ,
322+ ).order_by (FuelLog .odometer ).all ()
323+
324+ segments = []
325+ for start , end in zip (full_logs , full_logs [1 :]):
326+ span_logs = [log for log in range_logs
327+ if start .odometer < log .odometer <= end .odometer ]
328+ if any (log .is_missed for log in span_logs ):
329+ continue
330+ fuel = sum (log .volume for log in span_logs if log .volume )
331+ distance = end .odometer - start .odometer
332+ if distance > 0 and fuel > 0 :
333+ segments .append ((distance , fuel ))
334+ return segments
335+
336+ def get_average_consumption (self , consumption_unit = None , volume_unit = 'L' ):
337+ """Calculate average fuel consumption across full-tank fill-up spans.
338+
339+ Spans contaminated by a missed fill-up are excluded rather than
340+ poisoning the whole figure (issue #251); the average covers every
341+ remaining span, partial fills included (issue #169). Returns None
342+ when no honest span exists.
343+ """
344+ segments = self ._valid_consumption_segments ()
345+ if not segments :
322346 return None
323347
324- total_fuel = sum (log . volume for log in range_logs if log . volume )
325- total_distance = last_odo - first_odo
348+ total_distance = sum (distance for distance , _ in segments )
349+ total_fuel = sum ( fuel for _ , fuel in segments )
326350
327351 if total_distance > 0 and total_fuel > 0 :
328352 odometer_unit = self .get_effective_odometer_unit ()
@@ -348,28 +372,23 @@ def get_consumption_unavailable_reason(self):
348372 helpful empty state instead of a bare dash (issue #214):
349373
350374 - ``'insufficient_full_tanks'`` — fewer than two full-tank fill-ups
351- - ``'missed_fill_up'`` — a fill-up in the range is flagged missed
375+ - ``'missed_fill_up'`` — every span is invalidated by a missed fill-up
352376 - ``'insufficient_data'`` — not enough distance/volume to calculate
353377 """
354- full_logs = self .fuel_logs . filter_by ( is_full_tank = True ). order_by ( FuelLog . odometer ). all ()
355- if len ( full_logs ) < 2 :
378+ segments = self ._valid_consumption_segments ()
379+ if segments is None :
356380 return 'insufficient_full_tanks'
381+ if segments :
382+ return None
357383
358- first_odo = full_logs [0 ].odometer
359- last_odo = full_logs [- 1 ].odometer
384+ full_logs = self .fuel_logs .filter_by (is_full_tank = True ).order_by (FuelLog .odometer ).all ()
360385 range_logs = self .fuel_logs .filter (
361- FuelLog .odometer > first_odo ,
362- FuelLog .odometer <= last_odo ,
386+ FuelLog .odometer > full_logs [ 0 ]. odometer ,
387+ FuelLog .odometer <= full_logs [ - 1 ]. odometer ,
363388 ).all ()
364389 if any (log .is_missed for log in range_logs ):
365390 return 'missed_fill_up'
366-
367- total_fuel = sum (log .volume for log in range_logs if log .volume )
368- total_distance = last_odo - first_odo
369- if total_distance <= 0 or total_fuel <= 0 :
370- return 'insufficient_data'
371-
372- return None
391+ return 'insufficient_data'
373392
374393 def uses_tessie_odometer (self ):
375394 """Check if this vehicle uses Tessie for odometer tracking"""
0 commit comments