Initial OFAC Civil Penalties scraper
Scrapes https://ofac.treasury.gov/civil-penalties-and-enforcement-information for all years 2003-present. Downloads PDF documents and exports metadata.json per CGSH Publication spec (v3) to S3 experimental bucket under ofac/ prefix. Commands: ofac-full (all years), ofac-daily (current year incremental). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
bin/\nobj/\ndata/\n*.user\n.vs/
|
||||
93
src/OFACScraper/Application.cs
Normal file
93
src/OFACScraper/Application.cs
Normal file
@@ -0,0 +1,93 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using OFACScraper.Configuration;
|
||||
|
||||
namespace OFACScraper;
|
||||
|
||||
public class Application
|
||||
{
|
||||
private readonly OFACScraper _scraper;
|
||||
private readonly Exporter _exporter;
|
||||
private readonly CheckpointStore _checkpoint;
|
||||
private readonly OFACOptions _options;
|
||||
private readonly ILogger<Application> _logger;
|
||||
|
||||
public Application(
|
||||
OFACScraper scraper,
|
||||
Exporter exporter,
|
||||
CheckpointStore checkpoint,
|
||||
IOptions<OFACOptions> options,
|
||||
ILogger<Application> logger)
|
||||
{
|
||||
_scraper = scraper;
|
||||
_exporter = exporter;
|
||||
_checkpoint = checkpoint;
|
||||
_options = options.Value;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Full historical scrape: all years from StartYear to current year.
|
||||
/// Skips records already in checkpoint.
|
||||
/// </summary>
|
||||
public async Task<int> RunFullAsync(CancellationToken ct = default)
|
||||
{
|
||||
var currentYear = DateTime.UtcNow.Year;
|
||||
_logger.LogInformation("Starting full scrape {StartYear}–{EndYear}", _options.StartYear, currentYear);
|
||||
|
||||
var total = 0;
|
||||
for (var year = _options.StartYear; year <= currentYear; year++)
|
||||
{
|
||||
total += await ProcessYearAsync(year, ct);
|
||||
if (ct.IsCancellationRequested) break;
|
||||
}
|
||||
|
||||
_logger.LogInformation("Full scrape complete. {Total} new records exported. DB total: {DbTotal}",
|
||||
total, _checkpoint.GetTotalCount());
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Daily/incremental run: scrapes current year only, exports any new records.
|
||||
/// </summary>
|
||||
public async Task<int> RunDailyAsync(CancellationToken ct = default)
|
||||
{
|
||||
var currentYear = DateTime.UtcNow.Year;
|
||||
_logger.LogInformation("Starting daily scrape for {Year}", currentYear);
|
||||
|
||||
var newRecords = await ProcessYearAsync(currentYear, ct);
|
||||
_logger.LogInformation("Daily scrape complete. {New} new records exported.", newRecords);
|
||||
return 0;
|
||||
}
|
||||
|
||||
private async Task<int> ProcessYearAsync(int year, CancellationToken ct)
|
||||
{
|
||||
var records = await _scraper.GetYearRecordsAsync(year, ct);
|
||||
var newCount = 0;
|
||||
|
||||
foreach (var record in records)
|
||||
{
|
||||
if (ct.IsCancellationRequested) break;
|
||||
|
||||
if (_checkpoint.HasRecord(record.TextId))
|
||||
{
|
||||
_logger.LogDebug("Skipping {TextId} (already processed)", record.TextId);
|
||||
continue;
|
||||
}
|
||||
|
||||
var success = await _exporter.ExportRecordAsync(record, ct);
|
||||
if (success)
|
||||
{
|
||||
_checkpoint.MarkProcessed(
|
||||
record.TextId, record.Date, record.Name, record.PenaltyTotalUsd,
|
||||
record.DocumentUrl, record.FileName, record.Year);
|
||||
newCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (newCount > 0)
|
||||
_logger.LogInformation("Year {Year}: exported {Count} new records", year, newCount);
|
||||
|
||||
return newCount;
|
||||
}
|
||||
}
|
||||
79
src/OFACScraper/CheckpointStore.cs
Normal file
79
src/OFACScraper/CheckpointStore.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
using Dapper;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using OFACScraper.Configuration;
|
||||
|
||||
namespace OFACScraper;
|
||||
|
||||
/// <summary>
|
||||
/// SQLite-backed store tracking which OFAC records have been processed and synced to S3.
|
||||
/// </summary>
|
||||
public class CheckpointStore : IDisposable
|
||||
{
|
||||
private readonly SqliteConnection _db;
|
||||
private readonly ILogger<CheckpointStore> _logger;
|
||||
|
||||
public CheckpointStore(IOptions<StorageOptions> options, ILogger<CheckpointStore> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
var dbPath = options.Value.DatabasePath;
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(dbPath)!);
|
||||
_db = new SqliteConnection($"Data Source={dbPath}");
|
||||
_db.Open();
|
||||
InitSchema();
|
||||
}
|
||||
|
||||
public void Dispose() => _db.Dispose();
|
||||
|
||||
private void InitSchema()
|
||||
{
|
||||
_db.Execute("""
|
||||
CREATE TABLE IF NOT EXISTS records (
|
||||
text_id TEXT PRIMARY KEY,
|
||||
date TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
penalty_usd REAL,
|
||||
document_url TEXT NOT NULL,
|
||||
file_name TEXT NOT NULL,
|
||||
year INTEGER NOT NULL,
|
||||
processed_at TEXT NOT NULL,
|
||||
s3_synced INTEGER NOT NULL DEFAULT 0
|
||||
)
|
||||
""");
|
||||
}
|
||||
|
||||
public bool HasRecord(string textId) =>
|
||||
_db.ExecuteScalar<int>("SELECT COUNT(1) FROM records WHERE text_id = @textId", new { textId }) > 0;
|
||||
|
||||
public void MarkProcessed(string textId, DateTime date, string name, decimal? penaltyUsd,
|
||||
string documentUrl, string fileName, int year)
|
||||
{
|
||||
_db.Execute("""
|
||||
INSERT OR REPLACE INTO records (text_id, date, name, penalty_usd, document_url, file_name, year, processed_at, s3_synced)
|
||||
VALUES (@textId, @date, @name, @penaltyUsd, @documentUrl, @fileName, @year, @processedAt, 0)
|
||||
""",
|
||||
new
|
||||
{
|
||||
textId,
|
||||
date = date.ToString("yyyy-MM-dd"),
|
||||
name,
|
||||
penaltyUsd,
|
||||
documentUrl,
|
||||
fileName,
|
||||
year,
|
||||
processedAt = DateTime.UtcNow.ToString("O")
|
||||
});
|
||||
}
|
||||
|
||||
public void MarkS3Synced(string textId)
|
||||
{
|
||||
_db.Execute("UPDATE records SET s3_synced = 1 WHERE text_id = @textId", new { textId });
|
||||
}
|
||||
|
||||
public int GetTotalCount() =>
|
||||
_db.ExecuteScalar<int>("SELECT COUNT(1) FROM records");
|
||||
|
||||
public int GetYearCount(int year) =>
|
||||
_db.ExecuteScalar<int>("SELECT COUNT(1) FROM records WHERE year = @year", new { year });
|
||||
}
|
||||
37
src/OFACScraper/Configuration/AppOptions.cs
Normal file
37
src/OFACScraper/Configuration/AppOptions.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
namespace OFACScraper.Configuration;
|
||||
|
||||
public class OFACOptions
|
||||
{
|
||||
public const string SectionName = "OFAC";
|
||||
|
||||
public string BaseUrl { get; set; } = "https://ofac.treasury.gov";
|
||||
public string YearUrlTemplate { get; set; } = "/civil-penalties-and-enforcement-information/{0}-enforcement-information";
|
||||
public int StartYear { get; set; } = 2003;
|
||||
public string UserAgent { get; set; } = "Mozilla/5.0 (compatible; OFACBot/1.0; +https://ukdataservices.co.uk)";
|
||||
public int RequestDelayMs { get; set; } = 1000;
|
||||
}
|
||||
|
||||
public class StorageOptions
|
||||
{
|
||||
public const string SectionName = "Storage";
|
||||
|
||||
public string DatabasePath { get; set; } = "data/ofac.db";
|
||||
public string ExportDirectory { get; set; } = "data/exports";
|
||||
public string DownloadDirectory { get; set; } = "data/downloads";
|
||||
}
|
||||
|
||||
public class S3Options
|
||||
{
|
||||
public const string SectionName = "S3";
|
||||
|
||||
public string? BucketName { get; set; }
|
||||
public string? AccessKeyId { get; set; }
|
||||
public string? SecretAccessKey { get; set; }
|
||||
public string Region { get; set; } = "eu-west-3";
|
||||
public string Prefix { get; set; } = "ofac";
|
||||
|
||||
public bool IsConfigured =>
|
||||
!string.IsNullOrWhiteSpace(BucketName) &&
|
||||
!string.IsNullOrWhiteSpace(AccessKeyId) &&
|
||||
!string.IsNullOrWhiteSpace(SecretAccessKey);
|
||||
}
|
||||
120
src/OFACScraper/Exporter.cs
Normal file
120
src/OFACScraper/Exporter.cs
Normal file
@@ -0,0 +1,120 @@
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using OFACScraper.Configuration;
|
||||
using OFACScraper.Helpers;
|
||||
using OFACScraper.Models;
|
||||
using OFACScraper.Services;
|
||||
|
||||
namespace OFACScraper;
|
||||
|
||||
public class Exporter
|
||||
{
|
||||
private readonly HttpClient _http;
|
||||
private readonly S3UploadService _s3;
|
||||
private readonly StorageOptions _storage;
|
||||
private readonly S3Options _s3Options;
|
||||
private readonly ILogger<Exporter> _logger;
|
||||
|
||||
public Exporter(
|
||||
HttpClient http,
|
||||
S3UploadService s3,
|
||||
IOptions<StorageOptions> storage,
|
||||
IOptions<S3Options> s3Options,
|
||||
ILogger<Exporter> logger)
|
||||
{
|
||||
_http = http;
|
||||
_s3 = s3;
|
||||
_storage = storage.Value;
|
||||
_s3Options = s3Options.Value;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exports a single OFAC record: downloads PDF, writes metadata.json, syncs to S3.
|
||||
/// Returns true if the record was exported successfully.
|
||||
/// </summary>
|
||||
public async Task<bool> ExportRecordAsync(OFACRecord record, CancellationToken ct = default)
|
||||
{
|
||||
var pubId = DeterministicGuid.FromUrl(record.YearPageUrl + "#" + record.TextId);
|
||||
var docId = DeterministicGuid.FromUrl(record.DocumentUrl);
|
||||
|
||||
var exportDir = Path.Combine(_storage.ExportDirectory, pubId.ToString());
|
||||
Directory.CreateDirectory(exportDir);
|
||||
|
||||
// Download PDF
|
||||
var localPdfPath = Path.Combine(exportDir, record.FileName);
|
||||
if (!File.Exists(localPdfPath))
|
||||
{
|
||||
if (!await DownloadFileAsync(record.DocumentUrl, localPdfPath, ct))
|
||||
{
|
||||
_logger.LogError("Failed to download PDF for {TextId}", record.TextId);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Build publication metadata
|
||||
var pub = new Publication
|
||||
{
|
||||
Id = pubId,
|
||||
TextId = record.TextId,
|
||||
Source = PublicationSources.OfacCivilPenalty,
|
||||
WebsiteLink = record.YearPageUrl,
|
||||
Title = $"{record.Name} - OFAC Civil Penalty {record.Date:yyyy-MM-dd}",
|
||||
DatePublished = new DateTimeOffset(record.Date, TimeSpan.Zero),
|
||||
DateAccessed = DateTimeOffset.UtcNow,
|
||||
ScraperVersion = ScraperConstants.ScraperVersion,
|
||||
Documents =
|
||||
[
|
||||
new PublicationDocument
|
||||
{
|
||||
Id = docId,
|
||||
Name = record.FileName,
|
||||
Url = record.DocumentUrl
|
||||
}
|
||||
],
|
||||
Tags =
|
||||
[
|
||||
new PublicationTag { Slug = "penalty-date", Date = new DateTimeOffset(record.Date, TimeSpan.Zero) },
|
||||
new PublicationTag { Slug = "entity-name", Text = record.Name },
|
||||
new PublicationTag { Slug = "penalty-amount-usd", Text = record.PenaltyTotalUsd?.ToString("F2") ?? "N/A" },
|
||||
new PublicationTag { Slug = "year", Text = record.Year.ToString() }
|
||||
]
|
||||
};
|
||||
|
||||
// Write metadata.json (last — S3 sync uploads it after the PDF)
|
||||
var metadataPath = Path.Combine(exportDir, "metadata.json");
|
||||
var json = JsonSerializer.Serialize(pub, JsonConstants.Default);
|
||||
await File.WriteAllTextAsync(metadataPath, json, ct);
|
||||
|
||||
// Sync this record's directory to S3
|
||||
var s3Prefix = $"{_s3Options.Prefix}/{pubId}";
|
||||
var syncResult = await _s3.SyncDirectoryAsync(exportDir, s3Prefix);
|
||||
|
||||
if (syncResult.Failed > 0)
|
||||
{
|
||||
_logger.LogError("S3 sync failed for {TextId}: {Failed} files failed", record.TextId, syncResult.Failed);
|
||||
return false;
|
||||
}
|
||||
|
||||
_logger.LogInformation("Exported {TextId} → S3 ({Uploaded} uploaded, {Skipped} unchanged)",
|
||||
record.TextId, syncResult.Uploaded, syncResult.Skipped);
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task<bool> DownloadFileAsync(string url, string localPath, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var bytes = await _http.GetByteArrayAsync(url, ct);
|
||||
await File.WriteAllBytesAsync(localPath, bytes, ct);
|
||||
_logger.LogDebug("Downloaded {Url} → {Path}", url, localPath);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error downloading {Url}", url);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
44
src/OFACScraper/Helpers/DeterministicGuid.cs
Normal file
44
src/OFACScraper/Helpers/DeterministicGuid.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace OFACScraper.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Deterministic UUID v5 generation per CGSH spec (Ben Lok, 9 Mar 2026).
|
||||
/// Both pub_id and doc_id use namespace 6ba7b811-9dad-11d1-80b4-00c04fd430c8 with the URL as input.
|
||||
/// </summary>
|
||||
public static class DeterministicGuid
|
||||
{
|
||||
private static readonly Guid NamespaceUrl = Guid.Parse("6ba7b811-9dad-11d1-80b4-00c04fd430c8");
|
||||
|
||||
public static Guid FromUrl(string url) => CreateV5(NamespaceUrl, url);
|
||||
|
||||
private static Guid CreateV5(Guid namespaceId, string name)
|
||||
{
|
||||
var namespaceBytes = namespaceId.ToByteArray();
|
||||
SwapByteOrder(namespaceBytes);
|
||||
|
||||
var nameBytes = Encoding.UTF8.GetBytes(name);
|
||||
var combined = new byte[namespaceBytes.Length + nameBytes.Length];
|
||||
Buffer.BlockCopy(namespaceBytes, 0, combined, 0, namespaceBytes.Length);
|
||||
Buffer.BlockCopy(nameBytes, 0, combined, namespaceBytes.Length, nameBytes.Length);
|
||||
|
||||
var hashBytes = SHA1.HashData(combined);
|
||||
var guidBytes = new byte[16];
|
||||
Array.Copy(hashBytes, guidBytes, 16);
|
||||
|
||||
guidBytes[6] = (byte)((guidBytes[6] & 0x0F) | 0x50);
|
||||
guidBytes[8] = (byte)((guidBytes[8] & 0x3F) | 0x80);
|
||||
|
||||
SwapByteOrder(guidBytes);
|
||||
return new Guid(guidBytes);
|
||||
}
|
||||
|
||||
private static void SwapByteOrder(byte[] guid)
|
||||
{
|
||||
(guid[3], guid[0]) = (guid[0], guid[3]);
|
||||
(guid[2], guid[1]) = (guid[1], guid[2]);
|
||||
(guid[5], guid[4]) = (guid[4], guid[5]);
|
||||
(guid[7], guid[6]) = (guid[6], guid[7]);
|
||||
}
|
||||
}
|
||||
35
src/OFACScraper/Helpers/JsonConstants.cs
Normal file
35
src/OFACScraper/Helpers/JsonConstants.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System.Globalization;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace OFACScraper.Helpers;
|
||||
|
||||
public class TagDateConverter : JsonConverter<DateTimeOffset?>
|
||||
{
|
||||
public override DateTimeOffset? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (reader.TokenType == JsonTokenType.Null) return null;
|
||||
var s = reader.GetString();
|
||||
if (string.IsNullOrEmpty(s)) return null;
|
||||
return DateTimeOffset.Parse(s, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal);
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, DateTimeOffset? value, JsonSerializerOptions options)
|
||||
{
|
||||
if (value is null)
|
||||
writer.WriteNullValue();
|
||||
else
|
||||
writer.WriteStringValue(value.Value.ToString("yyyy-MM-ddTHH:mm:ss", CultureInfo.InvariantCulture));
|
||||
}
|
||||
}
|
||||
|
||||
public static class JsonConstants
|
||||
{
|
||||
public static readonly JsonSerializerOptions Default = new()
|
||||
{
|
||||
WriteIndented = true,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||
};
|
||||
}
|
||||
18
src/OFACScraper/Models/OFACRecord.cs
Normal file
18
src/OFACScraper/Models/OFACRecord.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
namespace OFACScraper.Models;
|
||||
|
||||
public record OFACRecord
|
||||
{
|
||||
public required DateTime Date { get; init; }
|
||||
public required string Name { get; init; }
|
||||
public required decimal? PenaltyTotalUsd { get; init; }
|
||||
public required string DocumentUrl { get; init; }
|
||||
public required string FileName { get; init; }
|
||||
public required int Year { get; init; }
|
||||
public required string YearPageUrl { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Stable text identifier derived from PDF filename (without extension).
|
||||
/// E.g. "20260317_tradestation" from "20260317_tradestation.pdf"
|
||||
/// </summary>
|
||||
public string TextId => Path.GetFileNameWithoutExtension(FileName);
|
||||
}
|
||||
81
src/OFACScraper/Models/Publication.cs
Normal file
81
src/OFACScraper/Models/Publication.cs
Normal file
@@ -0,0 +1,81 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using OFACScraper.Helpers;
|
||||
|
||||
namespace OFACScraper.Models;
|
||||
|
||||
public class Publication
|
||||
{
|
||||
[JsonPropertyName("id")]
|
||||
public required Guid Id { get; init; }
|
||||
|
||||
[JsonPropertyName("text_id")]
|
||||
public required string TextId { get; init; }
|
||||
|
||||
[JsonPropertyName("source")]
|
||||
public required string Source { get; init; }
|
||||
|
||||
[JsonPropertyName("website_link")]
|
||||
public required string WebsiteLink { get; init; }
|
||||
|
||||
[JsonPropertyName("title")]
|
||||
public required string Title { get; init; }
|
||||
|
||||
[JsonPropertyName("date_published")]
|
||||
public required DateTimeOffset DatePublished { get; init; }
|
||||
|
||||
[JsonPropertyName("date_accessed")]
|
||||
public required DateTimeOffset DateAccessed { get; set; }
|
||||
|
||||
[JsonPropertyName("scraper_version")]
|
||||
public required int ScraperVersion { get; init; }
|
||||
|
||||
[JsonPropertyName("deleted")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public bool? Deleted { get; set; }
|
||||
|
||||
[JsonPropertyName("documents")]
|
||||
public List<PublicationDocument> Documents { get; init; } = [];
|
||||
|
||||
[JsonPropertyName("tags")]
|
||||
public List<PublicationTag> Tags { get; init; } = [];
|
||||
}
|
||||
|
||||
public record PublicationDocument
|
||||
{
|
||||
[JsonPropertyName("id")]
|
||||
public required Guid Id { get; init; }
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
public required string Name { get; init; }
|
||||
|
||||
[JsonPropertyName("is_attachment")]
|
||||
public bool IsAttachment { get; init; } = false;
|
||||
|
||||
[JsonPropertyName("url")]
|
||||
public required string Url { get; init; }
|
||||
}
|
||||
|
||||
public record PublicationTag
|
||||
{
|
||||
[JsonPropertyName("slug")]
|
||||
public required string Slug { get; init; }
|
||||
|
||||
[JsonPropertyName("text")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? Text { get; init; }
|
||||
|
||||
[JsonPropertyName("date")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
[JsonConverter(typeof(TagDateConverter))]
|
||||
public DateTimeOffset? Date { get; init; }
|
||||
}
|
||||
|
||||
public static class PublicationSources
|
||||
{
|
||||
public const string OfacCivilPenalty = "OFAC_CIVIL_PENALTY";
|
||||
}
|
||||
|
||||
public static class ScraperConstants
|
||||
{
|
||||
public const int ScraperVersion = 3;
|
||||
}
|
||||
35
src/OFACScraper/OFACScraper.csproj
Normal file
35
src/OFACScraper/OFACScraper.csproj
Normal file
@@ -0,0 +1,35 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>OFACScraper</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dapper" Version="2.1.66" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.12.4" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="8.0.0" />
|
||||
<PackageReference Include="Serilog" Version="4.3.0" />
|
||||
<PackageReference Include="Serilog.Extensions.Hosting" Version="8.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||
<PackageReference Include="System.Text.Json" Version="9.0.0" />
|
||||
<PackageReference Include="AWSSDK.S3" Version="3.7.400.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="appsettings.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
67
src/OFACScraper/Program.cs
Normal file
67
src/OFACScraper/Program.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using OFACScraper;
|
||||
using OFACScraper.Configuration;
|
||||
using OFACScraper.Services;
|
||||
using Serilog;
|
||||
|
||||
var command = args.FirstOrDefault() ?? "ofac-daily";
|
||||
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.MinimumLevel.Information()
|
||||
.WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}")
|
||||
.WriteTo.File("logs/ofac-.log", rollingInterval: RollingInterval.Day,
|
||||
outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}")
|
||||
.CreateLogger();
|
||||
|
||||
try
|
||||
{
|
||||
Log.Information("OFAC Scraper starting. Command: {Command}", command);
|
||||
|
||||
var host = Host.CreateDefaultBuilder(args)
|
||||
.UseSerilog()
|
||||
.ConfigureServices((ctx, services) =>
|
||||
{
|
||||
services.Configure<OFACOptions>(ctx.Configuration.GetSection(OFACOptions.SectionName));
|
||||
services.Configure<StorageOptions>(ctx.Configuration.GetSection(StorageOptions.SectionName));
|
||||
services.Configure<S3Options>(ctx.Configuration.GetSection(S3Options.SectionName));
|
||||
|
||||
const string userAgent = "Mozilla/5.0 (compatible; OFACBot/1.0; +https://ukdataservices.co.uk)";
|
||||
|
||||
services.AddHttpClient<OFACScraper.OFACScraper>(client =>
|
||||
{
|
||||
client.DefaultRequestHeaders.Add("User-Agent", userAgent);
|
||||
client.Timeout = TimeSpan.FromSeconds(60);
|
||||
});
|
||||
|
||||
services.AddHttpClient<Exporter>(client =>
|
||||
{
|
||||
client.DefaultRequestHeaders.Add("User-Agent", userAgent);
|
||||
client.Timeout = TimeSpan.FromSeconds(120);
|
||||
});
|
||||
|
||||
services.AddSingleton<S3UploadService>();
|
||||
services.AddSingleton<CheckpointStore>();
|
||||
services.AddTransient<Application>();
|
||||
})
|
||||
.Build();
|
||||
|
||||
var app = host.Services.GetRequiredService<Application>();
|
||||
var exitCode = command switch
|
||||
{
|
||||
"ofac-full" => await app.RunFullAsync(),
|
||||
"ofac-daily" => await app.RunDailyAsync(),
|
||||
_ => throw new ArgumentException($"Unknown command: {command}. Use ofac-full or ofac-daily.")
|
||||
};
|
||||
|
||||
return exitCode;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Fatal(ex, "Unhandled exception");
|
||||
return 1;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Log.CloseAndFlush();
|
||||
}
|
||||
131
src/OFACScraper/Scraper.cs
Normal file
131
src/OFACScraper/Scraper.cs
Normal file
@@ -0,0 +1,131 @@
|
||||
using System.Globalization;
|
||||
using HtmlAgilityPack;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using OFACScraper.Configuration;
|
||||
using OFACScraper.Models;
|
||||
|
||||
namespace OFACScraper;
|
||||
|
||||
public class OFACScraper
|
||||
{
|
||||
private readonly HttpClient _http;
|
||||
private readonly OFACOptions _options;
|
||||
private readonly ILogger<OFACScraper> _logger;
|
||||
|
||||
public OFACScraper(HttpClient http, IOptions<OFACOptions> options, ILogger<OFACScraper> logger)
|
||||
{
|
||||
_http = http;
|
||||
_options = options.Value;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public string GetYearUrl(int year)
|
||||
{
|
||||
// The current year's data is on the main page, not a year subpath.
|
||||
// Past years use /{year}-enforcement-information.
|
||||
var currentYear = DateTime.UtcNow.Year;
|
||||
if (year == currentYear)
|
||||
return _options.BaseUrl + "/civil-penalties-and-enforcement-information";
|
||||
return _options.BaseUrl + string.Format(_options.YearUrlTemplate, year);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches and parses the OFAC civil penalties table for a given year.
|
||||
/// Returns all penalty records found on the page.
|
||||
/// </summary>
|
||||
public async Task<List<OFACRecord>> GetYearRecordsAsync(int year, CancellationToken ct = default)
|
||||
{
|
||||
var url = GetYearUrl(year);
|
||||
_logger.LogInformation("Scraping year {Year}: {Url}", year, url);
|
||||
|
||||
string html;
|
||||
try
|
||||
{
|
||||
html = await _http.GetStringAsync(url, ct);
|
||||
}
|
||||
catch (HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
|
||||
{
|
||||
_logger.LogWarning("No page found for year {Year} (404)", year);
|
||||
return [];
|
||||
}
|
||||
|
||||
var doc = new HtmlDocument();
|
||||
doc.LoadHtml(html);
|
||||
|
||||
var table = doc.DocumentNode.SelectSingleNode("//table[contains(@class,'usa-table')]");
|
||||
if (table == null)
|
||||
{
|
||||
_logger.LogWarning("No usa-table found for year {Year}", year);
|
||||
return [];
|
||||
}
|
||||
|
||||
var rows = table.SelectNodes(".//tbody/tr");
|
||||
if (rows == null) return [];
|
||||
|
||||
var records = new List<OFACRecord>();
|
||||
|
||||
foreach (var row in rows)
|
||||
{
|
||||
var cells = row.SelectNodes(".//td");
|
||||
if (cells == null || cells.Count < 4) continue;
|
||||
|
||||
// Date cell: <a href="/media/.../download?inline" title="filename.pdf">MM/DD/YYYY</a>
|
||||
var dateLink = cells[0].SelectSingleNode(".//a");
|
||||
if (dateLink == null) continue; // "Year to date totals" summary row
|
||||
|
||||
var dateText = HtmlEntity.DeEntitize(dateLink.InnerText).Trim();
|
||||
if (!DateTime.TryParseExact(dateText, "MM/dd/yyyy", CultureInfo.InvariantCulture,
|
||||
DateTimeStyles.None, out var date))
|
||||
{
|
||||
_logger.LogWarning("Could not parse date '{Date}' in year {Year}", dateText, year);
|
||||
continue;
|
||||
}
|
||||
|
||||
var docHref = dateLink.GetAttributeValue("href", "").Trim();
|
||||
if (string.IsNullOrEmpty(docHref)) continue;
|
||||
|
||||
var docUrl = docHref.StartsWith("http") ? docHref : _options.BaseUrl + docHref;
|
||||
var fileName = dateLink.GetAttributeValue("title", "").Trim();
|
||||
if (string.IsNullOrEmpty(fileName))
|
||||
fileName = $"{date:yyyyMMdd}_{Slugify(HtmlEntity.DeEntitize(cells[1].InnerText).Trim())}.pdf";
|
||||
|
||||
var name = HtmlEntity.DeEntitize(cells[1].InnerText).Trim();
|
||||
var penaltyText = HtmlEntity.DeEntitize(cells[3].InnerText).Trim();
|
||||
var penalty = ParsePenalty(penaltyText);
|
||||
|
||||
records.Add(new OFACRecord
|
||||
{
|
||||
Date = date,
|
||||
Name = name,
|
||||
PenaltyTotalUsd = penalty,
|
||||
DocumentUrl = docUrl,
|
||||
FileName = fileName,
|
||||
Year = year,
|
||||
YearPageUrl = url
|
||||
});
|
||||
}
|
||||
|
||||
_logger.LogInformation("Year {Year}: found {Count} records", year, records.Count);
|
||||
|
||||
if (_options.RequestDelayMs > 0)
|
||||
await Task.Delay(_options.RequestDelayMs, ct);
|
||||
|
||||
return records;
|
||||
}
|
||||
|
||||
private static decimal? ParsePenalty(string text)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text)) return null;
|
||||
var cleaned = text.Replace("$", "").Replace(",", "").Trim();
|
||||
return decimal.TryParse(cleaned, NumberStyles.Any, CultureInfo.InvariantCulture, out var value)
|
||||
? value
|
||||
: null;
|
||||
}
|
||||
|
||||
private static string Slugify(string text) =>
|
||||
new string(text.ToLowerInvariant()
|
||||
.Select(c => char.IsLetterOrDigit(c) ? c : '_')
|
||||
.ToArray())
|
||||
.Trim('_');
|
||||
}
|
||||
132
src/OFACScraper/Services/S3UploadService.cs
Normal file
132
src/OFACScraper/Services/S3UploadService.cs
Normal file
@@ -0,0 +1,132 @@
|
||||
using Amazon;
|
||||
using Amazon.S3;
|
||||
using Amazon.S3.Model;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using OFACScraper.Configuration;
|
||||
|
||||
namespace OFACScraper.Services;
|
||||
|
||||
public class S3UploadService : IDisposable
|
||||
{
|
||||
private readonly S3Options _options;
|
||||
private readonly ILogger<S3UploadService> _logger;
|
||||
private readonly IAmazonS3? _s3Client;
|
||||
|
||||
public S3UploadService(IOptions<S3Options> options, ILogger<S3UploadService> logger)
|
||||
{
|
||||
_options = options.Value;
|
||||
_logger = logger;
|
||||
|
||||
if (_options.IsConfigured)
|
||||
{
|
||||
var config = new AmazonS3Config
|
||||
{
|
||||
RegionEndpoint = RegionEndpoint.GetBySystemName(_options.Region)
|
||||
};
|
||||
_s3Client = new AmazonS3Client(_options.AccessKeyId, _options.SecretAccessKey, config);
|
||||
_logger.LogInformation("S3 configured: bucket={Bucket} region={Region} prefix={Prefix}",
|
||||
_options.BucketName, _options.Region, _options.Prefix);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("S3 not configured — uploads will be skipped.");
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() => (_s3Client as IDisposable)?.Dispose();
|
||||
|
||||
public async Task<bool> UploadFileAsync(string localPath, string s3Key)
|
||||
{
|
||||
if (_s3Client == null) return false;
|
||||
|
||||
try
|
||||
{
|
||||
await _s3Client.PutObjectAsync(new PutObjectRequest
|
||||
{
|
||||
BucketName = _options.BucketName,
|
||||
Key = s3Key,
|
||||
FilePath = localPath
|
||||
});
|
||||
_logger.LogDebug("Uploaded {Path} → s3://{Bucket}/{Key}", localPath, _options.BucketName, s3Key);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to upload {Path}", localPath);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Syncs localDirectory to S3 under s3Prefix, skipping files whose MD5 matches existing S3 ETag.
|
||||
/// Uploads metadata.json last so CGSH processing triggers only after all documents are present.
|
||||
/// </summary>
|
||||
public async Task<S3SyncResult> SyncDirectoryAsync(string localDirectory, string s3Prefix)
|
||||
{
|
||||
var result = new S3SyncResult();
|
||||
|
||||
if (_s3Client == null)
|
||||
{
|
||||
_logger.LogWarning("S3 not configured, skipping sync of {Path}", localDirectory);
|
||||
result.NotConfigured = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!Directory.Exists(localDirectory))
|
||||
{
|
||||
result.Error = $"Directory not found: {localDirectory}";
|
||||
return result;
|
||||
}
|
||||
|
||||
// List existing objects to skip unchanged files
|
||||
var existing = new Dictionary<string, string>();
|
||||
var listRequest = new ListObjectsV2Request { BucketName = _options.BucketName, Prefix = s3Prefix + "/" };
|
||||
ListObjectsV2Response listResponse;
|
||||
do
|
||||
{
|
||||
listResponse = await _s3Client.ListObjectsV2Async(listRequest);
|
||||
foreach (var obj in listResponse.S3Objects ?? [])
|
||||
existing[obj.Key] = obj.ETag?.Trim('"') ?? "";
|
||||
listRequest.ContinuationToken = listResponse.NextContinuationToken;
|
||||
} while (listResponse.IsTruncated == true);
|
||||
|
||||
// metadata.json last — CGSH triggers on its arrival
|
||||
var files = Directory.GetFiles(localDirectory, "*", SearchOption.AllDirectories)
|
||||
.OrderBy(f => Path.GetFileName(f) == "metadata.json" ? 1 : 0)
|
||||
.ThenBy(f => f)
|
||||
.ToArray();
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
var relativePath = Path.GetRelativePath(localDirectory, file).Replace('\\', '/');
|
||||
var s3Key = $"{s3Prefix}/{relativePath}";
|
||||
|
||||
if (existing.TryGetValue(s3Key, out var etag) && !string.IsNullOrEmpty(etag))
|
||||
{
|
||||
var localMd5 = Convert.ToHexString(
|
||||
System.Security.Cryptography.MD5.HashData(File.ReadAllBytes(file))
|
||||
).ToLowerInvariant();
|
||||
if (localMd5 == etag) { result.Skipped++; continue; }
|
||||
}
|
||||
|
||||
if (await UploadFileAsync(file, s3Key))
|
||||
result.Uploaded++;
|
||||
else
|
||||
result.Failed++;
|
||||
}
|
||||
|
||||
_logger.LogInformation("S3 sync: {Uploaded} uploaded, {Skipped} unchanged, {Failed} failed",
|
||||
result.Uploaded, result.Skipped, result.Failed);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public class S3SyncResult
|
||||
{
|
||||
public int Uploaded { get; set; }
|
||||
public int Skipped { get; set; }
|
||||
public int Failed { get; set; }
|
||||
public bool NotConfigured { get; set; }
|
||||
public string? Error { get; set; }
|
||||
}
|
||||
27
src/OFACScraper/appsettings.json
Normal file
27
src/OFACScraper/appsettings.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"OFAC": {
|
||||
"BaseUrl": "https://ofac.treasury.gov",
|
||||
"YearUrlTemplate": "/civil-penalties-and-enforcement-information/{0}-enforcement-information",
|
||||
"StartYear": 2003,
|
||||
"UserAgent": "Mozilla/5.0 (compatible; OFACBot/1.0; +https://ukdataservices.co.uk)",
|
||||
"RequestDelayMs": 1000
|
||||
},
|
||||
"Storage": {
|
||||
"DatabasePath": "/git/cgsh-ofac/data/ofac.db",
|
||||
"ExportDirectory": "/git/cgsh-ofac/data/exports",
|
||||
"DownloadDirectory": "/git/cgsh-ofac/data/downloads"
|
||||
},
|
||||
"S3": {
|
||||
"BucketName": "uk-data-services-experimental-927681712454-eu-west-3-an",
|
||||
"AccessKeyId": "AKIA5P7RDSFDK5MSRN6P",
|
||||
"SecretAccessKey": "r6MjrnzRVlo8/tcUXhxT4YvOPhO1vV7wjwqr0UxH",
|
||||
"Region": "eu-west-3",
|
||||
"Prefix": "ofac"
|
||||
},
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.Extensions.Http": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
src/OFACScraper/bin/Debug/net8.0/AWSSDK.Core.dll
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/AWSSDK.Core.dll
Executable file
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/AWSSDK.S3.dll
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/AWSSDK.S3.dll
Executable file
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/Dapper.dll
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/Dapper.dll
Executable file
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/HtmlAgilityPack.dll
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/HtmlAgilityPack.dll
Executable file
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/Microsoft.Data.Sqlite.dll
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/Microsoft.Data.Sqlite.dll
Executable file
Binary file not shown.
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Configuration.Binder.dll
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Configuration.Binder.dll
Executable file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Configuration.Json.dll
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Configuration.Json.dll
Executable file
Binary file not shown.
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Configuration.dll
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Configuration.dll
Executable file
Binary file not shown.
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.DependencyInjection.dll
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.DependencyInjection.dll
Executable file
Binary file not shown.
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Diagnostics.dll
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Diagnostics.dll
Executable file
Binary file not shown.
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.FileProviders.Physical.dll
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.FileProviders.Physical.dll
Executable file
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.FileSystemGlobbing.dll
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.FileSystemGlobbing.dll
Executable file
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Hosting.Abstractions.dll
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Hosting.Abstractions.dll
Executable file
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Hosting.dll
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Hosting.dll
Executable file
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Http.dll
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Http.dll
Executable file
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Logging.Abstractions.dll
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Logging.Abstractions.dll
Executable file
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Logging.Configuration.dll
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Logging.Configuration.dll
Executable file
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Logging.Console.dll
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Logging.Console.dll
Executable file
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Logging.Debug.dll
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Logging.Debug.dll
Executable file
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Logging.EventLog.dll
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Logging.EventLog.dll
Executable file
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Logging.EventSource.dll
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Logging.EventSource.dll
Executable file
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Logging.dll
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Logging.dll
Executable file
Binary file not shown.
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Options.dll
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Options.dll
Executable file
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Primitives.dll
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Primitives.dll
Executable file
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/OFACScraper
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/OFACScraper
Executable file
Binary file not shown.
1069
src/OFACScraper/bin/Debug/net8.0/OFACScraper.deps.json
Normal file
1069
src/OFACScraper/bin/Debug/net8.0/OFACScraper.deps.json
Normal file
File diff suppressed because it is too large
Load Diff
BIN
src/OFACScraper/bin/Debug/net8.0/OFACScraper.dll
Normal file
BIN
src/OFACScraper/bin/Debug/net8.0/OFACScraper.dll
Normal file
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/OFACScraper.pdb
Normal file
BIN
src/OFACScraper/bin/Debug/net8.0/OFACScraper.pdb
Normal file
Binary file not shown.
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"runtimeOptions": {
|
||||
"tfm": "net8.0",
|
||||
"framework": {
|
||||
"name": "Microsoft.NETCore.App",
|
||||
"version": "8.0.0"
|
||||
},
|
||||
"configProperties": {
|
||||
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
src/OFACScraper/bin/Debug/net8.0/SQLitePCLRaw.batteries_v2.dll
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/SQLitePCLRaw.batteries_v2.dll
Executable file
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/SQLitePCLRaw.core.dll
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/SQLitePCLRaw.core.dll
Executable file
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/SQLitePCLRaw.provider.e_sqlite3.dll
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/SQLitePCLRaw.provider.e_sqlite3.dll
Executable file
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/Serilog.Extensions.Hosting.dll
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/Serilog.Extensions.Hosting.dll
Executable file
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/Serilog.Extensions.Logging.dll
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/Serilog.Extensions.Logging.dll
Executable file
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/Serilog.Sinks.Console.dll
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/Serilog.Sinks.Console.dll
Executable file
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/Serilog.Sinks.File.dll
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/Serilog.Sinks.File.dll
Executable file
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/Serilog.dll
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/Serilog.dll
Executable file
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/System.Diagnostics.EventLog.dll
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/System.Diagnostics.EventLog.dll
Executable file
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/System.IO.Pipelines.dll
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/System.IO.Pipelines.dll
Executable file
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/System.Text.Encodings.Web.dll
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/System.Text.Encodings.Web.dll
Executable file
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/System.Text.Json.dll
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/System.Text.Json.dll
Executable file
Binary file not shown.
27
src/OFACScraper/bin/Debug/net8.0/appsettings.json
Normal file
27
src/OFACScraper/bin/Debug/net8.0/appsettings.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"OFAC": {
|
||||
"BaseUrl": "https://ofac.treasury.gov",
|
||||
"YearUrlTemplate": "/civil-penalties-and-enforcement-information/{0}-enforcement-information",
|
||||
"StartYear": 2003,
|
||||
"UserAgent": "Mozilla/5.0 (compatible; OFACBot/1.0; +https://ukdataservices.co.uk)",
|
||||
"RequestDelayMs": 1000
|
||||
},
|
||||
"Storage": {
|
||||
"DatabasePath": "/git/cgsh-ofac/data/ofac.db",
|
||||
"ExportDirectory": "/git/cgsh-ofac/data/exports",
|
||||
"DownloadDirectory": "/git/cgsh-ofac/data/downloads"
|
||||
},
|
||||
"S3": {
|
||||
"BucketName": "uk-data-services-experimental-927681712454-eu-west-3-an",
|
||||
"AccessKeyId": "AKIA5P7RDSFDK5MSRN6P",
|
||||
"SecretAccessKey": "r6MjrnzRVlo8/tcUXhxT4YvOPhO1vV7wjwqr0UxH",
|
||||
"Region": "eu-west-3",
|
||||
"Prefix": "ofac"
|
||||
},
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.Extensions.Http": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/runtimes/linux-arm/native/libe_sqlite3.so
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/runtimes/linux-arm/native/libe_sqlite3.so
Executable file
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/runtimes/linux-arm64/native/libe_sqlite3.so
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/runtimes/linux-arm64/native/libe_sqlite3.so
Executable file
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/runtimes/linux-armel/native/libe_sqlite3.so
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/runtimes/linux-armel/native/libe_sqlite3.so
Executable file
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/runtimes/linux-mips64/native/libe_sqlite3.so
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/runtimes/linux-mips64/native/libe_sqlite3.so
Executable file
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/runtimes/linux-musl-arm/native/libe_sqlite3.so
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/runtimes/linux-musl-arm/native/libe_sqlite3.so
Executable file
Binary file not shown.
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/runtimes/linux-musl-x64/native/libe_sqlite3.so
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/runtimes/linux-musl-x64/native/libe_sqlite3.so
Executable file
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/runtimes/linux-ppc64le/native/libe_sqlite3.so
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/runtimes/linux-ppc64le/native/libe_sqlite3.so
Executable file
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/runtimes/linux-s390x/native/libe_sqlite3.so
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/runtimes/linux-s390x/native/libe_sqlite3.so
Executable file
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/runtimes/linux-x64/native/libe_sqlite3.so
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/runtimes/linux-x64/native/libe_sqlite3.so
Executable file
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/runtimes/linux-x86/native/libe_sqlite3.so
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/runtimes/linux-x86/native/libe_sqlite3.so
Executable file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/runtimes/osx-arm64/native/libe_sqlite3.dylib
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/runtimes/osx-arm64/native/libe_sqlite3.dylib
Executable file
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/runtimes/osx-x64/native/libe_sqlite3.dylib
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/runtimes/osx-x64/native/libe_sqlite3.dylib
Executable file
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/runtimes/win-arm/native/e_sqlite3.dll
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/runtimes/win-arm/native/e_sqlite3.dll
Executable file
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/runtimes/win-arm64/native/e_sqlite3.dll
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/runtimes/win-arm64/native/e_sqlite3.dll
Executable file
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/runtimes/win-x64/native/e_sqlite3.dll
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/runtimes/win-x64/native/e_sqlite3.dll
Executable file
Binary file not shown.
BIN
src/OFACScraper/bin/Debug/net8.0/runtimes/win-x86/native/e_sqlite3.dll
Executable file
BIN
src/OFACScraper/bin/Debug/net8.0/runtimes/win-x86/native/e_sqlite3.dll
Executable file
Binary file not shown.
Binary file not shown.
Binary file not shown.
39
src/OFACScraper/logs/ofac-20260409.log
Normal file
39
src/OFACScraper/logs/ofac-20260409.log
Normal file
@@ -0,0 +1,39 @@
|
||||
[2026-04-09 15:27:43 INF] OFAC Scraper starting. Command: ofac-daily
|
||||
[2026-04-09 15:27:43 INF] S3 configured: bucket=uk-data-services-experimental-927681712454-eu-west-3-an region=eu-west-3 prefix=ofac
|
||||
[2026-04-09 15:27:43 INF] Starting daily scrape for 2026
|
||||
[2026-04-09 15:27:43 INF] Scraping year 2026: https://ofac.treasury.gov/civil-penalties-and-enforcement-information/2026-enforcement-information
|
||||
[2026-04-09 15:27:43 INF] Start processing HTTP request GET https://ofac.treasury.gov/civil-penalties-and-enforcement-information/2026-enforcement-information
|
||||
[2026-04-09 15:27:43 INF] Sending HTTP request GET https://ofac.treasury.gov/civil-penalties-and-enforcement-information/2026-enforcement-information
|
||||
[2026-04-09 15:27:54 INF] Received HTTP response headers after 10959.9307ms - 404
|
||||
[2026-04-09 15:27:54 INF] End processing HTTP request after 10981.345ms - 404
|
||||
[2026-04-09 15:27:54 WRN] No page found for year 2026 (404)
|
||||
[2026-04-09 15:27:54 INF] Daily scrape complete. 0 new records exported.
|
||||
[2026-04-09 15:28:07 INF] OFAC Scraper starting. Command: ofac-daily
|
||||
[2026-04-09 15:28:08 INF] S3 configured: bucket=uk-data-services-experimental-927681712454-eu-west-3-an region=eu-west-3 prefix=ofac
|
||||
[2026-04-09 15:28:08 INF] Starting daily scrape for 2026
|
||||
[2026-04-09 15:28:08 INF] Scraping year 2026: https://ofac.treasury.gov/civil-penalties-and-enforcement-information
|
||||
[2026-04-09 15:28:08 INF] Start processing HTTP request GET https://ofac.treasury.gov/civil-penalties-and-enforcement-information
|
||||
[2026-04-09 15:28:08 INF] Sending HTTP request GET https://ofac.treasury.gov/civil-penalties-and-enforcement-information
|
||||
[2026-04-09 15:28:17 INF] Received HTTP response headers after 9491.3954ms - 200
|
||||
[2026-04-09 15:28:17 INF] End processing HTTP request after 9512.7974ms - 200
|
||||
[2026-04-09 15:28:17 INF] Year 2026: found 3 records
|
||||
[2026-04-09 15:28:18 INF] Start processing HTTP request GET https://ofac.treasury.gov/media/935351/download?inline
|
||||
[2026-04-09 15:28:18 INF] Sending HTTP request GET https://ofac.treasury.gov/media/935351/download?inline
|
||||
[2026-04-09 15:28:27 INF] Received HTTP response headers after 8354.172ms - 200
|
||||
[2026-04-09 15:28:27 INF] End processing HTTP request after 8354.6368ms - 200
|
||||
[2026-04-09 15:28:28 INF] S3 sync: 2 uploaded, 0 unchanged, 0 failed
|
||||
[2026-04-09 15:28:28 INF] Exported 20260317_tradestation → S3 (2 uploaded, 0 unchanged)
|
||||
[2026-04-09 15:28:28 INF] Start processing HTTP request GET https://ofac.treasury.gov/media/935041/download?inline
|
||||
[2026-04-09 15:28:28 INF] Sending HTTP request GET https://ofac.treasury.gov/media/935041/download?inline
|
||||
[2026-04-09 15:28:36 INF] Received HTTP response headers after 8463.9922ms - 200
|
||||
[2026-04-09 15:28:36 INF] End processing HTTP request after 8465.4544ms - 200
|
||||
[2026-04-09 15:28:37 INF] S3 sync: 2 uploaded, 0 unchanged, 0 failed
|
||||
[2026-04-09 15:28:37 INF] Exported 20260225_individual → S3 (2 uploaded, 0 unchanged)
|
||||
[2026-04-09 15:28:37 INF] Start processing HTTP request GET https://ofac.treasury.gov/media/935006/download?inline
|
||||
[2026-04-09 15:28:37 INF] Sending HTTP request GET https://ofac.treasury.gov/media/935006/download?inline
|
||||
[2026-04-09 15:28:47 INF] Received HTTP response headers after 10407.4459ms - 200
|
||||
[2026-04-09 15:28:47 INF] End processing HTTP request after 10408.1018ms - 200
|
||||
[2026-04-09 15:28:48 INF] S3 sync: 2 uploaded, 0 unchanged, 0 failed
|
||||
[2026-04-09 15:28:48 INF] Exported 20260212_img_academy → S3 (2 uploaded, 0 unchanged)
|
||||
[2026-04-09 15:28:48 INF] Year 2026: exported 3 new records
|
||||
[2026-04-09 15:28:48 INF] Daily scrape complete. 3 new records exported.
|
||||
@@ -0,0 +1,4 @@
|
||||
// <autogenerated />
|
||||
using System;
|
||||
using System.Reflection;
|
||||
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v8.0", FrameworkDisplayName = ".NET 8.0")]
|
||||
22
src/OFACScraper/obj/Debug/net8.0/OFACScraper.AssemblyInfo.cs
Normal file
22
src/OFACScraper/obj/Debug/net8.0/OFACScraper.AssemblyInfo.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: System.Reflection.AssemblyCompanyAttribute("OFACScraper")]
|
||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("OFACScraper")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("OFACScraper")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
// Generated by the MSBuild WriteCodeFragment class.
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
c881e2e82ffe2e57dac7d53046c22b3e0303d8180aa5c669e5d28fdd74a6b73f
|
||||
@@ -0,0 +1,13 @@
|
||||
is_global = true
|
||||
build_property.TargetFramework = net8.0
|
||||
build_property.TargetPlatformMinVersion =
|
||||
build_property.UsingMicrosoftNETSdkWeb =
|
||||
build_property.ProjectTypeGuids =
|
||||
build_property.InvariantGlobalization =
|
||||
build_property.PlatformNeutralAssembly =
|
||||
build_property.EnforceExtendedAnalyzerRules =
|
||||
build_property._SupportedPlatformList = Linux,macOS,Windows
|
||||
build_property.RootNamespace = OFACScraper
|
||||
build_property.ProjectDir = /git/cgsh-ofac/src/OFACScraper/
|
||||
build_property.EnableComHosting =
|
||||
build_property.EnableGeneratedComInterfaceComImportInterop =
|
||||
@@ -0,0 +1,8 @@
|
||||
// <auto-generated/>
|
||||
global using global::System;
|
||||
global using global::System.Collections.Generic;
|
||||
global using global::System.IO;
|
||||
global using global::System.Linq;
|
||||
global using global::System.Net.Http;
|
||||
global using global::System.Threading;
|
||||
global using global::System.Threading.Tasks;
|
||||
BIN
src/OFACScraper/obj/Debug/net8.0/OFACScraper.assets.cache
Normal file
BIN
src/OFACScraper/obj/Debug/net8.0/OFACScraper.assets.cache
Normal file
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
644869aaa0ef17c645a59f18b03edb4fc63846d651862b56b6b165ca9d6a8716
|
||||
@@ -0,0 +1,85 @@
|
||||
/git/cgsh-ofac/src/OFACScraper/obj/Debug/net8.0/OFACScraper.csproj.AssemblyReference.cache
|
||||
/git/cgsh-ofac/src/OFACScraper/obj/Debug/net8.0/OFACScraper.GeneratedMSBuildEditorConfig.editorconfig
|
||||
/git/cgsh-ofac/src/OFACScraper/obj/Debug/net8.0/OFACScraper.AssemblyInfoInputs.cache
|
||||
/git/cgsh-ofac/src/OFACScraper/obj/Debug/net8.0/OFACScraper.AssemblyInfo.cs
|
||||
/git/cgsh-ofac/src/OFACScraper/obj/Debug/net8.0/OFACScraper.csproj.CoreCompileInputs.cache
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/appsettings.json
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/OFACScraper
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/OFACScraper.deps.json
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/OFACScraper.runtimeconfig.json
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/OFACScraper.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/OFACScraper.pdb
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/AWSSDK.Core.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/AWSSDK.S3.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/Dapper.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/HtmlAgilityPack.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/Microsoft.Data.Sqlite.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Configuration.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Configuration.Abstractions.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Configuration.Binder.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Configuration.CommandLine.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Configuration.EnvironmentVariables.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Configuration.FileExtensions.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Configuration.Json.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Configuration.UserSecrets.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.DependencyInjection.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Diagnostics.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Diagnostics.Abstractions.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.FileProviders.Abstractions.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.FileProviders.Physical.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.FileSystemGlobbing.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Hosting.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Hosting.Abstractions.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Http.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Logging.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Logging.Abstractions.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Logging.Configuration.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Logging.Console.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Logging.Debug.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Logging.EventLog.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Logging.EventSource.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Options.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Options.ConfigurationExtensions.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/Microsoft.Extensions.Primitives.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/Serilog.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/Serilog.Extensions.Hosting.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/Serilog.Extensions.Logging.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/Serilog.Sinks.Console.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/Serilog.Sinks.File.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/SQLitePCLRaw.batteries_v2.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/SQLitePCLRaw.core.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/SQLitePCLRaw.provider.e_sqlite3.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/System.Diagnostics.EventLog.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/System.IO.Pipelines.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/System.Text.Encodings.Web.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/System.Text.Json.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/runtimes/browser-wasm/nativeassets/net8.0/e_sqlite3.a
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/runtimes/linux-arm/native/libe_sqlite3.so
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/runtimes/linux-arm64/native/libe_sqlite3.so
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/runtimes/linux-armel/native/libe_sqlite3.so
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/runtimes/linux-mips64/native/libe_sqlite3.so
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/runtimes/linux-musl-arm/native/libe_sqlite3.so
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/runtimes/linux-musl-arm64/native/libe_sqlite3.so
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/runtimes/linux-musl-x64/native/libe_sqlite3.so
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/runtimes/linux-ppc64le/native/libe_sqlite3.so
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/runtimes/linux-s390x/native/libe_sqlite3.so
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/runtimes/linux-x64/native/libe_sqlite3.so
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/runtimes/linux-x86/native/libe_sqlite3.so
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/runtimes/maccatalyst-arm64/native/libe_sqlite3.dylib
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/runtimes/maccatalyst-x64/native/libe_sqlite3.dylib
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/runtimes/osx-arm64/native/libe_sqlite3.dylib
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/runtimes/osx-x64/native/libe_sqlite3.dylib
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/runtimes/win-arm/native/e_sqlite3.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/runtimes/win-arm64/native/e_sqlite3.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/runtimes/win-x64/native/e_sqlite3.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/runtimes/win-x86/native/e_sqlite3.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/runtimes/win/lib/net8.0/System.Diagnostics.EventLog.Messages.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/runtimes/win/lib/net8.0/System.Diagnostics.EventLog.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/bin/Debug/net8.0/runtimes/browser/lib/net8.0/System.Text.Encodings.Web.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/obj/Debug/net8.0/OFACScra.02733415.Up2Date
|
||||
/git/cgsh-ofac/src/OFACScraper/obj/Debug/net8.0/OFACScraper.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/obj/Debug/net8.0/refint/OFACScraper.dll
|
||||
/git/cgsh-ofac/src/OFACScraper/obj/Debug/net8.0/OFACScraper.pdb
|
||||
/git/cgsh-ofac/src/OFACScraper/obj/Debug/net8.0/OFACScraper.genruntimeconfig.cache
|
||||
/git/cgsh-ofac/src/OFACScraper/obj/Debug/net8.0/ref/OFACScraper.dll
|
||||
BIN
src/OFACScraper/obj/Debug/net8.0/OFACScraper.dll
Normal file
BIN
src/OFACScraper/obj/Debug/net8.0/OFACScraper.dll
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user