ver1-step2 implemented

This commit is contained in:
Jim Lancaster
2026-06-25 19:22:44 -05:00
parent d69e0db4df
commit e61f7e7518
14 changed files with 606 additions and 61 deletions
@@ -11,11 +11,11 @@ BOOK 1 had 54 pages; 14 chapters. BOOK 2 has 70 pages; 18 chapters. BOOK 1 con-
sisted largely of first generation family history. BOOK 2 throws more light on
the second generation. Sidney promises a BOOK 3 and that may begin to do justice
to the third generation. We suggest that Sidney get the help of Louis Shinn
who has a chapter in this book (Chapter 16 - The Last 25 Years on the Doumecq
who has a chapter in this book (Chapter 16 - The Last 25 Years on the Doumeeq
Plains. Louis has the gift of seeing, recalling and telling. One sentence in
his chapter gives a great tribute to the Doumecqers--so far as he knows no one
on the Doumecq Plains went on relief during the depression. That in a nutshell
shows the sturdy character of the residents of the Doumecq Plains.
his chapter gives a great tribute to the Doumeeqers - so far as he knows no one
on the Doumeeq Plains went on relief during the depression. That in a nutshell
shows the sturdy character of the residents of the Doumeeq Plains.
We promised in BOOK 1 that in BOOK 2 we would give the story of the trip of
John E. Cochran and wife to Tennessee, Cuba and the Panama Canal. You will see
@@ -18,6 +18,7 @@ the family newsletter two years ago.
Nome Alaska August 26, 1923
My Dear Ethel et al.
I don't know when I did write or when you did
but I am going to write now however and never
the less. But I wish I could talk (I can yet but I
@@ -26,6 +27,7 @@ and Polly sit up and listen and that little black
rascal of yours would fairly sparkle with
listening. Can't I see him listening now to all the
yarns we told last summer?
You see, we-Miss Saville and I, took a trip north
on the Buford and it was very interesting. We
went north thru the Bering Strait into the Arctic and as far as the Ice Pack. There the captain
@@ -36,18 +38,19 @@ along the icebergs and the walrus were 'heisted' on board by the use of the cran
tons of freight and the beasts were so huge that they made the pulleys just creak. They were
over 12 feet long, as big around as three cows, had no feet but toenails on their flippers or
flappers, no head but their body just suddenly ended with a hole for a mouth and big bristles
all around it similar to a currycomb in coarseness; no ears but huge tusks of ivory. They are
the most repulsive looking animals imaginable and tho I have always read about them I never
expect such disagreeable looking creatures. They had a rough brown hairy skin and some of
them looked warty. It must have weighed two ton at least. Ere we got them back to Nome
to the natives they were getting extremely odiferousin fact, you could scarcely stay on the
them looked warty. They must have weighed two ton at least. Ere we got them back to Nome
to the natives they were getting extremely odiferousin fact, you could scarcely stay on the
ship with any degree of comfort unless you had per chance lost your sense of smell.
Then we went north to a few minutes beyond the 70th degree of latitude and thot for awhile
we would go to Wrangell Island where some men from Steffonsons ship were supposed to be
stranded but we didn't get there and instead stopped at a small native village at Cape Serdz in
Then we went north to a few minutes beyond the 70th degree of latitude and thot [sic] for awhile
we would go to Wrangell Island where some men from Stefflonsons [sic] ship were supposed to be
stranded but we didn't get there and instead stopped at a small native village at Cape Serdz [sic] in
Siberia. These Eskimo were very primitive. One white squaw man lived there and had for 23
years. He was a Swede--who else could. Their houses were circular and built up with dirt 2 or
years. He was a Swedewho else could. Their houses were circular and built up with dirt 2 or
3 feet and then skins were stretched over it and weighted down with rocks. Inside, the room
was partitioned off at the sides with skins for sleeping quarters. In the main part they had the
fire on the ground and the fish drying on lines and the skins hanging around and the dogs and
@@ -62,24 +65,25 @@ The other place we stopped was at Whalen, a trading post in Siberia. There these
went wild. They rushed helter-skelter, hither and thither, here and there, trying to find
something to buy. Prices raised right before your eyes. One would but something for $1.00
and the next might have to pay $2.00, $4.00 or $10.00. That made no difference. They had to
have it. One man I was sort of taking care of, tho he had his son along for the purpose,
bought 2 ivory tusks, 1 pup, 2 moccasins, 3 or 4 billikens, 6 or 8 ivory and silver rings, one
have it. One man I was sort of taking care of, tho [sic] he had his son along for the purpose,
bought 2 ivory tusks, 1 pup, 2 moccasins, 3 or 4 billi[illegible]s, 6 or 8 ivory and silver rings, one
fishing line, hooks, floats, etc. and two bird slings. The slings have rocks at the end and the
little natives throw them at the flocks of geese and ducks which fly close over the village and
the slings entangle their wings and legs, sometimes more than one, and they can't fly. They
come down and the natives capture them. There was more junk brot aboard than baggage, I
come down and the natives capture them. There was more junk brot [sic] aboard than baggage, I
do believe. And they say that at the first stop it was worse than here. The red flag was flying
over Whalen and the Russian soldiers were therea few, one or two or three, I forget the
over Whalen and the Russian soldiers were therea few, one or two or three, I forget the
number.
We got home yesterday morning at 5 a.m. but missed the first lighter in so had to stay out
until 2:30. The girls had prepared a big meal for us and invited up the Hartfords and then let
us talk. Miss Saville talked quite a bit. Any how if you folks don't like this I don't care, it is
all I had to write about and I know Buster'ud listen anyway and I'd soak ole Peter's head if he
all I had to write about and I know Buster'd [sic] listen anyway and I'd soak ole Peter's head if he
didn't and Polly would in my lap and I don't know much about the youngest one of yours so
likely he would be squawling. But we did surely enjoy our trip and were gone just long enuf.
likely he would be squawling. But we did surely enjoy our trip and were gone just long enuf [sic].
I expect there were 150 passengers on board and almost or more of the crew and helpers. We
had a stateroom down next to the kitchen and 'twas plenty fierce for odor at times.
had a stateroom down next to the kitchen and 'twas pretty fierce for odor at times.
I have had jobs nearly all summer but not very much in them. Next week, September 4,
school opens. I wish they would wait for a week but you know these school men. Wouldn't
@@ -100,11 +104,13 @@ well this year. Even sent me a telegram a few weeks ago. Well, if anything else
I'll write again. Don't suppose it ever will, tho.
Lots of love to all,
Ome
Reprinted from Cochran Chronicles, Volume 9, Number 1, November 1986
© JECFA 1986
© [inserted: JECFA] 1986
Up
jecochranclan.org ~ Contact webmaster
@@ -8,23 +8,24 @@ ISBILL & MOSER
DEALERS IN
GENERAL MERCHANDISE
Vonore, Tenn., Jany 27 1913
Dear Uncle Aunt [Living?]
Was at home a
few nights ago & saw a
letter from you folks, So
I desired to write you
Vonore, Tenn., Jany 27- 1913
Dear Much Aunt Louie
How are you a
few nights ago I sewed a
letter from your folks, so
I decided to write you
a few lines myself ok
I am contemplateing a
trip out west next summer
& I want some of Elders to go
when I am [them?] .
Am getting
up in years & remarried,
So you see the object of
my trip, is to get a wife.
If there is any old maids
or widows out there, I
want you to have them
at my farm my at they
& I want Some Olders to go
where I and them.
I am getting
up in years & unmarried
so you see the object of
my trip, is to get a bunch
of Young & old maids
& widows out there. I
want you to kiss them
at my fans [sic] mug as they
as soon as I get there
+91 -5
View File
@@ -1,10 +1,13 @@
"""Tests for transcription.worker."""
from pathlib import Path
from threading import Event
import pytest
from sqlmodel import select
from transcription.config import Settings
from transcription.errors import AppError, ErrorCategory
from transcription.models import Document, Job, JobStatus, Transcript
from transcription.providers.base import TranscriptionResult
from transcription.worker import process_next_queued_job, run_worker_loop
@@ -37,7 +40,7 @@ class TestWorkerQueueBehavior:
class TestWorkerSuccessPath:
"""Verify worker success-path lifecycle transitions and transcript persistence."""
def test_transitions_processing_to_transcribed(self, session, monkeypatch):
def test_transitions_processing_to_transcribed(self, session, monkeypatch, tmp_path: Path):
"""process_next_queued_job transitions queued jobs to transcribed on success."""
job = _create_queued_job(session)
@@ -45,6 +48,10 @@ class TestWorkerSuccessPath:
return TranscriptionResult(text="ok", provider="openrouter", model="test-model")
monkeypatch.setattr("transcription.worker.transcribe_document_image", _fake_transcribe)
monkeypatch.setattr(
"transcription.worker.get_settings",
lambda: Settings(openrouter_api_key="test-key", prompt_dir=tmp_path),
)
processed = process_next_queued_job(session=session)
session.refresh(job)
@@ -52,7 +59,7 @@ class TestWorkerSuccessPath:
assert processed is True
assert job.status == JobStatus.TRANSCRIBED
def test_persists_transcript_text_on_success(self, session, monkeypatch):
def test_persists_transcript_text_on_success(self, session, monkeypatch, tmp_path: Path):
"""process_next_queued_job stores transcript text for successful jobs."""
job = _create_queued_job(session)
@@ -60,6 +67,10 @@ class TestWorkerSuccessPath:
return TranscriptionResult(text="Transcript body", provider="openrouter", model="test-model")
monkeypatch.setattr("transcription.worker.transcribe_document_image", _fake_transcribe)
monkeypatch.setattr(
"transcription.worker.get_settings",
lambda: Settings(openrouter_api_key="test-key", prompt_dir=tmp_path),
)
process_next_queued_job(session=session)
@@ -75,7 +86,7 @@ class TestWorkerSuccessPath:
class TestWorkerFailurePath:
"""Verify worker failure-path lifecycle transitions and error persistence."""
def test_sets_failed_and_error_detail_on_failure(self, session, monkeypatch):
def test_sets_failed_and_error_detail_on_failure(self, session, monkeypatch, tmp_path: Path):
"""process_next_queued_job marks failed and stores error detail on exception."""
job = _create_queued_job(session)
@@ -83,6 +94,10 @@ class TestWorkerFailurePath:
raise RuntimeError("provider failure")
monkeypatch.setattr("transcription.worker.transcribe_document_image", _fake_transcribe)
monkeypatch.setattr(
"transcription.worker.get_settings",
lambda: Settings(openrouter_api_key="test-key", prompt_dir=tmp_path),
)
processed = process_next_queued_job(session=session)
session.refresh(job)
@@ -99,7 +114,7 @@ class TestWorkerFailurePath:
assert "error_id=" in transcript.error_detail
assert "suggestion=" in transcript.error_detail
def test_updates_existing_transcript_if_present(self, session, monkeypatch):
def test_updates_existing_transcript_if_present(self, session, monkeypatch, tmp_path: Path):
"""process_next_queued_job updates existing transcript instead of duplicating."""
job = _create_queued_job(session)
existing = Transcript(job_id=job.id, text="old", error_detail=None)
@@ -111,6 +126,10 @@ class TestWorkerFailurePath:
raise RuntimeError("provider failure")
monkeypatch.setattr("transcription.worker.transcribe_document_image", _fake_transcribe)
monkeypatch.setattr(
"transcription.worker.get_settings",
lambda: Settings(openrouter_api_key="test-key", prompt_dir=tmp_path),
)
process_next_queued_job(session=session)
@@ -125,6 +144,73 @@ class TestWorkerFailurePath:
assert "error_id=" in transcripts[0].error_detail
@pytest.mark.integration
class TestWorkerRetryBehavior:
"""Verify worker retry and terminal failure policies."""
def test_retriable_failure_requeues_until_limit(self, session, monkeypatch, tmp_path: Path):
"""Retriable failures requeue jobs while retry budget remains."""
job = _create_queued_job(session)
def _fake_transcribe(_path):
raise AppError(
"temporary upstream outage",
category=ErrorCategory.EXTERNAL_PROVIDER,
suggestion="Retry from jobs page.",
retriable=True,
)
monkeypatch.setattr("transcription.worker.transcribe_document_image", _fake_transcribe)
monkeypatch.setattr(
"transcription.worker.get_settings",
lambda: Settings(
openrouter_api_key="test-key",
prompt_dir=tmp_path,
worker_max_retries=1,
worker_retry_backoff_seconds=0.0,
),
)
processed = process_next_queued_job(session=session)
session.refresh(job)
assert processed is True
assert job.status == JobStatus.QUEUED
assert job.retry_count == 1
def test_retriable_failure_exhaustion_sets_failed(self, session, monkeypatch, tmp_path: Path):
"""Retriable failures transition to failed when retry budget is exhausted."""
job = _create_queued_job(session)
job.retry_count = 1
session.add(job)
session.commit()
def _fake_transcribe(_path):
raise AppError(
"temporary upstream outage",
category=ErrorCategory.EXTERNAL_PROVIDER,
suggestion="Retry from jobs page.",
retriable=True,
)
monkeypatch.setattr("transcription.worker.transcribe_document_image", _fake_transcribe)
monkeypatch.setattr(
"transcription.worker.get_settings",
lambda: Settings(
openrouter_api_key="test-key",
prompt_dir=tmp_path,
worker_max_retries=1,
worker_retry_backoff_seconds=0.0,
),
)
process_next_queued_job(session=session)
session.refresh(job)
assert job.status == JobStatus.FAILED
assert job.retry_count == 1
@pytest.mark.unit
class TestWorkerLoopControl:
"""Verify worker loop start/stop behavior."""
@@ -136,7 +222,7 @@ class TestWorkerLoopControl:
called = {"value": False}
def _fake_process_next_queued_job():
def _fake_process_next_queued_job(**_kwargs):
called["value"] = True
return False
+10
View File
@@ -61,3 +61,13 @@ class TestPathSettings:
settings = _make_settings()
assert isinstance(settings.upload_dir, Path)
assert isinstance(settings.prompt_dir, Path)
class TestWorkerReliabilitySettings:
"""Verify worker retry settings defaults."""
def test_worker_retry_defaults(self):
"""worker retry settings default to no retries and no backoff."""
settings = _make_settings()
assert settings.worker_max_retries == 0
assert settings.worker_retry_backoff_seconds == 0.0
+1
View File
@@ -67,6 +67,7 @@ class TestJobModel:
doc = _persist_document(session)
job = _persist_job(session, doc)
assert job.status == JobStatus.QUEUED
assert job.retry_count == 0
assert job.created_at is not None
assert job.updated_at is not None