← Overview

How the project fits together

This example shows how the phases connect and how the work meets the rubric. Use it to understand the structure and level of detail, then apply the workflow to your own dataset and questions.

Project requirements and rubric · Student Colab template

Note to students: Points are earned for work that meets the rubric. Completing a section does not automatically earn full credit. The running totals include Phases 0 and 1. This sample earns full credit for the demonstrated work, reviewed across the report and saved outputs. This is an illustrative rubric assessment, not an official grade. Proposal timeliness and submission logistics are not assessed. Students submit this evidence in one executed notebook.

Phase 0 · Prepare documents

Question: Where and when do different fishing methods appear in observed activity, and how much activity lacks registry gear information?

Target: Tier 100. Public Global Fishing Watch data: 2023 monthly activity, January–March daily activity, and 2023 vessel metadata.

namerowsbytes
vessels9645013414239
daily_effort197511741006993374
monthly_effort145601711234778879

Download 142 Wikipedia introductions, then filter and deduplicate with Spark. The filter retains 141; Flag state does not match the selected keywords. Review 30 sampled introductions. Use article descriptions to map fishing methods to gear labels.

Article examples and summaries

Proposal result: keyword-based document/table joinApparent fishing hours for net-fishing categories selected using article text
Rubric: 10 points · 10 available through this section. Sample review: 10/10 earned here; 10/10 earned so far.

Phase 1 · Embeddings and semantic search

ArticlesPassagesText bytes (UTF-8)Embedding dimensions
141228191430384

Split introductions within MiniLM’s token limit, compute normalized embeddings, save Parquet, and load the passages into BigQuery. Search with cosine distance; inspect the returned text before mapping it to a gear category.

Rubric: 10 points · 20 available through this section. Sample review: 10/10 earned here; 20/20 earned so far.

Section 1 · Analytical SQL and charts

Five foundational queries. Sections 2 and 5 reuse these queries.

How do hook- and net-based gear categories divide the selected 2023 fishing hours?

gear_share

WITH mapped AS (
  SELECT DISTINCT
    geartype
  FROM `cs145-p2-sandbox-20261011.cs145_fishing_reference.document_map`
  WHERE
    document_id IN UNNEST(@candidate_ids)
), totals AS (
  SELECT
    m.geartype,
    SUM(m.fishing_hours) AS h
  FROM `cs145-p2-sandbox-20261011.cs145_fishing_reference.monthly_effort` AS m
  JOIN mapped AS g
    USING (geartype)
  WHERE
    date >= @start
    AND date < @end
    AND cell_ll_lat >= @min_lat
    AND cell_ll_lat < @max_lat
    AND fishing_hours > 0
  GROUP BY
    1
)
SELECT
  geartype,
  ROUND(h, 3) AS fishing_hours,
  ROUND(100 * SAFE_DIVIDE(h, SUM(h) OVER ()), 3) AS share_pct
FROM totals
ORDER BY
  fishing_hours DESC,
  geartype
geartypefishing_hoursshare_pct
trawlers59371797.09368.697
drifting_longlines12951238.36614.985
set_gillnets5658155.5886.547
set_longlines5031653.8725.822
squid_jigger1463113.0061.693
other_purse_seines1229496.5971.423
tuna_purse_seines367893.7440.426
purse_seines132393.3280.153
seiners117918.780.136
other_seines102527.3730.119
How does monthly activity differ between hooks and nets?

monthly_method

WITH mapped AS (
  SELECT DISTINCT
    geartype,
    family
  FROM `cs145-p2-sandbox-20261011.cs145_fishing_reference.document_map`
  WHERE
    family IN ('hooks', 'nets')
)
SELECT
  EXTRACT(MONTH FROM m.date) AS month,
  g.family,
  ROUND(SUM(m.fishing_hours), 3) AS fishing_hours
FROM `cs145-p2-sandbox-20261011.cs145_fishing_reference.monthly_effort` AS m
JOIN mapped AS g
  USING (geartype)
WHERE
  date >= @start AND date < @end AND fishing_hours > 0
GROUP BY
  1,
  2
ORDER BY
  1,
  2
monthfamilyfishing_hours
1hooks1366525.355
1nets3953309.058
2hooks1329971.126
2nets4973175.418
3hooks1544811.661
3nets6505557.73
4hooks1574267.098
4nets6429514.505
5hooks1624022.668
5nets2934887.034
6hooks1595971.881
6nets3253341.136
7hooks1670003.492
7nets3205973.074
8hooks1810169.848
8nets5384842.519
9hooks1827462.792
9nets8312605.895
10hooks1868443.458
10nets8442304.389

First 20 of 300 bins shown; the chart uses all 300.

Which one-degree bins have the most net-fishing activity?

hotspots

WITH mapped AS (
  SELECT DISTINCT
    geartype
  FROM `cs145-p2-sandbox-20261011.cs145_fishing_reference.document_map`
  WHERE
    document_id IN UNNEST(@candidate_ids)
)
SELECT
  CAST(FLOOR(cell_ll_lat) AS INT64) AS latitude,
  CAST(FLOOR(cell_ll_lon) AS INT64) AS longitude,
  ROUND(SUM(fishing_hours), 3) AS fishing_hours
FROM `cs145-p2-sandbox-20261011.cs145_fishing_reference.monthly_effort`
JOIN mapped
  USING (geartype)
WHERE
  date >= @start
  AND date < @end
  AND cell_ll_lat >= @min_lat
  AND cell_ll_lat < @max_lat
  AND fishing_hours > 0
GROUP BY
  1,
  2
ORDER BY
  fishing_hours DESC,
  latitude,
  longitude
LIMIT @n
latitudelongitudefishing_hours
301231304784.584
281221151008.815
221161087098.36
311231017043.077
29122892806.259
21113874491.546
29123872757.1
35120848426.09
31126781990.811
23117774658.004
28123764600.681
35119755425.284
23118752123.419
38121710289.146
30124702797.682
37122682842.394
24118682527.979
24119681091.911
10107670452.724
22117661392.064

First 20 of 300 bins shown; the chart uses all 300.

How many vessel IDs and fishing hours are observed by gear in January–March?

fleet_activity

SELECT
  COALESCE(v.vessel_class_gfw, 'unknown') AS geartype,
  COUNT(DISTINCT d.mmsi) AS vessels,
  ROUND(SUM(d.fishing_hours), 3) AS fishing_hours,
  ROUND(SAFE_DIVIDE(SUM(d.fishing_hours), COUNT(DISTINCT d.mmsi)), 3) AS hours_per_vessel
FROM `cs145-p2-sandbox-20261011.cs145_fishing_reference.daily_effort` AS d
LEFT JOIN `cs145-p2-sandbox-20261011.cs145_fishing_reference.vessels` AS v
  ON d.mmsi = v.mmsi AND EXTRACT(YEAR FROM d.date) = v.year
WHERE
  d.date >= @start AND d.date < @end AND d.fishing_hours > 0
GROUP BY
  1
ORDER BY
  fishing_hours DESC,
  geartype
geartypevesselsfishing_hourshours_per_vessel
trawlers3033813953389.63459.931
fishing151443939579.932260.141
drifting_longlines54992818405.807512.531
set_gillnets59021109499.015187.987
set_longlines3644930539.772255.362
fixed_gear3316656828.554198.079
squid_jigger893492312.528551.302
other_purse_seines1923214482.141111.535
pole_and_line996166629.725167.299
dredge_fishing874129046.205147.65
tuna_purse_seines51471441.652138.992
pots_and_traps39267915.233173.253
seiners13931786.106228.677
other_seines7725649.801333.114
purse_seines18725519.114136.466
trollers506845.58136.912
unknown174.82674.826
How much first-quarter activity lacks registry gear information?

metadata_audit

SELECT
  CASE
    WHEN v.mmsi IS NULL
    THEN 'no_metadata'
    WHEN v.vessel_class_registry IS NULL
    THEN 'no_registry_gear'
    ELSE 'registry_gear_available'
  END AS metadata_status,
  COUNT(DISTINCT d.mmsi) AS vessels,
  ROUND(SUM(d.fishing_hours), 3) AS fishing_hours
FROM `cs145-p2-sandbox-20261011.cs145_fishing_reference.daily_effort` AS d
LEFT JOIN `cs145-p2-sandbox-20261011.cs145_fishing_reference.vessels` AS v
  ON d.mmsi = v.mmsi AND EXTRACT(YEAR FROM d.date) = v.year
WHERE
  d.date >= @start AND d.date < @end AND d.fishing_hours > 0
GROUP BY
  1
ORDER BY
  1
metadata_statusvesselsfishing_hours
no_metadata174.826
no_registry_gear5241917767961.48
registry_gear_available174696871909.315
Top 300 net-fishing bins, 2023
Top 300 net-fishing bins, 2023
Monthly share of annual hours, hooks and nets
Monthly share of annual hours, hooks and nets
Registry gear availability: vessel IDs and fishing hours
Registry gear availability: vessel IDs and fishing hours
Rubric: 20 points · 40 available through this section. Sample review: 20/20 earned here; 40/40 earned so far.

Section 2 · Documents and tables

Retrieve once, check the document-to-gear mapping, then use the same candidate IDs in SQL.

How do hook- and net-based gear categories divide the selected 2023 fishing hours?

Search: Fishing techniques that use baited hooks or nets: longline fishing, jigging, gillnetting, seine fishing and trawling

Seine fishing

seine fishing ( or seine - haul fishing ; sayn ) is a method of fishing that employs a surrounding net, called a seine, that hangs vertically in the water with its bottom edge held down by weights and its top edge buoyed by floats. seine nets can be deployed from the shore as a beach seine, or from a boat. boats deploying seine nets are known as seiners. two main types of seine net are deployed from seiners : purse seines and danish seines. a seine differs from a gillnet, in that a seine encloses fish, where a gillnet directly snares fish.

Document 1513400 · cosine distance 0.274

Longline fishing

longline fishing, or longlining, is a commercial fishing angling technique that uses a long main line with baited hooks attached at intervals via short branch lines called snoods or gangions. a snood is attached to the main line using a clip or swivel, with the hook at the other end. longlines are classified mainly by where they are placed in the water column. this can be at the surface or at the bottom. lines can also be set by means of an anchor, or left to drift. hundreds or even thousands of baited hooks can hang from a single line. this can lead to the death of many different marine species known as bycatch. longliners – fishing vessels rigged for longlining – commonly target swordfish, tuna, halibut, sablefish and many other species. in some unstable fisheries, such as the patagonian toothfish, fishermen may be limited to as few as 25 hooks per line. in contrast, commercial longliners in certain robust fisheries of the bering sea and north pacific generally run over 2, 500 hand - baited hooks on a single series of connected lines many miles in length. longlines can be set to hang near

Document 338407 · cosine distance 0.321

Jigging

jigging is the practice of fishing with a jig, a type of weighted fishing lure. a jig consists of a heavy metal ( typically lead ) sinker with an attached fish hook that is usually obscured inside a soft lure or feather - like decorations. jigs are intended to create a jerky, vertical " jumping " motion to attract fish, as opposed to other common lures like swimbaits, spoons and spinnerbaits, which move through the water more or less horizontally. the jig is very versatile and can be used in both salt and fresh water. many deeper water fish species are attracted to the lure, which has made it popular among anglers for years. the jigging technique mainly involves rapid lifting motions of a fishing rod, which jerk the line exert a temporarily upward pull upon the sinking lure. when the target fish is enticed to swallow the lure, the angler then sets the hook to pierce and tether the fish in the mouth. because the jigs are weighted, a harder - than - usual hookset is often needed to overcome the added weight and provide sufficient momentum to pierce the hook point into the fish mouth. jigging gears are often involved with the lu

Document 764476 · cosine distance 0.343

Trawling

trawling is an industrial method of fishing that involves pulling a fishing net through the water behind one or more boats. the net used for trawling is called a trawl. this principle requires netting bags which are towed through water to catch different species of fishes or sometimes targeted species. trawls are often called towed gear or dragged gear. the boats that are used for trawling are called trawlers or draggers. trawlers vary in size from small open boats with as little as 30 hp ( 22 kw ) engines to large factory trawlers with over 10, 000 hp ( 7. 5 mw ). trawling can be carried out by one trawler or by two trawlers fishing cooperatively ( pair trawling ). trawling can be contrasted with trolling. while trawling involves a net and is typically done for commercial usage, trolling instead involves a reel, rod and a bait or a lure and is typically done for recreational purposes. trawling is also commonly used as a scientific sampling, or survey, method.

Document 335840 · cosine distance 0.365

Gillnetting

gillnetting is a fishing method that uses gillnets : vertical panels of netting that hang from a line with regularly spaced floaters that hold the line on the surface of the water. the floats are sometimes called " corks " and the line with corks is generally referred to as a " cork line. " the line along the bottom of the panels is generally weighted. traditionally this line has been weighted with lead and may be referred to as " lead line. " a gillnet is normally set in a straight line. gillnets can be characterized by mesh size, as well as colour and type of filament from which they are made. fish may be caught by gillnets in three ways : wedged – held by the mesh around the body. gilled – held by mesh slipping behind the opercula. tangled – held by teeth, spines, maxillaries, or other protrusions without the body penetrating the mesh. most fish have gills. a fish swims into a net and passes only part way through the mesh. when it struggles to free itself, the twine slips behind the gill cover and prevents escape. gillnets are so effective that their use is closely monitored and regulated by fisheries management and

Document 1003338 · cosine distance 0.396

IDs used: 1003338, 1513400, 335840, 338407, 764476

Source descriptions are checked against the explicit gear mappings. Duplicate evidence must not multiply activity.

SQL and checked result · Definition and test

Which one-degree bins have the most net-fishing activity?

Search: Fishing with nets that surround fish or are dragged through the water

Gillnetting

gillnetting is a fishing method that uses gillnets : vertical panels of netting that hang from a line with regularly spaced floaters that hold the line on the surface of the water. the floats are sometimes called " corks " and the line with corks is generally referred to as a " cork line. " the line along the bottom of the panels is generally weighted. traditionally this line has been weighted with lead and may be referred to as " lead line. " a gillnet is normally set in a straight line. gillnets can be characterized by mesh size, as well as colour and type of filament from which they are made. fish may be caught by gillnets in three ways : wedged – held by the mesh around the body. gilled – held by mesh slipping behind the opercula. tangled – held by teeth, spines, maxillaries, or other protrusions without the body penetrating the mesh. most fish have gills. a fish swims into a net and passes only part way through the mesh. when it struggles to free itself, the twine slips behind the gill cover and prevents escape. gillnets are so effective that their use is closely monitored and regulated by fisheries management and

Document 1003338 · cosine distance 0.349

Seine fishing

seine fishing ( or seine - haul fishing ; sayn ) is a method of fishing that employs a surrounding net, called a seine, that hangs vertically in the water with its bottom edge held down by weights and its top edge buoyed by floats. seine nets can be deployed from the shore as a beach seine, or from a boat. boats deploying seine nets are known as seiners. two main types of seine net are deployed from seiners : purse seines and danish seines. a seine differs from a gillnet, in that a seine encloses fish, where a gillnet directly snares fish.

Document 1513400 · cosine distance 0.357

Trawling

trawling is an industrial method of fishing that involves pulling a fishing net through the water behind one or more boats. the net used for trawling is called a trawl. this principle requires netting bags which are towed through water to catch different species of fishes or sometimes targeted species. trawls are often called towed gear or dragged gear. the boats that are used for trawling are called trawlers or draggers. trawlers vary in size from small open boats with as little as 30 hp ( 22 kw ) engines to large factory trawlers with over 10, 000 hp ( 7. 5 mw ). trawling can be carried out by one trawler or by two trawlers fishing cooperatively ( pair trawling ). trawling can be contrasted with trolling. while trawling involves a net and is typically done for commercial usage, trolling instead involves a reel, rod and a bait or a lure and is typically done for recreational purposes. trawling is also commonly used as a scientific sampling, or survey, method.

Document 335840 · cosine distance 0.383

IDs used: 1003338, 1513400, 335840

Source descriptions are checked against the explicit gear mappings. Duplicate evidence must not multiply activity.

The search also returned hook-fishing articles. They were excluded using the reviewed method mapping. The remaining IDs map to gillnet, seine, and trawl categories.

SQL and checked result · Definition and test

The broad trawlers label cannot distinguish bottom from midwater trawling. Unmapped gear categories remain outside hook/net comparisons.

Rubric: 15 points · 55 available through this section. Sample review: 15/15 earned here; 55/55 earned so far.

Section 3 · Query analysis

Measured scans for the five queries:

QueryBytes scannedSeconds
gear_share5740346911.59
monthly_method4575533000.87
hotspots6905160591.12
fleet_activity5359380321.67
metadata_audit5351563643.03

Model a sort-based grouping step for vessel activity using 128 KiB pages and 64 buffer pages (8 MiB). Assume the daily input grows proportionally, each pass writes its output, and each merge combines at most 63 runs. The estimate excludes join costs and does not describe BigQuery’s physical plan.

ScalePagesMerge passesPage I/Os
1×7683246098
10×768282460968
100×76827536146200

Page I/Os = 2 × input pages × (1 + merge passes).

Rubric: 10 points · 65 available through this section. Sample review: 10/10 earned here; 65/65 earned so far.

Section 4 · Findings and AI use

Findings. Net-fishing activity concentrates in a few coastal bins. Hook and net activity both peak in October in this extract. Registry gear is missing for 72.1% of first-quarter fishing hours.

AI as a consultant. For this staff example, we used AI to brainstorm questions about an unfamiliar domain: fishing activity. The example illustrates the report structure and workflow. Students should develop their own questions and draft their SQL and implementation first, then use AI to brainstorm, explain, or debug under the course AI policy. Check its suggestions and explain your final choices yourself.

Checks in this example. We checked document relevance, gear mappings, and SQL outputs. MiniLM retrieved some hook-fishing articles for a net-fishing question; we inspected the text and excluded them using the reviewed mappings. We joined distinct gear categories so duplicate document evidence would not multiply fishing hours.

Pipeline. Retrieve a seine-fishing article, check its description of a surrounding net, map the article to seine gear categories, and sum their monthly activity. The documents determine which gear categories enter the SQL result.

Tier 80 ends here. Describe AI assistance used during this work and how you checked it. Higher tiers report the A/B experiment separately in Section 7.

Rubric: 15 points · 80 available through this section. Sample review: 15/15 earned here; 80/80 earned so far.

Section 5 · Governed SQL library

The library is a Python dictionary of five reusable SQL definitions. Name each query from Section 1 and specify its rules. Each entry contains a description, typed parameters, output columns, domain rules, and SQL.

  1. Write the five analytical queries.
  2. Replace changing values with parameters and record each query’s rules.
  3. Test each definition on small inputs with known answers.
  4. Freeze the dictionary, then include it in B’s prompt.
DefinitionPurposeTest
gear_shareApparent fishing hours and percent of selected hours by gear; only document-mapped gears.Passed
monthly_methodMonthly apparent fishing hours by verified hook/net family.Passed
hotspotsTop one-degree bins of apparent fishing activity for retrieved document categories.Passed
fleet_activityQ1 observed active fishing vessels and hours by gear, retaining unmatched metadata.Passed
metadata_auditCount metadata availability and associated activity, keeping unmatched rows.Passed

Open a definition to see its exact contents and test. B receives these exact definitions.

gear_share

Apparent fishing hours and percent of selected hours by gear; only document-mapped gears.

Inputs and output

ParameterType
startDATE
endDATE
min_latFLOAT64
max_latFLOAT64

candidate_ids: ARRAY<STRING> is supplied separately by the shared binding helper. It contains checked Wikipedia document IDs.

Output columns: geartype, fishing_hours, share_pct

Rules

Use DISTINCT mapped geartypes from candidate document IDs to avoid multiplying activity. Half-open dates, inclusive lower and exclusive upper latitude. Denominator is only the selected mapped activity. Exclude nonpositive fishing hours.

SQL template

WITH mapped AS (
  SELECT DISTINCT
    geartype
  FROM `cs145-p2-sandbox-20261011.cs145_fishing_reference.document_map`
  WHERE
    document_id IN UNNEST(@candidate_ids)
), totals AS (
  SELECT
    m.geartype,
    SUM(m.fishing_hours) AS h
  FROM `cs145-p2-sandbox-20261011.cs145_fishing_reference.monthly_effort` AS m
  JOIN mapped AS g
    USING (geartype)
  WHERE
    date >= @start
    AND date < @end
    AND cell_ll_lat >= @min_lat
    AND cell_ll_lat < @max_lat
    AND fishing_hours > 0
  GROUP BY
    1
)
SELECT
  geartype,
  ROUND(h, 3) AS fishing_hours,
  ROUND(100 * SAFE_DIVIDE(h, SUM(h) OVER ()), 3) AS share_pct
FROM totals
ORDER BY
  fishing_hours DESC,
  geartype

Test: expected and actual

Expected
geartypefishing_hoursshare_pct
trawlers10050
drifting_longlines5025
set_gillnets5025
Actual
geartypefishing_hoursshare_pct
trawlers100.050.0
drifting_longlines50.025.0
set_gillnets50.025.0

Assertion passed.

monthly_method

Monthly apparent fishing hours by verified hook/net family.

Inputs and output

ParameterType
startDATE
endDATE

Output columns: month, family, fishing_hours

Rules

Use the reviewed gear taxonomy in document_map. DISTINCT geartype,family avoids repeated evidence. Coarse fishing/fixed_gear labels remain unclassified, not assigned to hooks or nets.

SQL template

WITH mapped AS (
  SELECT DISTINCT
    geartype,
    family
  FROM `cs145-p2-sandbox-20261011.cs145_fishing_reference.document_map`
  WHERE
    family IN ('hooks', 'nets')
)
SELECT
  EXTRACT(MONTH FROM m.date) AS month,
  g.family,
  ROUND(SUM(m.fishing_hours), 3) AS fishing_hours
FROM `cs145-p2-sandbox-20261011.cs145_fishing_reference.monthly_effort` AS m
JOIN mapped AS g
  USING (geartype)
WHERE
  date >= @start AND date < @end AND fishing_hours > 0
GROUP BY
  1,
  2
ORDER BY
  1,
  2

Test: expected and actual

Expected
monthfamilyfishing_hours
1hooks50
1nets150
Actual
monthfamilyfishing_hours
1hooks50.0
1nets150.0

Assertion passed.

hotspots

Top one-degree bins of apparent fishing activity for retrieved document categories.

Inputs and output

ParameterType
startDATE
endDATE
min_latFLOAT64
max_latFLOAT64
nINT64

candidate_ids: ARRAY<STRING> is supplied separately by the shared binding helper. It contains checked Wikipedia document IDs.

Output columns: latitude, longitude, fishing_hours

Rules

FLOOR latitude/longitude including negative coordinates. Bin coordinates are lower-left corners, not centroids. Rank hours descending then coordinates. Candidate mapping must be deduplicated.

SQL template

WITH mapped AS (
  SELECT DISTINCT
    geartype
  FROM `cs145-p2-sandbox-20261011.cs145_fishing_reference.document_map`
  WHERE
    document_id IN UNNEST(@candidate_ids)
)
SELECT
  CAST(FLOOR(cell_ll_lat) AS INT64) AS latitude,
  CAST(FLOOR(cell_ll_lon) AS INT64) AS longitude,
  ROUND(SUM(fishing_hours), 3) AS fishing_hours
FROM `cs145-p2-sandbox-20261011.cs145_fishing_reference.monthly_effort`
JOIN mapped
  USING (geartype)
WHERE
  date >= @start
  AND date < @end
  AND cell_ll_lat >= @min_lat
  AND cell_ll_lat < @max_lat
  AND fishing_hours > 0
GROUP BY
  1,
  2
ORDER BY
  fishing_hours DESC,
  latitude,
  longitude
LIMIT @n

Test: expected and actual

Expected
latitudelongitudefishing_hours
-1-1100
102050
112050
Actual
latitudelongitudefishing_hours
-1-1100.0
102050.0
112050.0

Assertion passed.

fleet_activity

Q1 observed active fishing vessels and hours by gear, retaining unmatched metadata.

Inputs and output

ParameterType
startDATE
endDATE

Output columns: geartype, vessels, fishing_hours, hours_per_vessel

Rules

Join on MMSI AND year. Count distinct vessel identifiers across grid cells and dates. Include unmatched daily records under unknown. One vessel-year metadata row per key is a checked prerequisite. Only positive fishing hours. Vessel IDs are AIS identifiers, not guaranteed physical-vessel identities.

SQL template

SELECT
  COALESCE(v.vessel_class_gfw, 'unknown') AS geartype,
  COUNT(DISTINCT d.mmsi) AS vessels,
  ROUND(SUM(d.fishing_hours), 3) AS fishing_hours,
  ROUND(SAFE_DIVIDE(SUM(d.fishing_hours), COUNT(DISTINCT d.mmsi)), 3) AS hours_per_vessel
FROM `cs145-p2-sandbox-20261011.cs145_fishing_reference.daily_effort` AS d
LEFT JOIN `cs145-p2-sandbox-20261011.cs145_fishing_reference.vessels` AS v
  ON d.mmsi = v.mmsi AND EXTRACT(YEAR FROM d.date) = v.year
WHERE
  d.date >= @start AND d.date < @end AND d.fishing_hours > 0
GROUP BY
  1
ORDER BY
  fishing_hours DESC,
  geartype

Test: expected and actual

Expected
geartypevesselsfishing_hourshours_per_vessel
trawlers13030
drifting_longlines11515
unknown155
Actual
geartypevesselsfishing_hourshours_per_vessel
trawlers130.030.0
drifting_longlines115.015.0
unknown15.05.0

Assertion passed.

metadata_audit

Count metadata availability and associated activity, keeping unmatched rows.

Inputs and output

ParameterType
startDATE
endDATE

Output columns: metadata_status, vessels, fishing_hours

Rules

Priority: unmatched MMSI-year -> no_metadata; matched with NULL registry gear -> no_registry_gear; otherwise registry_gear_available. Count DISTINCT MMSI, never sum grid-cell vessel counts. Preserve left join.

SQL template

SELECT
  CASE
    WHEN v.mmsi IS NULL
    THEN 'no_metadata'
    WHEN v.vessel_class_registry IS NULL
    THEN 'no_registry_gear'
    ELSE 'registry_gear_available'
  END AS metadata_status,
  COUNT(DISTINCT d.mmsi) AS vessels,
  ROUND(SUM(d.fishing_hours), 3) AS fishing_hours
FROM `cs145-p2-sandbox-20261011.cs145_fishing_reference.daily_effort` AS d
LEFT JOIN `cs145-p2-sandbox-20261011.cs145_fishing_reference.vessels` AS v
  ON d.mmsi = v.mmsi AND EXTRACT(YEAR FROM d.date) = v.year
WHERE
  d.date >= @start AND d.date < @end AND d.fishing_hours > 0
GROUP BY
  1
ORDER BY
  1

Test: expected and actual

Expected
metadata_statusvesselsfishing_hours
no_metadata15
no_registry_gear130
registry_gear_available115
Actual
metadata_statusvesselsfishing_hours
no_metadata15.0
no_registry_gear130.0
registry_gear_available115.0

Assertion passed.

How the library is set up and called

Store each entry under its name in LIBRARY. The helper binds typed values; it does not splice values into SQL strings.

LIBRARY = {
  "gear_share": {
    "description": "Apparent fishing hours and percent of selected hours by gear; only document-mapped gears.",
    "parameters": {
      "start": "DATE",
      "end": "DATE",
      "min_lat": "FLOAT64",
      "max_lat": "FLOAT64"
    },
    "output_columns": [
      "geartype",
      "fishing_hours",
      "share_pct"
    ],
    "domain_rules": "Use DISTINCT mapped geartypes from candidate document IDs to avoid multiplying activity. Half-open dates, inclusive lower and exclusive upper latitude. Denominator is only the selected mapped activity. Exclude nonpositive fishing hours.",
    "sql": "WITH mapped AS (SELECT DISTINCT geartype FROM `cs145-p2-sandbox-20261011.cs145_fishing_reference.document_map` WHERE document_id IN UNNEST(@candidate_ids)), totals AS (\nSELECT m.geartype,SUM(m.fishing_hours) h FROM `cs145-p2-sandbox-20261011.cs145_fishing_reference.monthly_effort` m JOIN mapped g USING(geartype)\nWHERE date>=@start AND date<@end AND cell_ll_lat>=@min_lat AND cell_ll_lat<@max_lat AND fishing_hours>0 GROUP BY 1)\nSELECT geartype,ROUND(h,3) fishing_hours,ROUND(100*SAFE_DIVIDE(h,SUM(h) OVER()),3) share_pct FROM totals ORDER BY fishing_hours DESC,geartype"
  },
  "monthly_method": {
    "description": "Monthly apparent fishing hours by verified hook/net family.",
    "parameters": {
      "start": "DATE",
      "end": "DATE"
    },
    "output_columns": [
      "month",
      "family",
      "fishing_hours"
    ],
    "domain_rules": "Use the reviewed gear taxonomy in document_map. DISTINCT geartype,family avoids repeated evidence. Coarse fishing/fixed_gear labels remain unclassified, not assigned to hooks or nets.",
    "sql": "WITH mapped AS (SELECT DISTINCT geartype,family FROM `cs145-p2-sandbox-20261011.cs145_fishing_reference.document_map` WHERE family IN ('hooks','nets'))\nSELECT EXTRACT(MONTH FROM m.date) month,g.family,ROUND(SUM(m.fishing_hours),3) fishing_hours FROM `cs145-p2-sandbox-20261011.cs145_fishing_reference.monthly_effort` m JOIN mapped g USING(geartype)\nWHERE date>=@start AND date<@end AND fishing_hours>0 GROUP BY 1,2 ORDER BY 1,2"
  },
  "hotspots": {
    "description": "Top one-degree bins of apparent fishing activity for retrieved document categories.",
    "parameters": {
      "start": "DATE",
      "end": "DATE",
      "min_lat": "FLOAT64",
      "max_lat": "FLOAT64",
      "n": "INT64"
    },
    "output_columns": [
      "latitude",
      "longitude",
      "fishing_hours"
    ],
    "domain_rules": "FLOOR latitude/longitude including negative coordinates. Bin coordinates are lower-left corners, not centroids. Rank hours descending then coordinates. Candidate mapping must be deduplicated.",
    "sql": "WITH mapped AS (SELECT DISTINCT geartype FROM `cs145-p2-sandbox-20261011.cs145_fishing_reference.document_map` WHERE document_id IN UNNEST(@candidate_ids))\nSELECT CAST(FLOOR(cell_ll_lat) AS INT64) latitude,CAST(FLOOR(cell_ll_lon) AS INT64) longitude,ROUND(SUM(fishing_hours),3) fishing_hours\nFROM `cs145-p2-sandbox-20261011.cs145_fishing_reference.monthly_effort` JOIN mapped USING(geartype)\nWHERE date>=@start AND date<@end AND cell_ll_lat>=@min_lat AND cell_ll_lat<@max_lat AND fishing_hours>0 GROUP BY 1,2 ORDER BY fishing_hours DESC,latitude,longitude LIMIT @n"
  },
  "fleet_activity": {
    "description": "Q1 observed active fishing vessels and hours by gear, retaining unmatched metadata.",
    "parameters": {
      "start": "DATE",
      "end": "DATE"
    },
    "output_columns": [
      "geartype",
      "vessels",
      "fishing_hours",
      "hours_per_vessel"
    ],
    "domain_rules": "Join on MMSI AND year. Count distinct vessel identifiers across grid cells and dates. Include unmatched daily records under unknown. One vessel-year metadata row per key is a checked prerequisite. Only positive fishing hours. Vessel IDs are AIS identifiers, not guaranteed physical-vessel identities.",
    "sql": "SELECT COALESCE(v.vessel_class_gfw,'unknown') geartype,COUNT(DISTINCT d.mmsi) vessels,ROUND(SUM(d.fishing_hours),3) fishing_hours,\nROUND(SAFE_DIVIDE(SUM(d.fishing_hours),COUNT(DISTINCT d.mmsi)),3) hours_per_vessel FROM `cs145-p2-sandbox-20261011.cs145_fishing_reference.daily_effort` d LEFT JOIN `cs145-p2-sandbox-20261011.cs145_fishing_reference.vessels` v ON d.mmsi=v.mmsi AND EXTRACT(YEAR FROM d.date)=v.year\nWHERE d.date>=@start AND d.date<@end AND d.fishing_hours>0 GROUP BY 1 ORDER BY fishing_hours DESC,geartype"
  },
  "metadata_audit": {
    "description": "Count metadata availability and associated activity, keeping unmatched rows.",
    "parameters": {
      "start": "DATE",
      "end": "DATE"
    },
    "output_columns": [
      "metadata_status",
      "vessels",
      "fishing_hours"
    ],
    "domain_rules": "Priority: unmatched MMSI-year -> no_metadata; matched with NULL registry gear -> no_registry_gear; otherwise registry_gear_available. Count DISTINCT MMSI, never sum grid-cell vessel counts. Preserve left join.",
    "sql": "SELECT CASE WHEN v.mmsi IS NULL THEN 'no_metadata' WHEN v.vessel_class_registry IS NULL THEN 'no_registry_gear' ELSE 'registry_gear_available' END metadata_status,\nCOUNT(DISTINCT d.mmsi) vessels,ROUND(SUM(d.fishing_hours),3) fishing_hours FROM `cs145-p2-sandbox-20261011.cs145_fishing_reference.daily_effort` d LEFT JOIN `cs145-p2-sandbox-20261011.cs145_fishing_reference.vessels` v ON d.mmsi=v.mmsi AND EXTRACT(YEAR FROM d.date)=v.year\nWHERE d.date>=@start AND d.date<@end AND d.fishing_hours>0 GROUP BY 1 ORDER BY 1"
  }
}
def parameters(entry,values,ids=None):
    out=[bigquery.ScalarQueryParameter(k,typ,values[k]) for k,typ in entry['parameters'].items()]
    if '@candidate_ids' in entry['sql']:out.append(bigquery.ArrayQueryParameter('candidate_ids','STRING',ids or []))
    return out

def run_definition(name,values,ids=None):
    entry=LIBRARY[name]
    return run_select(entry['sql'],parameters(entry,values,ids))

result = run_definition(
    'gear_share',
    dict(start='2023-01-01', end='2024-01-01', min_lat=-90., max_lat=90.),
    ids=['1003338', '1513400', '335840', '338407', '764476']
)
Fixture inputs and assertions for all five tests
# Five hand-checked fixtures run the same library SQL on tiny inline tables.
# Duplicate evidence rows must not duplicate activity; unmatched metadata survives.
fixture='''vessels AS (
 SELECT 'v1' mmsi,2023 year,'trawlers' vessel_class_gfw,CAST(NULL AS STRING) vessel_class_registry
 UNION ALL SELECT 'v2',2023,'drifting_longlines','drifting_longlines'),
daily_effort AS (
 SELECT DATE '2023-01-01' date,'v1' mmsi,10.0 fishing_hours
 UNION ALL SELECT DATE '2023-01-02','v1',20.0
 UNION ALL SELECT DATE '2023-01-01','v2',15.0
 UNION ALL SELECT DATE '2023-01-01','v3',5.0),
monthly_effort AS (
 SELECT DATE '2023-01-01' date,'trawlers' geartype,-0.2 cell_ll_lat,-0.2 cell_ll_lon,100.0 fishing_hours
 UNION ALL SELECT DATE '2023-01-01','drifting_longlines',10.2,20.1,50.0
 UNION ALL SELECT DATE '2023-01-01','set_gillnets',11.2,20.2,50.0),
document_map AS (
 SELECT 'd1' document_id,'trawlers' geartype,'nets' family
 UNION ALL SELECT 'd1','trawlers','nets'
 UNION ALL SELECT 'd2','drifting_longlines','hooks'
 UNION ALL SELECT 'd3','set_gillnets','nets')'''
expected={
'gear_share':[dict(geartype='trawlers',fishing_hours=100,share_pct=50),dict(geartype='drifting_longlines',fishing_hours=50,share_pct=25),dict(geartype='set_gillnets',fishing_hours=50,share_pct=25)],
'monthly_method':[dict(month=1,family='hooks',fishing_hours=50),dict(month=1,family='nets',fishing_hours=150)],
'hotspots':[dict(latitude=-1,longitude=-1,fishing_hours=100),dict(latitude=10,longitude=20,fishing_hours=50),dict(latitude=11,longitude=20,fishing_hours=50)],
'fleet_activity':[dict(geartype='trawlers',vessels=1,fishing_hours=30,hours_per_vessel=30),dict(geartype='drifting_longlines',vessels=1,fishing_hours=15,hours_per_vessel=15),dict(geartype='unknown',vessels=1,fishing_hours=5,hours_per_vessel=5)],
'metadata_audit':[dict(metadata_status='no_metadata',vessels=1,fishing_hours=5),dict(metadata_status='no_registry_gear',vessels=1,fishing_hours=30),dict(metadata_status='registry_gear_available',vessels=1,fishing_hours=15)]}
values=dict(start='2023-01-01',end='2024-01-01',min_lat=-90.,max_lat=90.,n=3)
LIBRARY_TESTS=[]
for name,entry in LIBRARY.items():
    sql=entry['sql']
    for table in ['vessels','daily_effort','monthly_effort','document_map']:sql=sql.replace(f'`{T}.{table}`',table)
    sql='WITH '+fixture+', '+sql[5:] if sql.startswith('WITH ') else 'WITH '+fixture+' '+sql
    result=run_select(sql,parameters(entry,values,['d1','d2','d3']))
    assert results_equal(result['rows'],expected[name],entry['output_columns'],ordered=True),(name,result)
    LIBRARY_TESTS.append({'definition':name,'expected':expected[name],'actual':result['rows'],'passed':True})
print('Five library fixtures passed: duplicate mappings, duplicate vessel observations, NULL registry gear, missing metadata, and negative-coordinate flooring.')
display(pd.DataFrame([{'definition':x['definition'],'passed':x['passed']} for x in LIBRARY_TESTS]))
(ROOT/'tests.json').write_text(json.dumps(LIBRARY_TESTS,default=str,indent=2))
Rubric: 7 points · 87 available through this section. Sample review: 7/7 earned here; 87/87 earned so far.

Section 6 · Ground truth

Choose five new questions that reuse or combine the Section 5 library. Write reference SQL, verify its results, and retain the answer key separately from the prompts.

Open the five questions and verified reference answers

The linked comparison record also includes the Tier 100 prompts and responses; these are not required for Tier 93.

Questions and coverage: 2 PTS; reference SQL: 2 PTS; verified results and checks: 2 PTS.

Rubric: 6 PTS · 93 available through this section. Example outputs are preserved; the revised rubric has not been re-scored.

Section 7 · Text-to-SQL comparison and evaluation

Use the five questions and reference answers from Section 6. A receives each question, schema, output columns, and candidate IDs. B also receives the Section 5 library. Neither receives the reference SQL or expected results.

We called GPT-5.2 separately for each A/B response and saved the original responses in the notebook for replay.

Prompt setup
messages = build_ab_messages(
    question, schema_notes, LIBRARY,
    candidate_ids=candidate_ids,
    output_columns=output_columns
)
QuestionSchema onlyWith definitions
q1CorrectCorrect
q2IncorrectCorrect
q3CorrectCorrect
q4CorrectCorrect
q5IncorrectCorrect

A: 3/5. B: 5/5. Question 5 accepts month-start dates and month numbers; all other values are compared exactly after the requested rounding. The original strict-format result is preserved.

The two-year counterexamples expose a missing join key even when current-data results agree. See the SQL comparison and counterexample.

A/B prompts and records: 2 PTS; safe execution: 2 PTS; correctness comparison: 2 PTS; error analysis: 1 PT.

Rubric: 7 PTS · 100 available through this section. Example outputs are preserved; the revised rubric has not been re-scored.

The required submission is one executed notebook. This page presents its saved results; it does not replace the notebook or a clean rerun.