centralhighlands.rb 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. # Central Highlands Council — Development Applications (site page)
  2. require "nokogiri"
  3. require "cgi"
  4. require_relative "../lib/http"
  5. require_relative "../lib/db"
  6. require_relative "../lib/util"
  7. require_relative "../lib/enrich"
  8. TABLE = ENV.fetch("TABLE_NAME") # run_all.sh -> da_centralhighlands
  9. URL = "https://centralhighlands.tas.gov.au/development-applications/"
  10. DB.ensure_table!(TABLE)
  11. # Optional: store the PDF URL
  12. begin
  13. DB.client.query("ALTER TABLE `#{DB.client.escape(TABLE)}` ADD COLUMN IF NOT EXISTS document_url VARCHAR(1024) NULL")
  14. rescue => e
  15. warn "document_url add skipped: #{e.class} #{e.message}"
  16. end
  17. def abs_url(base, href)
  18. return "" if href.to_s.strip.empty?
  19. URI.join(base, href).to_s rescue href.to_s
  20. end
  21. # DA 2025/42, DA2025/00042, etc.
  22. REF_RX = %r{\bDA\s*(20\d{2})\s*/\s*([A-Za-z0-9\-_.]+)}i
  23. def extract_ref(str)
  24. s = CGI.unescape(str.to_s)
  25. if (m = s.match(%r{\bDA\s*(20\d{2})\s*/\s*([A-Za-z0-9\-_.]+)}i))
  26. return "DA #{m[1]} / #{m[2]}"
  27. end
  28. if (m = s.match(%r{\bDA(20\d{2})\s*[-\/]?\s*([0-9]{3,})\b}i))
  29. return "DA #{m[1]} / #{m[2]}"
  30. end
  31. nil
  32. end
  33. def extract_addr(text)
  34. if (m = text.match(/Location:\s*(.+?)(?:\s*\/|\s{2,}|$)/i))
  35. m[1].strip
  36. else
  37. ""
  38. end
  39. end
  40. def extract_proposal(text)
  41. if (m = text.match(/Proposal:\s*(.+?)(?:\s{2,}|$)/i))
  42. m[1].strip
  43. else
  44. ""
  45. end
  46. end
  47. def extract_close_raw(text)
  48. s = text.gsub(/\s+/, " ")
  49. # “… until 20 August 2025”
  50. if (m = s.match(/\buntil\s+([0-9]{1,2}\s+[A-Za-z]{3,}\s+[0-9]{4})\b/i))
  51. return m[1]
  52. end
  53. # fallback date tokens
  54. return $1 if s =~ /(\b[0-9]{1,2}\/[0-9]{1,2}\/[0-9]{2,4}\b)/
  55. return $1 if s =~ /(\b[0-9]{1,2}\s+[A-Za-z]{3,}\s+[0-9]{4}\b)/
  56. ""
  57. end
  58. html = Http.get(URL)
  59. doc = Nokogiri::HTML(html)
  60. container = doc.at_css("main, .entry-content, article") || doc
  61. # Grab anchors that look like the advertised docs
  62. links = container.css("a").select { |a|
  63. a.text =~ /click here to view application/i || a["href"].to_s.downcase.end_with?(".pdf")
  64. }
  65. puts "Found #{links.length} candidate link(s) for #{TABLE}"
  66. saved = 0
  67. links.each_with_index do |a, idx|
  68. pdf = abs_url(URL, a["href"])
  69. # Walk up to a nearby block and use its text to find fields
  70. host = a.ancestors("p, li, div").first || a.parent
  71. text = host ? host.text.strip : ""
  72. text = text.gsub(/\s+/, " ")
  73. address = extract_addr(text)
  74. description = extract_proposal(text)
  75. on_notice_raw = extract_close_raw(text)
  76. on_notice = Util.parse_aus_date(on_notice_raw)
  77. # Reference: proposal first, then file name, then surrounding text
  78. ref = extract_ref(description) || extract_ref(File.basename(pdf)) || extract_ref(text)
  79. # If we still have no address, fall back to a slice of the text
  80. address = text[0, 140] if address.empty?
  81. next if address.empty? || ref.nil?
  82. DB.upsert(TABLE, {
  83. description: description.empty? ? "Development Application" : description,
  84. date_received: on_notice, # store close date here for consistency with others
  85. date_received_raw: on_notice_raw,
  86. address: address,
  87. council_reference: ref,
  88. applicant: "",
  89. owner: ""
  90. })
  91. enrich_after_upsert!(
  92. table: TABLE,
  93. council_reference: ref,
  94. address: address
  95. )
  96. begin
  97. upd = DB.client.prepare("UPDATE `#{DB.client.escape(TABLE)}` SET document_url = ? WHERE council_reference = ? AND address = ?")
  98. upd.execute(pdf, ref, address)
  99. rescue Mysql2::Error => e
  100. warn "[centralhighlands] db update skipped for #{ref}: #{e.message}"
  101. end
  102. puts "Upserted #{ref} -> #{address}"
  103. saved += 1
  104. end
  105. puts "Done #{TABLE}. Saved #{saved} item(s)."