← fly.sas.vc · SACAA PPL(A) · operations manual

Eight subjects,
one routine

Every subject follows the same twelve steps. Two of the eight cannot use the audio at all and are marked accordingly. Ticks are saved in this browser, so close the tab whenever you like and pick up where you stopped.

Progress

0 of 0
paste into chat
 

What this method can and cannot do

Worth being honest about before you plan around it. The pipeline turns prose into a spoken retrieval drill, and photographs of question pages into marked papers. It cannot help with anything that lives on a chart, a graph or a flight computer, and there will be a residue in every subject that needs sitting down with the book.

SubjectAudioWhere the method falls short
Aircraft GeneralStrongSchematics, instrument diagrams, magneto and fuel system layouts
Principles of FlightStrongAnything shaped rather than stated — aerofoil geometry, airflow, lift and drag curves. 110 figures prepared; the plates carry what the audio cannot
Human PerformanceStrongVery little. The best fit of the eight
Aviation MeteorologyPartialMETAR and TAF decoding, synoptic charts, station circles
NavigationNoneNearly all of it. Chart work and the CRP‑5 are motor skills
Flight Performance and PlanningNoneGraph interpolation, mass and balance, fuel planning
Air LawStrongLittle, but the source must be current SA material
Restricted RadiotelephonyStrongThe practical test. Phraseology drills well; the live exchange does not
The two that will decide this Navigation and Flight Performance and Planning get nothing from any of the tooling above. They are the longest preparation and the likeliest to run you out of the eighteen-month window. Book desk hours for them now, in parallel with everything else, rather than after the ear subjects are finished.

Reference

▸IMG

Filing figures

schema

Three tools and one indirection. The tools straighten, clean and label a folder of photographs; the indirection keeps a hand-typed key between the questions and the files, so reprocessing an image never breaks anything that points at it.

The order, and why it is fixed

StageToolProduces
Straightenimages.htmlTwo clicks along a line that should be level, corners filled with borrowed paper, then crop
Cleanbatch.htmlWhole folder at once, treatment per image, out as a zip
Cataloguecatalogue_images.pymanifest.json, figures.json, review-images.md, upload.sh, and renamed copies
Uploadbash upload.shSync to s3://sasvc-aviation/ppl/images/<subject>/, immutable cache headers, no invalidation
Linklink_figures.pyReads the live glossary.json; writes figure-links.json, term-figures.json and review-links.md
Mergelink_figures.py --mergeReuses the reviewed proposals; writes a new glossary with figures and figure_captions added
Combinebuild_figures.pyEvery subject's figures.json into one figures.combined.json, with a diff against the live file
PublishSiteGrounddata/glossary.json, data/figures.json, and a bumped ?v= on js/glossary.js
Verifyverify_figures.pyLive glossary → figure keys → images on the CDN. Non-zero exit if any link is broken
Never reprocess after cataloguing Ids are the SHA-256 of the file contents. Re-crop, re-level or re-knock-out an image and it becomes a different id. Get the picture how you want it, then catalogue.

Why a key and not the hash

The hash is right for the cataloguer — it dedupes identical scans and it changes loudly when a file changes. It is wrong for hand-authoring, because nobody types 0720cac7ee77 into forty records correctly. So figures.json carries a readable key onto the hashed file, and everything else references the key. It is written for you, not by you:

{
  "pof-3-8-wing-aspect-ratio-comparison": {
    "src":     "https://d31aog40626hbr.cloudfront.net/ppl/images/principles-of-flight/…png",
    "alt":     "Diagram of aerofoil section definitions",
    "caption": "A diagram from the manual showing aerofoil section definitions",
    "ref":     "3-8",
    "chapter": "3 Drag", "chapter_no": 3, "figure_no": 8,
    "kind":    "schematic-diagram",
    "role":    "plate",
    "w": 1800, "h": 1200,
    "sha": "f2bc0d96e6a8"
  }
}

Reprocess that figure and one line changes. Every question and glossary entry keeps working. The sha is kept only so a file can be traced back to its manifest record.

Where a figure earns its place

Most figures belong to the glossary, not to a question — Principles of Flight is 103 plates against 7 question figures. A picture next to prose is decoration and does nothing for recall; a picture that carries something the words cannot, or that you have to work, is worth its place. Three roles:

RoleRendersUse for
plateBeside a glossary entryThe bulk of them. Anything spatial that prose describes badly — the aerofoil section, a frontal cross-section, the thunderstorm life cycle
stimulusAbove the stem, before answeringQuestions unanswerable without it — the synoptic chart, the SigWx chart, winds and temperatures
revealWith the explanation, after answeringA diagram that would hand over the answer if shown first
The reveal rule A labelled cloud-types diagram placed above “lenticular cloud appears as…” is not a question any more. If the figure carries the answer in its labels, it is a reveal. When in doubt, ask whether you could answer without reading the stem.

Plates in the glossary

An entry takes an optional figure, or an ordered series where understanding is built rather than shown, and a names list where the figure's value is its labels. Formulas belong here too, as entries in their own right with every symbol defined — the plate then carries the geometry those symbols refer to.

{ "term": "Aerofoil",
  "def": "A shape that makes much more lift than drag when air flows past it.",
  "series": ["pof-02-venturi",
             "pof-02-flat-plate",
             "pof-02-aerofoil-development",
             "pof-02-aerofoil-definitions"],
  "figureRole": "plate",
  "names": ["leading edge","trailing edge","chord line","camber"] }
Why a series and not a plate A reader who cannot picture an aerofoil is not helped by a labelled cross-section — that labels a shape they still cannot see. Venturi, then flat plate at an angle, then the development of the section, then the definitions: each one answers the question the previous one raises. Four images in order teach what one cannot.
Two captions, and they are not interchangeable catalogue_images.py writes a caption that describes the image, which is correct for a manifest and useless in a glossary. The glossary caption — plain English, short, aimed at a reader whose English vocabulary may be small — is written by link_figures.py and lands in figure_captions on the entry itself. Keep the manifest alt for the screen-reader text.

What the merge writes

Two new fields on entries that gained a figure, and nothing else. Existing fields are never altered, no entry is added or removed, and the version is bumped:

{ "id": "chord-line",
  "term": "Chord line",
  "short": "…", "long": "…", "see_also": [...],
  "figures": ["pof-aerofoil-section-definitions",
              "pof-aerofoil-line-of-zero-lift"],
  "figure_captions": {
    "pof-aerofoil-section-definitions": "The side view of a wing. Look at the curve on top."
  } }

How the tray renders them

js/glossary.js fetches data/figures.json separately from the glossary and tolerates its absence, so a deployment without it still shows every definition. Plates render after the long text and before See also. Several plates on one entry are numbered as a progression; a single plate is labelled with its book reference. Images carry mix-blend-mode:multiply, which drops the white background of a lightened chart into the cream page so it sits like a knocked-out diagram rather than on a white card. Tapping an image lifts it to full size, because detail in a scanned diagram is often finer than the tray is wide.

When a photograph will not do

Where the concept is movement — relative airflow meeting the leading edge and dividing, the different path over the upper and lower surface, downwash, a thickening boundary layer — a cleaned photograph of a book page carries it poorly at any resolution. The page is a still, and the thing being taught is not. Those are worth redrawing as inline SVG with arrows, exactly as figure-dependent question diagrams already are. Send the plate to the chat and say what needs to move.

Figures in questions

{ "chapter": 17, "number": 5,
  "figure": "met-17-synoptic-1", "figureRole": "stimulus",
  "stem": "The height of the ceiling reported by Port Elizabeth is:",
  "options": [...], "answer": "c", "why": "…" }

The label drill

Where a figure's value is its labels, store two versions — the Avex original, and the same image with the labels painted out in clean.html. The drill shows the blank one and asks for the parts; the answer reveals the labelled one.

{ "key": "pof-02-aerofoil-definitions",
  "blank": "pof-02-aerofoil-definitions-blank",
  "names": ["leading edge","trailing edge","chord line","camber"] }

Naming four parts of an aerofoil from a blank drawing is genuine retrieval; looking at a labelled one is not. One extra image per diagram and no new tooling.

Figures and the audio

A deck item may carry a figure key. The renderer ignores it entirely — nothing changes about the MP3 or the cost. listen.html shows it beside that item in the transcript, so the run stays audio-only and the review afterwards has the picture attached to the fact. This is the cheapest of the three: the deck items already exist and are already tagged by section.

This one is not drop-in Adding a subject needs no code changes. Adding figures does — exam.html must render stimulus and reveal at the right moments, listen.html must show plates beside transcript items, and both must resolve keys through data/figures.json. Budget for that before filing forty figures against a renderer that cannot show them yet.
Keep the two slug lists in step catalogue_images.py carries its own subject list, and --subject is rejected if it is not on it. It now matches the slugs in data/subjects.json exactly, including flight-performance-planning and radiotelephony. If a slug ever changes here, change it there in the same sitting, or the catalogue step stops with an error naming the list.
▸✓

Setup — already done

complete

Kept for reference and for rebuilding on another machine. Nothing here needs doing again.

The site

Static files in public_html: index.html, exam.html, listen.html, manual.html, audio-sampler.html, sw.js, .htaccess, data/, audio/. Adding a subject means writing paper JSON into data/papers/ and listing the ids in data/subjects.json — no code changes.

The toolchain

brew install ffmpeg ocrmypdf awscli jq

cd ~/ppl
python3 -m venv venv          # only if ~/ppl/venv does not exist yet
source venv/bin/activate
pip install anthropic requests pydub pypdf numpy pillow

Scripts live in ~/ppl with the virtualenv: socratic_audio.py, clean_ocr.py, scan_source.py, audit_deck.py, sections.py, try_voices.py, probe.py, check_sample.py, compare_voice.py, publish_audio.sh, setup_bucket.sh, catalogue_images.py, link_figures.py, verify_figures.py, build_figures.py, prune_deck.py, preflight.py, reconcile_s3.py, add_terms.py, probe_api.py.

Keys, voices and storage

export ANTHROPIC_API_KEY=sk-ant-...
export ELEVENLABS_API_KEY=...
export ASK_VOICE_ID=...        # daughter, asks the questions
export ANSWER_VOICE_ID=...     # John, gives the answers
export FLY_BUCKET=sasvc-aviation
export FLY_PREFIX=ppl/audio
export FLY_REGION=us-east-1
export FLY_DISTRIBUTION=E2ALRGRER9YSF0
export FLY_AUDIO_BASE=https://d31aog40626hbr.cloudfront.net/ppl/audio

Images go to the same bucket under ppl/images/<subject>/ and are served from https://d31aog40626hbr.cloudfront.net/ppl/images/. That destination is fixed inside catalogue_images.py rather than set here, because it never varies — the generated upload.sh and the URLs in figures.json both come from it.

In ~/.zshrc. The ElevenLabs key needs Write on both Voices and Text to Speech. Model eleven_flash_v2_5, profile v3 — stability 0.50, similarity 0.85, style 0.10, speed 0.95 — found by scoring samples in audio-sampler.html, not by reasoning.

Never delete a cloned voice Deleting one orphans every cached clip made with it. Human Performance can no longer be re-synthesised at all because its voices were removed to free slots.

Naming

<slug>[_drill][-pNN].mp3 on the CDN, transcript of the same stem beside it, <slug>[_drill]-index.json for the part list. MP3s to S3; transcripts to SiteGround, because the page fetches those with JavaScript and that needs same-origin.