From 550fdfb5b5c363fc07d52848f8b3285c9ef4c707 Mon Sep 17 00:00:00 2001 From: Abhishekfm Date: Mon, 4 May 2026 13:50:01 +0530 Subject: [PATCH 1/9] Glossary Added --- app/[locale]/glossary/page.tsx | 35 ++ app/[locale]/layout.tsx | 22 +- components/glossary/glossary-client.tsx | 358 ++++++++++++++++++++ components/glossary/glossary-header-nav.tsx | 49 +++ config/site.ts | 4 + glossary/glossary.csv | 17 + glossary/index.tsx | 98 ++++++ glossary/terms.generated.ts | 257 ++++++++++++++ package.json | 1 + scripts/generate-glossary.ts | 180 ++++++++++ styles/tokens/tailwind/color.js | 4 + tsconfig.scripts.json | 10 + 12 files changed, 1025 insertions(+), 10 deletions(-) create mode 100644 app/[locale]/glossary/page.tsx create mode 100644 components/glossary/glossary-client.tsx create mode 100644 components/glossary/glossary-header-nav.tsx create mode 100644 glossary/glossary.csv create mode 100644 glossary/index.tsx create mode 100644 glossary/terms.generated.ts create mode 100644 scripts/generate-glossary.ts create mode 100644 tsconfig.scripts.json diff --git a/app/[locale]/glossary/page.tsx b/app/[locale]/glossary/page.tsx new file mode 100644 index 00000000..d5c779f1 --- /dev/null +++ b/app/[locale]/glossary/page.tsx @@ -0,0 +1,35 @@ +import { getGlossaryIndex } from '@/glossary/index'; +import { Text } from 'opub-ui'; + +import GlossaryClient from '@/components/glossary/glossary-client'; +import GlossaryHeaderNav from '@/components/glossary/glossary-header-nav'; + +export const dynamic = 'force-static'; + +export default async function GlossaryPage() { + const index = getGlossaryIndex(); + + return ( +
+
+
+ + Glossary + + + Understand key terms used across the IDS-DRR platform + + +
+
+ +
+ +
+
+ ); +} diff --git a/app/[locale]/layout.tsx b/app/[locale]/layout.tsx index 0a93b553..18bd1448 100644 --- a/app/[locale]/layout.tsx +++ b/app/[locale]/layout.tsx @@ -120,18 +120,20 @@ export default async function LocaleLayout({ - - - - - - +
+ + + + + + - {children} +
{children}
- -
- + +
+ +
diff --git a/components/glossary/glossary-client.tsx b/components/glossary/glossary-client.tsx new file mode 100644 index 00000000..662cf53a --- /dev/null +++ b/components/glossary/glossary-client.tsx @@ -0,0 +1,358 @@ +'use client'; + +import * as React from 'react'; +import type { GlossaryIndexItem } from '@/glossary/index'; +import { glossaryTags, slugsByTag, termsBySlug } from '@/glossary/index'; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, + Divider, + SearchInput, + Tab, + TabList, + TabPanel, + Tabs, + Tag, + Text, +} from 'opub-ui'; + +type Props = { + index: GlossaryIndexItem[]; +}; + +function groupByLetter(items: GlossaryIndexItem[]) { + const map = new Map(); + for (const item of items) { + const key = (item.letter || '#').toUpperCase(); + const existing = map.get(key); + if (existing) existing.push(item); + else map.set(key, [item]); + } + return [...map.entries()] + .sort(([a], [b]) => a.localeCompare(b)) + .map(([letter, group]) => ({ + letter, + items: group.sort((x, y) => x.term.localeCompare(y.term)), + })); +} + +export default function GlossaryClient({ index }: Props) { + const [selectedTag, setSelectedTag] = React.useState(null); + const [query, setQuery] = React.useState(''); + const deferredQuery = React.useDeferredValue(query); + + const { indexBySlug, termBySlugLower } = React.useMemo(() => { + const indexBySlug: Record = {}; + const termBySlugLower: Record = {}; + for (const item of index) { + indexBySlug[item.slug] = item; + termBySlugLower[item.slug] = (item.term ?? '').toLowerCase(); + } + return { indexBySlug, termBySlugLower }; + }, [index]); + + const normalizedQuery = deferredQuery.trim().toLowerCase(); + + const filteredIndex = React.useMemo(() => { + const base = + selectedTag === null + ? index + : (slugsByTag[selectedTag.toLowerCase()] ?? []) + .map((s) => indexBySlug[s]) + .filter(Boolean); + + if (!normalizedQuery) return base; + + return base.filter((item) => + (termBySlugLower[item.slug] ?? '').includes(normalizedQuery) + ); + }, [index, indexBySlug, normalizedQuery, selectedTag, termBySlugLower]); + + const grouped = React.useMemo( + () => groupByLetter(filteredIndex), + [filteredIndex] + ); + const [openSlug, setOpenSlug] = React.useState(null); + const [errorSlug, setErrorSlug] = React.useState(null); + + return ( +
+ setQuery(value)} + onClear={() => setQuery('')} + /> + +
+
{ + setSelectedTag(null); + }} + className="cursor-pointer" + > + + All + +
+ {glossaryTags.map((filter) => { + const isActive = selectedTag?.toLowerCase() === filter.toLowerCase(); + return ( +
{ + setSelectedTag(filter); + setOpenSlug(null); + }} + className="cursor-pointer" + > + + {filter} + +
+ ); + })} +
+ + {grouped.length > 0 ? ( + grouped.map(({ letter, items }) => ( +
+
+ + {letter} + +
+ + + { + const next = val || null; + setOpenSlug(next); + if (next) { + setErrorSlug(termsBySlug[next] ? null : next); + } + }} + className="divide-y " + > + {items.map((item) => { + const isOpen = openSlug === item.slug; + const isError = errorSlug === item.slug; + const full = termsBySlug[item.slug]; + + return ( + + +
+
+ {item.term} +
+ {item.short} +
+
+ + +
+ {isError && ( + + Couldn’t load details for this term. + + )} + + {isOpen && full && ( +
+
+ + Definition + + + + {full.definitions.long} + +
+ +
+ + + +
+ + + + {full.disaster_differences?.length && + full.disaster_differences.length > 0 && ( +
+ + How this differs across disaster contexts + +
+ {full.disaster_differences.map( + (d) => + d?.disaster_type && ( +
+ + {d?.disaster_type} + + + {d?.difference} + +
+ ) + )} +
+
+ )} + + + + {full.common_misinterpretation && ( +
+ + Common misinterpretation + + + + {full.common_misinterpretation} + +
+ )} +
+ )} +
+
+
+ ); + })} +
+
+ )) + ) : ( +
+ + No results found + +
+ )} +
+ ); +} + +function InfoCard({ title, body }: { title: string; body?: string }) { + if (!body) return null; + return ( +
+ + {title} + + +
+ {body.split(',').map((item) => ( + + {item.trim()} + + ))} +
+
+ ); +} + +function DetailsCard({ title, body }: { title: string; body: string }) { + return ( +
+ + {title} + + + {body} + +
+ ); +} + +function ContextTabs({ policy, model }: { policy?: string; model?: string }) { + if (!policy && !model) return null; + return ( +
+ + Contextual interpretation + + + + + {policy && In Policy} + {model && In Model} + + {policy && ( + +
+ + Policy + + + {policy} + +
+
+ )} + {model && ( + +
+ + Model + + + {model} + +
+
+ )} +
+
+ ); +} diff --git a/components/glossary/glossary-header-nav.tsx b/components/glossary/glossary-header-nav.tsx new file mode 100644 index 00000000..052c1c9a --- /dev/null +++ b/components/glossary/glossary-header-nav.tsx @@ -0,0 +1,49 @@ +'use client'; + +import React from 'react'; +import Link from 'next/link'; +import { Icon, Text } from 'opub-ui'; + +import { documentationLink } from '@/config/consts'; +import Icons from '../icons'; + +export default function GlossaryHeaderNav() { + return ( +
+ +
+ + User Manual + + +
+ + +
+ + Full Documentation + + +
+ +
+ ); +} diff --git a/config/site.ts b/config/site.ts index bfcb4513..6014bf59 100644 --- a/config/site.ts +++ b/config/site.ts @@ -86,6 +86,10 @@ export const mainConfig: MainConfig = { title: 'About us', href: AboutUsURL, }, + { + title: 'Glossary', + href: '/glossary', + }, ], sidebarNav: [ diff --git a/glossary/glossary.csv b/glossary/glossary.csv new file mode 100644 index 00000000..1ec6e0e5 --- /dev/null +++ b/glossary/glossary.csv @@ -0,0 +1,17 @@ +letter,term,tag,short,long,ids_drr,where_seen,why_it_matters,policy,model,disaster_type_0,difference_0,disaster_type_1,difference_1,disaster_type_2,difference_2,related_terms,common_misinterpretation +E,Exposure,data,The people property and assets located in hazard-prone areas,"Exposure refers to what or who is present in places where hazardous events might occur. This includes population counts, buildings, infrastructure, agricultural land, and economic activities in at-risk zones.","IDS-DRR maps exposure using census data, satellite imagery, infrastructure databases, and economic activity records to show exactly what could be affected by different hazard scenarios.","Exposure maps showing population density in risk zones, asset inventories, infrastructure overlays, and economic impact projections",Understanding exposure helps quantify potential losses and prioritize protection measures. Reducing exposure through land-use planning or relocation is one of the most effective ways to reduce disaster risk.,Used to determine land-use planning restrictions and relocation programs,"Models predict population exposure based on historical hazard data, socioeconomic indicators, and climate projections to anticipate future risks.",Flood Exposure,"Based on population/assets in flood plains, near rivers, or low-lying coastal areas",Heat Exposure,"Based on population in areas with high temperatures, poor ventilation, or limited cooling",Cyclone Exposure,Based on population/assets in coastal zones within cyclone-prone regions,"risk,hazard,vulnerability",High exposure doesn't mean high risk if those exposed are well-protected or the hazard is unlikely +H,Hazard ,Risk Component ,A potentially damaging natural event that may cause loss of life or property,"A process, phenomenon or human activity that may cause loss of life, injury or other health impacts, property damage, social and economic disruption or environmental degradation.","Calculates a Flood Hazard Score per revenue circle/District/Subsditrict using satellite-derived flood extent data, historical inundation records, and hydro-meteorological indicators.","Flood hazard maps, inundation layers, SDMA early warning bulletins, IMD rainfall alerts, CWC flood forecasting data","Hazard assessment shows where and how severely floods strike. Without it, risk scores cannot be computed.",,,,,,,,,, +V,Vulnerability,Risk Component ,The conditions that make people or assets more susceptible to harm from a hazard,"Social, economic, physical, and institutional factors that increase likelihood of harm. Includes housing quality, health status, access to infrastructure, and governance strength.","Measures demographic vulnerability using WORLDPOP, Bharat Map and Antodaya indicators: population density, sex ratio and household level data at the District/Subdistrict/Revenue Circle circle level.","Census of India tables, NFHS health indicators, SECC deprivation data, SDMA damage reports disaggregated by population group",Two areas with identical hazard and exposure can have very different outcomes depending on vulnerability,,,,,,,,,, +R,Risk Score ,Topographic Wetness Index," A composite index quantifying overall risk based on hazard, exposure, vulnerability, and government response",Aggregates multiple risk components into a single comparable value per geographic unit and hence enabling prioritisation and tracking of change over time.,"Computes a Comprehensive Risk Score for every revenue circle/district/subdistrict for a state combining sub-scores for Flood Hazard, Exposure, Vulnerability, and Government Response.","IDS-DRR dashboard, SDMA planning documents, district disaster management plans",Translates complex multi-dimensional data into an actionable number for resource allocation and progress tracking.,,,,,,,,,, +G,Government Response,Risk Component ,"The capacity of government institutions to prepare for, respond to, and recover from flood events","Measures how effectively public systems reduce harm before, during, and after an hazard event ncluding relief infrastructure, procurement efficiency, and recovery expenditure.",Incorporates public procurement and fiscal data from the state tender portal and SECC Meetings measure whether money for flood management is being spent effectively and on time.," state budget documents, SDMA flood reports, Open Contracting procurement data, Finance Department expenditure records, SECC meetings",Even high-hazard areas can have lower net risk with strong government response. Weak procurement amplifies disaster impacts.,,,,,,,,,, +I,Inundation,Data Source,The temporary flooding of land caused by overflow of water from rivers or heavy rainfall,"Physical covering of land by floodwater. Measured by extent, depth, and duration. the driving factor varies from geography to geographies but primarily driven by riverine overflowing during monsoon.",Uses Satellite imagery via Bhuvan to track spatial extent and duration of flooding across Districts/Subdistricts/Revenue circles each monsoon season.,"MODIS and Sentinel satellite flood maps, ASDMA FRIMS daily situation reports, BHUVAN portal, CWC flood bulletins",Inundation extent is the primary input for flood hazard scores. Historical frequency data identifies which administrative unit faces repeated flooding.,,,,,,,,,, +R,Revenue Circle,Administrative,"The smallest administrative unit used for land revenue and governance in Assam, below sub-district level","Administrative subdivision for land records, revenue collection and local governance. IDS-DRR uses revenue circles as the primary unit of analysis for all risk scoring.","Computes all risk scores hazard, vulnerability, exposure, and government response at the revenue circle level for the state of Assam.","Revenue department records, ASDMA damage reports, Census sub-district data, land record portal",Revenue circle granularity identifies high-risk pockets within a district that district-level averages would mask.,,,,,,,,,, +A,Access to Infrastructure,Risk Component ,"A measure of community proximity to critical physical infrastructure like roads, hospitals, railways, and Healthcare shelters ",Evaluates connectivity of populations to services essential during and after disaster. Poor access amplifies vulnerability and slows recovery.,"Evaluates proximity to roads, rail, hospitals, and shelters (Schools) at the revenue circle level. This layer feeds into the vulnerability score by identifying communities that would be cut off during floods.","OpenStreetMap road data, NHM facility registers, PMGSY rural roads data, Census 2011 village amenities",Road submergence during floods frequently cuts off communities. Poor infrastructure access disproportionately harms even moderate-hazard areas.,,,,,,,,,, +D,DRIMS,Operational ,Flood Reporting and Information Management System by ASDMA for tracking flood damage across Assam during flood seasons,"Primary operational data system for ASDMA to collect and publish daily flood situation reports during monsoon. Data covers population affected, houses damaged, livestock lost, crop inundated, and relief status.",DRIMS data forms a key component of IDS-DRR's past damages dataset. Provides the historical record of flood impacts at revenue circle level used to calibrate hazard scores.,"ASDMA website, daily monsoon situation reports, annual flood reports, IDS-DRR source dataset",One of the few sub-district flood damage datasets systematically maintained in India. Its longitudinal record enables trend analysis and model validation.,,,,,,,,,, +P,Procurement Data,Data Source,"Records of government contracts and tenders for an event-related goods, services, and works — used to assess efficiency and timeliness of public spending","Captures the full contracting cycle: what was procured, from whom, at what price, when awarded, and whether completed on time. Covers embankment works, relief supplies, early warning systems, and disaster equipment.","One of the first disaster risk models in India to incorporate procurement data. Uses assamtenders.gov.in and OCDS data to measure procurement timeliness, completion rates, and expenditure against budget.","gePNIC portal, Finance Department budget documents, Open Contracting Partnership India datasets, CAG audit reports","Budget allocation tells only part of the story. Late contracts, underperforming suppliers, or unspent funds mean allocations don't translate into protection",,,,,,,,,, +R,Resilience,Conceptual,"The ability of a community to absorb, adapt to, and recover from disaster impacts while maintaining essential functions","A resilient community can absorb shocks without catastrophic loss, adapt based on experience, and bounce back to pre-disaster or better conditions. Shaped by social cohesion, economic diversity, institutional strength, and resource access.","IDS-DRR proxies resilience through existing components: government response capacity (institutional), infrastructure access (physical), and low demographic deprivation (social). Improving any component increases resilience.","Sendai Framework 2015-2030, UNDP Human Development Index, World Bank Resilience Index, ASDMA community resilience programme reports",Resilience framing shifts DRR from reactive (responding) to proactive (building communities that can withstand and adapt). It is the long-term goal risk reduction investments work toward.,,,,,,,,,, +D,Demographic Vulnerability,Risk Component ,The degree to which a population's social and demographic characteristics increase susceptibility to a hazard harm,"Examines who the at-risk population is gender, age, economic deprivation and access to basic services. Populations with more women, elderly, deprived, or those lacking WASH (Water, Sanitation and Hygeine) face greater harm.","Measures census indicators per District/Subdistrict/Revenue Ciircle: total population, sex ratio households without safe drinking water, and households without sanitation to undertsand who is more vulnerable to a flood related disater ","Census of India 2011 primary census abstract, SECC 2011 deprivation data, NFHS-5 district health indicators",Reveals who bears the greatest burden. Poor WASH access amplifies flood health impacts through waterborne disease disproportionately affecting deprived communities.,,,,,,,,,, +E,Embankment,Risk Component ,Earthen or concrete flood protection structures built along riverbanks to prevent floodwater from entering inhabited or agricultural land,Linear flood protection bunds along rivers.Their condition directly determines whether a flood event becomes a catastrophe.,Incorporates embankment condition and breach data in the loss and damages layer. ,"Water Resources Department inspection records,SDMA flood damage reports, state budget allocations, procurement data ",Embankment breaches are the single most common cause of sudden large-scale inundation in certain geographies . A single breach can submerge dozens of villages overnight.,,,,,,,,,, +D,DEM,Data Source,Digital Elevation Model is a raster grid of bare-earth surface elevations,"Foundation for all topographic flood analysis. Used to calculate slope, identify low-lying zones, derive drainage networks, compute TWI (Topographic Wetness Index), and model flood accumulation",NASA DEM (30m) is a core input for Flood Hazard scoring. Elevation and slope derived from DEM identify District/Subdistrict and Revenue circlein low-lying flood-susceptible valley terrain. It is one of the main hazard data indicator.,"NASA Earth Data,USGS",Elevation is the most powerful predictor of flood susceptibility. Low-elevation floodplains are structurally flood-prone regardless of embankments.,,,,,,,,,, +S,Slope ,Data Source,Rate of terrain elevation change derived from DEM — predictor of water flow speed and accumulation,"Nearly flat slopes in floodplain cause slow-draining, long-duration inundation and Steep slopes in hill districts generate rapid flash flood runoff.",In IDS-DRR Low-slope floodplain areas assigned higher flood hazard weights due to water accumulation characteristics.,"NASA Earth Data,USGS",Structural reason some geographies experiences prolonged inundation near-flat valley prevents rapid drainage after embankment breach or river overflow.,,,,,,,,,, +N,NDVI,Data Source,Normalised Difference Vegetation Index — measures vegetation density from satellite imagery,Calculated from NIR and Red satellite bands. High values = dense vegetation (flood buffer); low values = barren/urban (faster runoff). Tracks seasonal land cover dynamics in the geographies,"Low NDVI areas (fallow land, chars, degraded forest) associated with higher surface runoff and flood susceptibility","NASA Earth Data,USGS,DICRA",Captures seasonal and multi-year land cover dynamics explaining year-to-year flood damage variation beyond rainfall alone.,,,,,,,,,, \ No newline at end of file diff --git a/glossary/index.tsx b/glossary/index.tsx new file mode 100644 index 00000000..e7eac0a2 --- /dev/null +++ b/glossary/index.tsx @@ -0,0 +1,98 @@ +import { GlossaryTermsSource } from './terms.generated'; + +export type GlossaryItem = { + letter: string; + term: string; + tag?: string; + definitions: { + short: string; + long: string; + }; + details: { + ids_drr: string; + where_seen: string; + why_it_matters: string; + }; + contexts?: { + policy: string; + model: string; + }; + disaster_differences?: [ + GlossaryDisasterDifference?, + GlossaryDisasterDifference?, + GlossaryDisasterDifference?, + ]; + related_terms?: string[]; + common_misinterpretation?: string; +}; + +export type GlossaryDisasterDifference = { + disaster_type: string; + difference: string; +}; + +export type GlossaryIndexItem = { + slug: string; + letter: string; + term: string; + short: string; +}; + +export function slugifyGlossaryTerm(term: string) { + return term + .toLowerCase() + .trim() + .replace(/['"]/g, '') + .replace(/[^a-z0-9]+/g, '-') + .replace(/(^-|-$)/g, ''); +} + +export const termsBySlug: Record = + GlossaryTermsSource.reduce>((acc, item) => { + const slug = slugifyGlossaryTerm(item.term); + acc[slug] = item; + return acc; + }, {}); + +export const slugsByLetter: Record = + GlossaryTermsSource.reduce>((acc, item) => { + const letter = (item.letter || '#').toUpperCase(); + const slug = slugifyGlossaryTerm(item.term); + acc[letter] = [...(acc[letter] ?? []), slug]; + return acc; + }, {}); + +export const slugsByTag: Record = GlossaryTermsSource.reduce< + Record +>((acc, item) => { + const tag = (item.tag ?? '').trim(); + if (!tag) return acc; + const key = tag.toLowerCase(); + const slug = slugifyGlossaryTerm(item.term); + acc[key] = [...(acc[key] ?? []), slug]; + return acc; +}, {}); + +export const glossaryTags: string[] = (() => { + const labelsByKey = new Map(); + for (const item of GlossaryTermsSource) { + const raw = (item.tag ?? '').trim(); + if (!raw) continue; + const key = raw.toLowerCase(); + if (!labelsByKey.has(key)) labelsByKey.set(key, raw); + } + return [...labelsByKey.values()].sort((a, b) => a.localeCompare(b)); +})(); + +export function getGlossaryIndex(): GlossaryIndexItem[] { + return Object.entries(termsBySlug).map(([slug, item]) => ({ + slug, + letter: item.letter, + term: item.term, + short: item.definitions.short, + })); +} + +export function getGlossaryTags() { + return glossaryTags; +} diff --git a/glossary/terms.generated.ts b/glossary/terms.generated.ts new file mode 100644 index 00000000..396b3a35 --- /dev/null +++ b/glossary/terms.generated.ts @@ -0,0 +1,257 @@ +/* eslint-disable */ +// THIS FILE IS AUTO-GENERATED. +// Source: glossary/glossary.csv +// Generated by: scripts/generate-glossary.ts + +import type { GlossaryItem } from './index'; + +export const GlossaryTermsSource: GlossaryItem[] = [ + { + "letter": "A", + "term": "Access to Infrastructure", + "tag": "Risk Component", + "definitions": { + "short": "A measure of community proximity to critical physical infrastructure like roads, hospitals, railways, and Healthcare shelters", + "long": "Evaluates connectivity of populations to services essential during and after disaster. Poor access amplifies vulnerability and slows recovery." + }, + "details": { + "ids_drr": "Evaluates proximity to roads, rail, hospitals, and shelters (Schools) at the revenue circle level. This layer feeds into the vulnerability score by identifying communities that would be cut off during floods.", + "where_seen": "OpenStreetMap road data, NHM facility registers, PMGSY rural roads data, Census 2011 village amenities", + "why_it_matters": "Road submergence during floods frequently cuts off communities. Poor infrastructure access disproportionately harms even moderate-hazard areas." + } + }, + { + "letter": "D", + "term": "DEM", + "tag": "Data Source", + "definitions": { + "short": "Digital Elevation Model is a raster grid of bare-earth surface elevations", + "long": "Foundation for all topographic flood analysis. Used to calculate slope, identify low-lying zones, derive drainage networks, compute TWI (Topographic Wetness Index), and model flood accumulation" + }, + "details": { + "ids_drr": "NASA DEM (30m) is a core input for Flood Hazard scoring. Elevation and slope derived from DEM identify District/Subdistrict and Revenue circlein low-lying flood-susceptible valley terrain. It is one of the main hazard data indicator.", + "where_seen": "NASA Earth Data,USGS", + "why_it_matters": "Elevation is the most powerful predictor of flood susceptibility. Low-elevation floodplains are structurally flood-prone regardless of embankments." + } + }, + { + "letter": "D", + "term": "Demographic Vulnerability", + "tag": "Risk Component", + "definitions": { + "short": "The degree to which a population's social and demographic characteristics increase susceptibility to a hazard harm", + "long": "Examines who the at-risk population is gender, age, economic deprivation and access to basic services. Populations with more women, elderly, deprived, or those lacking WASH (Water, Sanitation and Hygeine) face greater harm." + }, + "details": { + "ids_drr": "Measures census indicators per District/Subdistrict/Revenue Ciircle: total population, sex ratio households without safe drinking water, and households without sanitation to undertsand who is more vulnerable to a flood related disater", + "where_seen": "Census of India 2011 primary census abstract, SECC 2011 deprivation data, NFHS-5 district health indicators", + "why_it_matters": "Reveals who bears the greatest burden. Poor WASH access amplifies flood health impacts through waterborne disease disproportionately affecting deprived communities." + } + }, + { + "letter": "D", + "term": "DRIMS", + "tag": "Operational", + "definitions": { + "short": "Flood Reporting and Information Management System by ASDMA for tracking flood damage across Assam during flood seasons", + "long": "Primary operational data system for ASDMA to collect and publish daily flood situation reports during monsoon. Data covers population affected, houses damaged, livestock lost, crop inundated, and relief status." + }, + "details": { + "ids_drr": "DRIMS data forms a key component of IDS-DRR's past damages dataset. Provides the historical record of flood impacts at revenue circle level used to calibrate hazard scores.", + "where_seen": "ASDMA website, daily monsoon situation reports, annual flood reports, IDS-DRR source dataset", + "why_it_matters": "One of the few sub-district flood damage datasets systematically maintained in India. Its longitudinal record enables trend analysis and model validation." + } + }, + { + "letter": "E", + "term": "Embankment", + "tag": "Risk Component", + "definitions": { + "short": "Earthen or concrete flood protection structures built along riverbanks to prevent floodwater from entering inhabited or agricultural land", + "long": "Linear flood protection bunds along rivers.Their condition directly determines whether a flood event becomes a catastrophe." + }, + "details": { + "ids_drr": "Incorporates embankment condition and breach data in the loss and damages layer.", + "where_seen": "Water Resources Department inspection records,SDMA flood damage reports, state budget allocations, procurement data", + "why_it_matters": "Embankment breaches are the single most common cause of sudden large-scale inundation in certain geographies . A single breach can submerge dozens of villages overnight." + } + }, + { + "letter": "E", + "term": "Exposure", + "tag": "data", + "definitions": { + "short": "The people property and assets located in hazard-prone areas", + "long": "Exposure refers to what or who is present in places where hazardous events might occur. This includes population counts, buildings, infrastructure, agricultural land, and economic activities in at-risk zones." + }, + "details": { + "ids_drr": "IDS-DRR maps exposure using census data, satellite imagery, infrastructure databases, and economic activity records to show exactly what could be affected by different hazard scenarios.", + "where_seen": "Exposure maps showing population density in risk zones, asset inventories, infrastructure overlays, and economic impact projections", + "why_it_matters": "Understanding exposure helps quantify potential losses and prioritize protection measures. Reducing exposure through land-use planning or relocation is one of the most effective ways to reduce disaster risk." + }, + "contexts": { + "policy": "Used to determine land-use planning restrictions and relocation programs", + "model": "Models predict population exposure based on historical hazard data, socioeconomic indicators, and climate projections to anticipate future risks." + }, + "disaster_differences": [ + { + "disaster_type": "Flood Exposure", + "difference": "Based on population/assets in flood plains, near rivers, or low-lying coastal areas" + }, + { + "disaster_type": "Heat Exposure", + "difference": "Based on population in areas with high temperatures, poor ventilation, or limited cooling" + }, + { + "disaster_type": "Cyclone Exposure", + "difference": "Based on population/assets in coastal zones within cyclone-prone regions" + } + ], + "related_terms": [ + "risk", + "hazard", + "vulnerability" + ], + "common_misinterpretation": "High exposure doesn't mean high risk if those exposed are well-protected or the hazard is unlikely" + }, + { + "letter": "G", + "term": "Government Response", + "tag": "Risk Component", + "definitions": { + "short": "The capacity of government institutions to prepare for, respond to, and recover from flood events", + "long": "Measures how effectively public systems reduce harm before, during, and after an hazard event ncluding relief infrastructure, procurement efficiency, and recovery expenditure." + }, + "details": { + "ids_drr": "Incorporates public procurement and fiscal data from the state tender portal and SECC Meetings measure whether money for flood management is being spent effectively and on time.", + "where_seen": "state budget documents, SDMA flood reports, Open Contracting procurement data, Finance Department expenditure records, SECC meetings", + "why_it_matters": "Even high-hazard areas can have lower net risk with strong government response. Weak procurement amplifies disaster impacts." + } + }, + { + "letter": "H", + "term": "Hazard", + "tag": "Risk Component", + "definitions": { + "short": "A potentially damaging natural event that may cause loss of life or property", + "long": "A process, phenomenon or human activity that may cause loss of life, injury or other health impacts, property damage, social and economic disruption or environmental degradation." + }, + "details": { + "ids_drr": "Calculates a Flood Hazard Score per revenue circle/District/Subsditrict using satellite-derived flood extent data, historical inundation records, and hydro-meteorological indicators.", + "where_seen": "Flood hazard maps, inundation layers, SDMA early warning bulletins, IMD rainfall alerts, CWC flood forecasting data", + "why_it_matters": "Hazard assessment shows where and how severely floods strike. Without it, risk scores cannot be computed." + } + }, + { + "letter": "I", + "term": "Inundation", + "tag": "Data Source", + "definitions": { + "short": "The temporary flooding of land caused by overflow of water from rivers or heavy rainfall", + "long": "Physical covering of land by floodwater. Measured by extent, depth, and duration. the driving factor varies from geography to geographies but primarily driven by riverine overflowing during monsoon." + }, + "details": { + "ids_drr": "Uses Satellite imagery via Bhuvan to track spatial extent and duration of flooding across Districts/Subdistricts/Revenue circles each monsoon season.", + "where_seen": "MODIS and Sentinel satellite flood maps, ASDMA FRIMS daily situation reports, BHUVAN portal, CWC flood bulletins", + "why_it_matters": "Inundation extent is the primary input for flood hazard scores. Historical frequency data identifies which administrative unit faces repeated flooding." + } + }, + { + "letter": "N", + "term": "NDVI", + "tag": "Data Source", + "definitions": { + "short": "Normalised Difference Vegetation Index — measures vegetation density from satellite imagery", + "long": "Calculated from NIR and Red satellite bands. High values = dense vegetation (flood buffer); low values = barren/urban (faster runoff). Tracks seasonal land cover dynamics in the geographies" + }, + "details": { + "ids_drr": "Low NDVI areas (fallow land, chars, degraded forest) associated with higher surface runoff and flood susceptibility", + "where_seen": "NASA Earth Data,USGS,DICRA", + "why_it_matters": "Captures seasonal and multi-year land cover dynamics explaining year-to-year flood damage variation beyond rainfall alone." + } + }, + { + "letter": "P", + "term": "Procurement Data", + "tag": "Data Source", + "definitions": { + "short": "Records of government contracts and tenders for an event-related goods, services, and works — used to assess efficiency and timeliness of public spending", + "long": "Captures the full contracting cycle: what was procured, from whom, at what price, when awarded, and whether completed on time. Covers embankment works, relief supplies, early warning systems, and disaster equipment." + }, + "details": { + "ids_drr": "One of the first disaster risk models in India to incorporate procurement data. Uses assamtenders.gov.in and OCDS data to measure procurement timeliness, completion rates, and expenditure against budget.", + "where_seen": "gePNIC portal, Finance Department budget documents, Open Contracting Partnership India datasets, CAG audit reports", + "why_it_matters": "Budget allocation tells only part of the story. Late contracts, underperforming suppliers, or unspent funds mean allocations don't translate into protection" + } + }, + { + "letter": "R", + "term": "Resilience", + "tag": "Conceptual", + "definitions": { + "short": "The ability of a community to absorb, adapt to, and recover from disaster impacts while maintaining essential functions", + "long": "A resilient community can absorb shocks without catastrophic loss, adapt based on experience, and bounce back to pre-disaster or better conditions. Shaped by social cohesion, economic diversity, institutional strength, and resource access." + }, + "details": { + "ids_drr": "IDS-DRR proxies resilience through existing components: government response capacity (institutional), infrastructure access (physical), and low demographic deprivation (social). Improving any component increases resilience.", + "where_seen": "Sendai Framework 2015-2030, UNDP Human Development Index, World Bank Resilience Index, ASDMA community resilience programme reports", + "why_it_matters": "Resilience framing shifts DRR from reactive (responding) to proactive (building communities that can withstand and adapt). It is the long-term goal risk reduction investments work toward." + } + }, + { + "letter": "R", + "term": "Revenue Circle", + "tag": "Administrative", + "definitions": { + "short": "The smallest administrative unit used for land revenue and governance in Assam, below sub-district level", + "long": "Administrative subdivision for land records, revenue collection and local governance. IDS-DRR uses revenue circles as the primary unit of analysis for all risk scoring." + }, + "details": { + "ids_drr": "Computes all risk scores hazard, vulnerability, exposure, and government response at the revenue circle level for the state of Assam.", + "where_seen": "Revenue department records, ASDMA damage reports, Census sub-district data, land record portal", + "why_it_matters": "Revenue circle granularity identifies high-risk pockets within a district that district-level averages would mask." + } + }, + { + "letter": "R", + "term": "Risk Score", + "tag": "Topographic Wetness Index", + "definitions": { + "short": "A composite index quantifying overall risk based on hazard, exposure, vulnerability, and government response", + "long": "Aggregates multiple risk components into a single comparable value per geographic unit and hence enabling prioritisation and tracking of change over time." + }, + "details": { + "ids_drr": "Computes a Comprehensive Risk Score for every revenue circle/district/subdistrict for a state combining sub-scores for Flood Hazard, Exposure, Vulnerability, and Government Response.", + "where_seen": "IDS-DRR dashboard, SDMA planning documents, district disaster management plans", + "why_it_matters": "Translates complex multi-dimensional data into an actionable number for resource allocation and progress tracking." + } + }, + { + "letter": "S", + "term": "Slope", + "tag": "Data Source", + "definitions": { + "short": "Rate of terrain elevation change derived from DEM — predictor of water flow speed and accumulation", + "long": "Nearly flat slopes in floodplain cause slow-draining, long-duration inundation and Steep slopes in hill districts generate rapid flash flood runoff." + }, + "details": { + "ids_drr": "In IDS-DRR Low-slope floodplain areas assigned higher flood hazard weights due to water accumulation characteristics.", + "where_seen": "NASA Earth Data,USGS", + "why_it_matters": "Structural reason some geographies experiences prolonged inundation near-flat valley prevents rapid drainage after embankment breach or river overflow." + } + }, + { + "letter": "V", + "term": "Vulnerability", + "tag": "Risk Component", + "definitions": { + "short": "The conditions that make people or assets more susceptible to harm from a hazard", + "long": "Social, economic, physical, and institutional factors that increase likelihood of harm. Includes housing quality, health status, access to infrastructure, and governance strength." + }, + "details": { + "ids_drr": "Measures demographic vulnerability using WORLDPOP, Bharat Map and Antodaya indicators: population density, sex ratio and household level data at the District/Subdistrict/Revenue Circle circle level.", + "where_seen": "Census of India tables, NFHS health indicators, SECC deprivation data, SDMA damage reports disaggregated by population group", + "why_it_matters": "Two areas with identical hazard and exposure can have very different outcomes depending on vulnerability" + } + } +]; diff --git a/package.json b/package.json index b390d461..dcf3819b 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "build:tokens": "node ./config/tokens/generate.mjs", "postbuild:tokens": "node ./config/tokens/copy.js", "generate": "graphql-codegen --require dotenv/config --config ./config/codegen.ts --watch", + "generate:glossary": "ts-node -P tsconfig.scripts.json ./scripts/generate-glossary.ts", "clean": "rm -rf node_modules package-lock.json", "test": "jest", "test:watch": "jest - watch", diff --git a/scripts/generate-glossary.ts b/scripts/generate-glossary.ts new file mode 100644 index 00000000..89dc4a2e --- /dev/null +++ b/scripts/generate-glossary.ts @@ -0,0 +1,180 @@ +import fs from 'node:fs'; +import path from 'node:path'; + +type CsvRow = Record; + +function parseCsv(text: string): CsvRow[] { + const rows: string[][] = []; + let currentField = ''; + let currentRow: string[] = []; + let inQuotes = false; + + const pushField = () => { + currentRow.push(currentField); + currentField = ''; + }; + + const pushRow = () => { + // skip empty trailing lines + if (currentRow.length === 1 && currentRow[0].trim() === '') { + currentRow = []; + return; + } + rows.push(currentRow); + currentRow = []; + }; + + for (let i = 0; i < text.length; i++) { + const ch = text[i]; + + if (ch === '"') { + if (inQuotes && text[i + 1] === '"') { + currentField += '"'; + i++; + } else { + inQuotes = !inQuotes; + } + continue; + } + + if (!inQuotes && ch === ',') { + pushField(); + continue; + } + + if (!inQuotes && (ch === '\n' || ch === '\r')) { + // handle CRLF + if (ch === '\r' && text[i + 1] === '\n') i++; + pushField(); + pushRow(); + continue; + } + + currentField += ch; + } + + // final field/row + pushField(); + if (currentRow.length > 0) pushRow(); + + if (rows.length === 0) return []; + const headers = rows[0].map((h) => h.trim()); + + return rows.slice(1).map((r) => { + const obj: CsvRow = {}; + for (let i = 0; i < headers.length; i++) { + obj[headers[i]] = (r[i] ?? '').trim(); + } + return obj; + }); +} + +function toRelatedTerms(value: string): string[] { + const raw = value.trim(); + if (!raw) return []; + return raw + .split(',') + .map((x) => x.trim()) + .filter(Boolean); +} + +function buildDisasterDifferences(row: CsvRow) { + const diffs: { disaster_type: string; difference: string }[] = []; + for (let i = 0; i < 3; i++) { + const t = row[`disaster_type_${i}`]; + const d = row[`difference_${i}`]; + if (!t && !d) continue; + if (!t || !d) continue; + diffs.push({ disaster_type: t, difference: d }); + } + return diffs; +} + +function nonEmpty(value: string | undefined | null): value is string { + return typeof value === 'string' && value.trim().length > 0; +} + +function main() { + const repoRoot = process.cwd(); + const csvPath = path.join(repoRoot, 'glossary', 'glossary.csv'); + const outPath = path.join(repoRoot, 'glossary', 'terms.generated.ts'); + + const csvText = fs.readFileSync(csvPath, 'utf8'); + const rows = parseCsv(csvText); + + const items = rows + .filter((r) => r.term && r.letter) + .map((r) => { + const letter = r.letter.toUpperCase(); + const term = r.term; + const tag = (r.tag ?? '').trim(); + + const policy = (r.policy ?? '').trim(); + const model = (r.model ?? '').trim(); + const contexts = + nonEmpty(policy) || nonEmpty(model) ? { policy, model } : undefined; + + const disaster_differences = buildDisasterDifferences(r); + const related_terms = toRelatedTerms(r.related_terms ?? ''); + + const common_misinterpretation = (r.common_misinterpretation ?? '').trim(); + + return { + letter, + term, + ...(nonEmpty(tag) ? { tag } : {}), + definitions: { + short: (r.short ?? '').trim(), + long: (r.long ?? '').trim(), + }, + details: { + ids_drr: (r.ids_drr ?? '').trim(), + where_seen: (r.where_seen ?? '').trim(), + why_it_matters: (r.why_it_matters ?? '').trim(), + }, + ...(contexts ? { contexts } : {}), + ...(disaster_differences.length > 0 + ? { disaster_differences } + : {}), + ...(related_terms.length > 0 ? { related_terms } : {}), + ...(nonEmpty(common_misinterpretation) + ? { common_misinterpretation } + : {}), + }; + }) + .sort((a, b) => { + const byTerm = a.term.localeCompare(b.term, undefined, { + sensitivity: 'base', + numeric: true, + }); + if (byTerm !== 0) return byTerm; + + const byLetter = a.letter.localeCompare(b.letter, undefined, { + sensitivity: 'base', + }); + if (byLetter !== 0) return byLetter; + + return (a.tag ?? '').localeCompare(b.tag ?? '', undefined, { + sensitivity: 'base', + numeric: true, + }); + }); + + const file = `/* eslint-disable */ +// THIS FILE IS AUTO-GENERATED. +// Source: glossary/glossary.csv +// Generated by: scripts/generate-glossary.ts + +import type { GlossaryItem } from './index'; + +export const GlossaryTermsSource: GlossaryItem[] = ${JSON.stringify( + items, + null, + 2 + )}; +`; + + fs.writeFileSync(outPath, file, 'utf8'); +} + +main(); diff --git a/styles/tokens/tailwind/color.js b/styles/tokens/tailwind/color.js index ccb3f29d..1a63b21e 100644 --- a/styles/tokens/tailwind/color.js +++ b/styles/tokens/tailwind/color.js @@ -83,6 +83,10 @@ module.exports = { baseAmberSolid10: '#FFA01C', baseAmberSolid11: '#AD5700', baseAmberSolid12: '#4E2009', + baseSurfaceSubdued: '#FAFBFB', + baseWhite: '#FFFFFF', + baseAlertSubued: '#FFEBD3', + baseSurfacePressed: '#F1f2f3', basePureWhite: '#FFFFFF', basePureBlack: '#000000', textDefault: 'var(--base-gray-slate-solid-12)', diff --git a/tsconfig.scripts.json b/tsconfig.scripts.json new file mode 100644 index 00000000..8c386fdb --- /dev/null +++ b/tsconfig.scripts.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "CommonJS", + "moduleResolution": "Node", + "noEmit": true + }, + "include": ["scripts/**/*.ts"] +} + From c2aaacf224de54e42626a7e137f008c00f46f6ec Mon Sep 17 00:00:00 2001 From: James McKinney <26463+jpmckinney@users.noreply.github.com> Date: Mon, 11 May 2026 11:41:04 -0400 Subject: [PATCH 2/9] chore: Use routes.glossary instead of '/glossary' --- config/site.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/site.ts b/config/site.ts index ce5dd034..c5d9e293 100644 --- a/config/site.ts +++ b/config/site.ts @@ -64,6 +64,6 @@ export const mainNav: { key: NavLinkKey; href: string }[] = [ ? [{ key: 'aboutUs' as const, href: routes.aboutUs }] : []), ...(features.glossary - ? [{ key: 'glossary' as const, href: '/glossary' }] + ? [{ key: 'glossary' as const, href: routes.glossary }] : []), ]; From edaa4d922385ee3fed253f9504341e146c25bd38 Mon Sep 17 00:00:00 2001 From: James McKinney <26463+jpmckinney@users.noreply.github.com> Date: Mon, 11 May 2026 12:21:26 -0400 Subject: [PATCH 3/9] i18n: Extract text into messages --- app/[locale]/glossary/page.tsx | 11 ++- components/glossary/glossary-client.tsx | 38 ++++++----- components/glossary/glossary-header-nav.tsx | 74 +++++++++++---------- config/site.ts | 2 + locales/en.json | 35 ++++++++++ package-lock.json | 2 +- 6 files changed, 106 insertions(+), 56 deletions(-) diff --git a/app/[locale]/glossary/page.tsx b/app/[locale]/glossary/page.tsx index d5c779f1..2f12f413 100644 --- a/app/[locale]/glossary/page.tsx +++ b/app/[locale]/glossary/page.tsx @@ -1,12 +1,17 @@ -import { getGlossaryIndex } from '@/glossary/index'; +import { notFound } from 'next/navigation'; +import { getTranslations } from 'next-intl/server'; import { Text } from 'opub-ui'; +import { features } from '@/config/site'; +import { getGlossaryIndex } from '@/glossary/index'; import GlossaryClient from '@/components/glossary/glossary-client'; import GlossaryHeaderNav from '@/components/glossary/glossary-header-nav'; export const dynamic = 'force-static'; export default async function GlossaryPage() { + if (!features.glossary) notFound(); + const t = await getTranslations('glossary'); const index = getGlossaryIndex(); return ( @@ -14,14 +19,14 @@ export default async function GlossaryPage() {
- Glossary + {t('heading')} - Understand key terms used across the IDS-DRR platform + {t('description')}
diff --git a/components/glossary/glossary-client.tsx b/components/glossary/glossary-client.tsx index 662cf53a..66bb1d5c 100644 --- a/components/glossary/glossary-client.tsx +++ b/components/glossary/glossary-client.tsx @@ -1,6 +1,7 @@ 'use client'; import * as React from 'react'; +import { useTranslations } from 'next-intl'; import type { GlossaryIndexItem } from '@/glossary/index'; import { glossaryTags, slugsByTag, termsBySlug } from '@/glossary/index'; import { @@ -39,6 +40,8 @@ function groupByLetter(items: GlossaryIndexItem[]) { } export default function GlossaryClient({ index }: Props) { + const t = useTranslations('glossary'); + const tFilters = useTranslations('common.filters'); const [selectedTag, setSelectedTag] = React.useState(null); const [query, setQuery] = React.useState(''); const deferredQuery = React.useDeferredValue(query); @@ -80,8 +83,8 @@ export default function GlossaryClient({ index }: Props) { return (
- All + {tFilters('all')}
{glossaryTags.map((filter) => { @@ -177,7 +180,7 @@ export default function GlossaryClient({ index }: Props) {
{isError && ( - Couldn’t load details for this term. + {t('detail.error')} )} @@ -185,7 +188,7 @@ export default function GlossaryClient({ index }: Props) {
- Definition + {t('detail.headings.definition')} @@ -195,15 +198,15 @@ export default function GlossaryClient({ index }: Props) {
@@ -217,7 +220,7 @@ export default function GlossaryClient({ index }: Props) { full.disaster_differences.length > 0 && (
- How this differs across disaster contexts + {t('detail.headings.disasterMethodology')}
{full.disaster_differences.map( @@ -247,14 +250,14 @@ export default function GlossaryClient({ index }: Props) { )} {full.common_misinterpretation && (
- Common misinterpretation + {t('detail.headings.misinterpretation')} @@ -275,7 +278,7 @@ export default function GlossaryClient({ index }: Props) { ) : (
- No results found + {t('empty')}
)} @@ -316,23 +319,24 @@ function DetailsCard({ title, body }: { title: string; body: string }) { } function ContextTabs({ policy, model }: { policy?: string; model?: string }) { + const t = useTranslations('glossary.detail'); if (!policy && !model) return null; return (
- Contextual interpretation + {t('headings.interpretation')} - {policy && In Policy} - {model && In Model} + {policy && {t('tabs.policy.title')}} + {model && {t('tabs.model.title')}} {policy && (
- Policy + {t('tabs.policy.heading')} {policy} @@ -344,7 +348,7 @@ function ContextTabs({ policy, model }: { policy?: string; model?: string }) {
- Model + {t('tabs.model.heading')} {model} diff --git a/components/glossary/glossary-header-nav.tsx b/components/glossary/glossary-header-nav.tsx index 052c1c9a..676d8d72 100644 --- a/components/glossary/glossary-header-nav.tsx +++ b/components/glossary/glossary-header-nav.tsx @@ -2,48 +2,52 @@ import React from 'react'; import Link from 'next/link'; +import { useTranslations } from 'next-intl'; import { Icon, Text } from 'opub-ui'; -import { documentationLink } from '@/config/consts'; +import { docsLink, userManualLink } from '@/config/site'; import Icons from '../icons'; export default function GlossaryHeaderNav() { + const t = useTranslations('glossary'); return (
- -
- - User Manual - - -
- - -
- - Full Documentation - - -
- + {userManualLink && ( + +
+ + {t('userManualLink')} + + +
+ + )} + {docsLink && ( + +
+ + {t('docsLink')} + + +
+ + )}
); } diff --git a/config/site.ts b/config/site.ts index c5d9e293..77c80c47 100644 --- a/config/site.ts +++ b/config/site.ts @@ -25,6 +25,7 @@ export const openGraphImage: string = config.openGraphImage ?? ''; // Links export const userGuideLink: string = config.userGuideLink ?? ''; +export const userManualLink: string = config.userManualLink ?? ''; export const docsLink: string = config.docsLink ?? ''; // English is always loaded internally as the missing-key fallback, @@ -42,6 +43,7 @@ export const features = { datasets: dataSpaceEnabled, aboutUs: config.features?.aboutUs ?? false, reports: config.features?.reports ?? false, + glossary: config.features?.glossary ?? false, }; // Navigation diff --git a/locales/en.json b/locales/en.json index 1411aca1..37fa9241 100644 --- a/locales/en.json +++ b/locales/en.json @@ -10,6 +10,7 @@ "apply": "Apply", "reset": "Reset", "clearAll": "Clear All", + "all": "All", "close": "Close" }, "copy": { @@ -193,6 +194,40 @@ } } }, + "glossary": { + "heading": "Glossary", + "description": "Understand key terms used across the IDS-DRR platform", + "userManualLink": "User Manual", + "docsLink": "Full Documentation", + "search": { + "label": "Search", + "placeholder": "Search" + }, + "detail": { + "headings": { + "definition": "Definition", + "methodology": "IDS-DRR Context", + "disasterMethodology": "How this differs across disaster contexts", + "usage": "Where You See It", + "significance": "Why It Matters", + "interpretation": "Contextual interpretation", + "misinterpretation": "Common misinterpretation", + "related": "Related terms" + }, + "tabs": { + "policy": { + "title": "In Policy", + "heading": "Policy" + }, + "model": { + "title": "In Model", + "heading": "Model" + } + }, + "error": "Couldn’t load details for this term." + }, + "empty": "No results found" + }, "datasets": { "search": { "label": "Search", diff --git a/package-lock.json b/package-lock.json index 96009290..fbe4b614 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15632,7 +15632,7 @@ }, "node_modules/ids-drr-branding-types": { "version": "0.1.0", - "resolved": "git+ssh://git@github.com/open-contracting/ids-drr-branding-types.git#8c9942e543f76fccd1bb095b197b2d73eb628bc5" + "resolved": "git+ssh://git@github.com/open-contracting/ids-drr-branding-types.git#a9a89b0a4d922d90076a89115084e2574f643932" }, "node_modules/ieee754": { "version": "1.2.1", From a6ea55226b61b9dcae22c1f115b43d24cbf54710 Mon Sep 17 00:00:00 2001 From: Abhishekfm Date: Tue, 12 May 2026 17:26:22 +0530 Subject: [PATCH 4/9] chore: enhance CSV handling in webpack, refine stateCode handling in analytics components and tailwind config update for node_modules ids-drr-branding --- .gitignore | 2 + .../analytics/components/analytics-layout.tsx | 18 +++++++-- .../analytics/components/chart-view.tsx | 5 ++- .../components/default-output-window.tsx | 38 +++++++------------ .../analytics/components/factor-list.tsx | 3 +- app/[locale]/[state]/analytics/page.tsx | 2 +- app/[locale]/components/index.ts | 4 +- app/[locale]/glossary/page.tsx | 19 ++++++++-- components/main-nav.tsx | 2 +- config/graphql/analaytics-queries.ts | 2 +- config/site.ts | 13 ++++--- gql/generated/analytics/gql.ts | 4 +- gql/generated/analytics/graphql.ts | 8 ++-- next.config.ts | 5 +++ scripts/generate-glossary.ts | 8 ++-- tailwind.config.js | 3 ++ types/global.d.ts | 5 +++ 17 files changed, 86 insertions(+), 55 deletions(-) diff --git a/.gitignore b/.gitignore index d743649c..0a09f9a2 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,8 @@ # next.js /.next/ /out/ +public/robots.txt +public/sitemap*.xml # production /build diff --git a/app/[locale]/[state]/analytics/components/analytics-layout.tsx b/app/[locale]/[state]/analytics/components/analytics-layout.tsx index 0f95957b..1b95a315 100644 --- a/app/[locale]/[state]/analytics/components/analytics-layout.tsx +++ b/app/[locale]/[state]/analytics/components/analytics-layout.tsx @@ -96,7 +96,10 @@ export function AnalyticsMainLayout() { `${process.env.NEXT_PUBLIC_DATA_MANAGEMENT_LAYER_URL}/graphql`, ANALYTICS_INDICATORS_BY_CATEGORY, { - stateCode: currentSelectedState?.code, + stateCode: + currentSelectedState?.code != null + ? String(currentSelectedState.code) + : undefined, } as any ), enabled: Boolean(currentSelectedState?.code), @@ -323,7 +326,10 @@ export function AnalyticsMainLayout() { ANALYTICS_INDICATORS, { indcFilter: { slug: indicator }, - stateCode: currentSelectedState?.code, + stateCode: + currentSelectedState?.code != null + ? Number(currentSelectedState.code) + : undefined, } as any ), enabled: Boolean(isMapView && currentSelectedState?.code), @@ -341,7 +347,10 @@ export function AnalyticsMainLayout() { ANALYTICS_INDICATORS, { indcFilter: { slug: 'risk-score' }, - stateCode: currentSelectedState?.code, + stateCode: + currentSelectedState?.code != null + ? Number(currentSelectedState.code) + : undefined, } as any ), // Avoid a duplicate request when the selected indicator is already risk-score. @@ -694,7 +703,8 @@ export function OutputWindowComponent({ ANALYTICS_INDICATORS, { indcFilter: { slug: indicator }, - stateCode: currentState?.code, + stateCode: + currentState?.code != null ? Number(currentState.code) : undefined, } ), enabled: Boolean(currentState?.code), diff --git a/app/[locale]/[state]/analytics/components/chart-view.tsx b/app/[locale]/[state]/analytics/components/chart-view.tsx index 3fb33ac5..f41dfced 100644 --- a/app/[locale]/[state]/analytics/components/chart-view.tsx +++ b/app/[locale]/[state]/analytics/components/chart-view.tsx @@ -99,7 +99,10 @@ export const ChartView = ({ `${process.env.NEXT_PUBLIC_DATA_MANAGEMENT_LAYER_URL}/graphql`, ANALYTICS_INDICATORS_BY_CATEGORY, { - stateCode: currentSelectedState?.code, + stateCode: + currentSelectedState?.code != null + ? String(currentSelectedState.code) + : undefined, } ), refetchOnMount: false, diff --git a/app/[locale]/[state]/analytics/components/default-output-window.tsx b/app/[locale]/[state]/analytics/components/default-output-window.tsx index 87a87604..987a1c70 100644 --- a/app/[locale]/[state]/analytics/components/default-output-window.tsx +++ b/app/[locale]/[state]/analytics/components/default-output-window.tsx @@ -1,4 +1,9 @@ import React from 'react'; +import { useTranslations } from 'next-intl'; +import { Button, Icon, Text } from 'opub-ui'; + +import { docsLink, userGuideLink } from '@/config/site'; +import { cn } from '@/lib/utils'; import { Ellipse, Exposure, @@ -7,19 +12,11 @@ import { RiskScore, Vulnerability, } from '@/components/FactorIcons'; -import { useTranslations } from 'next-intl'; -import { Button, Icon, Text } from 'opub-ui'; - -import { docsLink, userGuideLink } from '@/config/site'; -import { cn } from '@/lib/utils'; import Icons from '@/components/icons'; import { MediaRendering } from '@/components/media-rendering'; import styles from './styles.module.scss'; -export function DefaultWindow({ - indicatorDescriptions, - onClose, -}: any) { +export function DefaultWindow({ indicatorDescriptions, onClose }: any) { const t = useTranslations('analytics.detail'); const tCommon = useTranslations('common'); const list: { title: string; slug: string; description: string }[] = []; @@ -57,11 +54,7 @@ export function DefaultWindow({ > {/* State-level header with only close button (no icon/title) */}
-
@@ -79,11 +72,7 @@ export function DefaultWindow({ ); } -export const AboutIndicator = ({ - IndicatorData, -}: { - IndicatorData: any; -}) => { +export const AboutIndicator = ({ IndicatorData }: { IndicatorData: any }) => { const t = useTranslations('analytics.about'); const IconMap: { [key: string]: React.ReactNode } = { 'risk-score': , @@ -114,10 +103,11 @@ export const AboutIndicator = ({ />
- - {t('calculation')} - - + {IndicatorData.length > 1 && ( + + {t('calculation')} + + )}
{IndicatorData.slice(1)?.map((indicator: any, index: number) => (
); }; - - diff --git a/app/[locale]/[state]/analytics/components/factor-list.tsx b/app/[locale]/[state]/analytics/components/factor-list.tsx index 1d652958..dae67d59 100644 --- a/app/[locale]/[state]/analytics/components/factor-list.tsx +++ b/app/[locale]/[state]/analytics/components/factor-list.tsx @@ -79,7 +79,8 @@ export function FactorList({ currentState }: any) { `${process.env.NEXT_PUBLIC_DATA_MANAGEMENT_LAYER_URL}/graphql`, ANALYTICS_INDICATORS_BY_CATEGORY, { - stateCode: currentState?.code, + stateCode: + currentState?.code != null ? String(currentState.code) : undefined, } ), refetchOnMount: false, diff --git a/app/[locale]/[state]/analytics/page.tsx b/app/[locale]/[state]/analytics/page.tsx index c80ff1f6..462cf0e3 100644 --- a/app/[locale]/[state]/analytics/page.tsx +++ b/app/[locale]/[state]/analytics/page.tsx @@ -40,7 +40,7 @@ export default async function Home({ queryFn: () => GraphQL(graphqlUrl, ANALYTICS_INDICATORS, { indcFilter: { slug }, - stateCode, + stateCode: Number(stateCode), }), }) ) diff --git a/app/[locale]/components/index.ts b/app/[locale]/components/index.ts index fbc309b0..40a1903c 100644 --- a/app/[locale]/components/index.ts +++ b/app/[locale]/components/index.ts @@ -4,5 +4,5 @@ export { QuickLinks } from './analytics-quick-links'; export { HeroSection } from './hero-section'; export { Stories } from './stories'; -export { Footer } from './footer'; -export { About } from './about'; +// export { Footer } from './footer'; +// export { About } from './about'; diff --git a/app/[locale]/glossary/page.tsx b/app/[locale]/glossary/page.tsx index 2f12f413..d2604e89 100644 --- a/app/[locale]/glossary/page.tsx +++ b/app/[locale]/glossary/page.tsx @@ -1,17 +1,28 @@ import { notFound } from 'next/navigation'; -import { getTranslations } from 'next-intl/server'; +import { getGlossaryIndex } from '@/glossary/index'; +import { + getTranslations, + setRequestLocale, +} from 'next-intl/server'; import { Text } from 'opub-ui'; import { features } from '@/config/site'; -import { getGlossaryIndex } from '@/glossary/index'; import GlossaryClient from '@/components/glossary/glossary-client'; import GlossaryHeaderNav from '@/components/glossary/glossary-header-nav'; export const dynamic = 'force-static'; -export default async function GlossaryPage() { +export default async function GlossaryPage({ + params, +}: { + params: Promise<{ locale: string }>; +}) { + const { locale } = await params; + setRequestLocale(locale); + if (!features.glossary) notFound(); - const t = await getTranslations('glossary'); + + const t = await getTranslations({ locale, namespace: 'glossary' }); const index = getGlossaryIndex(); return ( diff --git a/components/main-nav.tsx b/components/main-nav.tsx index 3a6d2c71..dff931ff 100644 --- a/components/main-nav.tsx +++ b/components/main-nav.tsx @@ -3,10 +3,10 @@ import React from 'react'; import Image from 'next/image'; import { useKeyDetect } from '@/hooks/use-key-detect'; -import { languages, logo, mainNav } from '@/config/site'; import { useTranslations } from 'next-intl'; import { Text } from 'opub-ui'; +import { languages, logo, mainNav } from '@/config/site'; import { routes } from '@/lib/routes'; import { TranslateDropdown } from './langSelect/lang-select'; import NavLink from './nav-link'; diff --git a/config/graphql/analaytics-queries.ts b/config/graphql/analaytics-queries.ts index 179f3f51..8b49b6fa 100644 --- a/config/graphql/analaytics-queries.ts +++ b/config/graphql/analaytics-queries.ts @@ -29,7 +29,7 @@ export const ANALYTICS_DISTRICT_DATA = graphql(` `); export const ANALYTICS_INDICATORS = graphql(` - query indicators($indcFilter: IndicatorFilter, $stateCode: String) { + query indicators($indcFilter: IndicatorFilter, $stateCode: Int) { indicators(indcFilter: $indcFilter, stateCode: $stateCode) } `); diff --git a/config/site.ts b/config/site.ts index 77c80c47..e0638ff0 100644 --- a/config/site.ts +++ b/config/site.ts @@ -2,8 +2,8 @@ import { config } from 'ids-drr-branding'; import type { Language, Resource, - StaticImageAsset, State, + StaticImageAsset, Story, } from 'ids-drr-branding-types'; @@ -17,7 +17,8 @@ export const languages: Language[] = config.languages ?? []; // Images export const logo: StaticImageAsset | undefined = config.logo; -export const heroForeground: StaticImageAsset | undefined = config.heroForeground; +export const heroForeground: StaticImageAsset | undefined = + config.heroForeground; export const heroBackground: string = config.heroBackground ?? ''; export const favicon: string = config.favicon ?? ''; export const appleIcon: string = config.appleIcon ?? ''; @@ -33,8 +34,10 @@ export const docsLink: string = config.docsLink ?? ''; export const FALLBACK_LOCALE = 'en'; export const locales: string[] = config.locales ?? [FALLBACK_LOCALE]; export const defaultLocale: string = config.defaultLocale ?? locales[0]; -export const messages: Record> = - config.messages ?? {}; +export const messages: Record< + string, + Record +> = config.messages ?? {}; // Feature flags const dataSpaceEnabled = Boolean(process.env.NEXT_PUBLIC_BACKEND_URL); @@ -43,7 +46,7 @@ export const features = { datasets: dataSpaceEnabled, aboutUs: config.features?.aboutUs ?? false, reports: config.features?.reports ?? false, - glossary: config.features?.glossary ?? false, + glossary: Boolean(config.glossaryCsv), }; // Navigation diff --git a/gql/generated/analytics/gql.ts b/gql/generated/analytics/gql.ts index 12e2ff9b..3e40ab60 100644 --- a/gql/generated/analytics/gql.ts +++ b/gql/generated/analytics/gql.ts @@ -15,7 +15,7 @@ import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/ const documents = { "\n query revCircleViewData(\n $indcFilter: IndicatorFilter!\n $dataFilter: DataFilter!\n $geoFilter: GeoFilter!\n ) {\n revCircleViewData(\n indcFilter: $indcFilter\n dataFilter: $dataFilter\n geoFilter: $geoFilter\n )\n }\n": types.RevCircleViewDataDocument, "\n query districtViewData(\n $indcFilter: IndicatorFilter!\n $dataFilter: DataFilter!\n $geoFilter: GeoFilter!\n ) {\n districtViewData(\n indcFilter: $indcFilter\n dataFilter: $dataFilter\n geoFilter: $geoFilter\n )\n }\n": types.DistrictViewDataDocument, - "\n query indicators($indcFilter: IndicatorFilter, $stateCode: String) {\n indicators(indcFilter: $indcFilter, stateCode: $stateCode)\n }\n": types.IndicatorsDocument, + "\n query indicators($indcFilter: IndicatorFilter, $stateCode: Int) {\n indicators(indcFilter: $indcFilter, stateCode: $stateCode)\n }\n": types.IndicatorsDocument, "\n query indicatorsByCategory($stateCode: String) {\n indicatorsByCategory(stateCode: $stateCode)\n }\n": types.IndicatorsByCategoryDocument, "\n query dataTimePeriods {\n getDataTimePeriods {\n value\n }\n }\n": types.DataTimePeriodsDocument, "\n query getDistrictRevCircle($geoFilter: GeoFilter!) {\n getDistrictRevCircle(geoFilter: $geoFilter)\n }\n": types.GetDistrictRevCircleDocument, @@ -50,7 +50,7 @@ export function graphql(source: "\n query districtViewData(\n $indcFilter: I /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n query indicators($indcFilter: IndicatorFilter, $stateCode: String) {\n indicators(indcFilter: $indcFilter, stateCode: $stateCode)\n }\n"): (typeof documents)["\n query indicators($indcFilter: IndicatorFilter, $stateCode: String) {\n indicators(indcFilter: $indcFilter, stateCode: $stateCode)\n }\n"]; +export function graphql(source: "\n query indicators($indcFilter: IndicatorFilter, $stateCode: Int) {\n indicators(indcFilter: $indcFilter, stateCode: $stateCode)\n }\n"): (typeof documents)["\n query indicators($indcFilter: IndicatorFilter, $stateCode: Int) {\n indicators(indcFilter: $indcFilter, stateCode: $stateCode)\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/gql/generated/analytics/graphql.ts b/gql/generated/analytics/graphql.ts index d8d9c2c1..35484d17 100644 --- a/gql/generated/analytics/graphql.ts +++ b/gql/generated/analytics/graphql.ts @@ -29,7 +29,7 @@ export type DataFilter = { period?: InputMaybe; }; -/** Geography(id, name, code, type, parentId, geom, simple_geom, slug) */ +/** Geography(id, name, code, type, parentId, geom, slug) */ export type GeoFilter = { AND?: InputMaybe; OR?: InputMaybe; @@ -90,7 +90,7 @@ export type QueryGetTimeTrendsArgs = { export type QueryIndicatorsArgs = { indcFilter?: InputMaybe; - stateCode?: InputMaybe; + stateCode?: InputMaybe; }; @@ -140,7 +140,7 @@ export type DistrictViewDataQuery = { __typename?: 'Query', districtViewData: an export type IndicatorsQueryVariables = Exact<{ indcFilter?: InputMaybe; - stateCode?: InputMaybe; + stateCode?: InputMaybe; }>; @@ -200,7 +200,7 @@ export type GetStatesListQuery = { __typename?: 'Query', getStates: any }; export const RevCircleViewDataDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"revCircleViewData"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"indcFilter"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"IndicatorFilter"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"dataFilter"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"DataFilter"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"geoFilter"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"GeoFilter"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"revCircleViewData"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"indcFilter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"indcFilter"}}},{"kind":"Argument","name":{"kind":"Name","value":"dataFilter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"dataFilter"}}},{"kind":"Argument","name":{"kind":"Name","value":"geoFilter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"geoFilter"}}}]}]}}]} as unknown as DocumentNode; export const DistrictViewDataDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"districtViewData"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"indcFilter"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"IndicatorFilter"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"dataFilter"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"DataFilter"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"geoFilter"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"GeoFilter"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"districtViewData"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"indcFilter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"indcFilter"}}},{"kind":"Argument","name":{"kind":"Name","value":"dataFilter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"dataFilter"}}},{"kind":"Argument","name":{"kind":"Name","value":"geoFilter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"geoFilter"}}}]}]}}]} as unknown as DocumentNode; -export const IndicatorsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"indicators"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"indcFilter"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"IndicatorFilter"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"stateCode"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"indicators"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"indcFilter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"indcFilter"}}},{"kind":"Argument","name":{"kind":"Name","value":"stateCode"},"value":{"kind":"Variable","name":{"kind":"Name","value":"stateCode"}}}]}]}}]} as unknown as DocumentNode; +export const IndicatorsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"indicators"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"indcFilter"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"IndicatorFilter"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"stateCode"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"indicators"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"indcFilter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"indcFilter"}}},{"kind":"Argument","name":{"kind":"Name","value":"stateCode"},"value":{"kind":"Variable","name":{"kind":"Name","value":"stateCode"}}}]}]}}]} as unknown as DocumentNode; export const IndicatorsByCategoryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"indicatorsByCategory"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"stateCode"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"indicatorsByCategory"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"stateCode"},"value":{"kind":"Variable","name":{"kind":"Name","value":"stateCode"}}}]}]}}]} as unknown as DocumentNode; export const DataTimePeriodsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"dataTimePeriods"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getDataTimePeriods"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"value"}}]}}]}}]} as unknown as DocumentNode; export const GetDistrictRevCircleDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"getDistrictRevCircle"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"geoFilter"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"GeoFilter"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getDistrictRevCircle"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"geoFilter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"geoFilter"}}}]}]}}]} as unknown as DocumentNode; diff --git a/next.config.ts b/next.config.ts index 8620e528..ab9dc648 100644 --- a/next.config.ts +++ b/next.config.ts @@ -11,6 +11,11 @@ const nextConfig: NextConfig = { serverExternalPackages: ['@prisma/instrumentation', '@fastify/otel'], // https://github.com/CivicDataLab/opub-mono/pull/403 webpack: (config) => { + config.module.rules.push({ + test: /\.csv$/, + type: 'asset/source', + include: /node_modules\/ids-drr-branding/, + }); config.module.rules.push({ test: /\.(js|mjs)$/, enforce: 'pre', diff --git a/scripts/generate-glossary.ts b/scripts/generate-glossary.ts index 89dc4a2e..2a4dca35 100644 --- a/scripts/generate-glossary.ts +++ b/scripts/generate-glossary.ts @@ -117,7 +117,9 @@ function main() { const disaster_differences = buildDisasterDifferences(r); const related_terms = toRelatedTerms(r.related_terms ?? ''); - const common_misinterpretation = (r.common_misinterpretation ?? '').trim(); + const common_misinterpretation = ( + r.common_misinterpretation ?? '' + ).trim(); return { letter, @@ -133,9 +135,7 @@ function main() { why_it_matters: (r.why_it_matters ?? '').trim(), }, ...(contexts ? { contexts } : {}), - ...(disaster_differences.length > 0 - ? { disaster_differences } - : {}), + ...(disaster_differences.length > 0 ? { disaster_differences } : {}), ...(related_terms.length > 0 ? { related_terms } : {}), ...(nonEmpty(common_misinterpretation) ? { common_misinterpretation } diff --git a/tailwind.config.js b/tailwind.config.js index 9d236092..eb7652b7 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -23,7 +23,10 @@ module.exports = { // ids-drr-branding is an npm `file:` dep symlinked to ./branding-stub/, // which in deployments is bind-mounted to the real branding package. // Scan it so branding-only Tailwind classes make it into the compiled CSS. + // Real branding is copied into branding-stub for Docker builds; local + // installs may also place the package under node_modules/ids-drr-branding. './branding-stub/src/**/*.{js,ts,jsx,tsx}', + './node_modules/ids-drr-branding/src/**/*.{js,ts,jsx,tsx}', ], theme: { colors, diff --git a/types/global.d.ts b/types/global.d.ts index a14fdefb..ebba9f6a 100644 --- a/types/global.d.ts +++ b/types/global.d.ts @@ -3,3 +3,8 @@ type IntlMessages = typeof import('../locales/en.json'); // Types derived from Messages are in global.d.ts for simplicity. type RiskLevel = keyof IntlMessages['analytics']['risk']; type NavLinkKey = keyof IntlMessages['nav']['links']; + +declare module '*.csv' { + const content: string; + export default content; +} From 09bd269713b9702d11bdfaaa5f9449bb34f9928f Mon Sep 17 00:00:00 2001 From: James McKinney <26463+jpmckinney@users.noreply.github.com> Date: Mon, 11 May 2026 12:21:26 -0400 Subject: [PATCH 5/9] i18n: Extract text into messages --- app/[locale]/glossary/page.tsx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/[locale]/glossary/page.tsx b/app/[locale]/glossary/page.tsx index d2604e89..6384b4e3 100644 --- a/app/[locale]/glossary/page.tsx +++ b/app/[locale]/glossary/page.tsx @@ -1,4 +1,5 @@ import { notFound } from 'next/navigation'; +<<<<<<< HEAD import { getGlossaryIndex } from '@/glossary/index'; import { getTranslations, @@ -7,11 +8,19 @@ import { import { Text } from 'opub-ui'; import { features } from '@/config/site'; +======= +import { getTranslations } from 'next-intl/server'; +import { Text } from 'opub-ui'; + +import { features } from '@/config/site'; +import { getGlossaryIndex } from '@/glossary/index'; +>>>>>>> edaa4d9 (i18n: Extract text into messages) import GlossaryClient from '@/components/glossary/glossary-client'; import GlossaryHeaderNav from '@/components/glossary/glossary-header-nav'; export const dynamic = 'force-static'; +<<<<<<< HEAD export default async function GlossaryPage({ params, }: { @@ -23,6 +32,11 @@ export default async function GlossaryPage({ if (!features.glossary) notFound(); const t = await getTranslations({ locale, namespace: 'glossary' }); +======= +export default async function GlossaryPage() { + if (!features.glossary) notFound(); + const t = await getTranslations('glossary'); +>>>>>>> edaa4d9 (i18n: Extract text into messages) const index = getGlossaryIndex(); return ( From 9f26d29f1cc0156cc1675d226111f89471580696 Mon Sep 17 00:00:00 2001 From: Abhishekfm Date: Tue, 12 May 2026 17:26:22 +0530 Subject: [PATCH 6/9] chore: enhance CSV handling in webpack, refine stateCode handling in analytics components and tailwind config update for node_modules ids-drr-branding --- app/[locale]/glossary/page.tsx | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/app/[locale]/glossary/page.tsx b/app/[locale]/glossary/page.tsx index 6384b4e3..7e4deb47 100644 --- a/app/[locale]/glossary/page.tsx +++ b/app/[locale]/glossary/page.tsx @@ -1,26 +1,14 @@ import { notFound } from 'next/navigation'; -<<<<<<< HEAD import { getGlossaryIndex } from '@/glossary/index'; -import { - getTranslations, - setRequestLocale, -} from 'next-intl/server'; +import { getTranslations, setRequestLocale } from 'next-intl/server'; import { Text } from 'opub-ui'; import { features } from '@/config/site'; -======= -import { getTranslations } from 'next-intl/server'; -import { Text } from 'opub-ui'; - -import { features } from '@/config/site'; -import { getGlossaryIndex } from '@/glossary/index'; ->>>>>>> edaa4d9 (i18n: Extract text into messages) import GlossaryClient from '@/components/glossary/glossary-client'; import GlossaryHeaderNav from '@/components/glossary/glossary-header-nav'; export const dynamic = 'force-static'; -<<<<<<< HEAD export default async function GlossaryPage({ params, }: { @@ -32,11 +20,6 @@ export default async function GlossaryPage({ if (!features.glossary) notFound(); const t = await getTranslations({ locale, namespace: 'glossary' }); -======= -export default async function GlossaryPage() { - if (!features.glossary) notFound(); - const t = await getTranslations('glossary'); ->>>>>>> edaa4d9 (i18n: Extract text into messages) const index = getGlossaryIndex(); return ( From 66d241fe16e6056c4a189f1d75fe239b50d7dd31 Mon Sep 17 00:00:00 2001 From: Abhishekfm Date: Tue, 12 May 2026 18:01:31 +0530 Subject: [PATCH 7/9] chore: update jest configuration to mock CSV files, enhance knip.jsonc for branding-stub, and clean up glossary types and functions --- glossary/index.tsx | 16 ++-------------- jest.config.ts | 1 + knip.jsonc | 8 +++++--- tests/__mocks__/csv.ts | 3 +++ 4 files changed, 11 insertions(+), 17 deletions(-) create mode 100644 tests/__mocks__/csv.ts diff --git a/glossary/index.tsx b/glossary/index.tsx index e7eac0a2..9abdf015 100644 --- a/glossary/index.tsx +++ b/glossary/index.tsx @@ -26,7 +26,7 @@ export type GlossaryItem = { common_misinterpretation?: string; }; -export type GlossaryDisasterDifference = { +type GlossaryDisasterDifference = { disaster_type: string; difference: string; }; @@ -38,7 +38,7 @@ export type GlossaryIndexItem = { short: string; }; -export function slugifyGlossaryTerm(term: string) { +function slugifyGlossaryTerm(term: string) { return term .toLowerCase() .trim() @@ -54,14 +54,6 @@ export const termsBySlug: Record = return acc; }, {}); -export const slugsByLetter: Record = - GlossaryTermsSource.reduce>((acc, item) => { - const letter = (item.letter || '#').toUpperCase(); - const slug = slugifyGlossaryTerm(item.term); - acc[letter] = [...(acc[letter] ?? []), slug]; - return acc; - }, {}); - export const slugsByTag: Record = GlossaryTermsSource.reduce< Record >((acc, item) => { @@ -92,7 +84,3 @@ export function getGlossaryIndex(): GlossaryIndexItem[] { short: item.definitions.short, })); } - -export function getGlossaryTags() { - return glossaryTags; -} diff --git a/jest.config.ts b/jest.config.ts index 1e783b6c..001cf774 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -23,6 +23,7 @@ const config: Config = { // Mock d3 modules '^d3-scale$': '/tests/__mocks__/d3-scale.ts', '^d3-scale-chromatic$': '/tests/__mocks__/d3-scale-chromatic.ts', + '\\.csv$': '/tests/__mocks__/csv.ts', }, collectCoverageFrom: [ '{app,components,config,hooks,i18n,lib}/**/*.{ts,tsx}', diff --git a/knip.jsonc b/knip.jsonc index b4bd694b..17fdda5a 100644 --- a/knip.jsonc +++ b/knip.jsonc @@ -9,7 +9,9 @@ // graphql-codegen output, over which we don't have full control. "gql/generated/**", // @import'd by styles/globals.css (knip doesn't follow CSS imports). - "styles/tokens/_variables.css" + "styles/tokens/_variables.css", + // Imported as ids-drr-branding; knip does not follow the file: link here. + "branding-stub/src/index.ts", ], "ignoreDependencies": [ // `graphql-codegen --require dotenv/config` in npm scripts. @@ -30,6 +32,6 @@ // direct source imports knip can trace. "@jest/globals", "ts-jest", - "ts-node" - ] + "ts-node", + ], } diff --git a/tests/__mocks__/csv.ts b/tests/__mocks__/csv.ts new file mode 100644 index 00000000..28d6a6e6 --- /dev/null +++ b/tests/__mocks__/csv.ts @@ -0,0 +1,3 @@ +const csvMock = ''; + +export default csvMock; From 9ebad29d2bd5dffa7fd9ba203be688814e729a53 Mon Sep 17 00:00:00 2001 From: James McKinney <26463+jpmckinney@users.noreply.github.com> Date: Tue, 12 May 2026 10:56:27 -0400 Subject: [PATCH 8/9] refactor: Ship glossary CSV from branding, parse at build time This replaces the previous CSV pipeline. Branding packages now ship a glossaryCsv string, and the frontend reads, parses, and statically renders it. Future deployments only need a CSV in their branding's data/: no generator script, no committed generated file. Refactor scripts/generate-glossary.ts - Refactor main() into parseGlossary() - Move parseCsv() to lib/glossary.ts - Inline toRelatedTerms() into parseGlossary() - Rename buildDisasterDifferences() to buildDisasterMethodology() Refactor glossary/index.tsx - Move slugifyGlossaryTerm() to lib/glossary.ts - Move glossaryTags computation into uniqueTags() in components/glossary/glossary-client.tsx - Drop the GlossaryIndexItem/GlossaryItem split; the client component consumes the full GlossaryTerm[] directly Other: - The GlossaryTerm contract moves to ids-drr-branding-types with fields mirroring the section headings (summary, definition, methodology, usage, significance, interpretation, disasterMethodology, related, misinterpretation) - features.glossary is now derived from glossaryCsv truthiness rather than being a separate boolean in the branding config - Remove the dead "couldn't load details" error UI and its glossary.detail.error message key - Drop ts-node dev dependency and its knip ignoreDependencies entry (its only consumer was the deleted generate:glossary script) - Drop the branding-stub/src/index.ts knip ignore entry; knip can trace the file: link without help --- app/[locale]/glossary/page.tsx | 8 +- components/glossary/glossary-client.tsx | 187 ++++++++--------- config/site.ts | 3 + glossary/glossary.csv | 17 -- glossary/index.tsx | 86 -------- glossary/terms.generated.ts | 257 ------------------------ knip.jsonc | 3 - lib/glossary.ts | 126 ++++++++++++ locales/en.json | 3 +- package-lock.json | 43 +++- package.json | 2 - scripts/generate-glossary.ts | 180 ----------------- tsconfig.scripts.json | 10 - 13 files changed, 253 insertions(+), 672 deletions(-) delete mode 100644 glossary/glossary.csv delete mode 100644 glossary/index.tsx delete mode 100644 glossary/terms.generated.ts create mode 100644 lib/glossary.ts delete mode 100644 scripts/generate-glossary.ts delete mode 100644 tsconfig.scripts.json diff --git a/app/[locale]/glossary/page.tsx b/app/[locale]/glossary/page.tsx index 7e4deb47..227a02be 100644 --- a/app/[locale]/glossary/page.tsx +++ b/app/[locale]/glossary/page.tsx @@ -1,9 +1,9 @@ import { notFound } from 'next/navigation'; -import { getGlossaryIndex } from '@/glossary/index'; import { getTranslations, setRequestLocale } from 'next-intl/server'; import { Text } from 'opub-ui'; -import { features } from '@/config/site'; +import { features, glossaryCsv } from '@/config/site'; +import { parseGlossary } from '@/lib/glossary'; import GlossaryClient from '@/components/glossary/glossary-client'; import GlossaryHeaderNav from '@/components/glossary/glossary-header-nav'; @@ -20,7 +20,7 @@ export default async function GlossaryPage({ if (!features.glossary) notFound(); const t = await getTranslations({ locale, namespace: 'glossary' }); - const index = getGlossaryIndex(); + const terms = parseGlossary(glossaryCsv); return (
@@ -41,7 +41,7 @@ export default async function GlossaryPage({
- +
); diff --git a/components/glossary/glossary-client.tsx b/components/glossary/glossary-client.tsx index 66bb1d5c..521ab167 100644 --- a/components/glossary/glossary-client.tsx +++ b/components/glossary/glossary-client.tsx @@ -2,8 +2,7 @@ import * as React from 'react'; import { useTranslations } from 'next-intl'; -import type { GlossaryIndexItem } from '@/glossary/index'; -import { glossaryTags, slugsByTag, termsBySlug } from '@/glossary/index'; +import type { GlossaryTerm } from 'ids-drr-branding-types'; import { Accordion, AccordionContent, @@ -19,17 +18,28 @@ import { Text, } from 'opub-ui'; +import { slugifyGlossaryTerm } from '@/lib/glossary'; + type Props = { - index: GlossaryIndexItem[]; + terms: GlossaryTerm[]; }; -function groupByLetter(items: GlossaryIndexItem[]) { - const map = new Map(); +type Indexed = GlossaryTerm & { slug: string; letter: string }; + +function indexTerms(terms: GlossaryTerm[]): Indexed[] { + return terms.map((t) => ({ + ...t, + slug: slugifyGlossaryTerm(t.term), + letter: (t.term[0] ?? '#').toUpperCase(), + })); +} + +function groupByLetter(items: Indexed[]) { + const map = new Map(); for (const item of items) { - const key = (item.letter || '#').toUpperCase(); - const existing = map.get(key); + const existing = map.get(item.letter); if (existing) existing.push(item); - else map.set(key, [item]); + else map.set(item.letter, [item]); } return [...map.entries()] .sort(([a], [b]) => a.localeCompare(b)) @@ -39,46 +49,41 @@ function groupByLetter(items: GlossaryIndexItem[]) { })); } -export default function GlossaryClient({ index }: Props) { +function uniqueTags(terms: GlossaryTerm[]): string[] { + const labelsByKey = new Map(); + for (const t of terms) { + const raw = t.tag?.trim(); + if (!raw) continue; + const key = raw.toLowerCase(); + if (!labelsByKey.has(key)) labelsByKey.set(key, raw); + } + return [...labelsByKey.values()].sort((a, b) => a.localeCompare(b)); +} + +export default function GlossaryClient({ terms }: Props) { const t = useTranslations('glossary'); const tFilters = useTranslations('common.filters'); + + const indexed = React.useMemo(() => indexTerms(terms), [terms]); + const tags = React.useMemo(() => uniqueTags(terms), [terms]); + const [selectedTag, setSelectedTag] = React.useState(null); const [query, setQuery] = React.useState(''); + const [openSlug, setOpenSlug] = React.useState(null); const deferredQuery = React.useDeferredValue(query); - - const { indexBySlug, termBySlugLower } = React.useMemo(() => { - const indexBySlug: Record = {}; - const termBySlugLower: Record = {}; - for (const item of index) { - indexBySlug[item.slug] = item; - termBySlugLower[item.slug] = (item.term ?? '').toLowerCase(); - } - return { indexBySlug, termBySlugLower }; - }, [index]); - const normalizedQuery = deferredQuery.trim().toLowerCase(); - const filteredIndex = React.useMemo(() => { - const base = - selectedTag === null - ? index - : (slugsByTag[selectedTag.toLowerCase()] ?? []) - .map((s) => indexBySlug[s]) - .filter(Boolean); - - if (!normalizedQuery) return base; + const filtered = React.useMemo(() => { + const tagKey = selectedTag?.toLowerCase(); + return indexed.filter((item) => { + if (tagKey && item.tag?.toLowerCase() !== tagKey) return false; + if (normalizedQuery && !item.term.toLowerCase().includes(normalizedQuery)) + return false; + return true; + }); + }, [indexed, selectedTag, normalizedQuery]); - return base.filter((item) => - (termBySlugLower[item.slug] ?? '').includes(normalizedQuery) - ); - }, [index, indexBySlug, normalizedQuery, selectedTag, termBySlugLower]); - - const grouped = React.useMemo( - () => groupByLetter(filteredIndex), - [filteredIndex] - ); - const [openSlug, setOpenSlug] = React.useState(null); - const [errorSlug, setErrorSlug] = React.useState(null); + const grouped = React.useMemo(() => groupByLetter(filtered), [filtered]); return (
@@ -107,7 +112,7 @@ export default function GlossaryClient({ index }: Props) { {tFilters('all')}
- {glossaryTags.map((filter) => { + {tags.map((filter) => { const isActive = selectedTag?.toLowerCase() === filter.toLowerCase(); return (
@@ -148,19 +152,12 @@ export default function GlossaryClient({ index }: Props) { collapsible value={openSlug ?? ''} onValueChange={(val) => { - const next = val || null; - setOpenSlug(next); - if (next) { - setErrorSlug(termsBySlug[next] ? null : next); - } + setOpenSlug(val || null); }} className="divide-y " > {items.map((item) => { const isOpen = openSlug === item.slug; - const isError = errorSlug === item.slug; - const full = termsBySlug[item.slug]; - return ( {item.term}
- {item.short} + {item.summary}
- {isError && ( - - {t('detail.error')} - - )} - - {isOpen && full && ( + {isOpen && (
{t('detail.headings.definition')} - - {full.definitions.long} + {item.definition}
- {full.disaster_differences?.length && - full.disaster_differences.length > 0 && ( + {item.disasterMethodology && + item.disasterMethodology.length > 0 && (
{t('detail.headings.disasterMethodology')}
- {full.disaster_differences.map( - (d) => - d?.disaster_type && ( -
- - {d?.disaster_type} - - - {d?.difference} - -
- ) - )} + {item.disasterMethodology.map((d) => ( +
+ + {d.disasterType} + + + {d.methodology} + +
+ ))}
)} - + {item.related && item.related.length > 0 && ( + + )} - {full.common_misinterpretation && ( + {item.misinterpretation && (
{t('detail.headings.misinterpretation')} - - {full.common_misinterpretation} + {item.misinterpretation}
)} @@ -286,18 +271,16 @@ export default function GlossaryClient({ index }: Props) { ); } -function InfoCard({ title, body }: { title: string; body?: string }) { - if (!body) return null; +function InfoCard({ title, items }: { title: string; items: string[] }) { return (
{title} -
- {body.split(',').map((item) => ( - - {item.trim()} + {items.map((item) => ( + + {item} ))}
diff --git a/config/site.ts b/config/site.ts index e0638ff0..69b0dc29 100644 --- a/config/site.ts +++ b/config/site.ts @@ -29,6 +29,9 @@ export const userGuideLink: string = config.userGuideLink ?? ''; export const userManualLink: string = config.userManualLink ?? ''; export const docsLink: string = config.docsLink ?? ''; +// Content +export const glossaryCsv: string = config.glossaryCsv ?? ''; + // English is always loaded internally as the missing-key fallback, // but a deployment can omit it from `locales` to disable /en/ URLs. export const FALLBACK_LOCALE = 'en'; diff --git a/glossary/glossary.csv b/glossary/glossary.csv deleted file mode 100644 index 1ec6e0e5..00000000 --- a/glossary/glossary.csv +++ /dev/null @@ -1,17 +0,0 @@ -letter,term,tag,short,long,ids_drr,where_seen,why_it_matters,policy,model,disaster_type_0,difference_0,disaster_type_1,difference_1,disaster_type_2,difference_2,related_terms,common_misinterpretation -E,Exposure,data,The people property and assets located in hazard-prone areas,"Exposure refers to what or who is present in places where hazardous events might occur. This includes population counts, buildings, infrastructure, agricultural land, and economic activities in at-risk zones.","IDS-DRR maps exposure using census data, satellite imagery, infrastructure databases, and economic activity records to show exactly what could be affected by different hazard scenarios.","Exposure maps showing population density in risk zones, asset inventories, infrastructure overlays, and economic impact projections",Understanding exposure helps quantify potential losses and prioritize protection measures. Reducing exposure through land-use planning or relocation is one of the most effective ways to reduce disaster risk.,Used to determine land-use planning restrictions and relocation programs,"Models predict population exposure based on historical hazard data, socioeconomic indicators, and climate projections to anticipate future risks.",Flood Exposure,"Based on population/assets in flood plains, near rivers, or low-lying coastal areas",Heat Exposure,"Based on population in areas with high temperatures, poor ventilation, or limited cooling",Cyclone Exposure,Based on population/assets in coastal zones within cyclone-prone regions,"risk,hazard,vulnerability",High exposure doesn't mean high risk if those exposed are well-protected or the hazard is unlikely -H,Hazard ,Risk Component ,A potentially damaging natural event that may cause loss of life or property,"A process, phenomenon or human activity that may cause loss of life, injury or other health impacts, property damage, social and economic disruption or environmental degradation.","Calculates a Flood Hazard Score per revenue circle/District/Subsditrict using satellite-derived flood extent data, historical inundation records, and hydro-meteorological indicators.","Flood hazard maps, inundation layers, SDMA early warning bulletins, IMD rainfall alerts, CWC flood forecasting data","Hazard assessment shows where and how severely floods strike. Without it, risk scores cannot be computed.",,,,,,,,,, -V,Vulnerability,Risk Component ,The conditions that make people or assets more susceptible to harm from a hazard,"Social, economic, physical, and institutional factors that increase likelihood of harm. Includes housing quality, health status, access to infrastructure, and governance strength.","Measures demographic vulnerability using WORLDPOP, Bharat Map and Antodaya indicators: population density, sex ratio and household level data at the District/Subdistrict/Revenue Circle circle level.","Census of India tables, NFHS health indicators, SECC deprivation data, SDMA damage reports disaggregated by population group",Two areas with identical hazard and exposure can have very different outcomes depending on vulnerability,,,,,,,,,, -R,Risk Score ,Topographic Wetness Index," A composite index quantifying overall risk based on hazard, exposure, vulnerability, and government response",Aggregates multiple risk components into a single comparable value per geographic unit and hence enabling prioritisation and tracking of change over time.,"Computes a Comprehensive Risk Score for every revenue circle/district/subdistrict for a state combining sub-scores for Flood Hazard, Exposure, Vulnerability, and Government Response.","IDS-DRR dashboard, SDMA planning documents, district disaster management plans",Translates complex multi-dimensional data into an actionable number for resource allocation and progress tracking.,,,,,,,,,, -G,Government Response,Risk Component ,"The capacity of government institutions to prepare for, respond to, and recover from flood events","Measures how effectively public systems reduce harm before, during, and after an hazard event ncluding relief infrastructure, procurement efficiency, and recovery expenditure.",Incorporates public procurement and fiscal data from the state tender portal and SECC Meetings measure whether money for flood management is being spent effectively and on time.," state budget documents, SDMA flood reports, Open Contracting procurement data, Finance Department expenditure records, SECC meetings",Even high-hazard areas can have lower net risk with strong government response. Weak procurement amplifies disaster impacts.,,,,,,,,,, -I,Inundation,Data Source,The temporary flooding of land caused by overflow of water from rivers or heavy rainfall,"Physical covering of land by floodwater. Measured by extent, depth, and duration. the driving factor varies from geography to geographies but primarily driven by riverine overflowing during monsoon.",Uses Satellite imagery via Bhuvan to track spatial extent and duration of flooding across Districts/Subdistricts/Revenue circles each monsoon season.,"MODIS and Sentinel satellite flood maps, ASDMA FRIMS daily situation reports, BHUVAN portal, CWC flood bulletins",Inundation extent is the primary input for flood hazard scores. Historical frequency data identifies which administrative unit faces repeated flooding.,,,,,,,,,, -R,Revenue Circle,Administrative,"The smallest administrative unit used for land revenue and governance in Assam, below sub-district level","Administrative subdivision for land records, revenue collection and local governance. IDS-DRR uses revenue circles as the primary unit of analysis for all risk scoring.","Computes all risk scores hazard, vulnerability, exposure, and government response at the revenue circle level for the state of Assam.","Revenue department records, ASDMA damage reports, Census sub-district data, land record portal",Revenue circle granularity identifies high-risk pockets within a district that district-level averages would mask.,,,,,,,,,, -A,Access to Infrastructure,Risk Component ,"A measure of community proximity to critical physical infrastructure like roads, hospitals, railways, and Healthcare shelters ",Evaluates connectivity of populations to services essential during and after disaster. Poor access amplifies vulnerability and slows recovery.,"Evaluates proximity to roads, rail, hospitals, and shelters (Schools) at the revenue circle level. This layer feeds into the vulnerability score by identifying communities that would be cut off during floods.","OpenStreetMap road data, NHM facility registers, PMGSY rural roads data, Census 2011 village amenities",Road submergence during floods frequently cuts off communities. Poor infrastructure access disproportionately harms even moderate-hazard areas.,,,,,,,,,, -D,DRIMS,Operational ,Flood Reporting and Information Management System by ASDMA for tracking flood damage across Assam during flood seasons,"Primary operational data system for ASDMA to collect and publish daily flood situation reports during monsoon. Data covers population affected, houses damaged, livestock lost, crop inundated, and relief status.",DRIMS data forms a key component of IDS-DRR's past damages dataset. Provides the historical record of flood impacts at revenue circle level used to calibrate hazard scores.,"ASDMA website, daily monsoon situation reports, annual flood reports, IDS-DRR source dataset",One of the few sub-district flood damage datasets systematically maintained in India. Its longitudinal record enables trend analysis and model validation.,,,,,,,,,, -P,Procurement Data,Data Source,"Records of government contracts and tenders for an event-related goods, services, and works — used to assess efficiency and timeliness of public spending","Captures the full contracting cycle: what was procured, from whom, at what price, when awarded, and whether completed on time. Covers embankment works, relief supplies, early warning systems, and disaster equipment.","One of the first disaster risk models in India to incorporate procurement data. Uses assamtenders.gov.in and OCDS data to measure procurement timeliness, completion rates, and expenditure against budget.","gePNIC portal, Finance Department budget documents, Open Contracting Partnership India datasets, CAG audit reports","Budget allocation tells only part of the story. Late contracts, underperforming suppliers, or unspent funds mean allocations don't translate into protection",,,,,,,,,, -R,Resilience,Conceptual,"The ability of a community to absorb, adapt to, and recover from disaster impacts while maintaining essential functions","A resilient community can absorb shocks without catastrophic loss, adapt based on experience, and bounce back to pre-disaster or better conditions. Shaped by social cohesion, economic diversity, institutional strength, and resource access.","IDS-DRR proxies resilience through existing components: government response capacity (institutional), infrastructure access (physical), and low demographic deprivation (social). Improving any component increases resilience.","Sendai Framework 2015-2030, UNDP Human Development Index, World Bank Resilience Index, ASDMA community resilience programme reports",Resilience framing shifts DRR from reactive (responding) to proactive (building communities that can withstand and adapt). It is the long-term goal risk reduction investments work toward.,,,,,,,,,, -D,Demographic Vulnerability,Risk Component ,The degree to which a population's social and demographic characteristics increase susceptibility to a hazard harm,"Examines who the at-risk population is gender, age, economic deprivation and access to basic services. Populations with more women, elderly, deprived, or those lacking WASH (Water, Sanitation and Hygeine) face greater harm.","Measures census indicators per District/Subdistrict/Revenue Ciircle: total population, sex ratio households without safe drinking water, and households without sanitation to undertsand who is more vulnerable to a flood related disater ","Census of India 2011 primary census abstract, SECC 2011 deprivation data, NFHS-5 district health indicators",Reveals who bears the greatest burden. Poor WASH access amplifies flood health impacts through waterborne disease disproportionately affecting deprived communities.,,,,,,,,,, -E,Embankment,Risk Component ,Earthen or concrete flood protection structures built along riverbanks to prevent floodwater from entering inhabited or agricultural land,Linear flood protection bunds along rivers.Their condition directly determines whether a flood event becomes a catastrophe.,Incorporates embankment condition and breach data in the loss and damages layer. ,"Water Resources Department inspection records,SDMA flood damage reports, state budget allocations, procurement data ",Embankment breaches are the single most common cause of sudden large-scale inundation in certain geographies . A single breach can submerge dozens of villages overnight.,,,,,,,,,, -D,DEM,Data Source,Digital Elevation Model is a raster grid of bare-earth surface elevations,"Foundation for all topographic flood analysis. Used to calculate slope, identify low-lying zones, derive drainage networks, compute TWI (Topographic Wetness Index), and model flood accumulation",NASA DEM (30m) is a core input for Flood Hazard scoring. Elevation and slope derived from DEM identify District/Subdistrict and Revenue circlein low-lying flood-susceptible valley terrain. It is one of the main hazard data indicator.,"NASA Earth Data,USGS",Elevation is the most powerful predictor of flood susceptibility. Low-elevation floodplains are structurally flood-prone regardless of embankments.,,,,,,,,,, -S,Slope ,Data Source,Rate of terrain elevation change derived from DEM — predictor of water flow speed and accumulation,"Nearly flat slopes in floodplain cause slow-draining, long-duration inundation and Steep slopes in hill districts generate rapid flash flood runoff.",In IDS-DRR Low-slope floodplain areas assigned higher flood hazard weights due to water accumulation characteristics.,"NASA Earth Data,USGS",Structural reason some geographies experiences prolonged inundation near-flat valley prevents rapid drainage after embankment breach or river overflow.,,,,,,,,,, -N,NDVI,Data Source,Normalised Difference Vegetation Index — measures vegetation density from satellite imagery,Calculated from NIR and Red satellite bands. High values = dense vegetation (flood buffer); low values = barren/urban (faster runoff). Tracks seasonal land cover dynamics in the geographies,"Low NDVI areas (fallow land, chars, degraded forest) associated with higher surface runoff and flood susceptibility","NASA Earth Data,USGS,DICRA",Captures seasonal and multi-year land cover dynamics explaining year-to-year flood damage variation beyond rainfall alone.,,,,,,,,,, \ No newline at end of file diff --git a/glossary/index.tsx b/glossary/index.tsx deleted file mode 100644 index 9abdf015..00000000 --- a/glossary/index.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import { GlossaryTermsSource } from './terms.generated'; - -export type GlossaryItem = { - letter: string; - term: string; - tag?: string; - definitions: { - short: string; - long: string; - }; - details: { - ids_drr: string; - where_seen: string; - why_it_matters: string; - }; - contexts?: { - policy: string; - model: string; - }; - disaster_differences?: [ - GlossaryDisasterDifference?, - GlossaryDisasterDifference?, - GlossaryDisasterDifference?, - ]; - related_terms?: string[]; - common_misinterpretation?: string; -}; - -type GlossaryDisasterDifference = { - disaster_type: string; - difference: string; -}; - -export type GlossaryIndexItem = { - slug: string; - letter: string; - term: string; - short: string; -}; - -function slugifyGlossaryTerm(term: string) { - return term - .toLowerCase() - .trim() - .replace(/['"]/g, '') - .replace(/[^a-z0-9]+/g, '-') - .replace(/(^-|-$)/g, ''); -} - -export const termsBySlug: Record = - GlossaryTermsSource.reduce>((acc, item) => { - const slug = slugifyGlossaryTerm(item.term); - acc[slug] = item; - return acc; - }, {}); - -export const slugsByTag: Record = GlossaryTermsSource.reduce< - Record ->((acc, item) => { - const tag = (item.tag ?? '').trim(); - if (!tag) return acc; - const key = tag.toLowerCase(); - const slug = slugifyGlossaryTerm(item.term); - acc[key] = [...(acc[key] ?? []), slug]; - return acc; -}, {}); - -export const glossaryTags: string[] = (() => { - const labelsByKey = new Map(); - for (const item of GlossaryTermsSource) { - const raw = (item.tag ?? '').trim(); - if (!raw) continue; - const key = raw.toLowerCase(); - if (!labelsByKey.has(key)) labelsByKey.set(key, raw); - } - return [...labelsByKey.values()].sort((a, b) => a.localeCompare(b)); -})(); - -export function getGlossaryIndex(): GlossaryIndexItem[] { - return Object.entries(termsBySlug).map(([slug, item]) => ({ - slug, - letter: item.letter, - term: item.term, - short: item.definitions.short, - })); -} diff --git a/glossary/terms.generated.ts b/glossary/terms.generated.ts deleted file mode 100644 index 396b3a35..00000000 --- a/glossary/terms.generated.ts +++ /dev/null @@ -1,257 +0,0 @@ -/* eslint-disable */ -// THIS FILE IS AUTO-GENERATED. -// Source: glossary/glossary.csv -// Generated by: scripts/generate-glossary.ts - -import type { GlossaryItem } from './index'; - -export const GlossaryTermsSource: GlossaryItem[] = [ - { - "letter": "A", - "term": "Access to Infrastructure", - "tag": "Risk Component", - "definitions": { - "short": "A measure of community proximity to critical physical infrastructure like roads, hospitals, railways, and Healthcare shelters", - "long": "Evaluates connectivity of populations to services essential during and after disaster. Poor access amplifies vulnerability and slows recovery." - }, - "details": { - "ids_drr": "Evaluates proximity to roads, rail, hospitals, and shelters (Schools) at the revenue circle level. This layer feeds into the vulnerability score by identifying communities that would be cut off during floods.", - "where_seen": "OpenStreetMap road data, NHM facility registers, PMGSY rural roads data, Census 2011 village amenities", - "why_it_matters": "Road submergence during floods frequently cuts off communities. Poor infrastructure access disproportionately harms even moderate-hazard areas." - } - }, - { - "letter": "D", - "term": "DEM", - "tag": "Data Source", - "definitions": { - "short": "Digital Elevation Model is a raster grid of bare-earth surface elevations", - "long": "Foundation for all topographic flood analysis. Used to calculate slope, identify low-lying zones, derive drainage networks, compute TWI (Topographic Wetness Index), and model flood accumulation" - }, - "details": { - "ids_drr": "NASA DEM (30m) is a core input for Flood Hazard scoring. Elevation and slope derived from DEM identify District/Subdistrict and Revenue circlein low-lying flood-susceptible valley terrain. It is one of the main hazard data indicator.", - "where_seen": "NASA Earth Data,USGS", - "why_it_matters": "Elevation is the most powerful predictor of flood susceptibility. Low-elevation floodplains are structurally flood-prone regardless of embankments." - } - }, - { - "letter": "D", - "term": "Demographic Vulnerability", - "tag": "Risk Component", - "definitions": { - "short": "The degree to which a population's social and demographic characteristics increase susceptibility to a hazard harm", - "long": "Examines who the at-risk population is gender, age, economic deprivation and access to basic services. Populations with more women, elderly, deprived, or those lacking WASH (Water, Sanitation and Hygeine) face greater harm." - }, - "details": { - "ids_drr": "Measures census indicators per District/Subdistrict/Revenue Ciircle: total population, sex ratio households without safe drinking water, and households without sanitation to undertsand who is more vulnerable to a flood related disater", - "where_seen": "Census of India 2011 primary census abstract, SECC 2011 deprivation data, NFHS-5 district health indicators", - "why_it_matters": "Reveals who bears the greatest burden. Poor WASH access amplifies flood health impacts through waterborne disease disproportionately affecting deprived communities." - } - }, - { - "letter": "D", - "term": "DRIMS", - "tag": "Operational", - "definitions": { - "short": "Flood Reporting and Information Management System by ASDMA for tracking flood damage across Assam during flood seasons", - "long": "Primary operational data system for ASDMA to collect and publish daily flood situation reports during monsoon. Data covers population affected, houses damaged, livestock lost, crop inundated, and relief status." - }, - "details": { - "ids_drr": "DRIMS data forms a key component of IDS-DRR's past damages dataset. Provides the historical record of flood impacts at revenue circle level used to calibrate hazard scores.", - "where_seen": "ASDMA website, daily monsoon situation reports, annual flood reports, IDS-DRR source dataset", - "why_it_matters": "One of the few sub-district flood damage datasets systematically maintained in India. Its longitudinal record enables trend analysis and model validation." - } - }, - { - "letter": "E", - "term": "Embankment", - "tag": "Risk Component", - "definitions": { - "short": "Earthen or concrete flood protection structures built along riverbanks to prevent floodwater from entering inhabited or agricultural land", - "long": "Linear flood protection bunds along rivers.Their condition directly determines whether a flood event becomes a catastrophe." - }, - "details": { - "ids_drr": "Incorporates embankment condition and breach data in the loss and damages layer.", - "where_seen": "Water Resources Department inspection records,SDMA flood damage reports, state budget allocations, procurement data", - "why_it_matters": "Embankment breaches are the single most common cause of sudden large-scale inundation in certain geographies . A single breach can submerge dozens of villages overnight." - } - }, - { - "letter": "E", - "term": "Exposure", - "tag": "data", - "definitions": { - "short": "The people property and assets located in hazard-prone areas", - "long": "Exposure refers to what or who is present in places where hazardous events might occur. This includes population counts, buildings, infrastructure, agricultural land, and economic activities in at-risk zones." - }, - "details": { - "ids_drr": "IDS-DRR maps exposure using census data, satellite imagery, infrastructure databases, and economic activity records to show exactly what could be affected by different hazard scenarios.", - "where_seen": "Exposure maps showing population density in risk zones, asset inventories, infrastructure overlays, and economic impact projections", - "why_it_matters": "Understanding exposure helps quantify potential losses and prioritize protection measures. Reducing exposure through land-use planning or relocation is one of the most effective ways to reduce disaster risk." - }, - "contexts": { - "policy": "Used to determine land-use planning restrictions and relocation programs", - "model": "Models predict population exposure based on historical hazard data, socioeconomic indicators, and climate projections to anticipate future risks." - }, - "disaster_differences": [ - { - "disaster_type": "Flood Exposure", - "difference": "Based on population/assets in flood plains, near rivers, or low-lying coastal areas" - }, - { - "disaster_type": "Heat Exposure", - "difference": "Based on population in areas with high temperatures, poor ventilation, or limited cooling" - }, - { - "disaster_type": "Cyclone Exposure", - "difference": "Based on population/assets in coastal zones within cyclone-prone regions" - } - ], - "related_terms": [ - "risk", - "hazard", - "vulnerability" - ], - "common_misinterpretation": "High exposure doesn't mean high risk if those exposed are well-protected or the hazard is unlikely" - }, - { - "letter": "G", - "term": "Government Response", - "tag": "Risk Component", - "definitions": { - "short": "The capacity of government institutions to prepare for, respond to, and recover from flood events", - "long": "Measures how effectively public systems reduce harm before, during, and after an hazard event ncluding relief infrastructure, procurement efficiency, and recovery expenditure." - }, - "details": { - "ids_drr": "Incorporates public procurement and fiscal data from the state tender portal and SECC Meetings measure whether money for flood management is being spent effectively and on time.", - "where_seen": "state budget documents, SDMA flood reports, Open Contracting procurement data, Finance Department expenditure records, SECC meetings", - "why_it_matters": "Even high-hazard areas can have lower net risk with strong government response. Weak procurement amplifies disaster impacts." - } - }, - { - "letter": "H", - "term": "Hazard", - "tag": "Risk Component", - "definitions": { - "short": "A potentially damaging natural event that may cause loss of life or property", - "long": "A process, phenomenon or human activity that may cause loss of life, injury or other health impacts, property damage, social and economic disruption or environmental degradation." - }, - "details": { - "ids_drr": "Calculates a Flood Hazard Score per revenue circle/District/Subsditrict using satellite-derived flood extent data, historical inundation records, and hydro-meteorological indicators.", - "where_seen": "Flood hazard maps, inundation layers, SDMA early warning bulletins, IMD rainfall alerts, CWC flood forecasting data", - "why_it_matters": "Hazard assessment shows where and how severely floods strike. Without it, risk scores cannot be computed." - } - }, - { - "letter": "I", - "term": "Inundation", - "tag": "Data Source", - "definitions": { - "short": "The temporary flooding of land caused by overflow of water from rivers or heavy rainfall", - "long": "Physical covering of land by floodwater. Measured by extent, depth, and duration. the driving factor varies from geography to geographies but primarily driven by riverine overflowing during monsoon." - }, - "details": { - "ids_drr": "Uses Satellite imagery via Bhuvan to track spatial extent and duration of flooding across Districts/Subdistricts/Revenue circles each monsoon season.", - "where_seen": "MODIS and Sentinel satellite flood maps, ASDMA FRIMS daily situation reports, BHUVAN portal, CWC flood bulletins", - "why_it_matters": "Inundation extent is the primary input for flood hazard scores. Historical frequency data identifies which administrative unit faces repeated flooding." - } - }, - { - "letter": "N", - "term": "NDVI", - "tag": "Data Source", - "definitions": { - "short": "Normalised Difference Vegetation Index — measures vegetation density from satellite imagery", - "long": "Calculated from NIR and Red satellite bands. High values = dense vegetation (flood buffer); low values = barren/urban (faster runoff). Tracks seasonal land cover dynamics in the geographies" - }, - "details": { - "ids_drr": "Low NDVI areas (fallow land, chars, degraded forest) associated with higher surface runoff and flood susceptibility", - "where_seen": "NASA Earth Data,USGS,DICRA", - "why_it_matters": "Captures seasonal and multi-year land cover dynamics explaining year-to-year flood damage variation beyond rainfall alone." - } - }, - { - "letter": "P", - "term": "Procurement Data", - "tag": "Data Source", - "definitions": { - "short": "Records of government contracts and tenders for an event-related goods, services, and works — used to assess efficiency and timeliness of public spending", - "long": "Captures the full contracting cycle: what was procured, from whom, at what price, when awarded, and whether completed on time. Covers embankment works, relief supplies, early warning systems, and disaster equipment." - }, - "details": { - "ids_drr": "One of the first disaster risk models in India to incorporate procurement data. Uses assamtenders.gov.in and OCDS data to measure procurement timeliness, completion rates, and expenditure against budget.", - "where_seen": "gePNIC portal, Finance Department budget documents, Open Contracting Partnership India datasets, CAG audit reports", - "why_it_matters": "Budget allocation tells only part of the story. Late contracts, underperforming suppliers, or unspent funds mean allocations don't translate into protection" - } - }, - { - "letter": "R", - "term": "Resilience", - "tag": "Conceptual", - "definitions": { - "short": "The ability of a community to absorb, adapt to, and recover from disaster impacts while maintaining essential functions", - "long": "A resilient community can absorb shocks without catastrophic loss, adapt based on experience, and bounce back to pre-disaster or better conditions. Shaped by social cohesion, economic diversity, institutional strength, and resource access." - }, - "details": { - "ids_drr": "IDS-DRR proxies resilience through existing components: government response capacity (institutional), infrastructure access (physical), and low demographic deprivation (social). Improving any component increases resilience.", - "where_seen": "Sendai Framework 2015-2030, UNDP Human Development Index, World Bank Resilience Index, ASDMA community resilience programme reports", - "why_it_matters": "Resilience framing shifts DRR from reactive (responding) to proactive (building communities that can withstand and adapt). It is the long-term goal risk reduction investments work toward." - } - }, - { - "letter": "R", - "term": "Revenue Circle", - "tag": "Administrative", - "definitions": { - "short": "The smallest administrative unit used for land revenue and governance in Assam, below sub-district level", - "long": "Administrative subdivision for land records, revenue collection and local governance. IDS-DRR uses revenue circles as the primary unit of analysis for all risk scoring." - }, - "details": { - "ids_drr": "Computes all risk scores hazard, vulnerability, exposure, and government response at the revenue circle level for the state of Assam.", - "where_seen": "Revenue department records, ASDMA damage reports, Census sub-district data, land record portal", - "why_it_matters": "Revenue circle granularity identifies high-risk pockets within a district that district-level averages would mask." - } - }, - { - "letter": "R", - "term": "Risk Score", - "tag": "Topographic Wetness Index", - "definitions": { - "short": "A composite index quantifying overall risk based on hazard, exposure, vulnerability, and government response", - "long": "Aggregates multiple risk components into a single comparable value per geographic unit and hence enabling prioritisation and tracking of change over time." - }, - "details": { - "ids_drr": "Computes a Comprehensive Risk Score for every revenue circle/district/subdistrict for a state combining sub-scores for Flood Hazard, Exposure, Vulnerability, and Government Response.", - "where_seen": "IDS-DRR dashboard, SDMA planning documents, district disaster management plans", - "why_it_matters": "Translates complex multi-dimensional data into an actionable number for resource allocation and progress tracking." - } - }, - { - "letter": "S", - "term": "Slope", - "tag": "Data Source", - "definitions": { - "short": "Rate of terrain elevation change derived from DEM — predictor of water flow speed and accumulation", - "long": "Nearly flat slopes in floodplain cause slow-draining, long-duration inundation and Steep slopes in hill districts generate rapid flash flood runoff." - }, - "details": { - "ids_drr": "In IDS-DRR Low-slope floodplain areas assigned higher flood hazard weights due to water accumulation characteristics.", - "where_seen": "NASA Earth Data,USGS", - "why_it_matters": "Structural reason some geographies experiences prolonged inundation near-flat valley prevents rapid drainage after embankment breach or river overflow." - } - }, - { - "letter": "V", - "term": "Vulnerability", - "tag": "Risk Component", - "definitions": { - "short": "The conditions that make people or assets more susceptible to harm from a hazard", - "long": "Social, economic, physical, and institutional factors that increase likelihood of harm. Includes housing quality, health status, access to infrastructure, and governance strength." - }, - "details": { - "ids_drr": "Measures demographic vulnerability using WORLDPOP, Bharat Map and Antodaya indicators: population density, sex ratio and household level data at the District/Subdistrict/Revenue Circle circle level.", - "where_seen": "Census of India tables, NFHS health indicators, SECC deprivation data, SDMA damage reports disaggregated by population group", - "why_it_matters": "Two areas with identical hazard and exposure can have very different outcomes depending on vulnerability" - } - } -]; diff --git a/knip.jsonc b/knip.jsonc index 17fdda5a..9b3fbc81 100644 --- a/knip.jsonc +++ b/knip.jsonc @@ -10,8 +10,6 @@ "gql/generated/**", // @import'd by styles/globals.css (knip doesn't follow CSS imports). "styles/tokens/_variables.css", - // Imported as ids-drr-branding; knip does not follow the file: link here. - "branding-stub/src/index.ts", ], "ignoreDependencies": [ // `graphql-codegen --require dotenv/config` in npm scripts. @@ -32,6 +30,5 @@ // direct source imports knip can trace. "@jest/globals", "ts-jest", - "ts-node", ], } diff --git a/lib/glossary.ts b/lib/glossary.ts new file mode 100644 index 00000000..c51f62ef --- /dev/null +++ b/lib/glossary.ts @@ -0,0 +1,126 @@ +import type { GlossaryTerm } from 'ids-drr-branding-types'; + +type CsvRow = Record; + +function parseCsv(text: string): CsvRow[] { + const rows: string[][] = []; + let currentField = ''; + let currentRow: string[] = []; + let inQuotes = false; + + const pushField = () => { + currentRow.push(currentField); + currentField = ''; + }; + + const pushRow = () => { + // skip empty trailing lines + if (currentRow.length === 1 && currentRow[0].trim() === '') { + currentRow = []; + return; + } + rows.push(currentRow); + currentRow = []; + }; + + for (let i = 0; i < text.length; i++) { + const ch = text[i]; + + if (ch === '"') { + if (inQuotes && text[i + 1] === '"') { + currentField += '"'; + i++; + } else { + inQuotes = !inQuotes; + } + continue; + } + + if (!inQuotes && ch === ',') { + pushField(); + continue; + } + + if (!inQuotes && (ch === '\n' || ch === '\r')) { + // handle CRLF + if (ch === '\r' && text[i + 1] === '\n') i++; + pushField(); + pushRow(); + continue; + } + + currentField += ch; + } + + // final field/row + pushField(); + if (currentRow.length > 0) pushRow(); + + if (rows.length === 0) return []; + const headers = rows[0].map((h) => h.trim()); + + return rows.slice(1).map((r) => { + const obj: CsvRow = {}; + for (let i = 0; i < headers.length; i++) { + obj[headers[i]] = (r[i] ?? '').trim(); + } + return obj; + }); +} + +function buildDisasterMethodology(row: CsvRow) { + const items: { disasterType: string; methodology: string }[] = []; + for (let i = 0; i < 3; i++) { + const t = row[`disaster_type_${i}`]; + const d = row[`difference_${i}`]; + if (t && d) items.push({ disasterType: t, methodology: d }); + } + return items; +} + +export function slugifyGlossaryTerm(term: string) { + return term + .toLowerCase() + .trim() + .replace(/['"]/g, '') + .replace(/[^a-z0-9]+/g, '-') + .replace(/(^-|-$)/g, ''); +} + +export function parseGlossary(csv: string): GlossaryTerm[] { + const rows = parseCsv(csv).filter((r) => r.term); + + return rows + .map((r) => { + const tag = r.tag?.trim(); + const policy = r.policy?.trim() ?? ''; + const model = r.model?.trim() ?? ''; + const interpretation = policy || model ? { policy, model } : undefined; + const disasterMethodology = buildDisasterMethodology(r); + const related = (r.related_terms ?? '') + .split(',') + .map((x) => x.trim()) + .filter(Boolean); + const misinterpretation = r.common_misinterpretation?.trim() ?? ''; + + return { + term: r.term, + ...(tag ? { tag } : {}), + summary: r.short ?? '', + definition: r.long ?? '', + methodology: r.ids_drr ?? '', + usage: r.where_seen ?? '', + significance: r.why_it_matters ?? '', + ...(disasterMethodology.length > 0 ? { disasterMethodology } : {}), + ...(interpretation ? { interpretation } : {}), + ...(misinterpretation ? { misinterpretation } : {}), + ...(related.length > 0 ? { related } : {}), + }; + }) + .sort((a, b) => + a.term.localeCompare(b.term, undefined, { + sensitivity: 'base', + numeric: true, + }) + ); +} diff --git a/locales/en.json b/locales/en.json index 37fa9241..2d1946c0 100644 --- a/locales/en.json +++ b/locales/en.json @@ -223,8 +223,7 @@ "title": "In Model", "heading": "Model" } - }, - "error": "Couldn’t load details for this term." + } }, "empty": "No results found" }, diff --git a/package-lock.json b/package-lock.json index fbe4b614..786f584f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -65,7 +65,6 @@ "tailwind-merge": "^1.12.0", "tailwindcss": "^3.3.2", "ts-jest": "^29.4.1", - "ts-node": "^10.9.2", "typescript": "^5.0.4" } }, @@ -1309,6 +1308,8 @@ "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@jridgewell/trace-mapping": "0.3.9" }, @@ -1322,6 +1323,8 @@ "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -10181,28 +10184,36 @@ "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/@tsconfig/node12": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/@tsconfig/node14": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/@tsconfig/node16": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/@tybys/wasm-util": { "version": "0.10.1", @@ -11340,6 +11351,8 @@ "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "acorn": "^8.11.0" }, @@ -12990,7 +13003,9 @@ "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/cross-fetch": { "version": "3.2.0", @@ -13464,6 +13479,8 @@ "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true, "license": "BSD-3-Clause", + "optional": true, + "peer": true, "engines": { "node": ">=0.3.1" } @@ -15632,7 +15649,7 @@ }, "node_modules/ids-drr-branding-types": { "version": "0.1.0", - "resolved": "git+ssh://git@github.com/open-contracting/ids-drr-branding-types.git#a9a89b0a4d922d90076a89115084e2574f643932" + "resolved": "git+ssh://git@github.com/open-contracting/ids-drr-branding-types.git#4b7ca97cb7fdd6f1ff31936e952c3774edc1607f" }, "node_modules/ieee754": { "version": "1.2.1", @@ -22803,6 +22820,8 @@ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -22846,7 +22865,9 @@ "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/tsconfig-paths": { "version": "3.15.0", @@ -23338,7 +23359,9 @@ "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/v8-to-istanbul": { "version": "9.3.0", @@ -23915,6 +23938,8 @@ "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=6" } diff --git a/package.json b/package.json index ac69a1bc..36264d7a 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,6 @@ "build:tokens": "node ./config/tokens/generate.mjs", "postbuild:tokens": "node ./config/tokens/copy.js", "generate": "graphql-codegen --require dotenv/config --config ./config/codegen.ts --watch", - "generate:glossary": "ts-node -P tsconfig.scripts.json ./scripts/generate-glossary.ts", "clean": "rm -rf node_modules package-lock.json", "test": "jest", "test:watch": "jest --watch", @@ -76,7 +75,6 @@ "tailwind-merge": "^1.12.0", "tailwindcss": "^3.3.2", "ts-jest": "^29.4.1", - "ts-node": "^10.9.2", "typescript": "^5.0.4" }, "overrides": { diff --git a/scripts/generate-glossary.ts b/scripts/generate-glossary.ts deleted file mode 100644 index 2a4dca35..00000000 --- a/scripts/generate-glossary.ts +++ /dev/null @@ -1,180 +0,0 @@ -import fs from 'node:fs'; -import path from 'node:path'; - -type CsvRow = Record; - -function parseCsv(text: string): CsvRow[] { - const rows: string[][] = []; - let currentField = ''; - let currentRow: string[] = []; - let inQuotes = false; - - const pushField = () => { - currentRow.push(currentField); - currentField = ''; - }; - - const pushRow = () => { - // skip empty trailing lines - if (currentRow.length === 1 && currentRow[0].trim() === '') { - currentRow = []; - return; - } - rows.push(currentRow); - currentRow = []; - }; - - for (let i = 0; i < text.length; i++) { - const ch = text[i]; - - if (ch === '"') { - if (inQuotes && text[i + 1] === '"') { - currentField += '"'; - i++; - } else { - inQuotes = !inQuotes; - } - continue; - } - - if (!inQuotes && ch === ',') { - pushField(); - continue; - } - - if (!inQuotes && (ch === '\n' || ch === '\r')) { - // handle CRLF - if (ch === '\r' && text[i + 1] === '\n') i++; - pushField(); - pushRow(); - continue; - } - - currentField += ch; - } - - // final field/row - pushField(); - if (currentRow.length > 0) pushRow(); - - if (rows.length === 0) return []; - const headers = rows[0].map((h) => h.trim()); - - return rows.slice(1).map((r) => { - const obj: CsvRow = {}; - for (let i = 0; i < headers.length; i++) { - obj[headers[i]] = (r[i] ?? '').trim(); - } - return obj; - }); -} - -function toRelatedTerms(value: string): string[] { - const raw = value.trim(); - if (!raw) return []; - return raw - .split(',') - .map((x) => x.trim()) - .filter(Boolean); -} - -function buildDisasterDifferences(row: CsvRow) { - const diffs: { disaster_type: string; difference: string }[] = []; - for (let i = 0; i < 3; i++) { - const t = row[`disaster_type_${i}`]; - const d = row[`difference_${i}`]; - if (!t && !d) continue; - if (!t || !d) continue; - diffs.push({ disaster_type: t, difference: d }); - } - return diffs; -} - -function nonEmpty(value: string | undefined | null): value is string { - return typeof value === 'string' && value.trim().length > 0; -} - -function main() { - const repoRoot = process.cwd(); - const csvPath = path.join(repoRoot, 'glossary', 'glossary.csv'); - const outPath = path.join(repoRoot, 'glossary', 'terms.generated.ts'); - - const csvText = fs.readFileSync(csvPath, 'utf8'); - const rows = parseCsv(csvText); - - const items = rows - .filter((r) => r.term && r.letter) - .map((r) => { - const letter = r.letter.toUpperCase(); - const term = r.term; - const tag = (r.tag ?? '').trim(); - - const policy = (r.policy ?? '').trim(); - const model = (r.model ?? '').trim(); - const contexts = - nonEmpty(policy) || nonEmpty(model) ? { policy, model } : undefined; - - const disaster_differences = buildDisasterDifferences(r); - const related_terms = toRelatedTerms(r.related_terms ?? ''); - - const common_misinterpretation = ( - r.common_misinterpretation ?? '' - ).trim(); - - return { - letter, - term, - ...(nonEmpty(tag) ? { tag } : {}), - definitions: { - short: (r.short ?? '').trim(), - long: (r.long ?? '').trim(), - }, - details: { - ids_drr: (r.ids_drr ?? '').trim(), - where_seen: (r.where_seen ?? '').trim(), - why_it_matters: (r.why_it_matters ?? '').trim(), - }, - ...(contexts ? { contexts } : {}), - ...(disaster_differences.length > 0 ? { disaster_differences } : {}), - ...(related_terms.length > 0 ? { related_terms } : {}), - ...(nonEmpty(common_misinterpretation) - ? { common_misinterpretation } - : {}), - }; - }) - .sort((a, b) => { - const byTerm = a.term.localeCompare(b.term, undefined, { - sensitivity: 'base', - numeric: true, - }); - if (byTerm !== 0) return byTerm; - - const byLetter = a.letter.localeCompare(b.letter, undefined, { - sensitivity: 'base', - }); - if (byLetter !== 0) return byLetter; - - return (a.tag ?? '').localeCompare(b.tag ?? '', undefined, { - sensitivity: 'base', - numeric: true, - }); - }); - - const file = `/* eslint-disable */ -// THIS FILE IS AUTO-GENERATED. -// Source: glossary/glossary.csv -// Generated by: scripts/generate-glossary.ts - -import type { GlossaryItem } from './index'; - -export const GlossaryTermsSource: GlossaryItem[] = ${JSON.stringify( - items, - null, - 2 - )}; -`; - - fs.writeFileSync(outPath, file, 'utf8'); -} - -main(); diff --git a/tsconfig.scripts.json b/tsconfig.scripts.json deleted file mode 100644 index 8c386fdb..00000000 --- a/tsconfig.scripts.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "module": "CommonJS", - "moduleResolution": "Node", - "noEmit": true - }, - "include": ["scripts/**/*.ts"] -} - From 93b7101f8fdd32b9ad455ace71a22d35c0d403f7 Mon Sep 17 00:00:00 2001 From: Abhishekfm Date: Tue, 12 May 2026 23:02:56 +0530 Subject: [PATCH 9/9] revert: Restore string stateCode for indicators GraphQL queries --- .../analytics/components/analytics-layout.tsx | 18 ++++-------------- .../analytics/components/chart-view.tsx | 5 +---- .../analytics/components/factor-list.tsx | 3 +-- app/[locale]/[state]/analytics/page.tsx | 2 +- config/graphql/analaytics-queries.ts | 2 +- gql/generated/analytics/gql.ts | 4 ++-- gql/generated/analytics/graphql.ts | 8 ++++---- 7 files changed, 14 insertions(+), 28 deletions(-) diff --git a/app/[locale]/[state]/analytics/components/analytics-layout.tsx b/app/[locale]/[state]/analytics/components/analytics-layout.tsx index 1b95a315..0f95957b 100644 --- a/app/[locale]/[state]/analytics/components/analytics-layout.tsx +++ b/app/[locale]/[state]/analytics/components/analytics-layout.tsx @@ -96,10 +96,7 @@ export function AnalyticsMainLayout() { `${process.env.NEXT_PUBLIC_DATA_MANAGEMENT_LAYER_URL}/graphql`, ANALYTICS_INDICATORS_BY_CATEGORY, { - stateCode: - currentSelectedState?.code != null - ? String(currentSelectedState.code) - : undefined, + stateCode: currentSelectedState?.code, } as any ), enabled: Boolean(currentSelectedState?.code), @@ -326,10 +323,7 @@ export function AnalyticsMainLayout() { ANALYTICS_INDICATORS, { indcFilter: { slug: indicator }, - stateCode: - currentSelectedState?.code != null - ? Number(currentSelectedState.code) - : undefined, + stateCode: currentSelectedState?.code, } as any ), enabled: Boolean(isMapView && currentSelectedState?.code), @@ -347,10 +341,7 @@ export function AnalyticsMainLayout() { ANALYTICS_INDICATORS, { indcFilter: { slug: 'risk-score' }, - stateCode: - currentSelectedState?.code != null - ? Number(currentSelectedState.code) - : undefined, + stateCode: currentSelectedState?.code, } as any ), // Avoid a duplicate request when the selected indicator is already risk-score. @@ -703,8 +694,7 @@ export function OutputWindowComponent({ ANALYTICS_INDICATORS, { indcFilter: { slug: indicator }, - stateCode: - currentState?.code != null ? Number(currentState.code) : undefined, + stateCode: currentState?.code, } ), enabled: Boolean(currentState?.code), diff --git a/app/[locale]/[state]/analytics/components/chart-view.tsx b/app/[locale]/[state]/analytics/components/chart-view.tsx index f41dfced..3fb33ac5 100644 --- a/app/[locale]/[state]/analytics/components/chart-view.tsx +++ b/app/[locale]/[state]/analytics/components/chart-view.tsx @@ -99,10 +99,7 @@ export const ChartView = ({ `${process.env.NEXT_PUBLIC_DATA_MANAGEMENT_LAYER_URL}/graphql`, ANALYTICS_INDICATORS_BY_CATEGORY, { - stateCode: - currentSelectedState?.code != null - ? String(currentSelectedState.code) - : undefined, + stateCode: currentSelectedState?.code, } ), refetchOnMount: false, diff --git a/app/[locale]/[state]/analytics/components/factor-list.tsx b/app/[locale]/[state]/analytics/components/factor-list.tsx index dae67d59..1d652958 100644 --- a/app/[locale]/[state]/analytics/components/factor-list.tsx +++ b/app/[locale]/[state]/analytics/components/factor-list.tsx @@ -79,8 +79,7 @@ export function FactorList({ currentState }: any) { `${process.env.NEXT_PUBLIC_DATA_MANAGEMENT_LAYER_URL}/graphql`, ANALYTICS_INDICATORS_BY_CATEGORY, { - stateCode: - currentState?.code != null ? String(currentState.code) : undefined, + stateCode: currentState?.code, } ), refetchOnMount: false, diff --git a/app/[locale]/[state]/analytics/page.tsx b/app/[locale]/[state]/analytics/page.tsx index 462cf0e3..c80ff1f6 100644 --- a/app/[locale]/[state]/analytics/page.tsx +++ b/app/[locale]/[state]/analytics/page.tsx @@ -40,7 +40,7 @@ export default async function Home({ queryFn: () => GraphQL(graphqlUrl, ANALYTICS_INDICATORS, { indcFilter: { slug }, - stateCode: Number(stateCode), + stateCode, }), }) ) diff --git a/config/graphql/analaytics-queries.ts b/config/graphql/analaytics-queries.ts index 8b49b6fa..179f3f51 100644 --- a/config/graphql/analaytics-queries.ts +++ b/config/graphql/analaytics-queries.ts @@ -29,7 +29,7 @@ export const ANALYTICS_DISTRICT_DATA = graphql(` `); export const ANALYTICS_INDICATORS = graphql(` - query indicators($indcFilter: IndicatorFilter, $stateCode: Int) { + query indicators($indcFilter: IndicatorFilter, $stateCode: String) { indicators(indcFilter: $indcFilter, stateCode: $stateCode) } `); diff --git a/gql/generated/analytics/gql.ts b/gql/generated/analytics/gql.ts index 3e40ab60..12e2ff9b 100644 --- a/gql/generated/analytics/gql.ts +++ b/gql/generated/analytics/gql.ts @@ -15,7 +15,7 @@ import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/ const documents = { "\n query revCircleViewData(\n $indcFilter: IndicatorFilter!\n $dataFilter: DataFilter!\n $geoFilter: GeoFilter!\n ) {\n revCircleViewData(\n indcFilter: $indcFilter\n dataFilter: $dataFilter\n geoFilter: $geoFilter\n )\n }\n": types.RevCircleViewDataDocument, "\n query districtViewData(\n $indcFilter: IndicatorFilter!\n $dataFilter: DataFilter!\n $geoFilter: GeoFilter!\n ) {\n districtViewData(\n indcFilter: $indcFilter\n dataFilter: $dataFilter\n geoFilter: $geoFilter\n )\n }\n": types.DistrictViewDataDocument, - "\n query indicators($indcFilter: IndicatorFilter, $stateCode: Int) {\n indicators(indcFilter: $indcFilter, stateCode: $stateCode)\n }\n": types.IndicatorsDocument, + "\n query indicators($indcFilter: IndicatorFilter, $stateCode: String) {\n indicators(indcFilter: $indcFilter, stateCode: $stateCode)\n }\n": types.IndicatorsDocument, "\n query indicatorsByCategory($stateCode: String) {\n indicatorsByCategory(stateCode: $stateCode)\n }\n": types.IndicatorsByCategoryDocument, "\n query dataTimePeriods {\n getDataTimePeriods {\n value\n }\n }\n": types.DataTimePeriodsDocument, "\n query getDistrictRevCircle($geoFilter: GeoFilter!) {\n getDistrictRevCircle(geoFilter: $geoFilter)\n }\n": types.GetDistrictRevCircleDocument, @@ -50,7 +50,7 @@ export function graphql(source: "\n query districtViewData(\n $indcFilter: I /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n query indicators($indcFilter: IndicatorFilter, $stateCode: Int) {\n indicators(indcFilter: $indcFilter, stateCode: $stateCode)\n }\n"): (typeof documents)["\n query indicators($indcFilter: IndicatorFilter, $stateCode: Int) {\n indicators(indcFilter: $indcFilter, stateCode: $stateCode)\n }\n"]; +export function graphql(source: "\n query indicators($indcFilter: IndicatorFilter, $stateCode: String) {\n indicators(indcFilter: $indcFilter, stateCode: $stateCode)\n }\n"): (typeof documents)["\n query indicators($indcFilter: IndicatorFilter, $stateCode: String) {\n indicators(indcFilter: $indcFilter, stateCode: $stateCode)\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/gql/generated/analytics/graphql.ts b/gql/generated/analytics/graphql.ts index 35484d17..d8d9c2c1 100644 --- a/gql/generated/analytics/graphql.ts +++ b/gql/generated/analytics/graphql.ts @@ -29,7 +29,7 @@ export type DataFilter = { period?: InputMaybe; }; -/** Geography(id, name, code, type, parentId, geom, slug) */ +/** Geography(id, name, code, type, parentId, geom, simple_geom, slug) */ export type GeoFilter = { AND?: InputMaybe; OR?: InputMaybe; @@ -90,7 +90,7 @@ export type QueryGetTimeTrendsArgs = { export type QueryIndicatorsArgs = { indcFilter?: InputMaybe; - stateCode?: InputMaybe; + stateCode?: InputMaybe; }; @@ -140,7 +140,7 @@ export type DistrictViewDataQuery = { __typename?: 'Query', districtViewData: an export type IndicatorsQueryVariables = Exact<{ indcFilter?: InputMaybe; - stateCode?: InputMaybe; + stateCode?: InputMaybe; }>; @@ -200,7 +200,7 @@ export type GetStatesListQuery = { __typename?: 'Query', getStates: any }; export const RevCircleViewDataDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"revCircleViewData"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"indcFilter"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"IndicatorFilter"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"dataFilter"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"DataFilter"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"geoFilter"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"GeoFilter"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"revCircleViewData"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"indcFilter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"indcFilter"}}},{"kind":"Argument","name":{"kind":"Name","value":"dataFilter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"dataFilter"}}},{"kind":"Argument","name":{"kind":"Name","value":"geoFilter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"geoFilter"}}}]}]}}]} as unknown as DocumentNode; export const DistrictViewDataDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"districtViewData"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"indcFilter"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"IndicatorFilter"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"dataFilter"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"DataFilter"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"geoFilter"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"GeoFilter"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"districtViewData"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"indcFilter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"indcFilter"}}},{"kind":"Argument","name":{"kind":"Name","value":"dataFilter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"dataFilter"}}},{"kind":"Argument","name":{"kind":"Name","value":"geoFilter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"geoFilter"}}}]}]}}]} as unknown as DocumentNode; -export const IndicatorsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"indicators"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"indcFilter"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"IndicatorFilter"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"stateCode"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"indicators"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"indcFilter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"indcFilter"}}},{"kind":"Argument","name":{"kind":"Name","value":"stateCode"},"value":{"kind":"Variable","name":{"kind":"Name","value":"stateCode"}}}]}]}}]} as unknown as DocumentNode; +export const IndicatorsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"indicators"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"indcFilter"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"IndicatorFilter"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"stateCode"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"indicators"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"indcFilter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"indcFilter"}}},{"kind":"Argument","name":{"kind":"Name","value":"stateCode"},"value":{"kind":"Variable","name":{"kind":"Name","value":"stateCode"}}}]}]}}]} as unknown as DocumentNode; export const IndicatorsByCategoryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"indicatorsByCategory"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"stateCode"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"indicatorsByCategory"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"stateCode"},"value":{"kind":"Variable","name":{"kind":"Name","value":"stateCode"}}}]}]}}]} as unknown as DocumentNode; export const DataTimePeriodsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"dataTimePeriods"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getDataTimePeriods"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"value"}}]}}]}}]} as unknown as DocumentNode; export const GetDistrictRevCircleDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"getDistrictRevCircle"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"geoFilter"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"GeoFilter"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getDistrictRevCircle"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"geoFilter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"geoFilter"}}}]}]}}]} as unknown as DocumentNode;