Drop stale evidence when the walkthrough seed is re-run
Evidence rows are keyed on the filename, so reassigning the in-flight batch's intakes left the old harvest_document row behind. Delete anything the case no longer references, mirroring the lineage cleanup in _batch.
This commit is contained in:
parent
88eede5d7a
commit
30d07dba41
1 changed files with 52 additions and 13 deletions
|
|
@ -173,7 +173,15 @@ class Command(BaseCommand):
|
|||
|
||||
# --------------------------------------------------------------------- steps
|
||||
|
||||
def _origin_chain(self, verifier: User) -> tuple[OriginZone, list[HarvestIntake]]:
|
||||
def _origin_chain(self, verifier: User) -> tuple[OriginZone, list[HarvestIntake], list[HarvestIntake]]:
|
||||
"""The zone, the groups, and two separate sets of harvest intakes.
|
||||
|
||||
Two sets, not one, because an intake is a finite quantity of shea: the
|
||||
completed batch and the in-flight batch each consume their own material.
|
||||
The command used to hand the same three intakes to both batches, which
|
||||
allocated 800 kg of an 820 kg intake twice over — invisible until the ops
|
||||
portal grew a lineage screen that refuses exactly that.
|
||||
"""
|
||||
facility, _ = Facility.objects.update_or_create(
|
||||
name="IXG Processing Facility - Katsina",
|
||||
defaults={
|
||||
|
|
@ -228,7 +236,13 @@ class Command(BaseCommand):
|
|||
("IXG-INTAKE-2026-0142", group_a, date(2026, 8, 27), Decimal("640.00"), "A"),
|
||||
("IXG-INTAKE-2026-0143", group_b, date(2026, 8, 29), Decimal("600.00"), "B"),
|
||||
]
|
||||
for ref, group, when, weight, grade in intake_specs:
|
||||
# The in-flight batch's own material, delivered the following week.
|
||||
inflight_specs = [
|
||||
("IXG-INTAKE-2026-0144", group_a, date(2026, 8, 30), Decimal("560.00"), "A"),
|
||||
("IXG-INTAKE-2026-0145", group_b, date(2026, 9, 1), Decimal("540.00"), "A"),
|
||||
("IXG-INTAKE-2026-0146", group_b, date(2026, 9, 3), Decimal("460.00"), "B"),
|
||||
]
|
||||
for ref, group, when, weight, grade in intake_specs + inflight_specs:
|
||||
intake, _ = HarvestIntake.objects.update_or_create(
|
||||
intake_reference=ref,
|
||||
defaults={
|
||||
|
|
@ -243,20 +257,27 @@ class Command(BaseCommand):
|
|||
},
|
||||
)
|
||||
intakes.append(intake)
|
||||
return zone, intakes
|
||||
return zone, intakes[: len(intake_specs)], intakes[len(intake_specs):]
|
||||
|
||||
def _batch(self, ref: str, intakes: list[HarvestIntake], enterer: User, **fields) -> ProcessingBatch:
|
||||
allocations = fields.pop("allocations", [Decimal("800.00"), Decimal("640.00"), Decimal("560.00")])
|
||||
batch, created = ProcessingBatch.objects.update_or_create(
|
||||
batch_reference=ref,
|
||||
defaults={"origin_facility": "ASI - Katsina State, Nigeria", "created_by": enterer, **fields},
|
||||
)
|
||||
allocations = [Decimal("800.00"), Decimal("640.00"), Decimal("560.00")]
|
||||
for intake, weight in zip(intakes, allocations):
|
||||
IntakeBatchLineage.objects.update_or_create(
|
||||
batch=batch,
|
||||
harvest_intake=intake,
|
||||
defaults={"weight_allocated_kg": weight},
|
||||
)
|
||||
# ...and drop anything this batch used to be linked to. Without this the
|
||||
# command is only idempotent while the chain does not change: re-running it
|
||||
# after a change leaves the old links in place, which is how both demo
|
||||
# batches came to be traced back to the same three intakes.
|
||||
IntakeBatchLineage.objects.filter(batch=batch).exclude(
|
||||
harvest_intake__in=[i.pk for i in intakes]
|
||||
).delete()
|
||||
return batch
|
||||
|
||||
def _qc(self, batch: ProcessingBatch, enterer: User, verifier: User, spec: ProductSpecification | None) -> QCResult:
|
||||
|
|
@ -495,11 +516,17 @@ class Command(BaseCommand):
|
|||
},
|
||||
)
|
||||
|
||||
evidence_files = [
|
||||
f"{zone.zone_code}-geolocation.json",
|
||||
f"{intakes[0].intake_reference}-weighbridge-ticket.pdf",
|
||||
"collector-group-register.pdf",
|
||||
"export-permit-and-land-use.pdf",
|
||||
]
|
||||
for etype, name in (
|
||||
(EUDREvidence.EvidenceType.GEOLOCATION, f"{zone.zone_code}-geolocation.json"),
|
||||
(EUDREvidence.EvidenceType.HARVEST_DOCUMENT, f"{intakes[0].intake_reference}-weighbridge-ticket.pdf"),
|
||||
(EUDREvidence.EvidenceType.SUPPLIER_IDENTITY, "collector-group-register.pdf"),
|
||||
(EUDREvidence.EvidenceType.LEGALITY, "export-permit-and-land-use.pdf"),
|
||||
(EUDREvidence.EvidenceType.GEOLOCATION, evidence_files[0]),
|
||||
(EUDREvidence.EvidenceType.HARVEST_DOCUMENT, evidence_files[1]),
|
||||
(EUDREvidence.EvidenceType.SUPPLIER_IDENTITY, evidence_files[2]),
|
||||
(EUDREvidence.EvidenceType.LEGALITY, evidence_files[3]),
|
||||
):
|
||||
EUDREvidence.objects.update_or_create(
|
||||
case=case, evidence_type=etype, file=name,
|
||||
|
|
@ -511,6 +538,12 @@ class Command(BaseCommand):
|
|||
"notes": "Synthetic evidence record for training.",
|
||||
},
|
||||
)
|
||||
# ...and drop anything this case used to carry. Evidence is keyed on the
|
||||
# filename, so reassigning the batch's intakes leaves the old
|
||||
# harvest_document row behind. Mirrors the lineage cleanup in _batch.
|
||||
EUDREvidence.objects.filter(case=case).exclude(
|
||||
file__in=evidence_files
|
||||
).delete()
|
||||
|
||||
RiskAssessment.objects.update_or_create(
|
||||
case=case, risk_level=DueDiligenceCase.RiskLevel.LOW,
|
||||
|
|
@ -616,8 +649,12 @@ class Command(BaseCommand):
|
|||
self.stderr.write("No ops user found - create one before seeding.")
|
||||
return
|
||||
|
||||
zone, intakes = self._origin_chain(verifier or enterer)
|
||||
self.stdout.write(f" origin: {zone.zone_code}, {len(intakes)} harvest intakes")
|
||||
zone, intakes, inflight_intakes = self._origin_chain(verifier or enterer)
|
||||
self.stdout.write(
|
||||
f" origin: {zone.zone_code}, "
|
||||
f"{len(intakes) + len(inflight_intakes)} harvest intakes "
|
||||
f"({len(intakes)} for {COMPLETED_BATCH}, {len(inflight_intakes)} for {INFLIGHT_BATCH})"
|
||||
)
|
||||
|
||||
# 1. completed happy path
|
||||
completed = self._batch(
|
||||
|
|
@ -636,9 +673,11 @@ class Command(BaseCommand):
|
|||
])
|
||||
self.stdout.write(f" {COMPLETED_BATCH}: delivered, QC pass, CoA published ({coa.sha256_hash[:12]}...), EUDR approved")
|
||||
|
||||
# 2. mid-flight
|
||||
# 2. mid-flight. Its own intakes: three intakes cannot be allocated into
|
||||
# two batches, and the lineage screen would refuse to save that.
|
||||
inflight = self._batch(
|
||||
INFLIGHT_BATCH, intakes, enterer,
|
||||
INFLIGHT_BATCH, inflight_intakes, enterer,
|
||||
allocations=[Decimal("540.00"), Decimal("520.00"), Decimal("440.00")],
|
||||
harvest_date=date(2026, 9, 4), weight_kg=Decimal("1500.00"),
|
||||
grade=ProcessingBatch.Grade.B, status=ProcessingBatch.Status.IN_TRANSIT,
|
||||
notes="Synthetic batch currently in transit - used to show work in progress.",
|
||||
|
|
@ -646,7 +685,7 @@ class Command(BaseCommand):
|
|||
qc2 = self._qc(inflight, enterer, verifier or enterer, spec)
|
||||
self._documents(inflight, qc2, enterer, verifier or enterer, date(2026, 9, 10),
|
||||
is_completed=False, coa_published=False)
|
||||
self._eudr(inflight, zone, intakes, enterer, verifier or enterer, approved=False)
|
||||
self._eudr(inflight, zone, inflight_intakes, enterer, verifier or enterer, approved=False)
|
||||
self._shipment(inflight, delivered=False)
|
||||
self._esg(inflight, [(date(2026, 9, 6), 27, 16, "Synthetic report: intake day observations.")])
|
||||
self.stdout.write(f" {INFLIGHT_BATCH}: in transit, CoA awaiting release, EUDR complete for review")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue