Add critical result curation metadata
This commit is contained in:
@@ -102,11 +102,23 @@ One record per lookup cell:
|
|||||||
|
|
||||||
This stores:
|
This stores:
|
||||||
|
|
||||||
|
- `is_curated`
|
||||||
- `raw_cell_text`
|
- `raw_cell_text`
|
||||||
- `description_text`
|
- `description_text`
|
||||||
- `raw_affix_text`
|
- `raw_affix_text`
|
||||||
- `parsed_json`
|
- `parsed_json`
|
||||||
- parse status / source metadata
|
- `parse_status`
|
||||||
|
- `source_page_number`
|
||||||
|
- `source_image_path`
|
||||||
|
- `source_image_crop`
|
||||||
|
|
||||||
|
`is_curated` is an explicit workflow flag. Once a result is curated in the web editor, later importer runs must preserve curator-owned content instead of replacing the row wholesale.
|
||||||
|
|
||||||
|
The source-image fields keep importer provenance separate from the editor snapshot stored in `parsed_json`:
|
||||||
|
|
||||||
|
- `source_page_number` points to the rendered PDF page used for review
|
||||||
|
- `source_image_path` stores the importer-managed relative PNG path for the cell crop
|
||||||
|
- `source_image_crop` stores the crop geometry that produced the PNG and can be used for debugging alignment problems
|
||||||
|
|
||||||
### 6. `critical_branch`
|
### 6. `critical_branch`
|
||||||
|
|
||||||
@@ -284,6 +296,7 @@ Current curation flow:
|
|||||||
- base raw cell text
|
- base raw cell text
|
||||||
- curated prose / description
|
- curated prose / description
|
||||||
- raw affix text
|
- raw affix text
|
||||||
|
- curated state
|
||||||
- parse status
|
- parse status
|
||||||
- parsed JSON
|
- parsed JSON
|
||||||
- nested `critical_branch` rows
|
- nested `critical_branch` rows
|
||||||
@@ -293,6 +306,7 @@ Current curation flow:
|
|||||||
The corresponding API endpoints are:
|
The corresponding API endpoints are:
|
||||||
|
|
||||||
- `GET /api/tables/critical/{slug}/cells/{resultId}`
|
- `GET /api/tables/critical/{slug}/cells/{resultId}`
|
||||||
|
- `GET /api/tables/critical/{slug}/cells/{resultId}/source-image`
|
||||||
- `PUT /api/tables/critical/{slug}/cells/{resultId}`
|
- `PUT /api/tables/critical/{slug}/cells/{resultId}`
|
||||||
|
|
||||||
The save operation replaces the stored branches and effects for that cell with the submitted payload. That keeps manual edits deterministic and avoids trying to reconcile partial child-row diffs against importer-generated data.
|
The save operation replaces the stored branches and effects for that cell with the submitted payload and updates the explicit curated flag. Importer-managed source provenance can still be refreshed on later imports without overwriting curated content.
|
||||||
|
|||||||
@@ -52,12 +52,15 @@ create table critical_result (
|
|||||||
critical_group_id bigint references critical_group(id) on delete cascade,
|
critical_group_id bigint references critical_group(id) on delete cascade,
|
||||||
critical_column_id bigint not null references critical_column(id) on delete cascade,
|
critical_column_id bigint not null references critical_column(id) on delete cascade,
|
||||||
critical_roll_band_id bigint not null references critical_roll_band(id) on delete cascade,
|
critical_roll_band_id bigint not null references critical_roll_band(id) on delete cascade,
|
||||||
|
is_curated boolean not null default false,
|
||||||
raw_cell_text text not null,
|
raw_cell_text text not null,
|
||||||
description_text text,
|
description_text text,
|
||||||
raw_affix_text text,
|
raw_affix_text text,
|
||||||
parsed_json jsonb not null default '{}'::jsonb,
|
parsed_json jsonb not null default '{}'::jsonb,
|
||||||
parse_status text not null default 'raw' check (parse_status in ('raw', 'partial', 'parsed', 'verified')),
|
parse_status text not null default 'raw' check (parse_status in ('raw', 'partial', 'parsed', 'verified')),
|
||||||
source_bbox jsonb,
|
source_page_number integer,
|
||||||
|
source_image_path text,
|
||||||
|
source_image_crop jsonb,
|
||||||
created_at timestamptz not null default now()
|
created_at timestamptz not null default now()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ public sealed class RolemasterDbContext(DbContextOptions<RolemasterDbContext> op
|
|||||||
{
|
{
|
||||||
entity.HasIndex(item => new { item.CriticalTableId, item.CriticalGroupId, item.CriticalColumnId, item.CriticalRollBandId }).IsUnique();
|
entity.HasIndex(item => new { item.CriticalTableId, item.CriticalGroupId, item.CriticalColumnId, item.CriticalRollBandId }).IsUnique();
|
||||||
entity.Property(item => item.ParseStatus).HasMaxLength(32);
|
entity.Property(item => item.ParseStatus).HasMaxLength(32);
|
||||||
|
entity.Property(item => item.SourceImagePath).HasMaxLength(512);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity<CriticalBranch>(entity =>
|
modelBuilder.Entity<CriticalBranch>(entity =>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ public static class RolemasterDbSchemaUpgrader
|
|||||||
public static async Task EnsureLatestAsync(RolemasterDbContext dbContext, CancellationToken cancellationToken = default)
|
public static async Task EnsureLatestAsync(RolemasterDbContext dbContext, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
await EnsureAttackTableFumbleColumnsAsync(dbContext, cancellationToken);
|
await EnsureAttackTableFumbleColumnsAsync(dbContext, cancellationToken);
|
||||||
|
await EnsureCriticalResultCurationColumnsAsync(dbContext, cancellationToken);
|
||||||
|
|
||||||
await dbContext.Database.ExecuteSqlRawAsync(
|
await dbContext.Database.ExecuteSqlRawAsync(
|
||||||
"""
|
"""
|
||||||
@@ -91,6 +92,49 @@ public static class RolemasterDbSchemaUpgrader
|
|||||||
cancellationToken);
|
cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static async Task EnsureCriticalResultCurationColumnsAsync(RolemasterDbContext dbContext, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (!await ColumnExistsAsync(dbContext, "CriticalResults", "IsCurated", cancellationToken))
|
||||||
|
{
|
||||||
|
await dbContext.Database.ExecuteSqlRawAsync(
|
||||||
|
"""
|
||||||
|
ALTER TABLE "CriticalResults"
|
||||||
|
ADD COLUMN "IsCurated" INTEGER NOT NULL DEFAULT 0;
|
||||||
|
""",
|
||||||
|
cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!await ColumnExistsAsync(dbContext, "CriticalResults", "SourcePageNumber", cancellationToken))
|
||||||
|
{
|
||||||
|
await dbContext.Database.ExecuteSqlRawAsync(
|
||||||
|
"""
|
||||||
|
ALTER TABLE "CriticalResults"
|
||||||
|
ADD COLUMN "SourcePageNumber" INTEGER NULL;
|
||||||
|
""",
|
||||||
|
cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!await ColumnExistsAsync(dbContext, "CriticalResults", "SourceImagePath", cancellationToken))
|
||||||
|
{
|
||||||
|
await dbContext.Database.ExecuteSqlRawAsync(
|
||||||
|
"""
|
||||||
|
ALTER TABLE "CriticalResults"
|
||||||
|
ADD COLUMN "SourceImagePath" TEXT NULL;
|
||||||
|
""",
|
||||||
|
cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!await ColumnExistsAsync(dbContext, "CriticalResults", "SourceImageCropJson", cancellationToken))
|
||||||
|
{
|
||||||
|
await dbContext.Database.ExecuteSqlRawAsync(
|
||||||
|
"""
|
||||||
|
ALTER TABLE "CriticalResults"
|
||||||
|
ADD COLUMN "SourceImageCropJson" TEXT NULL;
|
||||||
|
""",
|
||||||
|
cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static async Task EnsureAttackTableFumbleColumnsAsync(RolemasterDbContext dbContext, CancellationToken cancellationToken)
|
private static async Task EnsureAttackTableFumbleColumnsAsync(RolemasterDbContext dbContext, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (!await ColumnExistsAsync(dbContext, "AttackTables", "FumbleMinRoll", cancellationToken))
|
if (!await ColumnExistsAsync(dbContext, "AttackTables", "FumbleMinRoll", cancellationToken))
|
||||||
|
|||||||
@@ -7,11 +7,15 @@ public sealed class CriticalResult
|
|||||||
public int? CriticalGroupId { get; set; }
|
public int? CriticalGroupId { get; set; }
|
||||||
public int CriticalColumnId { get; set; }
|
public int CriticalColumnId { get; set; }
|
||||||
public int CriticalRollBandId { get; set; }
|
public int CriticalRollBandId { get; set; }
|
||||||
|
public bool IsCurated { get; set; }
|
||||||
public string RawCellText { get; set; } = string.Empty;
|
public string RawCellText { get; set; } = string.Empty;
|
||||||
public string DescriptionText { get; set; } = string.Empty;
|
public string DescriptionText { get; set; } = string.Empty;
|
||||||
public string? RawAffixText { get; set; }
|
public string? RawAffixText { get; set; }
|
||||||
public string ParsedJson { get; set; } = "{}";
|
public string ParsedJson { get; set; } = "{}";
|
||||||
public string ParseStatus { get; set; } = "verified";
|
public string ParseStatus { get; set; } = "verified";
|
||||||
|
public int? SourcePageNumber { get; set; }
|
||||||
|
public string? SourceImagePath { get; set; }
|
||||||
|
public string? SourceImageCropJson { get; set; }
|
||||||
public CriticalTable CriticalTable { get; set; } = null!;
|
public CriticalTable CriticalTable { get; set; } = null!;
|
||||||
public CriticalGroup? CriticalGroup { get; set; }
|
public CriticalGroup? CriticalGroup { get; set; }
|
||||||
public CriticalColumn CriticalColumn { get; set; } = null!;
|
public CriticalColumn CriticalColumn { get; set; } = null!;
|
||||||
|
|||||||
Reference in New Issue
Block a user