Permit Signal Scout avatar

Permit Signal Scout

Pricing

from $2.00 / 1,000 results

Go to Apify Store
Permit Signal Scout

Permit Signal Scout

Normalize and rank public permit records into construction opportunity signals.

Pricing

from $2.00 / 1,000 results

Rating

0.0

(0)

Developer

Carey Brown

Carey Brown

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

2 days ago

Last modified

Share

Permit Signal Scout turns selected official public building permit datasets into normalized, ranked construction opportunity signals.

It reads public Socrata open-data endpoints, normalizes source-specific permit records into one schema, scores each record with deterministic rules, and reports source health so users can see whether the data feed behaved as expected.

What It Does

  • Fetches recent public construction permit records from selected official city open-data sources.
  • Normalizes permit IDs, issue dates, permit types, descriptions, addresses, contractor/applicant/company names when available, estimated values, and statuses.
  • Classifies obvious trade signals such as roofing, HVAC, solar, plumbing, electrical, remodeling, ADU, multifamily, commercial, restoration, and general construction.
  • Scores records by freshness, estimated value, trade fit, and completeness.
  • Returns ranked permit opportunity signals with score reasons and risk flags.
  • Produces a run summary with per-source health.

Who It Is For

  • Contractors watching for nearby project activity.
  • Construction suppliers monitoring demand signals.
  • Real estate investors tracking permit activity.
  • Agencies building research-backed prospect lists manually.
  • Data buyers who need normalized permit feeds from selected cities.

Supported Sources / Cities

city idcitysourcedataset
austin_txAustin, TXAustin Open Data3syk-w9eu
chicago_ilChicago, ILChicago Data Portalydr8-5enu
nyc_nyNew York City, NYNYC Open Data DOB NOWrbx6-tga4
cincinnati_ohCincinnati, OHCincinnati Open Datauhjb-xac9
san_francisco_caSan Francisco, CADataSFi98e-djp9

What It Does Not Do

  • It does not find emails.
  • It does not verify owners or decision makers.
  • It does not guarantee contractor identity.
  • It does not send outreach.
  • It does not create ready-to-email lists.
  • It does not provide legal, compliance, or business advice.
  • It does not currently cover every city.
  • It does not use AI scoring in the MVP.
  • It does not perform contact enrichment, skip tracing, CRM sync, code-violation enrichment, or paid API calls.

Permit Signal Scout provides permit opportunity signals, not verified sales leads.

Example Use Cases

  • Find recent high-value roofing permits across the supported cities.
  • Monitor HVAC/mechanical permit activity in Austin and Chicago.
  • Compare recent permit volume and source health across all supported sources.
  • Pull raw normalized records for debugging a city adapter.
  • Build an internal research workflow around public permit activity.

Input Fields

fieldtypedefaultdescription
citiesstring arrayall supported citiesSource city IDs to query.
issuedAfterISO date stringtoday minus 30 daysInclude permits issued on or after this date.
issuedBeforeISO date string or nullnullOptional upper issued-date bound.
tradeKeywordsstring arraydefault trade termsUser keywords considered during trade matching.
minEstimatedValuenumber or nullnullOptional minimum estimated permit value. Records with null values are excluded when this is set.
maxResultsPerCityinteger500Maximum records fetched per city, 1-5000.
includeAiScoringbooleanfalseReserved for later. MVP uses deterministic scoring and emits a warning if true.
outputModestringrankedAccepted values: ranked, byCity, rawNormalized.
includeRawRecordbooleanfalseInclude original Socrata row on each output record.
sortBystringleadQualityScoreAccepted values: leadQualityScore, issuedDate, estimatedValue, sourceCity.
leadGradeFilterstring array["A","B","C","D"]Return only selected lead grades after scoring.

Example Input

{
"cities": ["austin_tx", "chicago_il", "nyc_ny"],
"issuedAfter": "2026-05-01",
"issuedBefore": null,
"tradeKeywords": ["roof", "roofing", "reroof", "membrane"],
"minEstimatedValue": 25000,
"maxResultsPerCity": 250,
"includeAiScoring": false,
"outputMode": "ranked",
"includeRawRecord": false,
"sortBy": "leadQualityScore",
"leadGradeFilter": ["A", "B"]
}

More examples are in examples/.

Output Fields

Each output record follows this normalized shape:

fielddescription
permitIdPermit identifier from the source, or blank if missing.
sourceCitySource city ID.
sourceSystemAlways Socrata for the MVP.
sourceDatasetSocrata dataset ID.
sourceUrlSource record link when available or safely generated, otherwise null.
issuedDateNormalized ISO date or null.
permitTypeNormalized permit/work type text.
descriptionSource work description.
addressNormalized address text when available.
contractorNameContractor/applicant/company field when available and appropriate.
estimatedValueParsed estimated value or null.
statusSource permit status.
matchedTradesMatched trade categories or user keyword matches.
freshnessScore0-100 issued-date freshness score.
valueScore0-100 estimated-value score.
tradeFitScore0-100 trade relevance score.
completenessScore0-100 record completeness score.
leadQualityScoreWeighted opportunity score rounded to one decimal.
leadGradeA, B, C, or D.
scoreReasonsConcise explanation of why the record scored as it did.
riskFlagsMissing/ambiguous/stale-data flags.
rawRecordOptional original source row when includeRawRecord is true.

Example Output

{
"permitId": "SAMPLE-2026-001",
"sourceCity": "austin_tx",
"sourceSystem": "Socrata",
"sourceDataset": "3syk-w9eu",
"sourceUrl": "https://example.test/public-permit/SAMPLE-2026-001",
"issuedDate": "2026-05-20",
"permitType": "Building Permit / Roofing",
"description": "Roof replacement and related exterior repair",
"address": "123 SAMPLE ST",
"contractorName": "SAMPLE ROOFING LLC",
"estimatedValue": 45000.0,
"status": "Issued",
"matchedTrades": ["roofing"],
"freshnessScore": 100,
"valueScore": 75,
"tradeFitScore": 100,
"completenessScore": 100,
"leadQualityScore": 93.8,
"leadGrade": "A",
"scoreReasons": [
"recent permit",
"solid estimated value",
"matched roofing trade",
"complete record",
"contractor/applicant present"
],
"riskFlags": []
}

This is a synthetic example. It shows the output shape, not a verified sales lead.

Run Summary

The run summary reports:

  • totalRecordsFetched
  • totalRecordsReturned
  • recordsByCity
  • averageLeadQualityScore
  • topMatchedTrades
  • sourceHealth
  • warnings

examples/run_summary_sample.json shows the expected shape.

Scoring Model

The MVP uses deterministic scoring only:

leadQualityScore =
freshnessScore * 0.30 +
valueScore * 0.25 +
tradeFitScore * 0.25 +
completenessScore * 0.20

leadQualityScore is rounded to one decimal.

Freshness score:

  • 0-7 days old: 100
  • 8-30 days old: 85
  • 31-90 days old: 65
  • 91-180 days old: 40
  • 181+ days old: 15
  • missing date: 0
  • future date: 50 and flagged as stale_record

Value score:

  • null: 35
  • under $5,000: 25
  • $5,000-$24,999.99: 50
  • $25,000-$99,999.99: 75
  • $100,000-$500,000: 90
  • over $500,000: 100

Trade fit score:

  • exact category keyword in permit type: 100
  • exact category keyword in description: 85
  • related keyword signal in description: 65
  • generic construction/remodel signal: 45
  • no match: 10

Completeness score:

  • +20 permit ID
  • +20 issued date
  • +20 address
  • +15 description
  • +15 permit type or status
  • +10 contractor/applicant/company

Lead Grades

gradescore range
A80-100
B65-79.999
C45-64.999
D0-44.999

Grades are opportunity-signal grades, not buyer-intent guarantees.

Source Health

Each selected source reports:

  • status: healthy, empty, degraded, or failed
  • recordsFetched
  • recordsReturned
  • latestIssuedDate
  • warnings
  • errors

Status meanings:

  • healthy: fetch and pipeline succeeded, or the source produced a valid result.
  • empty: fetch succeeded but no records matched.
  • degraded: partial failure, malformed rows skipped, or warnings crossed a threshold.
  • failed: source fetch failed completely or all fetched rows failed normalization.

Warnings use stable codes such as:

  • source_returned_zero_rows
  • source_fetch_failed
  • records_skipped_missing_date
  • records_skipped_min_estimated_value
  • malformed_records_skipped
  • includeAiScoring_requested_disabled_mvp
  • source_latest_date_stale
  • maxResultsPerCity_capped_results

Limitations

  • Coverage is limited to five supported public datasets.
  • Public data can lag, change schema, or include incomplete fields.
  • Some sources do not provide contractor fields.
  • Some contacts may be applicants, owners, or permit representatives, not sales contacts.
  • Estimated values may be missing, rounded, entered inconsistently, or source-specific.
  • Trade matching is deterministic keyword matching, not legal or professional classification.
  • Source URLs are not available for every dataset.

Ethical / Public-Data Boundaries

Use this Actor for lawful public-data analysis, market research, and operational planning.

Do not use it for spam, harassment, deceptive outreach, unlawful profiling, or automated contact enrichment. If you use public permit data for sales research, you are responsible for compliance with applicable laws, platform rules, and local norms.

Troubleshooting

issuelikely causewhat to check
No records returnedDate/value/grade filters too narrowLower minEstimatedValue, widen dates, include more grades
Source is emptyFetch worked but filters matched nothingCheck issuedAfter, issuedBefore, and source health
Source is degradedPartial failure or malformed rowsInspect sourceHealth[city].warnings
Source is failedEndpoint failure or all rows failed normalizationInspect sourceHealth[city].errors
includeAiScoring warningAI scoring is disabled in MVPLeave includeAiScoring as false
Missing contractor fieldsSource may not provide reliable contractor dataTreat output as permit intelligence, not verified leads

Local Development / Test Commands

Run the unit test suite:

$.venv/bin/pytest -q -s actors/permit-signal-scout/test/test_schema.py actors/permit-signal-scout/test/test_socrata_client.py actors/permit-signal-scout/test/test_source_adapters.py actors/permit-signal-scout/test/test_filters_dedupe.py actors/permit-signal-scout/test/test_scoring.py actors/permit-signal-scout/test/test_main_pipeline.py actors/permit-signal-scout/test/test_source_health.py actors/permit-signal-scout/test/test_examples.py actors/permit-signal-scout/test/test_local_actor_packaging.py

Run a tiny read-only endpoint smoke test:

$.venv/bin/python actors/permit-signal-scout/scripts/smoke_fetch_limit1.py

Run a small read-only local sample:

$.venv/bin/python actors/permit-signal-scout/scripts/run_local_sample.py

Run launch readiness:

$.venv/bin/python actors/permit-signal-scout/scripts/launch_readiness_check.py

Run local Actor-style storage output without publishing:

$.venv/bin/python actors/permit-signal-scout/scripts/run_actor_local.py --input actors/permit-signal-scout/examples/input_local_smoke.json --storage-dir actors/permit-signal-scout/.local-storage

This writes records to datasets/default/*.json and the run summary to key_value_stores/default/RUN_SUMMARY.json under the selected storage directory.

Launch Readiness

The launch readiness check fetches a small sample from the five supported public Socrata sources, runs the full local pipeline, prints source health, prints the top five records, and returns a verdict:

  • PASS: all selected sources are healthy or empty with no critical errors.
  • WARN: one or more sources degraded/failed but the pipeline still returned records.
  • FAIL: all sources failed or no useful pipeline result could be produced.

Next Step

PS-011 adds local Apify-compatible packaging. The next step is a private Apify test run after explicitly confirming the packaging and local storage output.