Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 11 additions & 9 deletions app/DTOs/OccurrenceDTO.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ class OccurrenceDTO
{
/**
* @param int $occurrenceNo oid — PBDB occurrence number
* @param string $acceptedName tna — accepted taxon name
* @param string $acceptedRank rnk — accepted taxonomic rank
* @param string|null $acceptedName tna — accepted taxon name (absent for some PBDB records)
* @param string|null $acceptedRank rnk — accepted taxonomic rank (absent for some PBDB records)
* @param string|null $phylum phl — phylum
* @param string|null $class cll — class
* @param string|null $order odl — order
Expand All @@ -31,8 +31,8 @@ class OccurrenceDTO
*/
public function __construct(
public readonly int $occurrenceNo,
public readonly string $acceptedName,
public readonly string $acceptedRank,
public readonly ?string $acceptedName,
public readonly ?string $acceptedRank,
public readonly ?string $phylum,
public readonly ?string $class,
public readonly ?string $order,
Expand Down Expand Up @@ -104,14 +104,16 @@ private static function parseId(mixed $value): int
*/
public static function fromArray(array $data): static
{
$rawRank = $data['rnk'];
$acceptedRank = is_int($rawRank)
? (self::RANK_LABELS[$rawRank] ?? (string) $rawRank)
: (string) $rawRank;
$rawRank = $data['rnk'] ?? null;
$acceptedRank = match (true) {
$rawRank === null => null,
is_int($rawRank) => self::RANK_LABELS[$rawRank] ?? (string) $rawRank,
default => (string) $rawRank,
};

return new static(
occurrenceNo: self::parseId($data['oid']),
acceptedName: $data['tna'],
acceptedName: $data['tna'] ?? null,
acceptedRank: $acceptedRank,
phylum: $data['phl'] ?? null,
class: $data['cll'] ?? null,
Expand Down
2 changes: 1 addition & 1 deletion docs/pbdb-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ The concrete implementation of `FossilOccurrenceServiceInterface`. Injected with

A **readonly value object** representing a single fossil occurrence record. Created by `OccurrenceDTO::fromArray()` which maps PBDB's terse field codes (e.g. `oid`, `tna`, `phl`) to readable property names.

All fields except `occurrenceNo`, `acceptedName`, `acceptedRank`, and `collectionNo` are nullable — PBDB does not guarantee every field is present in every record.
All fields except `occurrenceNo` and `collectionNo` are nullable — PBDB does not guarantee every field is present in every record. `acceptedName` and `acceptedRank` are both nullable; when a taxon has not been entered into PBDB taxonomy (`tdf: 'taxon not entered'`), both `tna` and `rnk` are omitted. This is common in deep-time queries (e.g. Cambrian Seas and Great Dying presets).

The `paleolat` / `paleolng` properties are only populated when `paleoloc` is in the `show` parameter.

Expand Down
2 changes: 1 addition & 1 deletion resources/views/occurrences/show.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class="inline-flex items-center gap-1 text-sm text-muted hover:text-text transit
</h1>

<span class="inline-block rounded-full bg-accent-subtle border border-accent-muted px-2.5 py-0.5 text-xs font-medium text-accent mb-4">
{{ ucfirst($occurrence->acceptedRank) }}
{{ $occurrence->acceptedRank ? ucfirst($occurrence->acceptedRank) : 'Unknown' }}
</span>

@php
Expand Down
20 changes: 20 additions & 0 deletions tests/Unit/DTOs/OccurrenceDTOTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,26 @@ public function test_from_array_preserves_string_rank_unchanged(): void
$this->assertSame('clade', $dto->acceptedRank);
}

public function test_from_array_sets_null_rank_when_rnk_key_absent(): void
{
$record = $this->fullRecord;
unset($record['rnk']);

$dto = OccurrenceDTO::fromArray($record);

$this->assertNull($dto->acceptedRank);
}

public function test_from_array_sets_null_accepted_name_when_tna_key_absent(): void
{
$record = $this->fullRecord;
unset($record['tna']);

$dto = OccurrenceDTO::fromArray($record);

$this->assertNull($dto->acceptedName);
}

public function test_from_array_maps_paleolat_and_paleolng(): void
{
$record = array_merge($this->fullRecord, [
Expand Down
Loading