{"record":{"id":"a3655957f55b6039","repo":"Freika/dawarich","slug":"failed-to-calculate-h3-hexagon-centers-e-messag","errorCode":null,"errorMessage":"Failed to calculate H3 hexagon centers: #{e.message}","messagePattern":"Failed to calculate H3 hexagon centers: #(.+?)","errorType":"exception","errorClass":"Stats::HexagonCalculator::PostGISError","httpStatus":null,"severity":"error","filePath":"app/services/stats/hexagon_calculator.rb","lineNumber":62,"sourceCode":"    if h3_hash.empty?\n      Rails.logger.info \"No H3 hex IDs calculated for user #{user.id}, #{year}-#{month} (no data)\"\n      return nil\n    end\n\n    if h3_hash.size > MAX_HEXAGONS\n      Rails.logger.warn \"Too many hexagons (#{h3_hash.size}), using lower resolution\"\n      # Try with lower resolution (larger hexagons)\n      lower_resolution = [h3_resolution - 2, 0].max\n      Rails.logger.info \"Recalculating with lower H3 resolution: #{lower_resolution}\"\n      return calculate_hexagons(lower_resolution)\n    end\n\n    Rails.logger.info \"Generated #{h3_hash.size} H3 hexagons at resolution #{h3_resolution} for user #{user.id}\"\n    h3_hash\n  rescue StandardError => e\n    message = \"Failed to calculate H3 hexagon centers: #{e.message}\"\n    ExceptionReporter.call(e, message) if defined?(ExceptionReporter)\n    raise PostGISError, message\n  end\n\n  def start_timestamp\n    (DateTime.new(year, month, 1) - 2.days).to_i\n  end\n\n  def end_timestamp\n    (DateTime.new(year, month, -1, 23, 59, 59) + 2.days).to_i\n  end\n\n  def points\n    return @points if defined?(@points)\n\n    tz = user.timezone_iana\n    @points = user\n              .points\n              .not_anomaly\n              .without_raw_data","sourceCodeStart":44,"sourceCodeEnd":80,"githubUrl":"https://github.com/Freika/dawarich/blob/97fad417c5a11b0eb11157890635e015723a2e97/app/services/stats/hexagon_calculator.rb#L44-L80","documentation":"Stats::HexagonCalculator::PostGISError raised from the rescue in calculate_hexagons: every StandardError thrown while batching through the user's monthly points (each_coordinate_batch) and computing H3 indexes (build_h3_hash) is wrapped and re-raised as PostGISError with the original message appended. Despite the class name, PostGIS itself is only one possible cause - H3 conversion failures, Date argument errors from start_timestamp/end_timestamp (DateTime.new with an invalid year/month), or a dropped PG connection all surface identically here. ExceptionReporter is called first when defined.","triggerScenarios":"Stats::CalculatingJob running for a user/month whose points include coordinates H3 rejects (NaN, infinity, out-of-range lat/lng) via H3.from_geo_coordinates; an invalid year/month (0, 13, negative) making DateTime.new raise; a query timeout or lost connection mid-batch on a very large month.","commonSituations":"Bad GPS data imported before anomaly filtering runs, stats jobs enqueued with garbage year/month values, statement timeouts on months with hundreds of thousands of points, or transient DB failovers during background stat recomputation.","solutions":["Read the message suffix - it is the wrapped original error ('Failed to calculate H3 hexagon centers: <original>') and points at the real cause","For H3/coordinate errors, run Points::AnomalyFilter over the affected range, then retry the job","For timeouts, raise the statement timeout or split the range; the calculator already batches at 50k rows and auto-lowers H3 resolution above 10k hexagons","Validate year/month are real calendar values (1..12, plausible year) before enqueuing Stats::CalculatingJob"],"exampleFix":"# before: job body lets PostGISError kill the job with no trace of the cause\nStats::HexagonCalculator.new(user_id, year, month).call\n\n# after: rescue, log full cause, and re-enqueue once\nbegin\n  Stats::HexagonCalculator.new(user_id, year, month).call\nrescue Stats::HexagonCalculator::PostGISError => e\n  Rails.logger.error(\"hexagons failed for #{year}-#{month}: #{e.message} cause=#{e.cause&.message}\")\n  retry_job wait: 5.minutes if executions < 2\nend","handlingStrategy":"try-catch","validationCode":"# Reject impossible periods before enqueuing stats jobs\nraise ArgumentError, 'bad period' unless year.between?(1970, 2100) && month.between?(1, 12)","typeGuard":"def calculable_period?(year, month)\n  year.to_i.between?(1970, 2100) && month.to_i.between?(1, 12)\nend","tryCatchPattern":"begin\n  Stats::HexagonCalculator.new(user_id, year, month).call\nrescue Stats::HexagonCalculator::PostGISError => e\n  # e.message ends with the wrapped original - log e.cause for the real stack\n  Rails.logger.error(\"hexagons failed #{year}-#{month}: #{e.message} cause=#{e.cause&.class}\")\n  retry_job wait: 10.minutes if executions < 3  # transient DB faults benefit from retry\nend","preventionTips":["Always log e.cause - PostGISError is a wrapper, the real class is underneath","Run Points::AnomalyFilter before regenerating stats for ranges with suspect GPS data","Cap statement timeouts vs. batch size when users have very dense months"],"tags":["stats","h3","postgis","error-wrapping","background-jobs"],"backgroundTag":"postgis-query-failed","analyzedSha":"97fad417c5a11b0eb11157890635e015723a2e97","analyzedAt":"2026-08-21T17:04:17.778Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}