open-navigator / web_docs /docs /integrations /gsa-domain-integration.md
jcbowyer's picture
Clean HuggingFace deployment without binary files
e59d91d
|
Raw
History Blame Contribute Delete
9.14 kB
---
title: GSA .gov Domain Integration
---
# GSA .gov Domain Integration
## Overview
This document describes the integration of **GSA (General Services Administration) .gov domain data** into the `jurisdictions_details_search` table.
**Data Source:** https://github.com/cisagov/dotgov-data
**Type of Load:** **ENRICHMENT/UPDATE LOAD**
- Does **NOT** create new jurisdiction records
- **Adds** official .gov domain information to existing jurisdictions
- **Updates** records with GSA-verified government contact data
---
## Data Fields Added
### New Columns in `jurisdictions_details_search`
| Column | Type | Description |
|--------|------|-------------|
| `gov_domains` | JSONB | Array of all official .gov domains registered to this jurisdiction |
| `security_contact_email` | TEXT | Official security/technical contact email from GSA registry |
| `gsa_organization_name` | TEXT | Official organization name as registered with GSA |
| `gsa_domain_type` | TEXT | GSA classification (City, County, State, Special District, etc.) |
| `gsa_last_updated` | TIMESTAMP | When GSA data was last loaded |
### GSA Source Data Fields
The GSA dataset contains:
- **Domain name** (e.g., "bostonma.gov")
- **Domain type** (Federal, State, County, City, Township, Special District, School District)
- **Organization name** (official registered name)
- **Suborganization name** (departments, divisions)
- **City** (jurisdiction location)
- **State** (2-letter code)
- **Security contact email** (technical/security contact)
---
## Matching Strategy
### City Matching
Matches by **normalized city name + state code**:
```python
# Examples:
"Town of Abington, MA" -> normalized to "abington" + "MA"
"City of Boston, MA" -> normalized to "boston" + "MA"
```
Common prefixes removed during normalization:
- "City of "
- "Town of "
- "Village of "
- "Township of "
- "Borough of "
### County Matching
Matches by **county name + state code**:
```python
# Examples:
"King County, WA" -> normalized to "king county" + "WA"
"Suffolk County, MA" -> normalized to "suffolk county" + "MA"
```
---
## Coverage Statistics (6 Dev States)
### Overall Summary
- **Total jurisdictions checked:** 4,383
- **Matched with GSA domains:** 679 (15.5%)
- **No GSA match found:** 3,704 (84.5%)
### By State and Type
| State | Type | Total | With GSA | Coverage | With Contact Email |
|-------|------|-------|----------|----------|-------------------|
| **AL** | City | 594 | 115 | **19.4%** | 75 |
| AL | County | 67 | 0 | 0.0% | 0 |
| **GA** | City | 675 | 174 | **25.8%** | 104 |
| GA | County | 159 | 0 | 0.0% | 0 |
| **IN** | City | 976 | 85 | **8.7%** | 54 |
| IN | County | 92 | 0 | 0.0% | 0 |
| **MA** | City | 248 | 36 | **14.5%** | 29 |
| MA | County | 14 | 0 | 0.0% | 0 |
| **WA** | City | 639 | 124 | **19.4%** | 94 |
| WA | County | 39 | 0 | 0.0% | 0 |
| **WI** | City | 808 | 143 | **17.7%** | 102 |
| WI | County | 72 | 2 | 2.8% | 1 |
### Key Insights
**Cities:** 8-26% have official .gov domains registered
- Best coverage: **Georgia (25.8%)**, **Alabama (19.4%)**, **Washington (19.4%)**
- Lowest coverage: **Indiana (8.7%)**
⚠️ **Counties:** Very low .gov domain registration
- Only **2 out of 443 counties** have .gov domains (0.5%)
- Counties typically use state-run websites or no dedicated domain
---
## Example Enriched Records
### City with Multiple Domains
```json
{
"jurisdiction_name": "Monroe",
"state_code": "WI",
"jurisdiction_type": "city",
"gov_domains": [
"cityofmonroewi.gov",
"pdmonroewi.gov",
"townofclarnowi.gov",
"townofjordanwi.gov",
"townofmonroewi.gov",
"townofsylvesterwi.gov"
],
"security_contact_email": "rjacobson@cityofmonroe.org",
"gsa_organization_name": "City of Monroe",
"gsa_domain_type": "City",
"website_url": "https://cityofmonroewi.gov"
}
```
### City with Security Contact
```json
{
"jurisdiction_name": "Beloit",
"state_code": "WI",
"jurisdiction_type": "city",
"gov_domains": [
"beloitwi.gov",
"newarkwi.gov",
"townofbeloitwi.gov",
"townofturtlewi.gov"
],
"security_contact_email": "adminnotification@beloitwi.gov",
"gsa_organization_name": "City of Beloit",
"gsa_domain_type": "City"
}
```
---
## Scripts
### Main Loading Script
**File:** `scripts/datasources/gsa/load_gsa_domains_to_postgres.py`
**Usage:**
```bash
# Load all states
python scripts/datasources/gsa/load_gsa_domains_to_postgres.py
# Load specific states
python scripts/datasources/gsa/load_gsa_domains_to_postgres.py --states AL,GA,MA,WA
# Dry run (preview without updating)
python scripts/datasources/gsa/load_gsa_domains_to_postgres.py --states AL,GA --dry-run
```
**Features:**
- ✅ Downloads latest GSA data from GitHub (cached for 24 hours)
- ✅ Normalizes jurisdiction names for matching
- ✅ Batch updates with ON CONFLICT handling
- ✅ Dry-run mode for testing
- ✅ Comprehensive statistics and logging
### Database Schema Updates
```sql
-- Add columns (idempotent)
ALTER TABLE jurisdictions_details_search
ADD COLUMN IF NOT EXISTS gov_domains JSONB DEFAULT '[]',
ADD COLUMN IF NOT EXISTS security_contact_email TEXT,
ADD COLUMN IF NOT EXISTS gsa_organization_name TEXT,
ADD COLUMN IF NOT EXISTS gsa_domain_type TEXT,
ADD COLUMN IF NOT EXISTS gsa_last_updated TIMESTAMP;
-- Create index for domain lookups
CREATE INDEX IF NOT EXISTS idx_jurisdiction_details_gov_domains
ON jurisdictions_details_search USING gin(gov_domains);
```
---
## Use Cases
### 1. Find Jurisdictions with Official Government Domains
```sql
SELECT
jurisdiction_name,
state_code,
gov_domains,
website_url
FROM jurisdictions_details_search
WHERE jsonb_array_length(gov_domains) > 0
ORDER BY state_code, jurisdiction_name;
```
### 2. Get Security Contact for a Jurisdiction
```sql
SELECT
jurisdiction_name,
security_contact_email,
gsa_organization_name
FROM jurisdictions_details_search
WHERE jurisdiction_name ILIKE '%boston%'
AND state_code = 'MA';
```
### 3. Find Jurisdictions with Multiple Domains
```sql
SELECT
jurisdiction_name,
state_code,
jsonb_array_length(gov_domains) as domain_count,
gov_domains
FROM jurisdictions_details_search
WHERE jsonb_array_length(gov_domains) > 3
ORDER BY domain_count DESC;
```
### 4. Validate Website URLs Against GSA Registry
```sql
SELECT
jurisdiction_name,
website_url,
gov_domains,
CASE
WHEN website_url IS NULL THEN 'No website'
WHEN gov_domains ? REPLACE(REPLACE(website_url, 'https://', ''), 'http://', '') THEN 'GSA verified'
ELSE 'Not in GSA registry'
END as verification_status
FROM jurisdictions_details_search
WHERE jsonb_array_length(gov_domains) > 0
LIMIT 20;
```
---
## Limitations
### Low County Coverage
- **Only 0.5% of counties** have registered .gov domains
- Most counties use state-operated websites (e.g., `county.state.gov`)
- Some counties have no dedicated web presence
### City Name Variations
Some jurisdictions may not match due to:
- Inconsistent naming (GSA: "City of X" vs Census: "X city")
- Merged jurisdictions (one domain, multiple census places)
- Special characters or apostrophes
### Domain Ownership
- GSA data shows **registered** domains, not necessarily **active** websites
- Some domains may redirect to other sites
- Multiple domains may point to the same website
---
## Maintenance
### Update Frequency
- GSA updates the domain list **continuously** as new domains are registered
- Recommend running enrichment load: **Monthly** or **Quarterly**
- Cache is valid for **24 hours** to avoid excessive downloads
### Re-running the Load
The load is **idempotent** and safe to re-run:
```bash
# Updates existing records with latest GSA data
python scripts/datasources/gsa/load_gsa_domains_to_postgres.py --states AL,GA,IN,MA,WA,WI
```
### Monitoring
Check for stale data:
```sql
SELECT
state_code,
COUNT(*) as jurisdictions_with_gsa,
MAX(gsa_last_updated) as most_recent_update,
MIN(gsa_last_updated) as oldest_update
FROM jurisdictions_details_search
WHERE gov_domains IS NOT NULL
GROUP BY state_code
ORDER BY state_code;
```
---
## Next Steps
### Potential Enhancements
1. **Expand to All 50 States**
```bash
python scripts/datasources/gsa/load_gsa_domains_to_postgres.py
# (no --states filter = all states)
```
2. **Domain Validation**
- Check if domains are actually active (HTTP status)
- Verify SSL certificates
- Update `website_url` with verified primary domain
3. **County Domain Discovery**
- Scrape state government portals for county websites
- Check for `<county-name>.<state>.gov` patterns
- Alternative sources: Wikipedia, county associations
4. **Integration with YouTube Discovery**
- Cross-reference .gov domains with YouTube channel URLs
- Identify official government channels
- Flag non-governmental channels
---
## References
- **GSA Domain Data:** https://github.com/cisagov/dotgov-data
- **GSA .gov Registry:** https://get.gov/
- **CISA Domain Security:** https://www.cisa.gov/topics/cyber-threats-and-advisories/federal-network-resilience/dotgov-program