diff --git a/apps/web/scripts/.ddg-progress.json b/apps/web/scripts/.ddg-progress.json index cf8a5d8..1f1ae6e 100644 --- a/apps/web/scripts/.ddg-progress.json +++ b/apps/web/scripts/.ddg-progress.json @@ -4349,7 +4349,707 @@ "wiki-distortion-mosaic", "wiki-dock-anthracnose", "wiki-dogwood-anthracnose", - "wiki-dogwood-leaf-spot" + "wiki-dogwood-leaf-spot", + "wiki-dogwood-spot-anthracnose", + "wiki-dollar-spot", + "wiki-double-blossom", + "wiki-douglas-fir-needle-cast", + "wiki-douglas-fir-root-disease", + "wiki-downy-spot", + "wiki-drop", + "wiki-dropping", + "wiki-drought", + "wiki-dryberry-disease", + "wiki-dutch-elm-disease", + "wiki-dwarf-mistletoe", + "wiki-dwarf-mosaic", + "wiki-dwarfism", + "wiki-early-leaf-spot", + "wiki-early-leaf-spot-of-peanut", + "wiki-eggplant-little-leaf", + "wiki-eggplant-mosaic", + "wiki-eggplant-mottle-dwarf", + "wiki-eggplant-phomopsis", + "wiki-elderberry-leaf-spot", + "wiki-elderberry-virus", + "wiki-elephantiasis", + "wiki-elm-leaf-scorch", + "wiki-elm-mosaic", + "wiki-elm-mottle", + "wiki-elm-stripe", + "wiki-elm-witches-broom", + "wiki-elm-yellows", + "wiki-epicarp-lesion", + "wiki-esca", + "wiki-eucalyptus-leaf-spot", + "wiki-euonymus-anthracnose", + "wiki-euphorbia-mosaic", + "wiki-european-stone-fruit-yellows", + "wiki-evergreen-blackberry", + "wiki-exobasidium-leaf-spot", + "wiki-fabraea-leaf-spot", + "wiki-fabrea-leaf-spot", + "wiki-fairy-ring", + "wiki-fairy-ring-leaf-spot", + "wiki-false-blossom", + "wiki-false-loose-smut", + "wiki-false-root-knot-nematode", + "wiki-fanleaf", + "wiki-ferrugem-do-feijoeiro-comum", + "wiki-filbert-stunt", + "wiki-filiform", + "wiki-flavescence-doree", + "wiki-fleck", + "wiki-fleur", + "wiki-flyspeck-of-apple", + "wiki-foliar-nematode", + "wiki-foliar-nematodes", + "wiki-foliar-vein-yellowing", + "wiki-freckle", + "wiki-frosty-pod", + "wiki-fruit-blotch-of-apple", + "wiki-fruit-chimera", + "wiki-fruit-drop", + "wiki-fungal-brown-spot", + "wiki-fungal-scald", + "wiki-fusariosis", + "wiki-fusarium-bark-disease", + "wiki-fused-fingers", + "wiki-galls", + "wiki-giantism", + "wiki-gnomonia-leaf-spot", + "wiki-golden-mosaic", + "wiki-grape-rust", + "wiki-grassy-shoot", + "wiki-gray-mold-of-apple", + "wiki-gray-snow-mold", + "wiki-gray-streak", + "wiki-gray-wall", + "wiki-grease-spot", + "wiki-greasy-blotch", + "wiki-greasy-center", + "wiki-green-blotch", + "wiki-green-mosaic", + "wiki-green-ring", + "wiki-green-scale", + "wiki-greening", + "wiki-ground-nut-rosette", + "wiki-groundnut-leaf-spot", + "wiki-guinea-grass-mosaic", + "wiki-gummosis", + "wiki-hairy-nightshade", + "wiki-hazelnut-mosaic", + "wiki-hazelnut-yellows", + "wiki-head-mold", + "wiki-head-smut", + "wiki-heart-leaf-disorder", + "wiki-heart-leaf-unfurling-disorder", + "wiki-helenium-s", + "wiki-helminthosporium-leaf-spot", + "wiki-hemp-mosaic", + "wiki-hemp-streak", + "wiki-high-mat", + "wiki-holeus-spot", + "wiki-honey-fungus", + "wiki-hop-latent", + "wiki-hop-mosaic", + "wiki-horn-sprout", + "wiki-horse", + "wiki-huanglongbing", + "wiki-impatiens-necrotic-spot", + "wiki-indian-cassava-mosaic", + "wiki-infectious-bud-failure", + "wiki-japanese-beetle", + "wiki-joint-infection", + "wiki-joint-infection-of-apple", + "wiki-kalancho-mosaic", + "wiki-karnal-bunt", + "wiki-kernel-decay", + "wiki-kernel-molds", + "wiki-kernel-shrivel", + "wiki-kernel-spot", + "wiki-khamedj", + "wiki-knot", + "wiki-lace-leaf", + "wiki-late-leaf-rust", + "wiki-latex-eruption", + "wiki-leaf-and-bud-nematode", + "wiki-leaf-and-pod-spot", + "wiki-leaf-and-stem-nematode", + "wiki-leaf-anthracnose", + "wiki-leaf-bleaching", + "wiki-leaf-blister", + "wiki-leaf-crinkle", + "wiki-leaf-cupping", + "wiki-leaf-glazing", + "wiki-leaf-miner", + "wiki-leaf-mottle", + "wiki-leaf-pucker-of-apple", + "wiki-leaf-roll", + "wiki-leaf-scorch", + "wiki-leaf-streak", + "wiki-leaf-stripe", + "wiki-leaf-yellowing", + "wiki-leak", + "wiki-lettuce-infectious-yellows", + "wiki-lettuce-mosaic", + "wiki-lichen", + "wiki-light-leaf-spot", + "wiki-lime-blotch", + "wiki-line-pattern", + "wiki-lint-contamination", + "wiki-little-cherry", + "wiki-liver-spot", + "wiki-lixa-pequena", + "wiki-loose-kernel-smut", + "wiki-lophodermium-leaf-spot", + "wiki-maize-bushy-stunt", + "wiki-maize-chlorotic-dwarf", + "wiki-maize-chlorotic-mottle", + "wiki-maize-dwarf-mosaic", + "wiki-maize-mosaic", + "wiki-maize-rayado-fino", + "wiki-maize-rough-dwarf", + "wiki-maize-stripe", + "wiki-maize-tassel-abortion", + "wiki-maize-white-line-mosaic", + "wiki-maize-yellow-stripe", + "wiki-malayan-leaf-spot", + "wiki-mancha-angular", + "wiki-mango-scale", + "wiki-marginal-leaf-burn", + "wiki-maturity-bronzing", + "wiki-mealiness", + "wiki-mealybug", + "wiki-mela", + "wiki-mid-vein-spot", + "wiki-mild-mosaic", + "wiki-mildew", + "wiki-milo-disease", + "wiki-minor-leaf-spot", + "wiki-mistletoe", + "wiki-mofo-branco", + "wiki-moko", + "wiki-mold", + "wiki-moldy-core", + "wiki-monilia", + "wiki-moorpark-mottle", + "wiki-mop-top", + "wiki-mosaic-disease", + "wiki-mosaic-or-ringspot", + "wiki-mottle-leaf", + "wiki-mouse-ear", + "wiki-murcha-de-fusario", + "wiki-mycorrhiza", + "wiki-myriogenospora-leaf-binding", + "wiki-myrothecium-leaf-spot", + "wiki-mystrosporium-leaf-spot", + "wiki-navel-orange-worm", + "wiki-neck-blast", + "wiki-necrosis", + "wiki-necrotic-leaf-blotch", + "wiki-necrotic-leaf-blotch-of-apple", + "wiki-necrotic-ring-spot", + "wiki-necrotic-ringspot", + "wiki-nematode", + "wiki-net-blotch", + "wiki-nettlehead", + "wiki-noninfectious-bud-failure", + "wiki-northern-anthracnose", + "wiki-oak-root-fungus", + "wiki-oat-leaf-blotch", + "wiki-oat-mosaic", + "wiki-oat-necrotic-mottle", + "wiki-oat-red-leaf", + "wiki-oat-smut", + "wiki-oidio-do-feijoeiro-comum", + "wiki-olive-leaf-spot", + "wiki-orange-dog", + "wiki-orange-rust", + "wiki-other-nematodes", + "wiki-pale-leaf", + "wiki-palm-leaf-spot", + "wiki-panagrolaimus-nematode", + "wiki-panama-disease", + "wiki-panicum-mosaic", + "wiki-papaya-bunchy-top", + "wiki-pasmo", + "wiki-pea-enation-mosaic", + "wiki-pea-leaf-roll", + "wiki-pea-mosaic", + "wiki-pea-seed-borne-mosaic", + "wiki-pea-seedborne-mosaic", + "wiki-peach-leaf-curl", + "wiki-peach-mosaic-virus", + "wiki-peach-rosette", + "wiki-peach-scab", + "wiki-peach-yellow-bud-mosaic", + "wiki-peach-yellow-mottle", + "wiki-peach-yellows", + "wiki-peanut-mottle-virus", + "wiki-peanut-rust", + "wiki-pear-leaf-blister-mite", + "wiki-pear-leaf-curl", + "wiki-pear-leaf-spot", + "wiki-pear-psylla", + "wiki-pear-rust", + "wiki-pear-slug", + "wiki-pear-stony-pit", + "wiki-pearly-root", + "wiki-pecan-scab", + "wiki-pecky-rice", + "wiki-pelargonium-flower-break", + "wiki-pelargonium-line-pattern", + "wiki-pelargonium-ring-pattern", + "wiki-pelargonium-ringspot", + "wiki-pelargonium-zonale-spot", + "wiki-penicillium-mold", + "wiki-pepper-golden-mosaic", + "wiki-pepper-huasteco", + "wiki-pepper-leaf-curl", + "wiki-pepper-mottle", + "wiki-pepper-veinal-mottle", + "wiki-periconia-leaf-spot", + "wiki-persimmon", + "wiki-pestalosphaeria-leaf-spot", + "wiki-pestalotia-leaf-spot", + "wiki-petal-fall", + "wiki-peter-s-scorch", + "wiki-phaeoisariopsis-leaf-spot", + "wiki-phaeoseptoria-leaf-spot", + "wiki-phomopsis-cane-and-leaf-spot", + "wiki-phony-peach", + "wiki-phyllody", + "wiki-phylloxera", + "wiki-physoderma-brown-spot", + "wiki-phytophthora-leaf-fall", + "wiki-phytoplasma", + "wiki-pierce-s-disease", + "wiki-pin-sized-lesion", + "wiki-pink-snow-mold", + "wiki-pistachio", + "wiki-pitting", + "wiki-pleospora-leaf-spot", + "wiki-plum-pox", + "wiki-pod-mottle", + "wiki-podridao-cinzenta-do-caule", + "wiki-podridao-do-colo", + "wiki-podridao-radicular-de-rhizoctonia", + "wiki-podridao-radicular-seca", + "wiki-pokkah-boeng", + "wiki-postbloom-fruit-drop", + "wiki-potato-leaf-roll", + "wiki-potato-mop-top", + "wiki-potato-scab", + "wiki-potato-spindle-tuber-viroid", + "wiki-potato-x", + "wiki-potato-yellow-dwarf", + "wiki-potexvirus-unnamed", + "wiki-powdery-scab", + "wiki-primula-mosaic", + "wiki-primula-mottle", + "wiki-proliferation", + "wiki-prune-dwarf", + "wiki-prunus-necrotic-ringspot", + "wiki-pseudomonas-leaf-spot", + "wiki-pucciniastrum-leaf-rust", + "wiki-pucker-leaf", + "wiki-purple-blotch", + "wiki-purple-top", + "wiki-pyricularia-leaf-spot", + "wiki-pythium-damping-off", + "wiki-pythium-leak", + "wiki-radish-mosaic", + "wiki-rai-mosaic", + "wiki-ramularia-leaf-spot", + "wiki-raspberry", + "wiki-raspberry-bushy-dwarf", + "wiki-raspberry-leaf-curl", + "wiki-raspberry-leaf-spot", + "wiki-raspberry-mosaic", + "wiki-raspberry-veinbanding-mosaic", + "wiki-raspberry-yellow-dwarf", + "wiki-raspberry-yellow-spot", + "wiki-ratoon-stunting", + "wiki-rayadilla", + "wiki-red-blister", + "wiki-red-blotch", + "wiki-red-boot", + "wiki-red-leaf-blotch", + "wiki-red-leather-disease", + "wiki-red-spider", + "wiki-red-stripe", + "wiki-red-thread", + "wiki-redberry-disease", + "wiki-replant-problem", + "wiki-rhizoctonia-damping-off", + "wiki-rhizoctonia-soreshin", + "wiki-rhizomania", + "wiki-rhynchosia-little-leaf", + "wiki-rhynchosia-mosaic", + "wiki-rice-black-streaked-dwarf", + "wiki-rice-blast", + "wiki-rice-bunts", + "wiki-rice-dwarf", + "wiki-rice-grassy-stunt", + "wiki-rice-ragged-stunt", + "wiki-rice-transitory-yellowing", + "wiki-rice-tungro", + "wiki-rice-yellow-mottle", + "wiki-rind-disease", + "wiki-rind-spot", + "wiki-ringspot", + "wiki-ringspots", + "wiki-rna-virus", + "wiki-root-smallpox-disease", + "wiki-rose-leaf", + "wiki-rose-leaf-curl", + "wiki-rose-mosaic", + "wiki-rose-rosette", + "wiki-rosette-of-apple", + "wiki-rosetting", + "wiki-rough-dwarf", + "wiki-roxana", + "wiki-rubbery-wood-of-apple", + "wiki-rubus-stunt", + "wiki-russet", + "wiki-rust-mite", + "wiki-rust-white", + "wiki-rusty-spot", + "wiki-rye-ergot", + "wiki-satellite-panicum-mosaic", + "wiki-scab-of-apple", + "wiki-scab-of-citrus", + "wiki-scab-of-peach", + "wiki-scald-of-apple", + "wiki-sclerotinia", + "wiki-scopulariopsis-leaf-spot", + "wiki-scorch", + "wiki-seedling-disease", + "wiki-seedling-rust", + "wiki-segmented-banana", + "wiki-septoria-brown-spot", + "wiki-septoria-leaf-blotch", + "wiki-septoria-nodorum-blotch", + "wiki-septoria-tritici-blotch", + "wiki-shenandoah", + "wiki-shothole", + "wiki-silver-leaf-of-apple", + "wiki-silvering-disease", + "wiki-sinaloa-tomato-leaf-curl", + "wiki-slime-flux", + "wiki-slime-mold", + "wiki-small-hop", + "wiki-smut", + "wiki-snow-mold", + "wiki-snow-scald", + "wiki-soilborne-mosaic", + "wiki-sooty-blotch-of-apple", + "wiki-sore-shin", + "wiki-southern-anthracnose", + "wiki-southern-root-knot-nematode", + "wiki-southern-rust", + "wiki-speckle", + "wiki-speckled-blotch", + "wiki-speckled-leaf-blotch", + "wiki-speckled-snow-mold", + "wiki-sphacelia", + "wiki-sphaerotheca-pannosa", + "wiki-spike", + "wiki-spike-leaf", + "wiki-spiral-nematode-root-damage", + "wiki-split-peel", + "wiki-spot-anthracnose", + "wiki-spraing", + "wiki-spring-black-stem", + "wiki-squirter", + "wiki-stackburn", + "wiki-stagonospora-blotch", + "wiki-stagonospora-leaf-spot", + "wiki-stalk-smut", + "wiki-stem-and-bulb-nematode", + "wiki-stem-break", + "wiki-stem-nematode", + "wiki-stem-pitting", + "wiki-stem-pitting-of-apple", + "wiki-stem-rust", + "wiki-stem-smut", + "wiki-stem-splitting", + "wiki-stem-streak", + "wiki-stem-twisting-and-bending", + "wiki-stemphylium-leaf-and-stem-spot", + "wiki-sterility-mosaic", + "wiki-sticky-disease", + "wiki-stigmatomycosis", + "wiki-stippling", + "wiki-stolon-decay", + "wiki-stone-pitting", + "wiki-stony-pit", + "wiki-storage-mold", + "wiki-strawberry", + "wiki-strawberry-leaf-roll", + "wiki-strawberry-leaf-spot", + "wiki-strawberry-yellow-edge", + "wiki-strawbreaker", + "wiki-streak", + "wiki-striatura-ulcerosa", + "wiki-stripe", + "wiki-stripe-rust", + "wiki-stubby-root", + "wiki-stylet-nematode", + "wiki-sudden-death", + "wiki-sugar-end", + "wiki-sugarcane-mosaic", + "wiki-sulfur-deficiency", + "wiki-summer-black-stem", + "wiki-sun-blotch", + "wiki-sun-scald", + "wiki-sunblotch", + "wiki-superelongation", + "wiki-sweet-potato", + "wiki-sweet-potato-virus-disease", + "wiki-sycamore", + "wiki-taches-brunes", + "wiki-taiwan-marginal-scorch", + "wiki-take-all", + "wiki-tan-spot", + "wiki-tatter-leaf", + "wiki-tea", + "wiki-terminal-mottle", + "wiki-thrips", + "wiki-tikka-disease", + "wiki-tilletia", + "wiki-tip-over", + "wiki-tobacco", + "wiki-tobacco-ringspot", + "wiki-tobacco-ringspot-virus", + "wiki-tomato", + "wiki-tomato-aspermy", + "wiki-tomato-leaf-curl", + "wiki-tomato-ringspot", + "wiki-top-spotting", + "wiki-tristeza", + "wiki-tropical-rust", + "wiki-trunk-pitting", + "wiki-turnip-mosaic", + "wiki-twig-die-back", + "wiki-vein-clearing", + "wiki-vein-enation", + "wiki-vein-spot", + "wiki-veinbanding", + "wiki-verbena-latent", + "wiki-water-mold", + "wiki-web-blotch", + "wiki-weevil", + "wiki-western-x-disease", + "wiki-wet-feet", + "wiki-wheat-curl-mite", + "wiki-wheat-soil-borne-mosaic", + "wiki-white-blister", + "wiki-white-bract", + "wiki-white-head", + "wiki-white-pine-blister-rust", + "wiki-white-thread", + "wiki-wilt-of-fruit-bunches", + "wiki-winter-injury", + "wiki-wire-stem", + "wiki-wirestem", + "wiki-wirrega-blotch", + "wiki-witch-s-broom", + "wiki-witches-broom-of-apple", + "wiki-withertip", + "wiki-wood-decay", + "wiki-wood-pocket", + "wiki-wood-stains", + "wiki-woolly-apple-aphid", + "wiki-x-disease", + "wiki-xanthomonas-leaf-spot", + "wiki-xyloporosis", + "wiki-xyloporosis-of-apple", + "wiki-yellow-edge", + "wiki-yellow-leaf-curl", + "wiki-yellow-leaf-spot", + "wiki-yellow-mat", + "wiki-yellow-mosaic", + "wiki-yellow-mottle", + "wiki-yellow-net-vein", + "wiki-yellow-pulp", + "wiki-yellow-shoot", + "wiki-yellow-sigatoka", + "wiki-yellow-stunt", + "wiki-yellowing", + "wiki-zebra-chip", + "wiki-zonate-eye-spot", + "tomato-tomato-fruitworm", + "tomato-tomato-hornworm", + "tomato-tobacco-hornworm", + "tomato-brown-tipped-pearl", + "tomato-eggplant-borer", + "tomato-tomato-fruit-borer", + "tomato-eggplant-leafroller", + "tomato-potato-tuber-moth", + "tomato-tomato-borer", + "tomato-tomato-pinworm", + "tomato-root-knot", + "tomato-sting", + "tomato-stubby-root", + "tomato-autogenous-necrosis", + "tomato-fruit-pox", + "tomato-gold-fleck", + "tomato-graywall", + "potato-potato-cyst-nematode", + "potato-lesion-nematode", + "potato-potato-rot-nematode", + "potato-root-knot-nematode", + "potato-sting-nematode", + "potato-stubby-root-nematode", + "potato-aerial-tubers", + "potato-air-pollution-injury", + "potato-black-heart", + "potato-blackspot-bruise", + "potato-elephant-hide", + "potato-hollow-heart", + "potato-internal-brown-spot-heat-necrosis", + "potato-jelly-end-rot", + "potato-physiological-leaf-roll", + "potato-psyllid-yellows", + "potato-shatter-bruise", + "potato-stem-end-browning", + "apple-dagger-nematode", + "apple-lesion-nematode", + "apple-pin-nematode", + "apple-ring-nematode", + "apple-root-knot-nematode", + "apple-bitter-pit", + "apple-blossom-blast", + "apple-fruit-cracking", + "apple-fruit-russet", + "apple-green-mottle", + "apple-hollow-apple", + "apple-internal-bark-necrosis-measles", + "apple-internal-browning", + "apple-jonathan-spot", + "apple-narrow-leaf", + "apple-necrotic-leaf-blotch-of-golden-delicious", + "apple-spray-injury", + "apple-storage-scald", + "apple-sunscald", + "apple-water-core", + "apricot-lesion", + "apricot-ring", + "apricot-root-knot", + "apricot-apricot-gumboil", + "avocado-algal-spot", + "avocado-blackstreak", + "avocado-littleleaf-rosette", + "avocado-tipburn", + "barley-cereal-cyst-nematode", + "barley-cereal-root-knot-nematode", + "barley-root-gall-nematode", + "barley-root-lesion-nematode", + "barley-stunt-nematode", + "barley-physiological-leaf-spot", + "carrot-cyst-nematode", + "carrot-dagger-nematode", + "carrot-lance-nematode", + "carrot-lesion-nematode", + "carrot-root-knot", + "carrot-sting-nematode", + "carrot-stubby-root-nematodes", + "carrot-crown-rot-disorder", + "carrot-heat-canker", + "carrot-hollow-black-heart", + "carrot-ozone-injury", + "carrot-root-scab", + "carrot-speckled-carrot", + "citrus-citrus-slump-nematode", + "citrus-dagger-nematode", + "citrus-lesion-nematode", + "citrus-needle-nematode", + "citrus-root-knot-nematode", + "citrus-sheath-nematode", + "citrus-slow-decline-citrus-nematode", + "citrus-spreading-decline-burrowing-nematode", + "citrus-sting-nematode", + "citrus-stubby-root-nematode", + "citrus-stunt-nematode", + "citrus-amachamiento", + "citrus-blossom-end-clearing", + "citrus-citrus-blight", + "citrus-creasing", + "citrus-crinkle-scurf", + "citrus-lemon-sieve-tube-necrosis", + "citrus-lime-blotch-wood-pocket", + "citrus-mesophyll-collapse", + "citrus-oleocellosis", + "citrus-postharvest-pitting", + "citrus-puffing", + "citrus-rind-breakdown", + "citrus-rind-staining", + "citrus-rind-stipple-of-grapefruit", + "citrus-rumple-of-lemon-fruit", + "citrus-shell-bark-complex", + "citrus-stem-end-rind-breakdown", + "citrus-stylar-end-breakdown-of-tahiti-lime", + "citrus-stylar-end-rind-breakdown", + "citrus-stylar-end-rot", + "citrus-tangerine-dieback", + "citrus-water-spot", + "citrus-zebra-skin", + "coconut-bristle-top", + "coconut-dry-bud-rot", + "coconut-finschafen-disease", + "coconut-frond-rot", + "coconut-leaf-scorch-decline", + "coconut-malaysia-wilt", + "coconut-red-ring-disease", + "coconut-porroca-disease", + "coconut-coconut-lethal-crown-atrophy", + "coffee-root-knot", + "coffee-", + "coffee-hot-and-cold-disease", + "coffee-physiological-effect-of-overbearing", + "corn-awl", + "corn-bulb-and-stem", + "corn-burrowing", + "corn-false-root-knot", + "corn-lance-columbia", + "corn-lesion", + "corn-needle", + "corn-ring", + "corn-root-knot", + "corn-sting", + "corn-stubby-root", + "cucumber-air-pollution-injury", + "cucumber-bitter-fruit", + "cucumber-blossom-end-rot", + "cucumber-bottle-neck-of-fruit", + "cucumber-sandburn", + "cucumber-sunscald-fruit", + "cucumber-windburn", + "cucumber-dagger-american", + "cucumber-lesion", + "cucumber-reniform", + "cucumber-ring", + "cucumber-root-knot", + "cucumber-sting", + "cucumber-stubby-root", + "grape-berry-rot", + "grape-black-measles", + "grape-esca-apoplexy", + "grape-little-leaf", + "grape-oxidant-stipple", + "grape-rupestris-speckle", + "grape-stem-necrosis-water-berry-grape-peduncle-necrosis", + "grape-", + "grape-dagger-american", + "grape-lesion", + "grape-needle", + "grape-reniform", + "grape-ring", + "grape-root-knot", + "grape-stubby-root", + "lettuce-brown-stain" ], - "totalFound": 4350 + "totalFound": 5050 } \ No newline at end of file diff --git a/apps/web/scripts/generate-flagged-report.ts b/apps/web/scripts/generate-flagged-report.ts index bae8055..0e8109d 100644 --- a/apps/web/scripts/generate-flagged-report.ts +++ b/apps/web/scripts/generate-flagged-report.ts @@ -89,6 +89,11 @@ const CONTENT_TYPE_LABELS: Record; + /** Phase checkpoint: 0=core, 1=full, 2=healthy. On resume, skip to this phase. */ + phase: number; + /** Index within the current phase's disease array. On resume, skip to this index. */ + phaseIndex: number; } // ─── DB Loading ────────────────────────────────────────────────────────────── @@ -358,7 +373,7 @@ async function searchImagesCommons( srlimit: "50", sroffset: String(sroffset), format: "json", - origin: "*", // server-side API call + // No origin needed — server-side fetch, Wikimedia ignores CORS headers on API calls }); const url = `https://commons.wikimedia.org/w/api.php?${params}`; @@ -472,9 +487,40 @@ async function downloadBatch( function loadProgress(): Progress { if (!existsSync(PROGRESS_FILE)) { - return { lastUpdated: new Date().toISOString(), classes: {} }; + return { + lastUpdated: new Date().toISOString(), + classes: {}, + phase: 0, + phaseIndex: 0, + }; + } + try { + const raw = JSON.parse(readFileSync(PROGRESS_FILE, "utf-8")) as Partial; + // Backward compat: ensure new fields exist + raw.phase ??= 0; + raw.phaseIndex ??= 0; + raw.classes ??= {}; + // Ensure each class has the sources field + for (const key of Object.keys(raw.classes)) { + const cp = raw.classes[key] as Partial; + cp.sources ??= { + db: { exhausted: false }, + duckduckgo: { exhausted: false }, + inaturalist: { exhausted: false }, + wikimedia: { exhausted: false }, + }; + cp.seenUrls ??= []; + } + return raw as Progress; + } catch { + console.warn(" ⚠ Corrupt progress file, starting fresh"); + return { + lastUpdated: new Date().toISOString(), + classes: {}, + phase: 0, + phaseIndex: 0, + }; } - return JSON.parse(readFileSync(PROGRESS_FILE, "utf-8")) as Progress; } function saveProgress(progress: Progress): void { @@ -490,6 +536,12 @@ function getClassProgress(progress: Progress, classId: string): ClassProgress { failed: 0, seenUrls: [], exhausted: false, + sources: { + db: { exhausted: false }, + duckduckgo: { exhausted: false }, + inaturalist: { exhausted: false }, + wikimedia: { exhausted: false }, + }, }; } return progress.classes[classId]; @@ -514,6 +566,37 @@ function buildHealthyQueries(plant: string): string[] { ]; } +// ─── File Reconciliation ─────────────────────────────────────────────────── + +/** + * Count actual image files in a class directory. + * Returns the count of files matching img_* pattern, OR 0 if dir doesn't exist. + */ +function countImagesInDir(classDir: string): number { + if (!existsSync(classDir)) return 0; + try { + const files = readdirSync(classDir); + return files.filter((f) => f.startsWith("img_")).length; + } catch { + return 0; + } +} + +/** + * Reconcile a class's progress count with actual files on disk. + * If files were deleted after the progress file was saved, this + * adjusts the count downward so we re-download the missing ones. + * Returns the reconciled count. + */ +function reconcileClassCount(classDir: string, progressCount: number): number { + const fileCount = countImagesInDir(classDir); + if (fileCount < progressCount) { + console.log(` ↻ File count (${fileCount}) < progress count (${progressCount}) — reconciling`); + return fileCount; + } + return progressCount; +} + // ─── Dataset Collection ───────────────────────────────────────────────────── async function collectClassImages( @@ -526,14 +609,32 @@ async function collectClassImages( fastMode = false, // Skip slow DuckDuckGo, use iNat + Commons only ): Promise { const cp = getClassProgress(progress, classId); + + // ── Reconcile with actual files on disk ───────────────────────────────── + const actualCount = reconcileClassCount(classDir, cp.count); + if (actualCount !== cp.count) { + cp.count = actualCount; + saveProgress(progress); + } + const seenUrls = new Set(cp.seenUrls); + const sources = cp.sources; if (cp.count >= target) { console.log(` ✓ Already have ${cp.count}/${target}`); return; } - if (cp.exhausted) { + // Check if ALL sources are exhausted + const allExhausted = + sources.db.exhausted && + sources.duckduckgo.exhausted && + sources.inaturalist.exhausted && + sources.wikimedia.exhausted; + + if (allExhausted) { + cp.exhausted = true; + saveProgress(progress); console.log(` ✓ Exhausted (${cp.count}/${target})`); return; } @@ -541,73 +642,111 @@ async function collectClassImages( mkdirSync(classDir, { recursive: true }); const allUrls: string[] = []; - let exhausted = false; + let anyNewResults = false; + const needed = target - cp.count; // ── Source 0: Existing DB URLs ────────────────────────────────────────── - const freshDbUrls = existingUrls.filter((u) => !seenUrls.has(u)); - if (freshDbUrls.length > 0) { - console.log(` DB: ${freshDbUrls.length} existing URLs`); - for (const url of freshDbUrls) { - if (allUrls.length >= target) break; - seenUrls.add(url); - allUrls.push(url); + if (!sources.db.exhausted) { + const freshDbUrls = existingUrls.filter((u) => !seenUrls.has(u)); + if (freshDbUrls.length > 0) { + console.log(` DB: ${freshDbUrls.length} existing URLs`); + for (const url of freshDbUrls) { + if (allUrls.length >= needed) break; + seenUrls.add(url); + allUrls.push(url); + } + if (freshDbUrls.length > 0) anyNewResults = true; } + // DB source is always "exhausted" after processing its initial URLs + sources.db.exhausted = true; } // ── Source 1: DuckDuckGo ────────────────────────────────────────────── - // Skip DDG in fast mode (full set — DDG is slowest source) - if (!fastMode && allUrls.length < target) { + if (!fastMode && !sources.duckduckgo.exhausted && allUrls.length < needed) { for (const query of queries) { - if (allUrls.length >= target) break; + if (allUrls.length >= needed) break; process.stdout.write(` DDG: "${query.substring(0, 40)}"... `); - const result = await collectImagesDuckDuckGo(query, target - allUrls.length, seenUrls); + const result = await collectImagesDuckDuckGo(query, needed - allUrls.length, seenUrls); allUrls.push(...result.urls); - if (result.exhausted) exhausted = true; + if (result.exhausted) { + sources.duckduckgo.exhausted = true; + } + if (result.urls.length > 0) anyNewResults = true; console.log(`${result.urls.length} new`); - if (allUrls.length >= target) break; + if (allUrls.length >= needed) break; + } + // If DDG never gave us anything, mark exhausted to avoid re-trying + if (!anyNewResults && sources.duckduckgo.exhausted) { + /* already marked */ } } // ── Source 2: iNaturalist ────────────────────────────────────────────── - if (allUrls.length < target) { + if (!sources.inaturalist.exhausted && allUrls.length < needed) { const primaryQuery = queries[0]; console.log(` iNat: Searching...`); - const result = await searchImagesInaturalist(primaryQuery, target - allUrls.length, seenUrls); + const result = await searchImagesInaturalist(primaryQuery, needed - allUrls.length, seenUrls); allUrls.push(...result.urls); - if (result.exhausted) exhausted = true; + if (result.exhausted) sources.inaturalist.exhausted = true; + if (result.urls.length > 0) anyNewResults = true; console.log(` iNat: ${result.urls.length} images`); } // ── Source 3: Wikimedia Commons ──────────────────────────────────────── - if (allUrls.length < target) { + if (!sources.wikimedia.exhausted && allUrls.length < needed) { const primaryQuery = queries[0]; console.log(` Commons: Searching...`); - const result = await searchImagesCommons(primaryQuery, target - allUrls.length, seenUrls); + const result = await searchImagesCommons(primaryQuery, needed - allUrls.length, seenUrls); allUrls.push(...result.urls); - if (result.exhausted) exhausted = true; + if (result.exhausted) sources.wikimedia.exhausted = true; + if (result.urls.length > 0) anyNewResults = true; console.log(` Commons: ${result.urls.length} images`); } if (allUrls.length === 0) { - cp.exhausted = exhausted; + cp.exhausted = true; saveProgress(progress); - console.log(` ✗ No images found`); + console.log(` ✗ No images found — exhausted`); return; } + if (!anyNewResults && allUrls.length > 0) { + // Only DB URLs survived — nothing more will come from searches + cp.exhausted = true; + saveProgress(progress); + } + // Save progress with seen URLs BEFORE downloading cp.seenUrls = Array.from(seenUrls); - cp.exhausted = exhausted; saveProgress(progress); console.log(` Downloading ${allUrls.length} images...`); - const { downloaded, failed } = await downloadBatch(allUrls, classDir, cp.count); + // Use actual file count as start index so filenames don't have gaps + const startIndex = countImagesInDir(classDir); + const { downloaded, failed } = await downloadBatch(allUrls, classDir, startIndex); - cp.count += downloaded; + // Re-count actual files on disk after download (more reliable than tracking) + const newTotal = countImagesInDir(classDir); + cp.count = newTotal; cp.downloaded += downloaded; cp.failed += failed; + // Check if all sources exhausted + if ( + sources.db.exhausted && + sources.duckduckgo.exhausted && + sources.inaturalist.exhausted && + sources.wikimedia.exhausted + ) { + cp.exhausted = true; + } + + // Don't mark exhausted if we still have room to grow + if (cp.count >= target) { + cp.exhausted = true; + } + saveProgress(progress); const pct = Math.round((cp.count / target) * 100); @@ -645,7 +784,12 @@ async function main() { console.log("PHASE 1: Core Diseases (100 images each)"); console.log("─".repeat(60)); - for (let i = 0; i < coreDiseases.length; i++) { + const coreStart = progress.phase === 0 ? progress.phaseIndex : 0; + if (coreStart > 0) { + console.log(` Resuming from disease #${coreStart + 1} (${((coreStart / coreDiseases.length) * 100).toFixed(0)}% done)`); + } + + for (let i = coreStart; i < coreDiseases.length; i++) { const d = coreDiseases[i]; const classDir = resolve(DATASET_DIR, d.id); const queries = buildSearchQueries(d); @@ -655,6 +799,11 @@ async function main() { console.log(`\n[${i + 1}/${coreDiseases.length}] (${pct}%) ${d.name || d.id} (${d.plantId})`); await collectClassImages(d.id, queries, TARGET_CORE, progress, classDir, existingUrls); + + // Save checkpoint: phase 0, at index i + progress.phase = 0; + progress.phaseIndex = i + 1; + saveProgress(progress); } // ── Phase 2: Full set ────────────────────────────────────────────────── @@ -663,7 +812,12 @@ async function main() { console.log("PHASE 2: Full Disease Set (10 images each)"); console.log("─".repeat(60)); - for (let i = 0; i < fullDiseases.length; i++) { + const fullStart = progress.phase === 1 ? progress.phaseIndex : 0; + if (fullStart > 0) { + console.log(` Resuming from disease #${fullStart + 1} (${((fullStart / fullDiseases.length) * 100).toFixed(0)}% done)`); + } + + for (let i = fullStart; i < fullDiseases.length; i++) { const d = fullDiseases[i]; const classDir = resolve(DATASET_DIR, d.id); const queries = buildSearchQueries(d); @@ -673,6 +827,11 @@ async function main() { console.log(`\n[${i + 1}/${fullDiseases.length}] (${pct}%) ${d.id} (${d.plantId})`); await collectClassImages(d.id, queries, TARGET_FULL, progress, classDir, existingUrls, true); + + // Save checkpoint: phase 1, at index i + progress.phase = 1; + progress.phaseIndex = i + 1; + saveProgress(progress); } // ── Phase 3: Healthy class ────────────────────────────────────────────── @@ -683,6 +842,14 @@ async function main() { const healthyDir = resolve(DATASET_DIR, HEALTHY_CLASS); const healthyCp = getClassProgress(progress, HEALTHY_CLASS); + + // Reconcile healthy class with files on disk + const healthyActualCount = reconcileClassCount(healthyDir, healthyCp.count); + if (healthyActualCount !== healthyCp.count) { + healthyCp.count = healthyActualCount; + saveProgress(progress); + } + const healthySeen = new Set(healthyCp.seenUrls); if (healthyCp.count >= TARGET_HEALTHY) { @@ -730,16 +897,23 @@ async function main() { saveProgress(progress); console.log(`\n Downloading ${totalHealthyUrls.length} healthy images...`); + const healthyStartIndex = countImagesInDir(healthyDir); const { downloaded, failed } = await downloadBatch( totalHealthyUrls, healthyDir, - healthyCp.count, + healthyStartIndex, ); - healthyCp.count += downloaded; + // Re-count actual files on disk + const newHealthyTotal = countImagesInDir(healthyDir); + healthyCp.count = newHealthyTotal; healthyCp.downloaded += downloaded; healthyCp.failed += failed; + if (healthyCp.count >= TARGET_HEALTHY) { + healthyCp.exhausted = true; + } + const pct = Math.round((healthyCp.count / TARGET_HEALTHY) * 100); console.log( ` Got ${downloaded} images. Total: ${healthyCp.count}/${TARGET_HEALTHY} (${pct}%)`, @@ -753,6 +927,11 @@ async function main() { // ── Summary ──────────────────────────────────────────────────────────────── + // Mark all phases complete + progress.phase = 3; + progress.phaseIndex = 0; + saveProgress(progress); + const elapsed = Math.round((Date.now() - startTime) / 1000); const mins = Math.floor(elapsed / 60); const hrs = Math.floor(mins / 60); @@ -765,7 +944,7 @@ async function main() { } console.log("\n" + "=".repeat(60)); - console.log("COMPLETE"); + console.log(" ✅ ALL PHASES COMPLETE"); console.log("=".repeat(60)); console.log(` Time: ${hrs}h ${mins % 60}m`); console.log(` Downloaded: ${totalDownloaded} images`); diff --git a/apps/web/src/app/api/flag/route.ts b/apps/web/src/app/api/flag/route.ts index c25ab99..54ad1e5 100644 --- a/apps/web/src/app/api/flag/route.ts +++ b/apps/web/src/app/api/flag/route.ts @@ -10,6 +10,7 @@ import { v4 as uuidv4 } from "uuid"; const VALID_CONTENT_TYPES = [ "plant_image", "disease_image", + "disease_description", "disease_symptoms", "disease_causes", "disease_treatment", diff --git a/apps/web/src/app/browse/[plantId]/DiseaseCards.tsx b/apps/web/src/app/browse/[plantId]/DiseaseCards.tsx index 9d6be50..1e6b7cb 100644 --- a/apps/web/src/app/browse/[plantId]/DiseaseCards.tsx +++ b/apps/web/src/app/browse/[plantId]/DiseaseCards.tsx @@ -140,9 +140,19 @@ function DiseaseCard({ /> -

- {disease.description} -

+
+

+ {disease.description} +

+ +
{/* Details grid */}
diff --git a/apps/web/src/app/browse/[plantId]/page.tsx b/apps/web/src/app/browse/[plantId]/page.tsx index 23767fa..6077ba5 100644 --- a/apps/web/src/app/browse/[plantId]/page.tsx +++ b/apps/web/src/app/browse/[plantId]/page.tsx @@ -4,6 +4,7 @@ import { notFound } from "next/navigation"; import type { Metadata } from "next"; import { getPlantWithDiseases } from "@/lib/api/diseases-db"; import { getPlantDescription } from "@/lib/display-helpers"; +import BetaNotice from "@/components/BetaNotice"; import DiseaseCards from "./DiseaseCards"; import PlantViewTracker from "@/components/PlantViewTracker"; import FlagPlantImage from "@/components/FlagPlantImage"; @@ -83,6 +84,8 @@ export default async function PlantDetailPage({ params }: Props) { + + {/* Plant hero */}
{/* Plant image */} diff --git a/apps/web/src/app/browse/page.tsx b/apps/web/src/app/browse/page.tsx index 4a0ca5d..9c8b1ec 100644 --- a/apps/web/src/app/browse/page.tsx +++ b/apps/web/src/app/browse/page.tsx @@ -1,7 +1,8 @@ -import React, { Suspense } from "react"; +import { Suspense } from "react"; import { getBrowsePlants } from "@/lib/api/browse"; import BrowseContent from "./BrowseContent"; import { PlantCardSkeleton } from "@/components/LoadingSkeleton"; +import BetaNotice from "@/components/BetaNotice"; /** * Browse page — fetches plants with disease counts from the database @@ -12,27 +13,30 @@ export default async function BrowsePage() { const allPlants = await getBrowsePlants(); return ( - -
-
-
+ <> + + +
+
+
+
+
+
+ {Array.from({ length: 5 }, (_, i) => ( +
+ ))} +
+
-
-
- {Array.from({ length: 5 }, (_, i) => ( -
- ))} -
- -
- } - > - - + } + > + + + ); } diff --git a/apps/web/src/components/BetaNotice.tsx b/apps/web/src/components/BetaNotice.tsx new file mode 100644 index 0000000..cd9673f --- /dev/null +++ b/apps/web/src/components/BetaNotice.tsx @@ -0,0 +1,44 @@ +/** + * BetaNotice — a banner informing users that the site is in beta, + * community-driven, and most data isn't reviewed by humans yet. + * Encourages use of the Flag button to flag content for review. + * + * Two layout variants: + * - "full-width" (default): stretches edge-to-edge with an inner max-w wrapper + * - "card": rounded card with border, suitable for inside content containers + */ + +export default function BetaNotice({ + variant = "full-width", + className = "", +}: { + variant?: "full-width" | "card"; + className?: string; +}) { + const containerClasses = + variant === "card" + ? `rounded-xl bg-warning-amber-50 dark:bg-warning-amber-950/60 border border-warning-amber-200 dark:border-warning-amber-800 ${className}` + : `bg-warning-amber-50 dark:bg-warning-amber-950/60 border-b border-warning-amber-200 dark:border-warning-amber-800 ${className}`; + + return ( +
+
+

+ 🚧 Beta — Community Driven. Most data here is not + reviewed by humans. Spot something wrong or it could be better? Use the{" "} + + + Flag + {" "} + button on any image or description to flag it for review. +

+
+
+ ); +} diff --git a/apps/web/src/components/DiseaseCard.tsx b/apps/web/src/components/DiseaseCard.tsx index f0aab85..d6cf40c 100644 --- a/apps/web/src/components/DiseaseCard.tsx +++ b/apps/web/src/components/DiseaseCard.tsx @@ -132,9 +132,18 @@ export default function DiseaseCard({ {/* Full description */}
-

- Description -

+
+

+ Description +

+ +

{disease.description}

diff --git a/apps/web/src/components/FlagButton.tsx b/apps/web/src/components/FlagButton.tsx index bf44037..ae46b51 100644 --- a/apps/web/src/components/FlagButton.tsx +++ b/apps/web/src/components/FlagButton.tsx @@ -8,6 +8,7 @@ import { useState, useCallback } from "react"; export type FlagContentType = | "plant_image" | "disease_image" + | "disease_description" | "disease_symptoms" | "disease_causes" | "disease_treatment" diff --git a/apps/web/src/components/PlantCard.tsx b/apps/web/src/components/PlantCard.tsx index 0e70198..f74a7c6 100644 --- a/apps/web/src/components/PlantCard.tsx +++ b/apps/web/src/components/PlantCard.tsx @@ -1,3 +1,5 @@ +"use client"; + import Image from "next/image"; import Link from "next/link"; import FlagButton from "@/components/FlagButton"; diff --git a/apps/web/src/lib/db/schema.ts b/apps/web/src/lib/db/schema.ts index 7b4fe42..afc1756 100644 --- a/apps/web/src/lib/db/schema.ts +++ b/apps/web/src/lib/db/schema.ts @@ -130,6 +130,7 @@ export const flaggedContent = sqliteTable( enum: [ "plant_image", "disease_image", + "disease_description", "disease_symptoms", "disease_causes", "disease_treatment",