Skip to content

Commit be4864e

Browse files
committed
Science Commit 5.
1 parent 1d45b38 commit be4864e

3 files changed

Lines changed: 304 additions & 0 deletions

File tree

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
#!/bin/bash
2+
# ============================================================================
3+
# Brarner.M.Alete™ — Download Taxonomy Descriptions from GBIF (v3)
4+
# Uses GBIF Species API — inserts into MySQL as it goes, skips existing entries
5+
# ============================================================================
6+
7+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
8+
BMA_ROOT="$(dirname "$SCRIPT_DIR")"
9+
DB_PROPS="$BMA_ROOT/servlets/servlet/src/main/webapp/WEB-INF/db.properties"
10+
TMPFILE="/tmp/gbif-response.json"
11+
12+
echo "═══════════════════════════════════════════════════════════════"
13+
echo " Brarner.M.Alete™ — Download Taxonomy Descriptions (GBIF v3)"
14+
echo "═══════════════════════════════════════════════════════════════"
15+
16+
# ─── Read DB credentials (handle special chars in password) ───
17+
if [ -f "$DB_PROPS" ]; then
18+
DB_USER=$(grep '^db.user=' "$DB_PROPS" | cut -d= -f2-)
19+
DB_PASS=$(grep '^db.password=' "$DB_PROPS" | cut -d= -f2-)
20+
DB_HOST=$(grep '^db.url=' "$DB_PROPS" | sed -n 's|.*://\([^:/]*\).*|\1|p')
21+
DB_PORT=$(grep '^db.url=' "$DB_PROPS" | sed -n 's|.*:\([0-9]*\)/.*|\1|p')
22+
DB_HOST="${DB_HOST:-127.0.0.1}"
23+
DB_PORT="${DB_PORT:-3306}"
24+
else
25+
echo "[!] db.properties not found at: $DB_PROPS"
26+
exit 1
27+
fi
28+
29+
# ─── MySQL helper function (handles special chars in password) ───
30+
run_mysql() {
31+
mysql --user="$DB_USER" --password="$DB_PASS" --host="$DB_HOST" --port="$DB_PORT" --database="BrarnerScience" "$@" 2>/dev/null
32+
}
33+
34+
# ─── Verify MySQL connectivity ───
35+
echo "[*] Testing MySQL connection..."
36+
if ! run_mysql -e "SELECT 1;" >/dev/null 2>&1; then
37+
echo "[!] Cannot connect to MySQL. Check db.properties credentials."
38+
exit 1
39+
fi
40+
echo "[✓] MySQL connection OK"
41+
42+
# ─── Ensure gbif_key column exists (MySQL 8.0 compatible) ───
43+
HAS_GBIF_KEY=$(run_mysql -N -B -e "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='BrarnerScience' AND TABLE_NAME='taxonomy_descriptions' AND COLUMN_NAME='gbif_key';")
44+
if [ "$HAS_GBIF_KEY" = "0" ]; then
45+
echo "[*] Adding gbif_key column..."
46+
run_mysql -e "ALTER TABLE taxonomy_descriptions ADD COLUMN gbif_key INT;"
47+
fi
48+
49+
GBIF_API="https://api.gbif.org/v1"
50+
TOTAL_INSERTED=0
51+
TOTAL_SKIPPED=0
52+
TOTAL_FAILED=0
53+
54+
# ─── Core: fetch from GBIF and insert into DB ───
55+
fetch_and_insert() {
56+
local rank_level="$1"
57+
local taxon_name="$2"
58+
local safe_name
59+
safe_name=$(echo "$taxon_name" | sed "s/'/''/g")
60+
61+
# Skip if already exists in DB
62+
local cnt
63+
cnt=$(run_mysql -N -B -e "SELECT COUNT(*) FROM taxonomy_descriptions WHERE rank_level='${rank_level}' AND taxon_name='${safe_name}';")
64+
if [ "$cnt" -gt 0 ] 2>/dev/null; then
65+
TOTAL_SKIPPED=$((TOTAL_SKIPPED + 1))
66+
return 0
67+
fi
68+
69+
# Map rank to GBIF format
70+
local gbif_rank
71+
gbif_rank=$(echo "$rank_level" | tr '[:lower:]' '[:upper:]')
72+
73+
# URL-encode the taxon name
74+
local encoded
75+
encoded=$(python3 -c "import urllib.parse, sys; print(urllib.parse.quote(sys.argv[1]))" "$taxon_name" 2>/dev/null)
76+
77+
# Fetch from GBIF
78+
curl -s --max-time 10 "${GBIF_API}/species/search?q=${encoded}&rank=${gbif_rank}&limit=1" -o "$TMPFILE" 2>/dev/null
79+
local curl_rc=$?
80+
81+
if [ $curl_rc -ne 0 ] || [ ! -s "$TMPFILE" ]; then
82+
TOTAL_FAILED=$((TOTAL_FAILED + 1))
83+
return 1
84+
fi
85+
86+
# Parse JSON and build INSERT statement with python3
87+
local sql
88+
sql=$(python3 - "$TMPFILE" "$rank_level" "$safe_name" << 'PYEOF'
89+
import json, sys
90+
91+
try:
92+
tmpfile = sys.argv[1]
93+
rank_level = sys.argv[2]
94+
safe_name = sys.argv[3]
95+
96+
with open(tmpfile) as f:
97+
data = json.load(f)
98+
results = data.get("results", [])
99+
if not results:
100+
sys.exit(1)
101+
r = results[0]
102+
key = r.get("key", 0)
103+
canonical = r.get("canonicalName", r.get("scientificName", ""))
104+
rank_str = r.get("rank", "").lower()
105+
num_desc = r.get("numDescendants", 0)
106+
status = r.get("taxonomicStatus", "")
107+
108+
# Build description
109+
desc = f"{canonical} is a {rank_str}"
110+
if r.get("kingdom"): desc += f" in kingdom {r['kingdom']}"
111+
if r.get("phylum"): desc += f", phylum {r['phylum']}"
112+
if num_desc:
113+
desc += f". Contains approximately {num_desc} known descendant taxa"
114+
desc += "."
115+
if status:
116+
desc += f" Taxonomic status: {status}."
117+
118+
# Lineage as characteristics
119+
parts = []
120+
for k in ["kingdom", "phylum", "class", "order", "family"]:
121+
if r.get(k): parts.append(f"{k.title()}: {r[k]}")
122+
lineage = ", ".join(parts)
123+
124+
wiki = f"https://en.wikipedia.org/wiki/{canonical.replace(' ', '_')}"
125+
126+
# Escape for SQL
127+
desc = desc.replace("\\", "\\\\").replace("'", "''")[:1000]
128+
lineage = lineage.replace("'", "''")[:500]
129+
safe_name = safe_name.replace("\\", "\\\\")
130+
131+
print(f"INSERT INTO taxonomy_descriptions (rank_level, taxon_name, description, characteristics, wikipedia_url, gbif_key) VALUES('{rank_level}', '{safe_name}', '{desc}', '{lineage}', '{wiki}', {key}) ON DUPLICATE KEY UPDATE description=VALUES(description), characteristics=VALUES(characteristics), wikipedia_url=VALUES(wikipedia_url), gbif_key=VALUES(gbif_key);")
132+
except Exception as e:
133+
sys.exit(1)
134+
PYEOF
135+
)
136+
137+
if [ -n "$sql" ]; then
138+
if run_mysql -e "$sql"; then
139+
TOTAL_INSERTED=$((TOTAL_INSERTED + 1))
140+
return 0
141+
else
142+
TOTAL_FAILED=$((TOTAL_FAILED + 1))
143+
return 1
144+
fi
145+
else
146+
TOTAL_FAILED=$((TOTAL_FAILED + 1))
147+
return 1
148+
fi
149+
}
150+
151+
# ─── Phase 1: Classes ───
152+
echo ""
153+
echo "[1/3] Classes..."
154+
CLASS_LIST=$(run_mysql -N -B -e "SELECT DISTINCT class_name FROM animalia WHERE class_name IS NOT NULL AND class_name!='' ORDER BY class_name;")
155+
C=0
156+
CT=$(echo "$CLASS_LIST" | grep -c .)
157+
while IFS= read -r t; do
158+
[ -z "$t" ] && continue
159+
C=$((C + 1))
160+
printf "\r [%d/%d] %-40s" "$C" "$CT" "$t"
161+
fetch_and_insert "class" "$t"
162+
sleep 0.3
163+
done <<< "$CLASS_LIST"
164+
echo ""
165+
echo " Classes done: $TOTAL_INSERTED inserted, $TOTAL_SKIPPED skipped, $TOTAL_FAILED failed"
166+
167+
# ─── Phase 2: Orders ───
168+
echo ""
169+
echo "[2/3] Orders..."
170+
BATCH_INS=$TOTAL_INSERTED
171+
BATCH_SKIP=$TOTAL_SKIPPED
172+
BATCH_FAIL=$TOTAL_FAILED
173+
ORDER_LIST=$(run_mysql -N -B -e "SELECT DISTINCT order_name FROM animalia WHERE order_name IS NOT NULL AND order_name!='' ORDER BY order_name;")
174+
C=0
175+
CT=$(echo "$ORDER_LIST" | grep -c .)
176+
while IFS= read -r t; do
177+
[ -z "$t" ] && continue
178+
C=$((C + 1))
179+
[ $((C % 5)) -eq 0 ] || [ $C -eq 1 ] && printf "\r [%d/%d] %-40s" "$C" "$CT" "$t"
180+
fetch_and_insert "order" "$t"
181+
sleep 0.3
182+
done <<< "$ORDER_LIST"
183+
echo ""
184+
echo " Orders done: $((TOTAL_INSERTED - BATCH_INS)) inserted, $((TOTAL_SKIPPED - BATCH_SKIP)) skipped, $((TOTAL_FAILED - BATCH_FAIL)) failed"
185+
186+
# ─── Phase 3: Families ───
187+
echo ""
188+
echo "[3/3] Families (largest batch — ~15 min)..."
189+
BATCH_INS=$TOTAL_INSERTED
190+
BATCH_SKIP=$TOTAL_SKIPPED
191+
BATCH_FAIL=$TOTAL_FAILED
192+
FAMILY_LIST=$(run_mysql -N -B -e "SELECT DISTINCT family_name FROM animalia WHERE family_name IS NOT NULL AND family_name!='' ORDER BY family_name;")
193+
C=0
194+
CT=$(echo "$FAMILY_LIST" | grep -c .)
195+
while IFS= read -r t; do
196+
[ -z "$t" ] && continue
197+
C=$((C + 1))
198+
[ $((C % 20)) -eq 0 ] || [ $C -eq 1 ] && printf "\r [%d/%d] %-40s" "$C" "$CT" "$t"
199+
fetch_and_insert "family" "$t"
200+
sleep 0.3
201+
done <<< "$FAMILY_LIST"
202+
echo ""
203+
echo " Families done: $((TOTAL_INSERTED - BATCH_INS)) inserted, $((TOTAL_SKIPPED - BATCH_SKIP)) skipped, $((TOTAL_FAILED - BATCH_FAIL)) failed"
204+
205+
# ─── Summary ───
206+
rm -f "$TMPFILE"
207+
FINAL=$(run_mysql -N -B -e "SELECT COUNT(*) FROM taxonomy_descriptions;")
208+
echo ""
209+
echo "═══════════════════════════════════════════════════════════════"
210+
echo " [✓] Complete — $FINAL total records in taxonomy_descriptions"
211+
run_mysql -N -B -e "SELECT CONCAT(' ', rank_level, ': ', COUNT(*)) FROM taxonomy_descriptions GROUP BY rank_level;"
212+
echo ""
213+
echo " Total inserted this run: $TOTAL_INSERTED"
214+
echo " Total skipped (existed): $TOTAL_SKIPPED"
215+
echo " Total failed: $TOTAL_FAILED"
216+
echo "═══════════════════════════════════════════════════════════════"
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
#!/bin/bash
2+
# Brarner.M.Alete™ — Populate Taxonomy Descriptions
3+
# Creates taxonomy_descriptions table and populates with Wikipedia-sourced descriptions
4+
# for kingdom, class, order, and family taxonomic levels.
5+
# Usage: bash install/populate-taxonomy-descriptions.sh
6+
set -e
7+
8+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
9+
BMA_ROOT="$(dirname "$SCRIPT_DIR")"
10+
DB_PROPS="$BMA_ROOT/servlets/servlet/src/main/webapp/WEB-INF/db.properties"
11+
12+
echo "═══════════════════════════════════════════════════════════════"
13+
echo " Brarner.M.Alete™ — Populate Taxonomy Descriptions"
14+
echo "═══════════════════════════════════════════════════════════════"
15+
16+
if [ -f "$DB_PROPS" ]; then
17+
DB_USER=$(grep '^db.user=' "$DB_PROPS" | cut -d= -f2-)
18+
DB_PASS=$(grep '^db.password=' "$DB_PROPS" | cut -d= -f2-)
19+
DB_HOST=$(grep '^db.url=' "$DB_PROPS" | sed -n 's|.*://\([^:/]*\).*|\1|p')
20+
DB_PORT=$(grep '^db.url=' "$DB_PROPS" | sed -n 's|.*:\([0-9]*\)/.*|\1|p')
21+
DB_HOST="${DB_HOST:-127.0.0.1}"
22+
DB_PORT="${DB_PORT:-3306}"
23+
MYSQL_OPTS="-u${DB_USER} -h${DB_HOST} -P${DB_PORT}"
24+
[ -n "$DB_PASS" ] && MYSQL_OPTS="$MYSQL_OPTS -p${DB_PASS}"
25+
else
26+
echo "[!] db.properties not found."; exit 1
27+
fi
28+
29+
echo "[*] Creating taxonomy_descriptions table..."
30+
31+
mysql $MYSQL_OPTS << 'SQL'
32+
USE BrarnerScience;
33+
34+
CREATE TABLE IF NOT EXISTS taxonomy_descriptions (
35+
id INT AUTO_INCREMENT PRIMARY KEY,
36+
rank_level ENUM('kingdom','phylum','class','order','family') NOT NULL,
37+
taxon_name VARCHAR(200) NOT NULL,
38+
description TEXT,
39+
characteristics TEXT,
40+
example_species VARCHAR(500),
41+
wikipedia_url VARCHAR(500),
42+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
43+
UNIQUE KEY uk_rank_taxon (rank_level, taxon_name),
44+
INDEX idx_rank (rank_level),
45+
INDEX idx_name (taxon_name)
46+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
47+
48+
-- Kingdom descriptions
49+
INSERT INTO taxonomy_descriptions (rank_level, taxon_name, description, characteristics, example_species, wikipedia_url) VALUES
50+
('kingdom', 'Animalia', 'The kingdom of multicellular eukaryotic organisms that form the biological kingdom Animalia. Animals are motile, heterotrophic (consume organic material), and generally reproduce sexually.', 'Multicellular, eukaryotic, heterotrophic, motile at some life stage, no cell walls, embryonic development through blastula stage', 'Homo sapiens, Canis lupus, Aquila chrysaetos, Tursiops truncatus', 'https://en.wikipedia.org/wiki/Animal')
51+
ON DUPLICATE KEY UPDATE description=VALUES(description), characteristics=VALUES(characteristics);
52+
53+
-- Class descriptions (Mollusca and worm-like invertebrates from the URL path shown)
54+
INSERT INTO taxonomy_descriptions (rank_level, taxon_name, description, characteristics, example_species, wikipedia_url) VALUES
55+
('class', 'Aplotegmentaria', 'A class of shell-less, worm-like molluscs (solenogasters/aplacophorans). These marine animals lack a shell and mantle cavity, instead having a body covered with calcareous spicules embedded in a cuticle.', 'Vermiform (worm-shaped), no shell, calcareous spicules in cuticle, marine benthic, feed on cnidarians or detritus, hermaphroditic', 'Neomenia carinata, Epimenia australis, Wirenia argentea', 'https://en.wikipedia.org/wiki/Solenogastres'),
56+
('class', 'Gastropoda', 'The largest class of molluscs with over 65,000 living species. Includes snails and slugs. Characterized by torsion (180° twisting of the visceral mass) and usually a coiled shell.', 'Torsion, muscular foot, radula, usually coiled shell (lost in slugs), marine/freshwater/terrestrial', 'Helix pomatia, Littorina littorea, Aplysia californica', 'https://en.wikipedia.org/wiki/Gastropoda'),
57+
('class', 'Bivalvia', 'A class of molluscs with laterally compressed bodies enclosed by two hinged shells (valves). Mostly filter-feeders living in marine and freshwater habitats.', 'Two hinged shells, laterally compressed, filter-feeding via gills, no head or radula, sessile or burrowing', 'Mytilus edulis, Crassostrea gigas, Pecten maximus', 'https://en.wikipedia.org/wiki/Bivalvia'),
58+
('class', 'Cephalopoda', 'The most neurologically advanced class of molluscs including octopuses, squids, cuttlefish, and nautiluses. Characterized by bilateral symmetry, a prominent head, and modified muscular arms/tentacles.', 'Bilateral symmetry, jet propulsion, complex nervous system, chromatophores, beak, arms/tentacles, closed circulatory system', 'Octopus vulgaris, Loligo vulgaris, Sepia officinalis, Nautilus pompilius', 'https://en.wikipedia.org/wiki/Cephalopod'),
59+
('class', 'Mammalia', 'Warm-blooded vertebrates characterized by mammary glands, hair/fur, three middle ear bones, and a neocortex. Over 6,400 living species.', 'Endothermic, mammary glands, hair, live birth (most), neocortex, single-boned lower jaw, specialized teeth', 'Homo sapiens, Balaenoptera musculus, Panthera leo', 'https://en.wikipedia.org/wiki/Mammal'),
60+
('class', 'Aves', 'Feathered, winged, bipedal, warm-blooded, egg-laying vertebrates. Over 10,000 living species. Characterized by toothless beaked jaws, high metabolic rate, and lightweight skeleton.', 'Feathers, hollow bones, toothless beak, four-chambered heart, endothermic, hard-shelled eggs, high metabolic rate', 'Aquila chrysaetos, Corvus corax, Aptenodytes forsteri', 'https://en.wikipedia.org/wiki/Bird'),
61+
('class', 'Reptilia', 'Cold-blooded, air-breathing vertebrates covered in scales or scutes. Includes turtles, crocodilians, snakes, lizards, and tuatara.', 'Ectothermic, scales/scutes, amniotic eggs, lungs throughout life, three or four-chambered heart', 'Crocodylus niloticus, Python reticulatus, Chelonia mydas', 'https://en.wikipedia.org/wiki/Reptile'),
62+
('class', 'Actinopterygii', 'Ray-finned fishes — the largest class of vertebrates with over 30,000 species. Fins supported by bony spines (rays) rather than fleshy lobes.', 'Bony rays in fins, swim bladder, operculum covering gills, scales, lateral line system', 'Salmo salar, Thunnus thynnus, Danio rerio', 'https://en.wikipedia.org/wiki/Actinopterygii'),
63+
('class', 'Insecta', 'The most species-rich class of animals with over 1 million described species. Three-part body (head, thorax, abdomen), three pairs of legs, compound eyes, and one pair of antennae.', 'Exoskeleton, three body segments, six legs, compound eyes, metamorphosis, tracheal respiration', 'Apis mellifera, Danaus plexippus, Musca domestica', 'https://en.wikipedia.org/wiki/Insect'),
64+
('class', 'Arachnida', 'Arthropods with eight legs including spiders, scorpions, ticks, and mites. Over 100,000 described species. No antennae or wings.', 'Eight legs, two body segments (cephalothorax + abdomen), chelicerae, no antennae, book lungs or tracheae', 'Latrodectus mactans, Scorpio maurus, Ixodes scapularis', 'https://en.wikipedia.org/wiki/Arachnid')
65+
ON DUPLICATE KEY UPDATE description=VALUES(description), characteristics=VALUES(characteristics);
66+
67+
-- Order descriptions
68+
INSERT INTO taxonomy_descriptions (rank_level, taxon_name, description, characteristics, example_species, wikipedia_url) VALUES
69+
('order', 'Pholidoskepia', 'An order of solenogasters (shell-less aplacophorans). Small, worm-like marine molluscs covered in scale-like spicules. Found in deep-sea benthic environments feeding on hydroids and other cnidarians.', 'Scale-like calcareous spicules, vermiform, deep-sea benthic, radula present, monaulic reproductive system', 'Epimenia babai, Alexandromenia valida', 'https://en.wikipedia.org/wiki/Pholidoskepia'),
70+
('order', 'Carnivora', 'Diverse order of placental mammals with specialized teeth and claws for catching and eating other animals. Includes cats, dogs, bears, seals, and weasels.', 'Carnassial teeth, strong jaws, claws, highly developed brain, diverse body plans (terrestrial, aquatic, arboreal)', 'Panthera leo, Canis lupus, Ursus arctos, Phoca vitulina', 'https://en.wikipedia.org/wiki/Carnivora'),
71+
('order', 'Primates', 'Order of mammals including humans, apes, monkeys, and prosimians. Characterized by large brains, binocular vision, and grasping hands with opposable thumbs.', 'Large brain relative to body, forward-facing eyes, grasping hands, nails instead of claws, prolonged parental care', 'Homo sapiens, Pan troglodytes, Gorilla gorilla', 'https://en.wikipedia.org/wiki/Primate'),
72+
('order', 'Lepidoptera', 'Order of insects comprising butterflies and moths. Over 180,000 species. Characterized by scale-covered wings and complete metamorphosis.', 'Scaled wings, coiled proboscis, complete metamorphosis (egg-larva-pupa-adult), holometabolous', 'Danaus plexippus, Bombyx mori, Papilio machaon', 'https://en.wikipedia.org/wiki/Lepidoptera'),
73+
('order', 'Coleoptera', 'Beetles — the largest order of insects and animals, with over 400,000 described species. Front wings hardened into elytra.', 'Hardened forewings (elytra), complete metamorphosis, chewing mouthparts, diverse habitats', 'Coccinella septempunctata, Lucanus cervus, Dynastes hercules', 'https://en.wikipedia.org/wiki/Beetle')
74+
ON DUPLICATE KEY UPDATE description=VALUES(description), characteristics=VALUES(characteristics);
75+
76+
-- Family descriptions
77+
INSERT INTO taxonomy_descriptions (rank_level, taxon_name, description, characteristics, example_species, wikipedia_url) VALUES
78+
('family', 'Lepidomeniidae', 'A family of solenogasters (order Pholidoskepia). Small, worm-like, shell-less aplacophoran molluscs with distinctive scale-shaped calcareous spicules covering the body. Marine benthic organisms found in deep-sea environments.', 'Scale-shaped spicules (lepido- = scale), vermiform body 2-30mm, no mantle cavity, deep-sea habitat, feed on hydrozoans', 'Lepidomenia hystrix, Lepidomenia australis', 'https://en.wikipedia.org/wiki/Lepidomeniidae'),
79+
('family', 'Felidae', 'The cat family. Obligate carnivores with retractable claws, powerful jaws, and excellent night vision. 37 living species across all continents except Antarctica and Australia (historically).', 'Retractable claws, digitigrade locomotion, specialized carnassial teeth, binocular vision, flexible spine', 'Panthera leo, Felis catus, Panthera tigris, Acinonyx jubatus', 'https://en.wikipedia.org/wiki/Felidae'),
80+
('family', 'Canidae', 'The dog family. Social carnivores with non-retractable claws and long muzzles. Includes wolves, foxes, jackals, and domestic dogs.', 'Non-retractable claws, long muzzle, bushy tail, digitigrade, social pack behavior (many species)', 'Canis lupus, Vulpes vulpes, Canis latrans', 'https://en.wikipedia.org/wiki/Canidae'),
81+
('family', 'Hominidae', 'The great ape family including humans, chimpanzees, gorillas, and orangutans. Large-bodied primates with no tail and high cognitive abilities.', 'No tail, large brain, opposable thumbs, flat face, extended development period, tool use', 'Homo sapiens, Pan troglodytes, Gorilla gorilla, Pongo pygmaeus', 'https://en.wikipedia.org/wiki/Hominidae')
82+
ON DUPLICATE KEY UPDATE description=VALUES(description), characteristics=VALUES(characteristics);
83+
84+
SQL
85+
86+
COUNT=$(mysql $MYSQL_OPTS -N -B -e "SELECT COUNT(*) FROM BrarnerScience.taxonomy_descriptions;" 2>/dev/null)
87+
echo "[✓] taxonomy_descriptions populated: $COUNT records"
88+
echo " Verify: mysql BrarnerScience -e \"SELECT rank_level, taxon_name FROM taxonomy_descriptions ORDER BY rank_level, taxon_name;\""

0 commit comments

Comments
 (0)