Monday, September 15, 2025

Integrating UK Government EPC Data for Automated Compliance

Paul (Founder)
Development

Every UK rental property must have a valid Energy Performance Certificate (EPC) rating E or above. Properties with F or G ratings can't be legally let, and certificates expire after ten years. For letting agencies managing hundreds of properties, tracking EPC status manually creates compliance risk: expired certificates go unnoticed, rating requirements aren't verified, and portal listings lack mandatory energy efficiency information.

Week 38 integrated LetAdmin directly with the UK Government's EPC database, transforming EPC management from manual document handling to automated data retrieval with built-in compliance checking. This article explores how we built this integration whilst handling the complexities of address matching and data quality in government datasets.

What Your Team Will Notice

Finding a property's EPC becomes instant. Open the EPC modal, enter the property's postcode, and see all registered certificates at that address—no more requesting PDFs from landlords or searching government websites manually. Click the relevant certificate and LetAdmin imports full details: current rating, potential rating, energy efficiency scores, environmental impact ratings, certificate validity dates, and a direct link to the official government certificate.

The system highlights compliance status automatically. Properties with expired certificates show warning indicators. Those rated F or G display alerts that they can't be legally let. EPCs approaching expiry (within six months) prompt proactive renewal reminders, preventing last-minute scrambles when certificates lapse.

When advertising properties, EPC data synchronises to Rightmove automatically. The current rating, energy efficiency score, and environmental impact rating appear on portal listings without manual data entry. If certificates update or expire, portal listings reflect changes immediately through webhook-triggered synchronisation.

For properties lacking government EPC records (new builds awaiting assessment, recent renovations), staff can manually upload certificate PDFs with required details. The system calculates ratings automatically from energy scores, computes expiry dates (ten years minus one day from issue date), and validates data completeness before saving.

Under the Bonnet: Government API Integration

The UK Government provides EPC data through a public API, but the data quality and structure present challenges. Properties might have multiple historical certificates, addresses vary in format between EPC records and property databases, and not all certificate data is complete or current.

Address-Based Certificate Lookup

EPC lookup uses postcode as the primary search key:

class EpcLookupService
  def initialize(postcode)
    @postcode = normalise_postcode(postcode)
  end

  def find_certificates
    # Query government API with postcode
    # Returns array of all certificates at that address
    # Includes current and historical EPCs
  end

  private

  def normalise_postcode(postcode)
    # Removes spaces, uppercases, handles format variations
    # "WR1 2AB" → "WR12AB"
    postcode.to_s.gsub(/\s+/, '').upcase
  end
end

The API returns all certificates registered at addresses matching the postcode, which might include multiple properties (flats in a building, separate units at the same location). The lookup interface displays all results, letting staff select the correct certificate for the specific property.

Confidence Scoring for Address Matching

Since properties might not have exact address matches between LetAdmin's records and EPC database entries, we implement confidence scoring:

def calculate_match_confidence(property_address, epc_address)
  score = 0

  # Check postcode match (required)
  return 0 unless postcodes_match?(property_address, epc_address)
  score += 30

  # Check building number match
  if building_numbers_match?(property_address, epc_address)
    score += 40
  end

  # Check street name similarity
  street_similarity = calculate_street_similarity(property_address, epc_address)
  score += (street_similarity * 30)

  score.round
end

Certificates scoring above 70% confidence display prominently with "High Confidence Match" indicators. Lower-scoring matches show with caution indicators, prompting staff to verify details before importing.

Data Import and Normalisation

EPC certificates contain extensive data, but not all fields are consistently populated. Our import process handles this variability:

class EnergyPerformanceCertificate
  def self.import_from_government_api(certificate_data)
    # Extract core fields with fallback handling
    current_rating = extract_rating(certificate_data, 'current-energy-rating')
    potential_rating = extract_rating(certificate_data, 'potential-energy-rating')

    # Parse dates with format handling
    issue_date = parse_date(certificate_data['lodgement-date'])
    expiry_date = calculate_expiry(issue_date)

    # Import energy scores
    current_score = certificate_data['current-energy-efficiency'].to_i
    potential_score = certificate_data['potential-energy-efficiency'].to_i

    # Environmental impact ratings (carbon emissions)
    current_co2 = certificate_data['current-environmental-impact-rating']
    potential_co2 = certificate_data['potential-environmental-impact-rating']

    create!(
      certificate_number: certificate_data['lmk-key'],
      current_energy_rating: current_rating,
      potential_energy_rating: potential_rating,
      current_energy_efficiency: current_score,
      potential_energy_efficiency: potential_score,
      issue_date: issue_date,
      expiry_date: expiry_date,
      certificate_url: build_certificate_url(certificate_data['lmk-key'])
    )
  end

  def self.calculate_expiry(issue_date)
    # EPCs valid for 10 years minus 1 day
    issue_date + 10.years - 1.day
  end
end

This import approach ensures we capture all available data whilst gracefully handling missing or malformed fields.

Rating Calculation from Scores

Government EPC data includes numeric scores (1-100) and letter ratings (A-G). Sometimes ratings are missing but scores are present. We implement automatic rating calculation:

def calculate_rating_from_score(score)
  return nil if score.blank?

  case score.to_i
  when 92..100 then 'A'
  when 81..91 then 'B'
  when 69..80 then 'C'
  when 55..68 then 'D'
  when 39..54 then 'E'
  when 21..38 then 'F'
  when 1..20 then 'G'
  else nil
  end
end

This ensures consistent rating display even when government data is incomplete.

Manual Upload Workflow

For properties not in the government database, staff can upload certificate PDFs with manual data entry:

<!-- Manual EPC upload form -->
<form action="/properties/<%= @property.id %>/epc" method="post" enctype="multipart/form-data">
  <!-- Certificate PDF upload -->
  <input type="file" name="certificate_pdf" accept="application/pdf" required />

  <!-- Certificate number -->
  <input type="text" name="certificate_number"
         pattern="[0-9]{4}-[0-9]{4}-[0-9]{4}-[0-9]{4}-[0-9]{4}" />

  <!-- Energy scores (auto-calculate ratings) -->
  <input type="number" name="current_energy_efficiency" min="1" max="100" />
  <input type="number" name="potential_energy_efficiency" min="1" max="100" />

  <!-- Dates (auto-calculate expiry) -->
  <input type="date" name="issue_date" />
  <p>Expiry: <span id="calculated-expiry"></span></p>
</form>

<script>
// Auto-calculate expiry date as issue_date + 10 years - 1 day
document.querySelector('[name="issue_date"]').addEventListener('change', (e) => {
  const issueDate = new Date(e.target.value);
  const expiryDate = new Date(issueDate);
  expiryDate.setFullYear(expiryDate.getFullYear() + 10);
  expiryDate.setDate(expiryDate.getDate() - 1);

  document.getElementById('calculated-expiry').textContent =
    expiryDate.toLocaleDateString('en-GB');
});

// Auto-calculate rating from energy efficiency score
document.querySelector('[name="current_energy_efficiency"]').addEventListener('input', (e) => {
  const score = parseInt(e.target.value);
  const rating = calculateRating(score);
  document.getElementById('current-rating').textContent = rating;
});
</script>

This manual workflow matches the automatic import structure, ensuring consistent data regardless of source.

Compliance Tracking and Alerts

With EPC data in the system, compliance tracking becomes automated:

class Property
  def epc_valid?
    return false unless current_epc.present?
    current_epc.expiry_date >= Date.today
  end

  def epc_allows_letting?
    return false unless current_epc.present?
    return false unless epc_valid?

    # Ratings E, D, C, B, A are acceptable
    # Ratings F and G cannot be legally let
    %w[A B C D E].include?(current_epc.current_energy_rating)
  end

  def epc_expiring_soon?
    return false unless current_epc.present?
    return false if current_epc.expired?

    # Alert if expiry within 6 months
    current_epc.expiry_date < 6.months.from_now
  end
end

These methods power dashboard alerts and status indicators, ensuring agencies never miss compliance requirements.

Webhook Integration for Portal Sync

When EPC data updates, webhooks notify external systems automatically:

class EnergyPerformanceCertificate
  include Webhookable

  WEBHOOK_ATTRIBUTES = %w[
    certificate_number current_energy_rating potential_energy_rating
    current_energy_efficiency issue_date expiry_date
  ].freeze

  after_commit :trigger_property_epc_webhook, on: [:create, :update]

  private

  def trigger_property_epc_webhook
    property.trigger_webhooks('property.epc.updated')
  end
end

Rightmove and other portals subscribed to EPC webhooks receive updates immediately, keeping listing data current without manual intervention.

Testing EPC Integration

Comprehensive tests verify government API integration and data handling:

RSpec.describe EpcLookupService do
  it "retrieves certificates for valid postcodes" do
    service = described_class.new('WR1 2AB')
    certificates = service.find_certificates

    expect(certificates).not_to be_empty
    expect(certificates.first).to have_key('current-energy-rating')
  end

  it "handles postcodes with no registered certificates" do
    service = described_class.new('XX99 9XX')
    expect(service.find_certificates).to eq([])
  end

  it "calculates address match confidence accurately" do
    property_address = '123 High Street, Worcester, WR1 2AB'
    epc_address = '123 HIGH STREET, WORCESTER, WR1 2AB'

    confidence = service.calculate_match_confidence(property_address, epc_address)
    expect(confidence).to be > 90
  end
end

RSpec.describe EnergyPerformanceCertificate do
  it "calculates ratings from energy scores correctly" do
    epc = build(:epc, current_energy_efficiency: 75)
    expect(epc.calculate_current_rating).to eq('C')
  end

  it "calculates expiry dates as issue + 10 years - 1 day" do
    issue = Date.new(2025, 1, 1)
    epc = create(:epc, issue_date: issue)

    expect(epc.expiry_date).to eq(Date.new(2034, 12, 31))
  end

  it "identifies properties that cannot be legally let" do
    property = create(:property)
    create(:epc, property: property, current_energy_rating: 'F')

    expect(property.epc_allows_letting?).to be false
  end
end

These tests ensure compliance logic and API integration work correctly under various scenarios.

What's Next

The EPC foundation enables several advanced features: automatic expiry reminders sent to landlords 90 days before certificates lapse, EPC rating trends showing improvements over time as properties are upgraded, and energy efficiency recommendations pulled from government data to suggest practical improvements.

Integration with property advertising workflows prevents accidentally listing properties with invalid or F/G-rated EPCs, catching compliance issues before they reach portals. Bulk EPC lookup for entire portfolios identifies all properties needing attention, making compliance audits straightforward.

By connecting directly to authoritative government data, LetAdmin transforms EPC management from reactive paperwork handling to proactive compliance automation, ensuring every property meets legal requirements whilst minimising administrative burden.