ESCWN

Check for estimated catch entered as weight instead of numbers

For a few species, and specific forms and methods, estimated catch should be recorded in numbers rather than weight. This check is designed to find and change those records where the weight was recorded instead of numbers.

This is done by comparing the estimated catch with the landings for each trip for each species. If the ratio of landings to estimated catch is less than a specified threshold then the estimated catch is assumed to have been misreported as a catch weight and is adjusted by dividing by a specified average weight.

The check is restricted to those situations where estimated catch in numbers is expected and is only applied to those cases where only an estimate of numbers is available.

First, create a (persistent) table of trip-level summary information. This should be done after effort grooming but is done before estimated catch grooming as the information is used in that process.

-- © Copyright 2026 The Kahawai Collective
-- SPDX-License-Identifier: MIT
\echo 'Trip information'
DROP TABLE IF EXISTS groomed.trip_info;
CREATE TABLE groomed.trip_info AS
WITH expanded_events AS
      (SELECT unnest(array_fill(f.event_key, array[utils.event_num(f.effort_num, f.form_type, f.primary_method)])) AS event_key,
              f.trip,
              f.vessel_id,
              f.form_type,
              f.primary_method,
              f.target_species,
              f.start_stats_area_code,
              f.start_datetime,
              f.end_datetime
         FROM groomed.effort f),

     effort_summary AS
      (SELECT DISTINCT
              x.trip,
              x.vessel_id,
              MIN(DATE(x.start_datetime)) AS first_date_fished,
              MAX(DATE(COALESCE(x.end_datetime,x.start_datetime))) AS last_date_fished,
              MAX(COALESCE(x.end_datetime,x.start_datetime)) AS last_fishing_datetime,
              MODE() WITHIN GROUP (ORDER BY x.form_type) AS modal_form,
              array_agg(DISTINCT x.form_type) AS forms,
              MODE() WITHIN GROUP (ORDER BY x.primary_method) AS modal_method,
              array_agg(DISTINCT x.primary_method) AS methods,
              MODE() WITHIN GROUP (ORDER BY x.target_species) AS modal_target,
              array_agg(DISTINCT x.target_species) AS targets,
              MODE() WITHIN GROUP (ORDER BY x.start_stats_area_code) AS modal_stat_area,
              array_agg(DISTINCT x.start_stats_area_code) AS stat_areas_fished,
              COUNT(DISTINCT DATE(x.start_datetime)) AS days_fished
         FROM expanded_events x
        WHERE x.trip IS NOT NULL
     GROUP BY x.trip,
              x.vessel_id),

     landing_summary AS
      (SELECT DISTINCT
              l.trip,
              l.vessel_id,
              MIN(DATE(l.landing_datetime)) AS first_date_landed,
              MAX(DATE(l.landing_datetime)) AS last_date_landed,
              MAX(l.landing_datetime) AS last_landing_datetime
         FROM groomed.landings l
        WHERE l.trip is not NULL
     GROUP BY l.trip,
              l.vessel_id),

     derived_trip_info AS
     (SELECT DISTINCT trip,
                vessel_id,
                fishyear(COALESCE(l.last_landing_datetime,e.last_fishing_datetime)) AS fishyear,
                EXTRACT(MONTH FROM COALESCE(l.last_landing_datetime,e.last_fishing_datetime)) AS month,
                e.first_date_fished,
                e.last_date_fished,
                l.first_date_landed,
                l.last_date_landed,
                e.days_fished,
                e.modal_form,
                e.forms,
                e.modal_method,
                e.methods,
                e.modal_target,
                e.targets,
                e.modal_stat_area,
                e.stat_areas_fished
           FROM effort_summary e
FULL OUTER JOIN landing_summary l
          USING (trip, vessel_id))

   SELECT d.*,
          t.trip_start_datetime AS mpi_trip_start,
          t.trip_end_datetime AS mpi_trip_end
     FROM derived_trip_info d
LEFT JOIN groomed.raw_trip_details t USING (trip);

CREATE INDEX ON groomed.trip_info (trip, vessel_id);
-- © Copyright 2026 The Kahawai Collective
-- SPDX-License-Identifier: MIT
INSERT INTO grooming_rules
VALUES ('ESCWN', 'edw', 'est_catch', 'Fix', 'Correct cases where estimated catch is recorded in weight but number of fish is expected');

DROP TABLE IF EXISTS groomed.est_catch_grooming;
CREATE TABLE groomed.est_catch_grooming AS
SELECT * FROM (
    VALUES
    -- ('ZZZ', 1, 1),
    ('ALB', 1.5, 5, 20)
) AS t(species_code, minimum, average, maximum);

For the species of interest, calculate the mean weight of fish by method and year for trips where the mean weight does not indicate a weight/numbers reporting problem.

-- © Copyright 2026 The Kahawai Collective
-- SPDX-License-Identifier: MIT
DROP TABLE IF EXISTS groomed.est_catch_grooming_meanwt;
CREATE TABLE groomed.est_catch_grooming_meanwt AS
WITH trip_species_gwt AS (
    SELECT   trip,
             species_code,
             SUM(green_weight) AS sum_green_weight
    FROM     groomed.landings
    WHERE    species_code IN (SELECT species_code FROM groomed.est_catch_grooming)
    AND      trip IS NOT NULL
    AND      green_weight IS NOT NULL
    GROUP BY trip,
             species_code
), trip_species_est_subcatch AS (
    SELECT   e.trip,
             c.species_code,
             SUM(c.catch_number) AS sum_catch_number
    FROM     groomed.effort e
    JOIN     groomed.est_catch c USING (event_key)
    WHERE    e.trip IS NOT NULL
    AND      c.species_code IN (SELECT species_code FROM groomed.est_catch_grooming)
    GROUP BY e.trip,
             c.species_code
), trip_species_gwt_cn AS (
    SELECT g.trip,
           g.species_code,
           g.sum_green_weight,
           c.sum_catch_number
    FROM   trip_species_gwt g
    JOIN   trip_species_est_subcatch c USING (trip, species_code)
)
    SELECT i.fishyear,
           t.species_code,
           i.modal_method,
           SUM(t.sum_green_weight)/SUM(t.sum_catch_number) AS meanwt,
           COUNT(t.trip) AS n_trips
    FROM   trip_species_gwt_cn t
    JOIN   groomed.trip_info i USING (trip)
    JOIN   groomed.est_catch_grooming g USING (species_code)
   WHERE   t.sum_green_weight > t.sum_catch_number * g.minimum
     AND   t.sum_green_weight < t.sum_catch_number * g.maximum
GROUP BY   i.fishyear,
           t.species_code,
           i.modal_method
ORDER BY   i.fishyear,
           t.species_code,
           i.modal_method;


CREATE OR REPLACE FUNCTION ESCWN()
RETURNS VOID AS $$

WITH trip_species_gwt AS (
    SELECT   trip,
             fishyear(MAX(landing_datetime)) AS fishyear,
             species_code,
             sum(green_weight) AS sum_green_weight
    FROM     groomed.landings
    WHERE    species_code IN (SELECT species_code FROM groomed.est_catch_grooming)
    AND      trip IS NOT NULL
    AND      green_weight IS NOT NULL
    GROUP BY trip,
             species_code
), trip_species_est_subcatch AS (
    SELECT   e.trip,
             c.species_code,
             sum(c.catch_weight) AS sum_catch_weight,
             sum(c.catch_number) AS sum_catch_number
    FROM     groomed.effort e
    JOIN     groomed.est_catch c USING (event_key)
    WHERE    e.trip IS NOT NULL
    AND      c.species_code IN (SELECT species_code FROM groomed.est_catch_grooming)
    GROUP BY e.trip,
             c.species_code
), trip_species_gwt_cwtn AS (
    SELECT g.trip,
           g.fishyear,
           g.species_code,
           g.sum_green_weight,
           c.sum_catch_weight,
           c.sum_catch_number
    FROM   trip_species_gwt g
    JOIN   trip_species_est_subcatch c USING (trip, species_code)
)
INSERT INTO groomed.checks (code, "table", "column", id, orig, new, details)
SELECT 'ESCWN',
       'est_catch',
       'catch_number',
       c.id,
       c.catch_number::TEXT AS orig,
       CASE WHEN ROUND(c.catch_number / a.meanwt)> 0 THEN ROUND(c.catch_number / a.meanwt)::TEXT
            ELSE '1'
       END AS new,
       e.trip
FROM   groomed.est_catch c
JOIN   groomed.est_catch_grooming g USING (species_code)
JOIN   groomed.effort e USING (event_key)
JOIN   groomed.est_catch_grooming_meanwt a ON fishyear(e.start_datetime) = a.fishyear
                                           AND e.primary_method = a.modal_method
                                           AND c.species_code = a.species_code
WHERE e.trip IN (
    SELECT trip
    FROM   trip_species_gwt_cwtn
    JOIN   groomed.est_catch_grooming USING (species_code)
    WHERE  sum_green_weight < sum_catch_number * minimum
    AND    trip IS NOT NULL
)
AND utils.expected_quantity(e.form_type, e.primary_method, c.species_code) = 'number'
AND c.catch_weight IS NULL;

UPDATE groomed.est_catch
SET    checks = checks || '{ESCWN}', catch_number = u.new::NUMERIC
FROM   groomed.checks u
WHERE  u.code = 'ESCWN'
AND    groomed.est_catch.id = u.id;

$$ LANGUAGE SQL;
SELECT ESCWN();
Effects of grooming
Figure 1: The number of records affected by the ESCWN rule, by year and source.
Figure 2: The proportion of records affected by the ESCWN rule, by form type, for all form types where some records are groomed by the ESCWN rule.