πŸ“‹ Report Generation API

βœ“ API Online

Report Generation API

A comprehensive Flask-based API for generating Phase I Environmental Site Assessment reports with integrated QuickBooks Online support, BigQuery data integration, and advanced spatial analysis capabilities.

Table of Contents

Features

Technology Stack

Unified Tax Parcels Workflow

The API uses a unified format for tax parcels across all jurisdictions (NYC, NYS, Nassau County, NJ, FL, Broome County). This simplifies the workflow:

  1. Get Nearby Lots: Call GET /api/map/get_nearby_lots_by_address with an address
  2. Unified Response: API returns lots with TaxParcels and Jurisdiction fields in a consistent format
  3. Add Project: Pass selected TaxParcels and Jurisdiction to POST /api/project/add_project

Key Unified Fields: - TaxParcels: Standardized parcel identifier (format varies by jurisdiction) - Jurisdiction: One of "NYC", "NYS", "Nassau County", "NJ", "FL", "Broome County"

Example TaxParcels Formats by Jurisdiction: - NYC: "3012340056" (10-digit BBL) - NYS: "123.-4-56.7" (SBL) - Nassau: "456.-78-90" (SBL) - NJ: "12345678901234" (PAMS_PIN) - FL: "12-34-56-789-0123" (PARCELNO) - Broome: "123456" (CODE)

πŸ“– See UNIFIED_TAXPARCELS_WORKFLOW.md for complete workflow documentation and examples.

API Endpoints

Authentication & Health

Client Management (/api/client)

Project Management (/api/project)

Site Diagram API (Unified Architecture)

Overview: Site diagrams use a unified upsert pattern that intelligently manages subject property and adjacent property data: - Property_Boundary (and other layer columns) = Subject property geographic layers - adjacents array = Adjacent properties information - Automatically created during add_project, updated via set_adjacents or upsert_site_diagram

Core Endpoints: - POST /api/project/upsert_site_diagram - [NEW] Create or update site diagram from project data - Body: {"ACT_Project_Number": 11099} - Purpose: Unified endpoint that creates/updates site diagram using data from ACT_Projects table - Features: - Automatically fetches subject property Geography and BBL from ACT_Projects - Updates Property_Boundary with subject property geometry - Preserves existing adjacents if present - Handles versioning and streaming buffer issues - Can be called at any time to refresh site diagram from project data - Returns: {"success": true, "ACT_Project_Number": 11099, "version": 1, "subject_property": "updated", "adjacents_count": 0, "operation": "created"} - Use Cases: Manual site diagram creation, refresh after project updates, iOS app initialization

Unified Internal Function: upsert_site_diagram_with_data() - Used internally by add_project, set_adjacents, and upsert_site_diagram endpoints - Intelligently merges subject property data and/or adjacents data - Parameters: - subject_property_data: Dict with geo (WKT), borough, block, lot, owner, addresses - adjacents_data: List of dicts with adjacent property info - generated_by: Source identifier (e.g., 'project_creation', 'set_adjacents_api', 'upsert_api') - Behavior: - On add_project: Populates Property_Boundary from Geography, adjacents=NULL - On set_adjacents: Updates adjacents array, preserves Property_Boundary - On upsert_site_diagram: Updates subject property from ACT_Projects, preserves adjacents - Handles versioning automatically (increments version on updates) - Marks old versions as 'outdated', new version as 'active' - Works around BigQuery streaming buffer limitations

Legacy Site Diagram (Deprecated)

Report Compilation (/api/compile)

PDF Report Compilation with Advanced Processing

Advanced Photo Processing Features

Intelligent Appendix Management

Photo Generation (/api/report)

Step 1: Get Report Variables for Review - POST /api/report/get_report_variables - Fetch and map all template variables without creating report - Body: {"ACT_Project_Number": "string"} - Purpose: Allows users to review and edit variables before report generation - Returns: Complete field mappings, data sources, unmapped fields, and editable variable structure - Benefits: Data validation, manual override capability, blank field identification

Step 2: Create Report from Reviewed Variables - POST /api/report/create_report_from_variables - Generate report using reviewed/edited variables - Body: {"variables": {...}, "ACT_Project_Number": "string"} - Purpose: Creates final report from user-reviewed variable set - Features: Uses provided variables exactly as specified, maintains data integrity, supports all manual overrides

Legacy Single-Step Generation (Deprecated)

Document Generation & Downloads

QuickBooks Integration (/api/qb)

Map & Spatial Data (/api/map)

Aerial Maps Generation (/api/aerial)

Property Boundary & Project Data

Distance-Based Spatial Queries

Quick Start

Prerequisites

Environment Setup

  1. Clone and Install bash git clone <repository-url> cd Report_Generation python3 -m venv venv source venv/bin/activate # On Windows: venv\Scripts\activate pip install -r requirements.txt

  2. Configure Environment Variables bash export GCP_PROJECT_ID="your-gcp-project-id" export GOOGLE_CLIENT_ID="your-google-oauth-client-id" export QB_CLIENT_ID="your-quickbooks-client-id" export QB_CLIENT_SECRET="your-quickbooks-client-secret"

  3. Run Locally bash python app.py # Or using the Flask task chmod +x run_flask.sh ./run_flask.sh

Docker Deployment

# Build image
docker build -t report-generation-api .

# Run container
docker run -p 8080:8080 \
  -e GCP_PROJECT_ID="your-project-id" \
  -e GOOGLE_CLIENT_ID="your-client-id" \
  report-generation-api

Google Cloud Run Deployment

# Deploy to Cloud Run
gcloud run deploy report-generation-api \
  --source . \
  --platform managed \
  --region your-region \
  --allow-unauthenticated

API Usage Examples

Aerial Maps Generation

Generate Historical Aerial Maps for a Project

curl -X POST "https://your-api-url/api/aerial/generate_aerial_maps" \
  -H "Authorization: Bearer YOUR_GOOGLE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "ACT_Project_Number": "10991"
  }'

Response Example:

{
  "status": "success",
  "project_number": "10991",
  "maps_generated": 12,
  "location_type": "NYC",
  "years_processed": [
    "2024", "2022", "2020", "2018", "2016", 
    "2014", "2012", "2010", "2008", "2006",
    "2004", "2001-2"
  ],
  "skipped_years": ["1996", "1951", "1924"],
  "skipped_reason": "No image coverage available",
  "pdf_url": "gs://act_aerials/project_10991/aerials_10991_20251105_012212.pdf",
  "bigquery_inserted": true,
  "processing_details": {
    "zoom_level": 22,
    "tile_grid": "5x5",
    "image_size": "1280x1280 pixels",
    "pdf_page_size": "Letter (8.5x11 inches)",
    "total_tiles_fetched": 300,
    "blank_images_filtered": 15
  }
}

Features: - Automatically detects NYC vs non-NYC based on project BBL - Fetches up to 25 historical years (12-25 maps depending on coverage) - NYC: High-resolution tile service (zoom 22, 5Γ—5 grid) - Non-NYC: NYS Orthos WMS with blank image detection - Compiled multi-page PDF stored in GCS bucket act_aerials - BigQuery tracking in Phase_I_Report.Aerials table

Site Diagram API for iOS Apps (Two-Endpoint Architecture)

Step 1: Generate and Store Site Diagram with Elevation Data

curl -X POST "https://your-api-url/api/project/generate_site_diagram_with_elevation" \
  -H "Authorization: Bearer YOUR_GOOGLE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "ACT_Project_Number": "10918"
  }'

Response Example:

{
  "success": true,
  "bigquery_row_id": "10918_v1",
  "message": "Site diagram generated and stored successfully",
  "data_types_generated": [
    "property_boundary",
    "building_footprints", 
    "curb_lines",
    "elevation_raster"
  ]
}

Step 2: Retrieve Site Diagram for iOS App

curl -X GET "https://your-api-url/api/project/get_site_diagram?ACT_Project_Number=10918" \
  -H "Authorization: Bearer YOUR_GOOGLE_TOKEN"

Response Example (iOS-Ready Format):

{
  "ACT_Project_Number": "10918",
  "success": true,
  "status": "active",
  "version": 1,
  "created_at": "2025-09-12T23:50:23.850741+00:00",
  "generated_by": "api_request",
  "data_availability": {
    "property_boundary": true,
    "building_footprints": true,
    "curb_lines": true,
    "elevation_raster": true
  },
  "project_info": {
    "address": "5818 5th Ave, Brooklyn, NY",
    "bbl": [{"Borough": "BK", "Block": "855", "Lot": "44"}],
    "center_coordinates": {
      "latitude": 40.64038907013211,
      "longitude": -74.0156322709292
    }
  },
  "metadata": {
    "api_version": "2.0",
    "core_data_types": ["property_boundary", "building_footprints", "curb_lines", "elevation_raster"],
    "elevation_raster_status": "generated",
    "generation_method": "api_core_data_with_elevation_raster"
  },
  "site_diagram_data": {
    "property_boundary": {
      "type": "Polygon",
      "coordinates": [[[longitude, latitude], ...]]
    },
    "building_footprints": {
      "type": "Polygon", 
      "coordinates": [[[longitude, latitude], ...]]
    },
    "curb_lines": {
      "type": "MultiLineString",
      "coordinates": [[[longitude, latitude], ...]]
    },
    "elevation_raster": {
      "url": "https://storage.googleapis.com/act-phase-i-site-diagrams/elevation_rasters/project_10918_elevation_v20250912_195021.png",
      "bounds": [-74.015797, 40.640282, -74.015468, 40.640496],
      "width_pixels": 512,
      "height_pixels": 512,
      "resolution_meters": 0.07,
      "image_format": "PNG",
      "color_scheme": "topographic_gradient",
      "dem_source": "NYC DEM 2017 1-foot LiDAR",
      "processing_method": "nyc_dem_real_data_pil_color_coding",
      "elevation_stats": {
        "min": 99.09,
        "max": 122.44,
        "range": 23.35
      },
      "elevation_bands": [
        {
          "band_id": 1,
          "elevation_min": 99.1,
          "elevation_max": 101,
          "color_hex": "#000080",
          "pixel_count": 538
        },
        {
          "band_id": 2, 
          "elevation_min": 101,
          "elevation_max": 103,
          "color_hex": "#0040FF",
          "pixel_count": 889
        },
        // ... 10 more elevation bands with detailed color mapping
      ]
    }
  }
}

iOS Integration Benefits

PDF Report Compilation (Primary Workflow)

Compile Complete PDF Report with All Appendices

curl -X POST "https://your-api-url/api/compile/compile_report/123" \
  -H "Authorization: Bearer YOUR_GOOGLE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}'

Default Behavior (Recommended): - Word document template automatically found in Google Drive - Photos embedded directly in Word template (no separate appendix) - All historical research files (aerials) always included - All database search results always included - No compression applied for best quality - Smart duplicate filtering and file preferences

Custom Compilation Options (Compression Only)

curl -X POST "https://your-api-url/api/compile/compile_report/123" \
  -H "Authorization: Bearer YOUR_GOOGLE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "compress_pdf": true
  }'

Response Example

{
  "success": true,
  "pdf_url": "gs://compiled_reports/project_123/Phase_I_Report_123.pdf",
  "file_size_mb": 15.2,
  "pages": 45,
  "compilation_time_seconds": 67.5,
  "appendices_included": {
    "photos": "embedded_in_template",
    "aerials": true,
    "database_files": true
  }
}

Step 1: Get Report Variables for Review

curl -X POST "https://your-api-url/api/report/get_report_variables" \
  -H "Authorization: Bearer YOUR_GOOGLE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "ACT_Project_Number": "123"
  }'

Response includes: - Complete variable mappings for template - Data sources and coverage analysis
- Unmapped/blank fields identification - Field edit capabilities

Step 2: Create Report from Reviewed Variables

curl -X POST "https://your-api-url/api/report/create_report_from_variables" \
  -H "Authorization: Bearer YOUR_GOOGLE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "ACT_Project_Number": "123",
    "variables": {
      "PropertyAddress": "123 Main Street, Brooklyn, NY 11201",
      "ActProject": "123",
      "ClientContact": "John Doe",
      "Elevation": "Custom elevation description",
      "BuildingArea": "2,500 sq ft"
    }
  }'

Benefits of Two-Step Workflow: - Review all variables before report generation - Edit any field with custom values - Identify and fill blank/missing fields - Better data validation and quality control - Maintain audit trail of data sources

Legacy Single-Step Report Generation (Deprecated)

Create Phase I Report (Legacy Method)

curl -X POST "https://your-api-url/api/report/create_project_report" \
  -H "Authorization: Bearer YOUR_GOOGLE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "ACT_Project_Number": "123"
  }'

⚠️ Note: This legacy endpoint is deprecated. Use the two-step workflow above for better control and data validation.

Client and Project Management

Add a New Client

curl -X POST "https://your-api-url/api/client/add_client" \
  -H "Authorization: Bearer YOUR_GOOGLE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "ClientContact": "John Doe",
    "ClientCompany": "Example Corp",
    "ClientAdress": "123 Main St",
    "ClientEmail": "john@example.com",
    "ClientPhone": "555-1234",
    "ClientAddress": "123 Main St, New York, NY"
  }'

Add a New Project

curl -X POST "https://your-api-url/api/project/add_project" \
  -H "Authorization: Bearer YOUR_GOOGLE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "Address": "123 Main Street, New York, NY",
    "ClientKey": 1,
    "BBL": [
      {
        "Borough": "1",
        "Block": "123",
        "Lot": "45"
      }
    ],
    "TaxID": "12345",
    "Project_Type": "Phase I Environmental Site Assessment"
  }'

Set Adjacent Properties for a Project

curl -X POST "https://your-api-url/api/project/set_adjacents" \
  -H "Authorization: Bearer YOUR_GOOGLE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "ACT_Project_Number": 10890,
    "adjacents": [
      {
        "BBL": {
          "Borough": 4,
          "Block": 506,
          "Lot": 29
        },
        "adjacentNumber": 1,
        "geo": "POINT(-73.935242 40.730610)"
      },
      {
        "BBL": {
          "Borough": 4,
          "Block": 506,
          "Lot": 30
        },
        "adjacentNumber": 2,
        "geo": "POINT(-73.935300 40.730650)"
      }
    ]
  }'

Response Example:

{
  "status": "success",
  "message": "Adjacents updated successfully for project 10890",
  "adjacents_count": 2,
  "adjacents": [
    {
      "BBL": {"Borough": 4, "Block": 506, "Lot": 29},
      "bbl_string": "4005060029",
      "adjacentNumber": 1,
      "direction": "Southwest",
      "geo": "POINT(-73.935242 40.730610)"
    },
    {
      "BBL": {"Borough": 4, "Block": 506, "Lot": 30},
      "bbl_string": "4005060030",
      "adjacentNumber": 2,
      "direction": "South",
      "geo": "POINT(-73.935300 40.730650)"
    }
  ]
}

Features: - Automatic Direction Calculation: Determines cardinal direction from geography coordinates - BBL Validation: Validates Borough (1-5), Block (1-99999), Lot (1-9999) - 10-Digit BBL String: Automatically generates standardized BBL string - Upsert Operation: Replaces all existing adjacents for the project - ArcGIS Integration: Looks up property data from MAPPLUTO REST API

PDF Compilation with Cross-Project Files

curl -X POST "https://your-api-url/api/compile/compile_report/10890" \
  -H "Authorization: Bearer YOUR_GOOGLE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "compress_pdf": false,
    "files_project_number": "64"
  }'

Use Case: Compile report for project 10890 but use appendix files (aerials, sanborns, database files, city directories) from project 64. Useful for: - Shared sites across multiple projects - Multi-phase environmental assessments - Historical file reuse - Site boundary changes with same location

Response:

{
  "success": true,
  "pdf_url": "gs://compiled_reports/project_10890/Phase_I_Report_10890.pdf",
  "file_size_mb": 15.2,
  "pages": 45,
  "compilation_time_seconds": 67.5,
  "files_source": {
    "report_project": 10890,
    "files_project": 64,
    "cross_project_lookup": true
  },
  "appendices_included": {
    "photos": "embedded_in_template",
    "aerials": true,
    "database_files": true
  }
}

Generate City Directory Document

curl -X POST "https://your-api-url/api/report/generate_city_directory_document" \
  -H "Authorization: Bearer YOUR_GOOGLE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "act_project_number": "123",
    "adjacent_north": "North Property Description",
    "adjacent_south": "South Property Description", 
    "adjacent_east": "East Property Description",
    "adjacent_west": "West Property Description"
  }'

Get All Project Data

curl -X GET "https://your-api-url/api/report/get_all_project_data?ACT_Project_Number=123" \
  -H "Authorization: Bearer YOUR_GOOGLE_TOKEN"

Get Projects in GeoJSON Format (for Mapping)

# Get specific project with all spatial data
curl -X GET "https://your-api-url/api/map/get_projects_geojson?act_project_number=123" \
  -H "Authorization: Bearer YOUR_GOOGLE_TOKEN"

# Get all projects for mapping overview
curl -X GET "https://your-api-url/api/map/get_projects_geojson" \
  -H "Authorization: Bearer YOUR_GOOGLE_TOKEN"

Get Adjacent Properties for Mapping

curl -X GET "https://your-api-url/api/map/get_adjacents_geojson?act_project_number=123" \
  -H "Authorization: Bearer YOUR_GOOGLE_TOKEN"

Get Nearby Lots within Distance

# Get lots within 100 feet (default)
curl -X GET "https://your-api-url/api/map/get_nearby_lots_geojson?act_project_number=123" \
  -H "Authorization: Bearer YOUR_GOOGLE_TOKEN"

# Get lots within custom distance (e.g., 200 feet)
curl -X GET "https://your-api-url/api/map/get_nearby_lots_geojson?act_project_number=123&distance_feet=200" \
  -H "Authorization: Bearer YOUR_GOOGLE_TOKEN"

Response includes subject lot identification:

{
  "status": "success",
  "act_project_number": 123,
  "distance_feet": 200,
  "format": "geojson",
  "data_source": "NYC PLUTO",
  "geojson": {
    "type": "FeatureCollection",
    "features": [
      {
        "type": "Feature",
        "geometry": { "type": "Polygon", "coordinates": [...] },
        "properties": {
          "BBL": {"Borough": "3", "Block": 855, "Lot": 25},
          "bbl_string": "3008550025",
          "Address": "123 MAIN ST",
          "is_subject_lot": true,
          "distance_meters": 0,
          "distance_feet": 0,
          "OwnerName": "PROJECT OWNER LLC"
        }
      },
      {
        "type": "Feature",
        "properties": {
          "BBL": {"Borough": "3", "Block": 855, "Lot": 23},
          "bbl_string": "3008550023",
          "is_subject_lot": false,
          "distance_meters": 15.2,
          "distance_feet": 49.9
        }
      }
    ]
  }
}

Subject Lot Logic: - NYC Projects: Lot is marked is_subject_lot: true if its BBL matches any of the project's BBL(s) - Non-NYC Projects: Lot is marked is_subject_lot: true if its geometry intersects with the project's geography - Uses Geography field from ACT_Projects table (no address geocoding) - Correctly identifies project lot even when multiple lots are in search radius

# Find lots near an address (200 feet default)
curl -X GET "https://your-api-url/api/map/get_nearby_lots_by_address?address=123%20Main%20St%2C%20Brooklyn%2C%20NY%2011201" \
  -H "Authorization: Bearer YOUR_GOOGLE_TOKEN"

# Find lots near an address with custom distance (500 feet)
curl -X GET "https://your-api-url/api/map/get_nearby_lots_by_address?address=123%20Main%20St%2C%20Albany%2C%20NY%2012207&distance_feet=500" \
  -H "Authorization: Bearer YOUR_GOOGLE_TOKEN"

Response Format for get_nearby_lots_by_address

The endpoint returns different lot identification formats depending on location:

For NYC addresses:

{
  "status": "success",
  "query_address": "123 Montague Street, Brooklyn, NY 11201",
  "formatted_address": "123 Montague St, Brooklyn, NY 11201, USA",
  "geocoded_location": {"lat": 40.6935, "lon": -73.9932},
  "distance_feet": 200,
  "data_source": "NYC PLUTO",
  "is_nyc": true,
  "borough": "Brooklyn",
  "geojson": {
    "type": "FeatureCollection",
    "features": [
      {
        "type": "Feature",
        "geometry": { "type": "Polygon", "coordinates": [...] },
        "properties": {
          // UNIFIED FIELDS (use these for add_project)
          "TaxParcels": "3002650001",
          "Jurisdiction": "NYC",

          // Legacy fields (still included)
          "BBL": {
            "Borough": "3",
            "Block": 265,
            "Lot": 1
          },
          "bbl_string": "3002650001",
          "address": "123 MONTAGUE ST",
          "owner_name": "EXAMPLE OWNER LLC",
          "building_class": "D1",
          "lot_area": 5000,
          "building_area": 15000,
          "num_floors": 5,
          "year_built": 1920,
          "zone_district": "R6",
          "distance_meters": 45.2,
          "distance_feet": 148.3,
          "is_subject_lot": true,
          "has_geometry": true,
          "data_source": "NYC_PLUTO",
          "is_nyc": true,
          "borough": "Brooklyn"
        }
      }
    ],
    "properties": {
      "query_address": "123 Montague Street, Brooklyn, NY 11201",
      "formatted_address": "123 Montague St, Brooklyn, NY 11201, USA",
      "geocoded_location": {"lat": 40.6935, "lon": -73.9932},
      "search_distance_feet": 200,
      "search_distance_meters": 60.96,
      "total_nearby_lots": 12,
      "data_source": "NYC PLUTO",
      "is_nyc": true,
      "borough": "Brooklyn"
    }
  }
}

For non-NYC addresses:

{
  "status": "success",
  "query_address": "100 State St, Albany, NY 12207",
  "formatted_address": "100 State St, Albany, NY 12207, USA",
  "geocoded_location": {"lat": 42.6526, "lon": -73.7562},
  "distance_feet": 200,
  "data_source": "NYS Tax Parcels",
  "is_nyc": false,
  "borough": null,
  "geojson": {
    "type": "FeatureCollection",
    "features": [
      {
        "type": "Feature",
        "geometry": { "type": "Polygon", "coordinates": [...] },
        "properties": {
          // UNIFIED FIELDS (use these for add_project)
          "TaxParcels": "65.2-1-35",
          "Jurisdiction": "NYS",

          // Legacy fields (still included)
          "lot_number": "65.2-1-35",
          "SBL": "65.2-1-35",
          "SWIS": "010100",
          "address": "100 STATE ST",
          "street_number": "100",
          "street_name": "STATE ST",
          "city": "ALBANY",
          "county": "ALBANY",
          "zip": "12207",
          "owner": "STATE OF NEW YORK",
          "property_class": "612",
          "land_value": 500000,
          "total_value": 5000000,
          "acreage": 0.5,
          "distance_meters": 35.8,
          "distance_feet": 117.5,
          "is_subject_lot": true,
          "has_geometry": true,
          "data_source": "NYS_Tax_Parcels",
          "is_nyc": false
        }
      }
    ],
    "properties": {
      "query_address": "100 State St, Albany, NY 12207",
      "formatted_address": "100 State St, Albany, NY 12207, USA",
      "geocoded_location": {"lat": 42.6526, "lon": -73.7562},
      "search_distance_feet": 200,
      "search_distance_meters": 60.96,
      "total_nearby_lots": 8,
      "data_source": "NYS Tax Parcels",
      "is_nyc": false
    }
  }
}

Key Differences: - All jurisdictions now include TaxParcels and Jurisdiction fields (unified format) - NYC lots use BBL (Borough-Block-Lot) in legacy format - Non-NYC lots use lot_number (SBL - Section-Block-Lot) in legacy format - Different property attributes are available depending on the data source

✨ Recommended Workflow: 1. Call get_nearby_lots_by_address to get nearby lots 2. Extract TaxParcels and Jurisdiction from the response 3. Pass these values to add_project endpoint

πŸ“– See UNIFIED_TAXPARCELS_WORKFLOW.md for complete examples.

Inspection Management

# Schedule a new inspection
curl -X POST "https://your-api-url/api/project/schedule_inspection" \
  -H "Authorization: Bearer YOUR_GOOGLE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "ACT_Project_Number": "123",
    "assignee_emails": ["inspector@company.com"],
    "date": "2025-08-01T10:00:00",
    "site_contact_name": "John Doe",
    "site_contact_email": "john@example.com",
    "description": "Phase I environmental inspection"
  }'

# Update an existing inspection
curl -X PUT "https://your-api-url/api/project/update_inspection" \
  -H "Authorization: Bearer YOUR_GOOGLE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "event_id": "abc123def456",
    "date": "2025-08-01T14:00:00",
    "assignee_emails": ["newinspector@company.com"],
    "description": "Updated inspection time"
  }'

# Delete an inspection
curl -X DELETE "https://your-api-url/api/project/delete_inspection" \
  -H "Authorization: Bearer YOUR_GOOGLE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "event_id": "abc123def456",
    "send_cancellation": true
  }'

Get Adjacent Properties for Mapping

curl -X GET "https://your-api-url/api/map/get_adjacents_geojson?act_project_number=123" \
  -H "Authorization: Bearer YOUR_GOOGLE_TOKEN"

QuickBooks Integration

# Get QB authorization URL
curl -X GET "https://your-api-url/api/qb/qb_auth_url" \
  -H "Authorization: Bearer YOUR_GOOGLE_TOKEN"

# Get QB customers
curl -X GET "https://your-api-url/api/qb/qb_customers" \
  -H "Authorization: Bearer YOUR_GOOGLE_TOKEN"

# Create QB customer
curl -X POST "https://your-api-url/api/qb/qb_create_customer" \
  -H "Authorization: Bearer YOUR_GOOGLE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "Name": "Example Customer",
    "CompanyName": "Example Corp",
    "BillAddr": {
      "Line1": "123 Main St",
      "City": "New York",
      "CountrySubDivisionCode": "NY",
      "PostalCode": "10001"
    },
    "PrimaryEmailAddr": {
      "Address": "customer@example.com"
    },
    "PrimaryPhone": {
      "FreeFormNumber": "555-1234"
    }
  }'

Map API Best Practices & Common Use Cases

Choosing the Right Endpoint

Use get_nearby_lots_geojson when: - You have an existing ACT project number - You need to find lots around a project site - You want to exclude the project's own lots from results - You're doing environmental radius searches for Phase I reports - Default distance is 500 feet (customizable)

Use get_nearby_lots_by_address when: - You need to research a location without creating a project - You're doing preliminary site analysis - You want to find properties near any address (NYC or non-NYC) - You're scouting for new business opportunities - Default distance is 200 feet (customizable)

Use get_adjacents_geojson when: - You only need properties that share a boundary with the project - You're compiling adjacent property lists for reports - You need directional information (North, South, East, West)

Distance Recommendations

Use Case Recommended Distance Endpoint
Adjacent properties only Use adjacents endpoint get_adjacents_geojson
Immediate neighbors 100-200 feet get_nearby_lots_by_address
Phase I environmental (standard) 200-500 feet Either nearby endpoint
Neighborhood analysis 500-1000 feet Either nearby endpoint
Market research 1000-2000 feet Either nearby endpoint

NYC vs Non-NYC Data Differences

NYC Properties (PLUTO Data)

Non-NYC Properties (NYS Tax Parcels)

Example Workflows

Workflow 1: Complete Site Research from Address

# Step 1: Find nearby lots from address
curl -X GET "https://app.act.earth/api/map/get_nearby_lots_by_address?address=123%20Main%20St%2C%20Brooklyn%2C%20NY&distance_feet=300" \
  -H "Authorization: Bearer $TOKEN"

# Step 2: Identify subject property BBL from results
# Step 3: Create project with that BBL
# Step 4: Get detailed adjacent properties
curl -X GET "https://app.act.earth/api/map/get_adjacents_geojson?act_project_number=12345" \
  -H "Authorization: Bearer $TOKEN"
# For existing project - use project-based search
curl -X GET "https://app.act.earth/api/map/get_nearby_lots_geojson?act_project_number=12345&distance_feet=500" \
  -H "Authorization: Bearer $TOKEN"

# Returns lots within 500 feet, excluding project's own parcels
# Perfect for "within one-eighth mile" environmental requirements

Workflow 3: Market Comparison Analysis

# Find comparable properties in area
curl -X GET "https://app.act.earth/api/map/get_nearby_lots_by_address?address=350%205th%20Ave%2C%20NY&distance_feet=1000" \
  -H "Authorization: Bearer $TOKEN"

# Analyze returned properties for:
# - Similar building classes
# - Comparable lot sizes
# - Same zoning districts
# - Recent development activity

Integration with Mapping Libraries

Leaflet.js Example

// Fetch and display nearby lots on map
async function displayNearbyLots(address, distanceFeet = 200) {
    const response = await fetch(
        `https://app.act.earth/api/map/get_nearby_lots_by_address?` +
        `address=${encodeURIComponent(address)}&distance_feet=${distanceFeet}`,
        { headers: { 'Authorization': `Bearer ${token}` } }
    );

    const data = await response.json();

    // Add GeoJSON to map
    L.geoJSON(data.geojson, {
        style: feature => ({
            color: feature.properties.is_nyc ? '#0000ff' : '#ff0000',
            weight: 2,
            fillOpacity: 0.3
        }),
        onEachFeature: (feature, layer) => {
            const props = feature.properties;
            const id = props.is_nyc ? props.bbl_string : props.lot_number;
            layer.bindPopup(`
                <strong>${id}</strong><br>
                ${props.address}<br>
                Distance: ${props.distance_feet} ft
            `);
        }
    }).addTo(map);

    // Center map on geocoded location
    map.setView([data.geocoded_location.lat, data.geocoded_location.lon], 17);
}

Mapbox GL JS Example

// Add nearby lots as a layer
map.on('load', async () => {
    const response = await fetch(
        'https://app.act.earth/api/map/get_nearby_lots_by_address?' +
        'address=Times Square, NY&distance_feet=300',
        { headers: { 'Authorization': `Bearer ${token}` } }
    );
    const data = await response.json();

    map.addSource('nearby-lots', {
        type: 'geojson',
        data: data.geojson
    });

    map.addLayer({
        id: 'lots-fill',
        type: 'fill',
        source: 'nearby-lots',
        paint: {
            'fill-color': [
                'case',
                ['get', 'is_nyc'], '#4264fb',
                '#42b983'
            ],
            'fill-opacity': 0.4
        }
    });

    map.addLayer({
        id: 'lots-outline',
        type: 'line',
        source: 'nearby-lots',
        paint: {
            'line-color': '#000',
            'line-width': 1
        }
    });
});

Performance Tips

  1. Use Appropriate Distance: Larger distances return more results and take longer
  2. Keep under 1000 feet for best response times
  3. For large areas, consider multiple smaller queries

  4. Cache Geocoding Results: The address geocoding step adds latency

  5. Cache frequently used addresses
  6. Store geocoded coordinates for repeated searches

  7. Filter Results Client-Side:

  8. Filter by property type, zoning, or other attributes after receiving data
  9. More efficient than making multiple API calls

  10. Batch Related Queries:

  11. If you need both adjacent and nearby lots, fetch in parallel
  12. Combine results on client side

Error Handling Best Practices

async function safelyFetchNearbyLots(address, distanceFeet = 200) {
    try {
        const response = await fetch(
            `https://app.act.earth/api/map/get_nearby_lots_by_address?` +
            `address=${encodeURIComponent(address)}&distance_feet=${distanceFeet}`,
            { 
                headers: { 'Authorization': `Bearer ${token}` },
                timeout: 30000  // 30 second timeout
            }
        );

        if (!response.ok) {
            const error = await response.json();
            if (response.status === 404) {
                console.error('Address not found:', error.error);
                // Show user-friendly "address not found" message
                return null;
            } else if (response.status === 400) {
                console.error('Invalid parameters:', error.error);
                // Validate input and retry
                return null;
            } else {
                throw new Error(error.error);
            }
        }

        const data = await response.json();

        if (data.geojson.properties.total_nearby_lots === 0) {
            console.warn('No lots found within distance');
            // Suggest increasing search radius
        }

        return data;

    } catch (error) {
        console.error('Failed to fetch nearby lots:', error);
        // Implement retry logic or fallback
        return null;
    }
}

Authentication

All endpoints except /health require Google OAuth 2.0 authentication. Include the bearer token in the Authorization header:

Authorization: Bearer YOUR_GOOGLE_OAUTH_TOKEN

Required OAuth Scopes

Your Google OAuth token must include the following scopes for full API functionality:

BigQuery Access (Required for all data endpoints): - https://www.googleapis.com/auth/bigquery.readonly - Read-only access to BigQuery - https://www.googleapis.com/auth/bigquery - Full BigQuery access (recommended)

Google Cloud Platform (Recommended): - https://www.googleapis.com/auth/cloud-platform - Full Google Cloud access

Google Calendar (Required for inspection management): - https://www.googleapis.com/auth/calendar - Calendar access for scheduling inspections

Required IAM Permissions

Your Google account must have these IAM roles in the act-phase-i project: - BigQuery Data Viewer - Read access to BigQuery datasets - BigQuery Job User - Permission to run BigQuery queries - BigQuery User - Recommended for full functionality

Token Validation

Test your token before making API calls:

curl "https://oauth2.googleapis.com/tokeninfo?access_token=YOUR_TOKEN"

Data Mapping & Field Enhancements

Building Data Integration

The API now includes comprehensive building data mapping from multiple sources:

Location Field Improvements

Enhanced location data formatting provides user-friendly field population:

{
  "Borough": "Brooklyn",                    // Full name vs "BK"
  "PortionOfBorough": "Boerum Hill",       // Specific neighborhood vs generic text
  "CrossStreets": "Court St & State St"    // Real streets vs "Street 1"
}

Field Mapping Coverage

Complete template field mapping includes:

Spatial Data & GeoJSON Features

Enhanced mapping capabilities provide comprehensive spatial analysis:

{
  "geojson": {
    "type": "FeatureCollection",
    "features": [
      {
        "type": "Feature",
        "properties": {
          "BBL": {"Borough": "1", "Block": "123", "Lot": "45"},
          "bbl_string": "1001230045",
          "Address": "123 Main St",
          "distance_feet": 75.5,
          "has_geometry": true
        },
        "geometry": {...}
      }
    ]
  }
}

Spatial Analysis Features: - Adjacent Properties: Direct property boundary neighbors with BBL and ownership data - Proximity Search: Customizable distance-based lot searches (default 100 feet, configurable) - PLUTO Integration: Complete NYC property database with building characteristics - WKT Conversion: BigQuery GEOGRAPHY fields converted to client-parseable formats - Distance Calculations: Precise measurements in both feet and meters

Database Schema

NYC Property Data Sources

ArcGIS PLUTO REST API (Primary for Property Data)

The API now uses the ArcGIS REST Feature Service for NYC PLUTO data instead of BigQuery PLUTOSHAPE:

Parcel Identifiers (Multi-Jurisdiction)

The API supports jurisdiction-specific parcel identifiers stored in the TaxID field:

NYC - BBL (Borough-Block-Lot)

10-Digit String Format (Preferred): - Structure: BBBBBLLLL - Position 0: Borough code (1=Manhattan, 2=Bronx, 3=Brooklyn, 4=Queens, 5=Staten Island) - Positions 1-5: Tax Block (5 digits, zero-padded) - Positions 6-9: Tax Lot (4 digits, zero-padded) - Examples: - "1008470040" = Manhattan (1), Block 847, Lot 40 - "4005060029" = Queens (4), Block 506, Lot 29 - "3012345678" = Brooklyn (3), Block 12345, Lot 678 - API Input: {"BBL": "1008470040"} - Data Source: NYC MAPPLUTO

Legacy Object Format (Still Supported):

{
  "BBL": [{"Borough": "MN", "Block": "847", "Lot": "40"}]
}
NYS - SBL (Section-Block-Lot)
Nassau County, NY - SBL
New Jersey - PAMS_PIN
Florida - PARCELNO

Validation Rules: - NYC Borough: 1-5 only - NYC Block: 1-99999 - NYC Lot: 1-9999 - Other jurisdictions: Validated against respective APIs

Database Schema

The API integrates with these BigQuery tables:

act-phase-i.Phase_I_Report.ACT_Projects

act-phase-i.Phase_I_Report.adjacents

act-phase-i.Phase_I_Report.adjacents

act-phase-i.Phase_I_Report.Client_List

act-phase-i.Phase_I_Report.City_Directories

act-phase-i.Phase_I_Report.Database_Files

act-phase-i.Phase_I_Report.Sanborns

act-phase-i.Phase_I_Report.PLUTOSHAPE

Response Formats

Success Response

{
  "status": "success",
  "message": "Operation completed successfully",
  "data": { ... },
  "total_count": 10
}

Error Response

{
  "error": "Error description"
}

File Structure

Report_Generation/
β”œβ”€β”€ app.py                    # Main Flask application with blueprint registration
β”œβ”€β”€ auth.py                   # Google OAuth authentication middleware
β”œβ”€β”€ config.py                 # Configuration settings and environment variables
β”œβ”€β”€ client_routes.py          # Client management endpoints
β”œβ”€β”€ project_routes.py         # Project management endpoints  
β”œβ”€β”€ report_routes.py          # Report generation endpoints
β”œβ”€β”€ quickbooks_routes.py      # QuickBooks integration endpoints
β”œβ”€β”€ map_routes.py             # Map layers and spatial data API endpoints (GeoJSON, adjacents, nearby lots)
β”œβ”€β”€ city_directory.py         # City directory document generation
β”œβ”€β”€ quickbooks_utils.py       # QuickBooks API utilities
β”œβ”€β”€ requirements.txt          # Python dependencies (29 packages)
β”œβ”€β”€ Dockerfile               # Container configuration (Python 3.11)
β”œβ”€β”€ run_flask.sh             # Development server script
β”œβ”€β”€ README.md               # This documentation
└── postman/                # API testing collections
    β”œβ”€β”€ Report_Generation_API_Complete_Updated.postman_collection.json
    β”œβ”€β”€ Report_Generation_API_QuickBooks.postman_collection.json
    └── examples/

Development

VS Code Tasks Available

Testing with Postman

Import the included Postman collections for comprehensive API testing: - Report_Generation_API_Complete_Updated.postman_collection.json - Complete API collection with enhanced field mapping features (v2.3.0) - Report_Generation_API_QuickBooks.postman_collection.json - QuickBooks-focused collection - Environment variables: Set base_url, access_token, act_project_number, and client_key

Enhanced Field Mapping Testing: Test the new field mapping improvements by creating reports for projects with complete data: - Building dimensions will be populated from PLUTO data - Borough names will show as full names (Brooklyn, Manhattan) instead of abbreviations - Specific neighborhoods will be shown instead of generic text - Cross streets will display actual street names from Google Maps geocoding - Construction materials will be pulled from form submission data

Recent Updates

Version 4.2.0 (Current) - November 2025

Version 4.1.0 - November 2025

Version 4.0.0 - November 2025

Version 2.7.0

Version 2.6.0

Version 2.5.0

Version 2.4.0

Version 2.3.1

Version 2.3.0

Version 2.2.0

Version 2.1.0

Version 2.0.0

Support

For issues or questions: 1. Check the API documentation in this README 2. Review Postman collection examples 3. Verify authentication tokens are valid 4. Ensure BigQuery permissions are configured

Common Issues & Troubleshooting

403 Forbidden Error on Map Endpoints

If you receive a 403 Forbidden error on /api/map/get_projects_geojson or other map endpoints:

1. Token Issues:

# Test your token validity
curl "https://oauth2.googleapis.com/tokeninfo?access_token=YOUR_TOKEN"

2. Required OAuth Scopes: Your Google OAuth token must include these scopes: - https://www.googleapis.com/auth/bigquery.readonly (minimum) - https://www.googleapis.com/auth/bigquery (full access) - https://www.googleapis.com/auth/cloud-platform (recommended)

3. BigQuery Permissions: Ensure your Google account has these IAM roles in the act-phase-i project: - BigQuery Data Viewer (minimum for read operations) - BigQuery Job User (required to run queries) - BigQuery User (recommended)

4. Debug Token Information:

# Check token scopes and validity
curl -H "Authorization: Bearer YOUR_TOKEN" \
  "https://your-api-url/auth_test"

5. Project Access: Verify you have access to the act-phase-i BigQuery project and the following datasets: - act-phase-i.Phase_I_Report.ACT_Projects - act-phase-i.Phase_I_Report.adjacents - act-phase-i.Phase_I_Report.PLUTOSHAPE

BigQuery STRUCT Type Mismatch (RESOLVED in v2.3.1)

Issue: "Invalid value for type: STRUCT is not a valid value"

Root Cause: Parameter data types didn't match table schema types in spatial queries.

Resolution Applied: - Updated STRUCT parameters to match PLUTOSHAPE table schema exactly - Borough: STRING, Block: INT64, Lot: INT64 (not all strings) - Proper data type conversion in parameter preparation - Eliminated CAST operations in SQL queries for direct type matching

Prevention: This issue has been permanently resolved through proper schema alignment. All spatial proximity queries now execute successfully.

Authentication Best Practices

  1. Use Fresh Tokens: OAuth access tokens typically expire after 1 hour
  2. Include Required Scopes: When requesting OAuth tokens, include BigQuery scopes
  3. Test with curl: Verify endpoints work with curl before using in applications
  4. Check Project Permissions: Ensure your Google account has BigQuery access to the act-phase-i project

License

[Your License Here]