Mitä uutta?
Tälle sivulle on koottu palvelun versiohistoria, uudet ominaisuudet ja keskeiset parannukset.
Automaattitestit ajetaan kerran: vain pull requestissa, ei enää uudelleen mainiin pushattaessa.
- Push-ajo pois
tests.yml:stä jadocs-check.yml:stä. PR-ajo testaa jo merge-refin eli yhdistetyn tuloksen, joten mergen jälkeinen main-push-ajo testasi saman koodin toiseen kertaan. 1.–22.9.2026 main-push-ajoja oli 47, joista 41 PR-mergejä ja loput käsin pushattuja dokumenttipäivityksiä, yhteensä 47 Actions-minuuttia. Viikkoajon commit ei käynnistänyt push-ajoa muutenkaan (H21). Mainin voi yhä testata käsin (workflow_dispatch, käytössä molemmissa työnkuluissa). - Sivustoon ja uutiskirjeeseen muutos ei vaikuta.
Tietosuojalinkki jokaisen sivun alatunnisteeseen ja tiivis tietosuojaseloste.
- Löydettävyys. Jokaisen sivun alatunnisteessa on linkki Tietosuoja, joka vie Tietoja-sivun Tietosuoja-osioon: etusivu, Tietoja, Tilaa, Mitä uutta?, 404-sivu, tilausfunktioiden sivut ja koostesivut. Julkaistuihin koosteisiin lisättiin vain tämä linkki; niitä ei renderöity uudelleen (päätös 9), joten niiden versio, fontit ja aikaleimat säilyvät. Tilauslomakkeessa on linkki selosteeseen keräyshetkellä.
- Seloste yhteen paikkaan. Tietosuojatiedot olivat kahdessa paikassa: rekisterinpitäjä Tietoja-sivulla, muu Tilaa-sivulla. Seloste on nyt Tietoja-sivun Tietosuoja-osio, ja Tilaa-sivulla on lyhyt ilmoitus rekisterinpitäjästä ja tarkoituksesta sekä linkki selosteeseen. Omistajan linjauksen (22.9.2026) mukaan seloste on tiivis eikä kerro teknisiä yksityiskohtia.
- Korjatut väitteet. Seloste sanoi palvelun keräävän ainoastaan sähköpostiosoitteen, mutta myös erikoisalavalinnat tallennetaan tilaajalistaan, ja lähetyslokiin kirjataan, kenelle kooste on lähetetty. Säilytysajaksi luvattiin "kunnes peruutat tilauksen", mutta peruutus merkitsee osoitteen postituspalvelussa peruutetuksi eikä poista sitä. Vastaanottajista puuttuivat sivuston isännöinti (Vercel) ja viikkoajo (GitHub). Tilaa-sivun "ennen sen avaamista osoitettasi ei tallenneta mihinkään" on nyt "ei lisätä tilaajalistalle", koska vahvistusviesti lähtee postituspalvelun kautta. Lisäksi seloste kertoo nyt EU:n ulkopuoliset siirrot, rekisteröidyn oikeudet ja valitusoikeuden, jotka siitä puuttuivat.
Tietosuojaosio nimeää rekisterinpitäjän.
- Tilaa-sivun Tietosuoja-osio sanoi rekisterinpitäjäksi "sivuston ylläpitäjä (yhteystiedot Tietoja-sivulla)". Viittaus osoitti tyhjään:
site/tietoja.htmlei sisältänyt yhteystietoja eikä sähköpostiosoitetta lainkaan. Rekisterinpitäjä on nyt nimetty — CCode, y-tunnus 3649639-4 — ja yhteysosoitteena on uutiskirjeen lähettäjäosoiteuutiskirje@pediatrics.fi. Sama tieto lisättiin Tietoja-sivulle omaksi osiokseen, joten viittaus pitää nyt paikkansa molempiin suuntiin. Pelkkä nimi ei yksilöi yhtiötä, joten y-tunnus on mukana (GDPR 13 art. 1 a).
Toiminnallisuuskierros: idempotenssi, valvonnan havaintokynnys ja yksi totuuslähde erikoisaloille.
- Pelastusajo ei enää postita uudelleen jo palvelluille (A1).
save_senttiivisti listan, joka oli jo tiivistetty:main()kutsuu sitä muodossaalready + sent, jossaalreadytulee lokista. Vanhat rivit muuttuivat tiivisteen tiivisteiksi, eikächoose_recipientsenää tunnistanut heitä — toinen ajo samalle päivälle olisi lähettänyt kirjeen kahdesti. Reprodusoitu ja korjattu (log_keyon idempotentti,hash_emailei). Vika syntyi v1.22.0:ssa eikä ehtinyt laueta: v1.22.0:n jälkeen ei ole tehty pelastusajoa, ja 13.9. lokin rivit vastaavat yhämessage_ids-avaimia. - Puuttuva kirje on havainto, ei varoitus (A2, H34). Jos kirje meni kolmelle neljästä, lähetin palautti 0, viikkoajo nieli koodin ja tuoreusvalvonta tulosti varoituksen, jota workflow ei edes annotoinut. Nyt tarkistus erottaa lähetyksen jälkeen liittyneen (lokin
queued_atvs. kontaktin luontihetki) aidosti palvelematta jääneestä, ja jälkimmäinen laukaisee saman hälytyksen kuin puuttuva kooste. Tuntematon liittymishetki lasketaan havainnoksi: väärä hälytys maksaa yhden viestin, väärä hiljaisuus viikon kirjeen. - Uudelleentilaajan tuoreet valinnat tallentuvat (A3). Peruuttanut, joka tilasi uudelleen eri erikoisaloilla, sai vanhan valintansa mukaisen kirjeen, vaikka vahvistussivu kertoi tilauksen onnistuneen. Vahvistuslinkin toistosuoja (AT9) koskee nyt vain aktiivista tilaajaa — peruuttaneen uudelleentilaus on tuore, allekirjoitettu suostumus juuri niillä aloilla, jotka hän valitsi. Tämä kumoaa aiemman päätöksen, jonka mukaan kategorioita ei ylikirjoiteta missään tilanteessa.
- Viikkoajon idempotenssia vartioidaan suorittamalla (A4). Kaksi vartijaa tarkisti vain, että tietyt merkkijonot ovat tiedostossa. Todiste: injektio, joka teki koko lohkosta kuollutta koodia merkkijonoja koskematta, meni vanhoista testeistä läpi. Kuusi uutta testiä poimii lohkon ja ajaa sen telineessä (sama tekniikka kuin tuoreusvalvonnan askeleella): pelastusajo, käsitelty kirje, yksi lippu, kaksi lippua, uusi päivä ja rikkinäinen manifesti.
- Rikkinäinen manifesti pysäyttää ajon (A7). Lukuvirhe meni samaan haaraan kuin "päivää ei ole julkaistu", joten se olisi johtanut jo julkaistun koosteen uudelleengenerointiin — sama vikaluokka kuin 30.8.2026. Nyt lukuvirhe on oma tilansa ja vaatii
FORCE_REGENERATE=1. - Yksi totuuslähde erikoisaloille (A5). Suodatus on aina toiminut koosteen
###-otsikoista. Mallin kirjoittamaa[kategoria: X]-leimaa ei lukenut renderöinnin jälkeen mikään, jafetch_articles.py:n MeSH-luokittelijan tulosta ei lukenut yksikään moduuli. Molemmat poistettu;config/categories.jsonjää taksonomiaksi, ja uusi vartija kaatuu, jos kehotteen otsikot, taksonomia ja suodattimen tunnistus eriytyvät. Tunnistamaton otsikko menee yhä kaikille (epäonnistuu auki), joten vartija on se mekanismi, joka pitää personoinnin elossa. - tekoälymallin uusinnat mahtuvat työn aikakattoon (A6). Neljä 600 sekunnin kutsua teki pahimmasta tapauksesta ~40 min, kun workflow'n katto on 30 min: runner olisi tappanut työn kesken uusinnan, jolloin bashin EXIT-käsittelijä ei suoritu eikä
/fail-pingiä lähde. Aikakatto on nyt 300 s ja testi vartioi suhteen. - Todennus. 299 testiä läpi; kuusi vikainjektiota, joista jokainen kaatoi oman vartijansa.
Lähdeluettelon otsikot merkitään englanninkielisiksi.
- Koostesivun lähdeluettelossa on PubMedin englanninkieliset otsikot suomenkielisen sivun sisällä. Ilman
lang="en"-merkintää ruudunlukija lausuu ne suomen ääntämyssäännöillä (WCAG 3.1.2, osan kieli). Lehden nimi, vuosi ja kirjoittajat jäävät merkitsemättä: ne ovat erisnimiä. Näkyy seuraavasta viikkoajosta alkaen — vanhoja koosteita ei renderöidä uudelleen (päätös 9).
Käytettävyyskierroksen korjaukset: kontrastit, 404-sivu, tagitettu PDF ja selkeämmät virhepolut.
- Erottimen kontrasti (A3). Metatietojen
·oli 1,35–1,66:1 jokaisella sivulla molemmissa teemoissa — vaatimus on 4,5:1. Se on nyt samaa sävyä kuin ympäröivä metateksti (5,3:1). Merkin ympärille lisättiin myös oikea välilyönti, joten ruudunlukija ei enää lue "sunnuntaisin·PDF" yhtenä sanana. - Sivuston tunnus luettavissa (A9). Tunnuksen osien väli oli pelkkä marginaali, joten linkin nimi oli "Lastentauditviikkokooste". Väli on nyt merkkauksessa.
- Oma 404-sivu (A4). Rikkinäinen linkki vei Vercelin englanninkieliselle tekstisivulle, jolla ei ollut navigaatiota eikä paluupolkua. Uusi sivu on sivuston näköinen ja tarjoaa neljä reittiä eteenpäin.
- PDF on tagitettu (A5).
pdf_variant="pdf/ua-1"tuottaa rakennepuun (otsikot, kappaleet, listat, linkit),/MarkInfo-merkinnän jaDisplayDocTitlen. Ruudunlukija ei enää arvaa lukujärjestystä. Mitattu: teksti ja sivumäärä ennallaan, koko +24 kt. - Tulostus tummasta tilasta (A11).
@media printunohti kaksi muuttujaa, joten tummaa tilaa käyttävän tulosteessa painikkeen teksti oli 2,78:1 ja osiotunniste 2,63:1 valkoisella paperilla. - Tilausfunktioiden sivut vastaavat sivustoa (A6). Vahvistus-, peruutus- ja asetussivuilta puuttui hyppylinkki ja koko alatunniste; ne olivat umpikujia.
- Virhepolku ei hukkaa lomaketta (A7). "Valitse vähintään yksi erikoisala" neuvoi linkillä tyhjälle tilaussivulle, jolloin osoite ja 13 valintaa piti syöttää uudelleen. Nyt neuvo on paluupainike, kuten asetussivulla jo oli. Nopeusrajoituksen sivu kertoo myös jäähyn pituuden (A17).
- Kuittaus vastaa valintaa (A12), arkiston yhdeksän "PDF"-linkkiä sai erottuvan nimen (A10), uutiskirjeen runkoon tuli kielimääre (A16), ja hunajapurkin hylkäys kirjataan maskattuna lokiin (A14) — aiemmin väärä positiivinen oli näkymätön sekä käyttäjälle että ylläpitäjälle.
- Navigaatio kestää kirjainvälien kasvatuksen (A15): 320 px:n näytöllä se vei aiemmin 335 px (WCAG 1.4.12); nyt se rivittyy.
- Todennus. axe-core: 0 rikkomusta kuudella sivulla molemmissa teemoissa (aiemmat "incomplete"-solmut mitattiin käsin — ne olivat juuri ne, jotka alittivat rajan). Kontrasti mitattuna selaimesta, tekstivälit ja kohdekoot mitattuina, neljä vikainjektiota.
Kategorialeiman poisto ei enää jätä koodijaksoa koosteeseen.
- Mikä näkyi lukijalle. Tiivistin merkitsee jokaisen artikkelin erikoisalan leimalla, joka poistetaan ennen julkaisua. Kun leima oli kirjoitettu koodijaksoon (
`[kategoria: x]`), poisto vei vain hakasulkeisen osan ja riville jäi tyhjä koodijakso. Se näkyi koostesivulla, sivuston PDF:ssä ja tilaajan liitteessä: kuudessa kymmenestä koosteesta, 33–63 kappaletta koostetta kohden (19.7.–13.9.2026). Korjaus tekee ympäröivistä backtickeistä osan kuviota. - Etusivun nostokortti kertoo taas sisällöstä. Nostojen laskenta vaati rivin päättyvän lihavointiin, mutta rivi päättyi leimaan — tulos oli 0 jokaisessa kymmenessä koosteessa, ja kuvaus putosi varapolulle "N artikkelia.". Se toisti kortin yläpuolella olevan artikkelimäärän eikä kertonut koosteesta mitään. Laskenta poistaa leimat ensin, jolloin teksti on jälleen "N artikkelista tehtiin M nostoa.".
- Kolme vartijaa, kaksi injektiota. Yksi testi väittää nimenomaan backtickeistä (aiempi väite "[kategoria: ei esiinny" meni läpi koko ajan), yksi ajaa renderöintiketjun läpi ja yksi laskee nostot leimatuilta riveiltä. Vanhan kuvion palautus kaataa kaikki kolme.
- Jo julkaistuja koosteita ei renderöidä uudelleen (päätös 9): niiden versioleima, fontit ja tuotantoaikaleima säilyvät. Korjaus näkyy seuraavasta viikkoajosta alkaen.
Mallinimet pois julkaistulta sivulta; tuoreusvalvonnan kattavuustarkistus korjattu.
- Suodatus renderöinnissä (päätös 4). "Mitä uutta?" -sivu generoidaan tästä muutoslokista jokaisessa ajossa, joten sivulta oli luettavissa tiivistimen mallinimi ja mallitunnus. Renderöinti korvaa ne nyt neutraalilla ilmauksella; muutosloki itse säilyy tarkkana. Päätös tehtiin 3.9.2026, mutta korjaus jäi erän 4 rajauksen ulkopuolelle, joten nimi oli tuotannossa v1.22.0 asti.
- Vartija koko
site/-puulle. Testi kaatuu, jos mallinimi esiintyy missään julkaistavassa tekstitiedostossa. Ilman sitä käsin siivottu sivu palautuisi ennalleen seuraavassa viikkoajossa — sivu ei ole lähde vaan tuloste. Suodattimessa on lisäksi varapolku, joka nappaa myös nimen, jota ei ole vielä kirjoitettu; todennettu vikainjektiolla molempiin suuntiin. - Tuoreusvalvonnan kattavuustarkistus vertaa jälleen oikeita arvoja. v1.22.0 tiivisti lähetyslokin osoitteet, mutta
check_freshness.pyjäi vertaamaan Resendin selkokielistä tilaajalistaa tiivisteisiin. Yksikään vertailu ei osunut, joten tarkistus ilmoitti joka viikko kaikki tilaajat puuttuviksi — 14.9.2026 lokissa luki "7/7 tilaajaa ei ole lähetyslokissa", vaikka kirje oli lähtenyt kuudelle. Aitoa osittaista lähetysvirhettä ei olisi enää erottanut mikään.hash_emailjalog_keysiirrettiinapi/_common.py:hyn, jotta lokin kirjoittaja ja sen lukija käyttävät samaa muotoa; vanhat selkokieliset lokit kelpaavat yhä. Kolme vartijaa, yksi vikainjektio. - Kuollut koodi poistettu.
get_latest_digest_pdfoli merkitty käyttämättömäksi (tervetuloviestiä ei kytketty päälle, päätös c) ja säilytetty vain omia testejään varten. Sen takia jokaiseen Vercel-funktioon niputettiinsite/manifest.jsonja kaikki koosteiden PDF:t;vercel.jsonsisältää nyt vainapi/**. Samalla poistuirender.extract_category_tags, jonka dokumentaatio väitti sitä käytettävän personoinnissa, vaikka tuotannossa sillä ei ollut yhtään kutsujaa. Neljä testiä poistui mukana; vahvistussivun AT8-vartija säilyi ilman kuollutta puoliskoaan. - Muutosloki v1.22.0 korjattiin väittämästään. Se sanoi, ettei
check_freshness.pymuutu — kenttä oli kuitenkin sillä kaksi lukijaa, ja toinen jäi päivittämättä. Formaattimuutoksessa lukijat on laskettava, ei arvattava.
Tilaajien osoitteet pois lähetyslokista (tiivisteinä).
- Mikä muuttui.
data/<pvm>/newsletter_sent.jsonkirjaa vastaanottajat nyt muodossasha256:<hex>selkokielisen osoitteen sijaan, ja sama koskeemessage_ids-kentän avaimia. Viisi olemassa olevaa lokia on muunnettu; lukumäärät säilyivät ennallaan. - Miksi. Loki committoidaan jokaisen ajon mukana, joten se on ainoa paikka jossa tilaajalista päätyy versionhallintaan. Moduulin dokumentaatio on luvannut §6:ssa jo pitkään, ettei osoitteita lokiteta selväkielisenä —
mask_emailtoteutti sen stdout-tulosteessa, mutta tiedosto itse jäi ulkopuolelle. - Idempotenssi säilyy molempiin suuntiin.
sent-listaa käytetään vain jäsenyystestiin, eikä osoitetta lueta siitä koskaan takaisin.choose_recipientshyväksyy sekä vanhan selkokielisen että uuden tiivistetyn muodon, joten formaatin vaihto ei postita jo palveltuja uudelleen.check_freshness.pylukee vain listan pituuden eikä muutu. - Kaksi vartijaa. Yksi kaatuu jos lokiin päätyy
@-merkkinen osoite — se olisi mennyt läpi, jos vainsentolisi tiivistetty jamessage_idsjäänyt avaimennetuksi osoitteella. Toinen kaatuu jos vanha selkokielinen loki lakkaa täsmäämästä. - Mitä tämä EI korjaa. Osoitteet ovat yhä git-historiassa. Niiden poistaminen vaatisi historian uudelleenkirjoituksen, joka on erillinen omistajan päätös.
Ajastuspuskuri kasvatettu: cron 13 14 * * 0 → 13 9 * * 0 (omistajan päätös 7.9.2026).
- Miksi. Vanha puskuri oli 2 h 47 min, ja mitattu ajautuma ylitti sen: 6.9.2026 ajo käynnistyi +2 h 48 min myöhässä eli klo 20:00 Suomen aikaa,
scheduled_send_atpalauttiNoneja kirje lähti heti klo 20:04 kuudelle oikealle tilaajalle. Neljä seitsemästä ajosta oli ylittänyt puskurin — klo 20:00 oli säilynyt sattumalta, ei rakenteen ansiosta. - Uusi puskuri on 7 h 47 min (kesä) / 8 h 47 min (talvi), eli noin kaksi kertaa pahin mitattu ajautuma (3 h 50 min) ja yhä yli sisarrepon pahimman (5 h 10 min).
- Miksi siirto on turvallinen. Katsauksen päivä ja viikkonumero tulevat ajopäivästä (
run_weekly.sh:DATE=$(date +%F), ajuri UTC:ssä), ja sunnuntai pysyy sunnuntaina molemmilla kellonajoilla — aikaisempi ajo itse asiassa loitontaa maanantain rajasta. Artikkeliaukkoa ei synny:reldate_days: 7+reldate_overlap_days: 1+ PMID-dedup kattavat siirron (ks.config/journals.json_dedup_note). - Varapolku säilyy.
scheduled_send_atinNone-paluu on yhä invariantti: ajastimen viivettä ei ole ylhäältä rajattu, joten puskuria ei voi mitoittaa niin isoksi että varapolun voisi poistaa. - Vartija cronin ja dokumentaation erkaantumiselle. Uusi testi vaatii, että
weekly-digest.yml:n cron esiintyy sellaisenaanAGENTS.md:ssä jaARCHITECTURE.md:ssä ja että ihmisluettava kellonaika vastaa sitä. Tämä on sama vikaluokka kuin H22: väärä luku ei kaada mitään, se vain ohjaa seuraavan lukijan päättelemään väärin. - Omistajan tehtävä: healthchecks.io:n aikataulu on siirrettävä samaan aikaan (
13 9 * * 0), muuten dead man's switch hälyttää myöhästymisestä jota ei ole.
Lähetysajan todennettavuus ja ajastuksen ajautuman mitattu luku (H22, H23).
- Lähetysloki kirjaa lähetysajan (H23).
data/<pvm>/newsletter_sent.jsonsisältää nytqueued_at-,scheduled_at- jamessage_ids-kentätsent-listan rinnalla.scheduled_at: nulltarkoittaa varapolkua, eli lähetystä heti ilman ajastusta. Aiemmin lähetysaikaa ei tallennettu mihinkään, joten sitä ei voinut todentaa jälkikäteen. Muoto on taaksepäin yhteensopiva:load_sent,choose_recipientsjacheck_freshness.pylukevat yhä pelkkääsent-listaa, ja vanhat viikot kelpaavat sellaisenaan. Ensimmäisen erän aikaleimoja ei ylikirjoiteta, jotta myöhemmin herännyt varmistusajo ei peitä sitä että kirje lähti alun perin ajastettuna. - Tuoreusvalvonta erottaa ajastetun lähetyksen varapolusta (H23).
check_freshness.pytulostaa koneluettavanLAHETYSAIKA:-rivin (ajastettu/varapolku/tuntematon) jafreshness.ymlnostaa varapolusta::warning::-huomion. Varapolku ei muuta tarkistuksen tulosta eikä nosta hälytystä "kirje ei lähtenyt": kirje meni perille, vain myöhässä, ja väärä otsikko ohjaisi etsimään vikaa väärästä paikasta. - Ajastimen ajautuman luku korjattu mitatuksi (H22). Väite "31–60 min" oli kuudessa paikassa (
weekly-digest.yml,send_newsletter.py,AGENTS.md,README.md,CHANGELOG.md,site/mita-uutta.html). Mitattu ajautuma 19.7.–6.9.2026 on 23 min – 3 h 50 min, ja sisarrepossasote-uutiset5 h 10 min. Luku ei ole kosmeettinen: 6.9.2026 ajautuma oli +2 h 48 min eli yli cronin 2 h 47 min puskurin, jolloinscheduled_send_atpalauttiNoneja kirje lähti heti klo 20:04 kuudelle oikealle tilaajalle. Tämä oli ensimmäinen kerta kun varapolku laukesi tuotannossa oikeille tilaajille. Cronia ei muutettu — puskurin kasvattaminen on omistajan päätös.
HTML-suodatuksen ja varamuuntimen korjaukset, valvontaketjun tiukennus sekä API-kerroksen suojaus ja toistettavuus (erä 4 / tarkastuskierros 9).
- HTML-suodatuksen taulukkokohdistus (D5):
sanitize_htmlkääntää taulukoidentext-align-tyylitalign-attribuuteiksi (left,right,center), jotta WeasyPrint kohdistaa numeeriset ja tekstisarakkeet oikein ilman että yleistästyle-attribuuttia tarvitsee sallia. - Tagien lainausmerkkitietoisuus ja self-closing tagit (H80, H82): Tagien attribuutit suodatetaan lainausmerkit huomioivalla jäsentimellä, ja itsesulkeutuvat tagit (kuten
<hr/>ja<br/>) normalisoidaan standardimuotoon (<hr />). Lainausmerkkitietoisen haun rinnalla on pakollinen varapolku (H82): pariton lainausmerkki ei täsmää lainausmerkkitietoiseen hakuun lainkaan, joten ilman varapolkua koko tagi olisi ohittanut sanitoinnin ja<script>,<img>tai<iframe>olisi päätynyt sivulle elävänä elementtinä. Vartija käyttää oraakkelina HTML-jäsennintä, ei merkkijonohakua. - _inline-varamuuntimen NUL-suojaus (H81): Varamuunnin poistaa mahdolliset NUL-tavut syötteestä ennen paikkamerkkien luontia, jotta koodijaksojen suojamerkit eivät voi törmätä syötteen sisältöön.
- HTML-suodatuksen ja varapolun testivartijat (C13, C14, C15): Lisätty testivartijat sille, että
render.py:nmain()-polku ja muutoslokisivun renderöinti ajavat tulosteensa ainasanitize_html:n läpi, sekä sille, että Markdown-varapolku toimii itsenäisesti ilman ulkoistamarkdown-kirjastoa. - Dokumentaatiovartija (AT15):
HANDOFF.md:stä siivottu vanhentunut kaksoisotsikko ja lisätty testivartija otsikkorakenteelle. - Valvonnan ja tuoreustarkistuksen kattavuus (H53–H58):
check_freshness.pyraportoi ja diagnosoi puuttuvat uutiskirjeet, virheelliset manifestit ja tulevaisuuden päivämäärät; komentoriviparametrit--dateja--sitemahdollistavat täyden paikallisen testattavuuden ilman verkkokutsuja. - Peruutetun tilauksen palautus (H83):
add_contactkääntääunsubscribed-lipun takaisin vain kun kutsuja pyytää sitä eksplisiittisesti (allow_resubscribe=True), ja vahvistuspolkuconfirm.pytekee juuri niin: tuore, allekirjoitettu ja enintään 24 h ikäinen tunniste on todiste uudesta suostumuksesta. Ilman tätä/tilaa-polku oli peruuttaneelle pysyvä umpikuja —update_contactkirjoittaa vainfirst_name:n, joten mikään muu kohta ei palauta tilausta, ja vahvistussivu kertoi silti tilauksen onnistuneen. Samalla korjattu suojan sattumanvaraisuus: kontaktihaun kaatuessa tila jäi tuntemattomaksi ja peruuttanut heräsi henkiin. - API-kerroksen toistettavuussuoja ja virhekäsittely (AT9–AT12): Tilausvahvistustunnisteen voimassaoloaika lyhennetty 24 tuntiin ja vahvistussivun toistuva käyttö suojattu (olemassa olevia tilauksia ja niiden asetuksia ei ylikirjoiteta). Resendin 422-virheet käsitellään
subscribe.py:ssä hallitusti ja/api/preferences-käsittelijä erottaa Resendin 404- ja 5xx-virheet sekä ohjaa aiemmin peruuttaneen tilaajan suoraan uudelleentilaussivulle. - API-kerroksen lisäsuojaukset ja yhtenäistykset (AH15–AH21): Eroteltu puuttuva kontakti (404) tyhjästä nimikentästä (200 Kaikki erikoisalat), uutiskirjeen lähetin strippaa salaisuuden välilyönnit, paikallisen PDF-manifestin luku suojattu poikkeuksilta, base64-dekoodaus hylkää kielletyt merkit (
validate=True), kontaktilistan haulle lisätty muistinvarainen välimuisti, erikoisalojen nimet keskitetty_common.py:hyn jasubscribe.py:n 303-uudelleenohjaukseen lisätty tarvittavat välimuisti- ja pituusotsakkeet. Keskityksen vartija lukitsee myös konkreettiset arvot: aiempiprefs.X == common.X-vertailu oli tautologia, koska molemmat viittasivat samaan importattuun olioon.
HTML-attribuuttien sallintalista, shell-syntaksin tarkistus ja putkivartijoiden täydennys (tarkastuskierros 8 / erä 3A).
- HTML-attribuuttien sallintalista (H75):
sanitize_htmlsuodattaa nyt myös tagien attribuutit (_ALLOWED_ATTRS = {"href", "title", "id", "colspan", "rowspan", "align"}) ja pudottaa kaikki muut (kutenonclick,onmouseover,style). Lisäksihref-suodatus laajennettu kattamaan kaksoislainausmerkkien ohella yksinkertaiset lainausmerkit ja lainausmerkittömät arvot. - PDF-kaksoisescapoinnin vartija (C8): Varmennettu, ettei personoidun PDF-liitteen polku (
render_filtered_pdf) tee esiescapointia ja että koodijaksot sekä tilastolliset operaattorit renderöityvät oikein. - Artikkelien duplikaattikarsinnan kutsutason vartija (C9): Varmennettu testillä, että
fetch_articles.main()kutsuu karsintafunktiota ja poistaa aiemmissa katsauksissa esiintyneet PMID:t levylle kirjoitettavasta tuloksesta. - Esipuheen poiston pariteettivartija molempiin suuntiin (C10):
test_render_main_strips_preambleparametrisoitu kattamaan sarkaimella erotetut otsikot (#\t), mikä varmistaa ettämain()-polku ei kavennu vanhaan regexiin. - Shell-skriptien syntaksitarkistus (H77, C12, H79): Jokaisen
scripts/*.sh-skriptin syntaksi tarkistetaanbash -n:llä sekä CI:n omana askeleena että yksikkötestinätest_shell_scripts_are_syntactically_valid. CI-askel ajaabash -n:n silmukassa tiedosto kerrallaan:bash -n a.sh b.shtarkistaisi vaina.sh:n ja antaisi lopuille positionaalisen argumentin roolin, jolloin rikkinäinenscripts/setup_venv.shei kaataisi askelta.test_ci_shell_syntax_step_checks_every_scriptsuorittaa CI-askeleen ja varmistaa, että se huomaa syntaksivirheen myös aakkosjärjestyksessä viimeisessä skriptissä. - _inline-varamuuntimen regressioiden korjaus (H78): Varamuunnin käyttää paikkamerkkimenetelmää parillisille backtick-jaksoille. Parittomat backtickit säilyvät literaaleina eikä lihavointi koodijakson ympärillä (
**`koodi`**) rikkoudu.
Uutiskirjeen lähetysketjun varmennus, idempotenssisuoja ja putken testivartijat (tarkastuskierrokset 6 ja 7).
- Uutiskirjeen lähetys suojattu virheiltä (H40, C2): Jos yksittäisen tilaajan PDF-liitteen muodostus epäonnistuu, virhe ei enää keskeytä muiden viestien lähetystä. Jo lähetettyjen tilaajien kirjaus varmistetaan aina (
finally), mikä estää kaksoislähetykset. - Lähetyslokin tallennuksen uudelleenyritys (H29, C4): Viikkoajon lähetyslokin git push yrittää neljästi takaisinväistöllä ja kaatuu virhekoodiin 1, jos tallennus ei onnistu.
- Päiväkohtainen idempotenssisuoja ja pelastusajo (H27, H73, C4): Viikkoajo kieltäytyy generoimasta uudelleen jo julkaistua päivää (
site/manifest.json). Jos uutiskirjettä ei ole lähetetty (newsletter_sent.jsonpuuttuu tai on tyhjä), pelastusajo ohittaa vain generoinnin (SKIP_GENERATION=1) ja suorittaa uutiskirjeen lähetyksen. Epätyhjän lokin ylikirjoitus vaatii liputFORCE_REGENERATE=1jaFORCE_REGENERATE_SENT=1. - HTML-suodatus ja koodijaksojen korjaus (D1, H46, H51): Siirretty HTML-suodatus Markdown-muunnoksen jälkeiseksi (
sanitize_html), mikä korjaa koodijaksojen ja vertailuoperaattoreiden kaksoisescapoinnin (kuten<code>ja<pvm>) sallien turvalliset tagit ja attribuutit ja neutraloiden raa'at<script>- ja<img>-tagit myös suodatetuissa PDF-liitteissä. - Tiedostosuhteelliset polut API-kutsussa (AT13, C1):
get_latest_digest_pdfkäyttää tiedostosuhteellista polkua levylukuihin, mikä estää tarpeettomat verkkohaut ja tekee toiminnasta työhakemistosta riippumatonta. - Vanhat koosteet säilyttävät julkaisuversionsa (H74): Versionosto ei enää renderöi vanhoja koosteita uudelleen, jolloin niiden fontit ja tuotantoaikaleimat eivät muutu.
- Testien laadunvalvonta CI:ssä (H48, C6, C7):
tests/-hakemisto lisätty CI:n tyyppitarkistukseen (--check-untyped-defs) ja lintiin. CI asentaa tarvittavat fontit (fonts-liberation,fonts-dejavu-core) weasyprintille. - Putken luotettavuustestit ja invarianttivartijat (H37, H38, H39, H44, H47, H49, H50, H52, C5): Varmennettu artikkelien duplikaattikarsinta historiasta, esipuheen poisto eri poluissa (myös sarkaimella erotetut otsikot), tuntemattomien erikoisalojen käsittely ja oikean PDF-moottorin käyttö.
- Tilausvahvistuksen teksti (AT8): Vahvistussivulta poistettu virheellinen lupaus erillisestä tervetulosähköpostista.
Tilauksen vahvistus ja peruutus vaativat nyt painikkeen painalluksen.
- Mikä muuttui: Sähköpostin linkin avaaminen näyttää vahvistussivun, jolla on painike. Vasta painikkeen painallus vahvistaa tilauksen tai peruuttaa sen. Aiemmin pelkkä linkin avaaminen riitti.
- Miksi: Työsähköpostien turvaohjelmat (esimerkiksi Microsoft Defenderin Safe Links) avaavat viestien linkkejä automaattisesti tarkistaakseen ne. Ne saattoivat siis vahvistaa tilauksen kenenkään klikkaamatta — jolloin osoite päätyi listalle ilman että sen omistaja oli sitä varmistanut — tai peruuttaa tilauksen huomaamatta, jolloin lehti vain lakkasi tulemasta. Peruutuslinkki on voimassa viisi vuotta, joten riski säilyy pitkään.
- Peruutus ei vaikeudu: Sähköpostiohjelman oma "peruuta tilaus" -toiminto (Gmail, Yahoo, Outlook) toimii edelleen yhdellä klikkauksella. Vahvistussivu koskee vain viestin tekstissä olevaa linkkiä.
- Tilannekuvan tarkistus:
scripts/check_handoff.pyei enää hälytä turhaan päähaarassa. Se erottaa nyt normaalin mergen jälkeisen tilan aidosta vanhentumisesta.
Tarkastuskierroksen valvonta ja kaksi päätöstä ihmiselle (AH1, AH14, H15).
HANDOFF.md:n tilannekuvan tarkistus (H15): Uusiscripts/check_handoff.pyvertaa tiedoston SIJAINTI-osiota gitin todelliseen tilaan (haara, versio,mainin commit, onko työ pushattu) ja kaatuu, jos jokin väite on väärä. Tiedosto pysyy käsin kirjoitettuna — tarkistus vain estää sitä valehtelemasta. Tämä on aito ongelma: 20.8.2026 vanhentunut tilannekuva ohjasi tarkastuksen väärään haaraan, jolloin edellisen kierroksen korjaukset päätyivät tuotantoon tarkastamattomina.- Kaksi kysymystä siirretty ihmisen päätettäväksi (AH1, AH14): Sekä tilauksen vahvistus että peruutus tapahtuvat linkin avaamisella, jolloin sähköpostin turvaskanneri voi laukaista ne käyttäjän puolesta. Vahvistuksessa se loisi tilaajan ja lähettäisi tervetuloviestin; peruutuksessa se poistaisi aidon tilaajan huomaamatta, ja peruutuslinkki on voimassa viisi vuotta. Korjaus (painike linkin sijaan) lisäisi klikkauksen, joten kyse on punninnasta, ei teknisestä valinnasta.
- Konventiot kirjattu
AGENTS.md:hen: versionoston vaatima uudelleenrenderöinti, selaintarkistusten työkalujen lainaaminen ja tarkastuskierroksen haaran todentaminen.
Tarkastuskierroksen 5 havaintojen korjaukset (AH12, AH13, H14).
- "Mitä uutta?" -sivun otsikot (H14): Muutoslokin tiivistelmärivillä käytetty inline-koodi näkyi sivun otsikoissa raakoina backtickeinä (
`api/`), vaikka sama merkintä renderöi rungossa<code>-elementit oikein. Otsikot menevät nyt saman muotoilun läpi. Korvaus tehdään escapoinnin jälkeen, jotteiCHANGELOG.md:stä pääse HTML:ää sivulle (K1/T4). - Vartijatesti, joka ei vartioinut (AH13):
test_error_pages_do_not_duplicate_heading_as_ledekovakoodasi otsikko–runko-parit, joten se vertasi testin omaa vakiota runkovakioon. Koko testisarja meni läpi, vaikka yhden virhesivun otsikko palautettiin korjausta edeltäneeseen arvoon. Testi ajaa nyt kahdeksan virhepolkua oikeiden käsittelijöiden läpi ja lukee otsikon samasta vastauksesta kuin rungon; ledetön virhesivu ei enää ohitu hiljaa. - Dokumentaatio (AH12):
ARCHITECTURE.md:131väittidocs-check-työnkulun vartioivan README/HANDOFF-lohkoja; kohteet ovat README/ARCHITECTURE. Kierroksen 4 korjaus osui riviin 102 mutta ohitti tämän. HANDOFF.md(H15): Kirjoitettu uusiksi; se kuvasi haaratilannetta, joka oli ohitettu kahdesti.
Korjattu uutiskirjeen lähetys, joka keskeytyi liitteen muodostukseen.
- Vika: Viikon 33 kooste (16.8.2026) julkaistiin verkkosivulle normaalisti, mutta uutiskirje ei lähtenyt tilaajille: lukijakohtaisen PDF-liitteen kokoaminen keskeytyi virheeseen. Vika tuli mukana lukijakohtaisten uutiskirjeiden myötä (versio 1.9.0) ja ilmeni ensimmäisessä sitä seuranneessa viikkoajossa.
- Korjaus: Artikkelilistan lukeminen yhtenäistetty samaksi kuin muualla putkessa. Viikon 33 kirje lähetettiin tilaajille jälkikäteen 17.8.2026.
- Varmistus jatkoa varten: Lisätty testit, jotka havaitsevat vastaavan virheen ennen julkaisua.
- Lähetysmerkinnän tallennus korjattu: Merkintä lähetetystä kirjeestä jäi tallentumatta viikkoajossa, minkä vuoksi valvonta ilmoitti kirjeen puuttuvan vaikka se oli lähtenyt — ja uusinta-ajo olisi voinut lähettää saman kirjeen kahdesti. Merkintä tallentuu nyt automaattisesti.
- Versionumero päivittyy kaikille sivuille automaattisesti: Aiemmin Tietoja- ja Tilaa-sivujen versiotieto piti päivittää käsin, jolloin se saattoi jäädä jälkeen muusta sivustosta.
Sivuston versionhallinta, alatunnisteen versioilmoitus ja "Mitä uutta?" -sivu.
- Alatunnisteen versioilmoitus ja linkki: Kaikkien sivujen alatunnisteeseen (
.footer-fineja.footer-links) lisätty ilmoitus nykyisestä sovellusversiosta ja suora linkki "Mitä uutta?" -sivulle (mita-uutta.html). - Uusi "Mitä uutta?" -sivu (
site/mita-uutta.html): Esteetön ja selkeästi jäsennelty versiohistoriasivu, joka esittelee kunkin version uudistukset ja parannukset. - Automaattinen generointi: "Mitä uutta?" -sivu generoidaan automaattisesti
CHANGELOG.md:stä jaVERSION-tiedostosta osana sivuston renderöintiä (scripts/render.py), jolloin sivuston muutosloki pysyy aina automaattisesti ajan tasalla.
Uudet seurattavat julkaisut (20 → 25 lehteä).
- Lisätty seurantaan 5 uutta lehteä (
config/journals.json,site/tietoja.html,README.md,ARCHITECTURE.md):- Diabetes Care (
Diabetes Care, yleis-/aikuislehdet) - Diabetes, Obesity and Metabolism (
Diabetes Obes Metab, yleis-/aikuislehdet) - Diabetes Technology & Therapeutics (
Diabetes Technol Ther, yleis-/aikuislehdet) - Diabetologia (
Diabetologia, yleis-/aikuislehdet) - Acta Paediatrica (
Acta Paediatr, lastentautien erikoislehdet)
- Diabetes Care (
- Yleis- ja aikuislehdistä poimitaan lapsia koskevat artikkelit (
pediatric_filter); lastentautilehdistä kaikki.
Tarkastuskierroksen 4 havaintojen korjaukset (AH10–AH13).
- AH10: Erotettu palvelimen konfiguraatiovirhe (
ConfigError) virheellisestä linkistäapi/preferences.py:ndo_GET-metodissa (palauttaa nyt 500_SERVER_ERROReikä 400 "Linkki ei kelpaa"). - AH11: Korjattu
api/_common.py:nread_token-funktion kommentti vastaamaan nykytilaa putkimerkin osalta. - AH12: Korjattu
ARCHITECTURE.md:ssä ollut kuvausupdate_docs.py:n kohteista (README/ARCHITECTURE). - AH13: Poistettu virhesivujen otsikon toistuminen sanatarkasti ledenä kaikista 8 lomake- ja virhevastauksesta.
Tervetuloviesti ja PDF-liite uusille tilaajille.
- Uutiskirjeen tilausvahvistuksen yhteydessä lähetetään automaattisesti tervetuloviesti, jossa on
liitteenä uusin sivustolla julkaistu ei-painotettu viikkokooste (PDF). Tervetuloviestin saatetekstissä
informoidaan tilaajaa selkeästi, että kyseessä on ei-painotettu kooste ja jatkossa tulevat koosteet
ovat heidän omien valintojensa mukaisesti painotettuja. Vercel-funktiot lukevat
manifest.json:n ja tuoreimman PDF:n, jotka toimitetaan serverless-funktioillevercel.jsonincludeFiles-määrittelyn avulla.
Tumman tilan kontrastikorjaus ja erikoisalojen ehdollinen näyttö.
- Kontrasti kuntoon tummassa tilassa: Valitun scope-laatikon taustaväriksi vaihdettu
var(--accent-bg), jolloin teksti erottuu selkeästi ja korkealla kontrastilla sekä vaaleassa että tummassa tilassa. - Erikoisalojen ehdollinen aktivointi: Erikoisalojen valintaruudukko pidetään piilossa,
kun Kaikki erikoisalat on valittuna, ja avataan näkyviin vasta kun tilaaja valitsee
Vain valitut erikoisalat (toteutettu puhtaalla CSS
:has()-säännöllä).
Uutiskirjeen sisällön laajuusvalinta (Kaikki erikoisalat vs. Vain valitut erikoisalat).
- Selkeä tilauskokemus: Tilaussivulla (
site/tilaa.html) ja asetussivulla (api/preferences.py) on nyt oletuksena valittuna Kaikki erikoisalat (täysi viikkokooste ilman rajausta). Tilaaja voi halutessaan valita Vain valitut erikoisalat ja poimia kiinnostuksen kohteensa. - Puhdas JS-vapaa toteutus: Toimii 100 % ilman JavaScriptiä standardeilla radio- ja
checkbox-elementeillä sekä CSS
:has()-säännöillä sivuston tiukkaa CSP-tietoturvapolitiikkaa noudattaen. - Taustalogiikka ja testit:
api/subscribe.pyjaapi/preferences.pykäsittelevätscope-parametrin (allvs.custom). Lisätty 5 uutta yksikkötestiä (yht. 134 testiä).
Diabetes omaksi valittavaksi kategoriaksi uutiskirjeeseen.
- Uusi kategoria: Lisätty
diabetes(13. erikoisala)config/categories.jsoniin,api/_common.py:nVALID_CATEGORIES-joukkoon japrompts/summary_instructions.md:n luokitteluun ja### Diabetes-väliotsikoksi. - Hierarkinen kytkös:
scripts/filter_summary.pylaajentaaendokrinologia-tilauksen kattamaan automaattisesti myösdiabetes-artikkelit. Pelkändiabetes-kategorian valitseva saa koosteen vain diabeteksesta (ja nostot). - Käyttöliittymä: Lisätty Diabetes-valintaruutu tilaussivulle (
site/tilaa.html) ja asetussivulle (api/preferences.py). - Testit: Päivitetty
test_pipeline.pyjatest_newsletter.pykattamaan 13 kategoriaa sekä diabeteksen ja endokrinologian suodatushierarkia.
Tarkastuskierros 3:n api/-korjaukset.
- Osoitevalidointi vastaa nyt Resendiä (AT6a).
_EMAIL_REhyväksyi mm.a|b@…,a/b@…jaa,b@…, joihin Resend vastaa 422: osoite kelpasi lomakkeella ja tilaus hajosi vasta vahvistuslinkin takana. Local part on nyt RFC 5322:n atext ilman|- ja/-merkkejä, verkkotunnus LDH-nimiöitä. - Resendin 422 on pysyvä virhe (AT6b).
/api/unsubscribeja/api/confirmpalauttavat siitä 400 eivätkä 502:ta; teksti ei enää kehota yrittämään myöhemmin uudelleen, koska se ei koskaan auta. - Tyhjä kategoriavalinta hylätään (AT7).
/api/preferencestallensi tyhjän valinnan, ja lähetysvaiheessa tyhjä kenttä laajenee tarkoituksella kaikiksi 12 kategoriaksi — kaikki rastit poistanut käyttäjä sai siis enemmän sisältöä. Nyt 400 eikä tallennusta lainkaan. Haun epäonnistuessa ei enää näytetä tyhjää lomaketta, joka pyyhkisi valinnat huomaamatta. - Vahvistusviesteille osoitekohtainen raja (AT4). Sama osoite saa enintään yhden viestin per 15 min; avaimena tiiviste, joten osoite ei jää muistiin selväkielisenä. Vanhentuneet avaimet siivotaan (AH4).
- Suomenkielinen virhesivu myös toteuttamattomille metodeille (AH2).
Uusi yhteinen kantaluokka
_common.FinnishHandler;PUT /api/subscribepalautti ennen englanninkielisen oletussivun jaCache-Control: public. - Kovennukset:
Content-Length-suojauspreferences.py:hen (AH7), tunniste escapataan lomakkeen attribuuttiin (AH8),page()escapaa otsikon (AH5),</body>suljetaan (AH6). - AT3 mitattiin ja kumoutui. Nopeusrajoitinta ei voi ohittaa omalla
X-Forwarded-For-otsakkeella: Vercel normalisoi otsakkeen. Koodia ei muutettu; mittaus on kirjattu kommenttiin, koska "oikeanpuoleisin arvo" olisi tässä ollut väärä korjaus. - Testit 106 → 126 (AH3): nopeusrajoitin, hunajapurkki,
page()jahtml_to_textolivat täysin testaamattomia. render.py:n docstring ei enää lupaa julkaistaarticles.jsonia sivustolle — kopiointi poistui jo T5:ssä (AH9).
Neutraali prompti ja personoitu PDF-johdanto.
- Prompti neutralisoitu. Poistettu endokrinologian painotus
summary_instructions.md:stä: nostot valitaan nyt tasapuolisesti kaikista erikoisaloista kliinisen merkittävyyden perusteella. - Kaikki 5 koostetta regeneroitu tekoälymallilla neutralisoidulla promptilla (DOI-kattavuus säilytetty).
- Etusivun hero-teksti päivitetty: poistettu painotusvirke, lisätty
personoinnin CTA uutiskirje-osioon (
render.pyINDEX_TEMPLATE). - Personoitu johdanto.
filter_summary.py: uusicategory_labels- parametri injektoi tilaajan valitsemat erikoisalat PDF-johdantoon ("Tämä kooste on räätälöity sinulle. Valitsemasi erikoisalat: …"). send_newsletter.py: välittää_CAT_LABELS-dictfilter_summarylle.site/tietoja.html,site/tilaa.html: päivitetyt tekstit kuvaamaan neutraali sivuston PDF + personoitu uutiskirje-PDF -mallia.
Personoidut uutiskirjeet: tilaaja valitsee erikoisalat.
- 12 kategoriaa. Artikkelit luokitellaan automaattisesti MeSH-termien ja
avainsanojen perusteella (
config/categories.json,fetch_articles.py). - Prompt-päivitys. Tekoälytiivistys ryhmittelee artikkelit 12 kategoriaan
ja merkitsee ne
[kategoria: X]-leimoin (prompts/summary_instructions.md). - Render-puhdistus.
strip_category_tags()jaextract_category_tags()poistavat/lukevat leimat HTML:ssä ja PDF:ssä (render.py). - Tilauslomake. Checkbox-ruudukko erikoisaloille (
site/tilaa.html,site/styles.css). - Token-laajennus. HMAC-token kuljettaa kategoriat puolipisteellä
erotettuina (
_common.py),read_tokenpalauttaa(email, categories). - Resend-kontaktit. Kategoriat tallennetaan
first_name-kenttään CSV-muodossa (confirm.py,_common.py). - Asetussivu.
api/preferences.py— palvelinrenderöity lomake, jolla tilaaja voi muuttaa kategorioitaan allekirjoitetun linkin avulla ilman kirjautumista. - Uutiskirje. "Muokkaa kiinnostuksiasi" -linkki jokaisessa viestissä
(
send_newsletter.py), vastaanottajien kategoriat parsitaan kontaktista. - Suodatus.
scripts/filter_summary.pysuodattaa Markdown-tiivistyksen tilaajan kategorioiden mukaan (Nostot aina mukana). - 102 → 106 testiä; ruff, mypy puhtaat.
Erä 1a: pysyvä virhetila ei enää esiinny tilapäisenä häiriönä
(ANALYSIS.md kierros 3, AT1–AT2 ja HANDOFF.md §6.3). Kolme kohtaa, yksi vika:
- AT1 — putkimerkki osoitteessa rikkoi vahvistuksen pysyvästi. read_token
purki hyötykuorman split("|", 2):lla, mutta | on RFC 5322:n mukaan laillinen
merkki local partissa ja _EMAIL_RE päästää sen läpi. Osoitteella
a|b@esimerkki.fi osoitteeksi tuli a ja vanhenemisajaksi
b@esimerkki.fi|…, int() kaatui ja linkki oli pysyvästi rikki — ilman
lokimerkintää syystä. Nyt käyttötarkoitus irrotetaan vasemmalta ja
vanhenemisaika oikealta.
- AT2 — kauttaviiva osoitteessa ohjasi peruutuksen väärään päätepisteeseen.
quote() jättää oletuksena / koodaamatta, joten a/b@esimerkki.fi tuotti
polun /contacts/a/b%40esimerkki.fi. Nyt safe='' (myös list_contacts).
- §6.3 — peruutus tulkitsi Resendin 404:n häiriöksi. Osoite, jota ei ole
yhteystietona, ei ole listalla — juuri sitä käyttäjä pyysi. Aiemmin näytettiin
502 "avaa linkki myöhemmin uudelleen", vaikka tilanne on pysyvä. RFC 8058:n
yhden klikkauksen peruutuksessa Gmail ja Yahoo tulkitsivat 502:n
epäonnistuneeksi peruutukseksi. Nyt 404 → 200. Muut virhekoodit yhä 502.
- Viisi uutta testiä vartioivat kaikkia kolmea sekä sitä, ettei purkulogiikan
löysentäminen päästä läpi hyötykuormaa ilman vanhenemisaikaa (76 → 81).
- AT5 ei ole tässä erässä. Sen todentaminen vaatii tiedon siitä, mitä Resend
vastaa duplikaattiyhteystiedolle, eikä sitä saa selville luomatta oikeaa
yhteystietoa. Este oli 9.8. lähetys, joka olisi mennyt myös koekontaktille; se
on nyt takana ja seuraava lähetys on 16.8., joten todennus on tehtävissä
väliajalla (luo, mittaa, poista heti). Korjaus omana eränään sen jälkeen.
Lähetysaika irti cronista + tuoreusvalvonta:
- Uutiskirje on tilaajilla klo 20 Suomen aikaa riippumatta siitä milloin ajo laukeaa.
Lähetys jonotetaan Resendiin
scheduled_at-kentällä (scripts/send_newsletter.py:scheduled_send_at,api/_common.py:send_email). Syy: GitHubin ajastin ei ole täsmällinen — tämän repon ajot ovat käynnistyneet 23 min – 3 h 50 min myöhässä (kirje 20:57–21:00), ja sote-uutiset-repossa 5 h 10 min. (Luku korjattu mitatuksi v1.20.0:ssa; v1.8.0:ssa arvio oli 31–60 min.) - Cron
0 17 * * 0→13 14 * * 0(su 14:13 UTC). Ajo aikaistettiin, jotta lähetykselle jää n. 2 h 47 min puskuria. Jos ajo silti myöhästyy yli klo 20:n,scheduled_send_atpalauttaaNoneja kirje lähtee heti — menneeseen hetkeen ei voi ajastaa, eikä myöhässä oleva sunnuntaikirje kuulu seuraavaan päivään. - Kesä- ja talviaika korjaantuivat sivutuotteena. Vanha cron osui klo 20:aan vain
kesällä ja klo 19:aan talvella. Aika lasketaan nyt
Europe/Helsinki-vyöhykkeellä. - Uusi tuoreusvalvonta (
.github/workflows/freshness.yml→scripts/check_freshness.py, ma 06:47 UTC): tarkistaa että odotetun sunnuntain kooste on manifestissa ja ettädata/<pvm>/newsletter_sent.jsonon olemassa eikä tyhjä. Sulkee heartbeatin sokean pisteen:run_weekly.shei kaada ajoa lähetyksen epäonnistuessa, joten valvonta on voinut näyttää vihreää vaikka kirje ei lähtenyt (näin kävi 9.8.2026). Vain luku, pelkkä vakiokirjasto. - HUOM ylläpidolle: healthchecks.io-checkin aikataulu on päivitettävä vastaamaan
uutta cronia
13 14 * * 0, muuten se näyttää myöhässä olevalta joka sunnuntai.
CLI-varapolun mallitunnus ajan tasalle:
- tiivistimen oletusmalli päivitetty uudempaan tunnukseen
(
scripts/run_weekly.sh:37ja README:n ympäristömuuttujalohko). Vanha tunnus ei vastaa mitään olemassa olevaa mallia: genai-rajapinta palauttaa sille404 NOT_FOUND, kun taasmodels.listtarjoaa kaksi uudempaa tunnusta. - Ajastettuun ajoon tällä ei ole vaikutusta. Oletus on rajapintatila, joka lukee mallin asetustiedostosta; muuttuja koskee vain paikallista komentorivivarapolkua. Nyt molemmat polut osoittavat samaan malliin — juuri siksi virhe ehti jäädä huomaamatta, sillä rikkinäistä polkua ei ajeta CI:ssä.
- Löytyi API-avaimen vaihdon yhteydessä ajetussa savutestissä, jossa kaikki repon käyttämät mallitunnukset kutsuttiin läpi uudella avaimella.
Kuollut RESEND_AUDIENCE_ID pois + väitteet todennettu paikallisesti:
RESEND_AUDIENCE_IDpoistettu.github/workflows/weekly-digest.yml:stä. Resendin Audiences-ratkaisusta luovuttiin globaalien yhteystietojen hyväksi (api/_common.py:229), mutta työnkulun env-rivi jäi jäljelle.gh secret listosoittaa, ettei salaisuutta ole olemassa: rivi laajeni tyhjäksi merkkijonoksi eikä rikkonut mitään, minkä vuoksi se jäi huomaamatta.tests/test_newsletter.py:199vartioi koodipolkua, ei työnkulkutiedostoa.- Sama kuvio kuin 1.7.2:ssa: valmiiksi merkityn työn keskeneräinen siivous, ei uusi vika.
- Todennukset, joita pilvihiekkalaatikko ei voinut ajaa, ajettiin macOS:llä: testisarja
71/71 (löydösten H3/H4 varsinainen alusta),
digDMARC/SPF/DKIM:lle, tuotannoncurl(julkaistu versio 1.7.2) jaghCI:lle. Punainenmainosoittautui GitHubin Actions-häiriöksi 6.8. — ei koodivika; uudelleenajot 7.8. menivät läpi 29 s ja 12 s. Yksityiskohdat:HANDOFF.md§2 ja §5.
docs-check-työnkululle aikakatto:
timeout-minutes: 10lisätty.github/workflows/docs-check.yml:ään. Se oli ainoa työnkulku ilman aikakattoa:tests.yml(10) jaweekly-digest.yml(30) saivat sen ANALYSIS-löydöksen T6 korjauksessa, mutta tämä jäi väliin. Ilman kattoa jumiutunut ajo varaa runnerin GitHubin kuuden tunnin oletusrajaan asti.- Puute löytyi todentamalla, pitääkö T6 yhä — ei uudesta muutoksesta.
Dokumentaatio jaettu kahtia elinkaaren mukaan:
ARCHITECTURE.md(uusi) — pysyvä tekninen viite: arkkitehtuuri, putki, asetukset, toimitukselliset säännöt ja ratkaistut sudenkuopat. Muuttuu harvoin.HANDOFF.md— enää pelkkä istunnon tilannekatsaus (129 riviä): missä työ on, mikä on todennettu ajamalla ja mitä ympäristössä ei voitu testata. Kirjoitetaan uusiksi joka istunnossa. Nimi säilyi tällä tiedostolla, koska toistuva handoff-kehote kirjoittaa nimenomaanHANDOFF.md:hen — muuten jokainen istunto ylikirjoittaisi viitteen.- Uutiskirjeosio siirretty järjestykseen. Se oli numeroitu §14:ksi mutta sijaitsi
§11:n ja §12:n välissä. Nyt lopussa, joten §1–§13 säilyttävät numeronsa eivätkä
AGENTS.md:n ristiviittaukset (§7, §9, §13) katkea. - AUTO-STATUS-lohko seuraa mukana:
scripts/update_docs.py,docs-check.ymljarun_weekly.shosoittavat nytARCHITECTURE.md:hen.HANDOFF.md:ssä ei ole enää generoitua sisältöä, joten sen voi kirjoittaa vapaasti uusiksi ilman että CI kaatuu.
Macin launchd-varajärjestelmä poistettu; ajastus on vain GitHub Actionsissa:
launchd/fi.bwwf.pediatrics-digest.plistjascripts/install_launchd.shpoistettu. Varajärjestelmä ei ollut turvaverkko vaan riski: se olisi julkaissut saman päivän katsauksen toiseen kertaan Actionsin rinnalla, ja sen launchd-ympäristöstä puuttuiAPI-avain, joten jokainen ajastettu ajo kaatui riviinAPI-avain puuttuu— viimeksi 2.8.2026. Toinen julkaisukykyinen ajoympäristö, joka ei ole koskaan onnistunut, on ylläpitovelkaa.- Käsiajo säilyy:
./scripts/run_weekly.sh(esikatseluunGIT_PUSH=0) toimii yhä paikallisesti. Vain ajastus poistui. - Dokumentaatio siivottu README/HANDOFF/AGENTS: kaksoisajovaroitukset, plistin
EnvironmentVariables-ohjeet ja asennusaskeleet pois; HANDOFF §5 kertoo nyt myös miksi varajärjestelmä poistettiin, jottei sitä lisätä takaisin. run_weekly.sh:n virheilmoitus ei enää ohjaa tiedostoonlogs/run.log— sen kirjoitti launchd-plistin uudelleenohjaus, ei skripti itse.
Väliaikainen diagnostiikka pois, kun uutiskirjeketju on todettu toimivaksi:
api/diag.pypoistettu. Se oli alusta asti merkitty väliaikaiseksi. Tilaus → vahvistus → peruutus on nyt todettu toimivaksi päästä päähän tuotannossa, joten päätepiste ei ole enää perusteltu — se ei paljastanut salaisuuksien arvoja, mutta julkinen ajoympäristön kuvaus on turhaa pintaa. Tiedosto löytyy git-historiasta, jos vastaava läpinäkymätön vika joskus palaa.- Vanhentuneet Audience-maininnat korjattu koodin dokumenttimerkkijonoissa ja
viikkoajon työnkulussa.
RESEND_AUDIENCE_IDei ole ollut olemassa sitten v1.6.2:n, mutta työnkulun kommentti listasi sen yhä pakollisena salaisuutena. Samalla kirjattu, että avaimen on oltava Full access — pelkkä "Sending access" ei riitä. - HANDOFF: neljän tuotantovian ketju ja diagnostiikkapäätepisteen rooli kirjattu muistiin, jotta seuraava lukija tietää mistä aloittaa vastaavassa tilanteessa.
Roskapostivaroitus kevennetään, kun viestit alkoivat mennä perille:
- Tilaussivun näkyvä varoitus poistettu. Ennen tilaamista se lähinnä herätti epäilyn palvelun toimivuudesta. Poiston peruste on kirjattu HTML-kommenttina tiedostoon, jottei sitä lisätä takaisin vahingossa.
- Onnistumissivun ohje lyhennetty yhdeksi riviksi, joka mainitsee yhä
lähettäjän
uutiskirje@pediatrics.fi. Sitä ei poistettu:pediatrics.fion yhä nuori lähettäjä, eikä yhden postilaatikon onnistuminen yleisty muihin. - Testi kääntyi ympäri: aiempi
test_subscribe_page_warns_about_the_spam_folderkorvattiin testillä, joka vaatii ohjeen onnistumissivulta ja kieltää sen tilaussivun näkyvästä sisällöstä. Molemmat puolet todennettu kaatumaan. - DMARC (
p=none, raportitdmarc@pediatrics.fi) on lisätty nimipalveluun; dokumentoitu HANDOFF:iin. Tekstiversio ja DKIM-kohdistus säilyvät ennallaan.
Vahvistusviesti löydettävämmäksi ja vähemmän roskapostimaiseksi:
- Tekstiversio jokaiseen viestiin. Pelkkä HTML on tunnettu
roskapostisuodattimien miinus, ja iCloud on tässä erityisen tiukka.
html_to_text()avaa linkit muotoon "teksti (URL)", jotta osoite näkyy myös tekstiversiossa. Testi vartioi, ettei tekstiversio pääse katoamaan. - Valinnainen
NEWSLETTER_REPLY_TO— oikea vastausosoite parantaa mainetta. - Roskapostihuomautus näkyviin sekä tilaussivulle että lähetyksen onnistumissivulle: lähettäjäosoite ja otsikko kerrotaan, jotta viestin osaa etsiä, ja kehotetaan merkitsemään se "Ei roskapostia".
- Huom: nämä eivät korjaa uuden lähetysdomainin puuttuvaa mainetta, joka on todennäköisesti pääsyy. Se paranee ajan ja positiivisen vuorovaikutuksen myötä.
Korjattu Cloudflaren torjuma Resend-kutsu:
- Tilaus kaatui tuotannossa virhesivuun. Diagnostiikkakoetin paljasti syyn:
403ja rungossaerror code: 1010— se on Cloudflaren, ei Resendin, virhe. Pyyntö ei siis päätynyt Resendille lainkaan. - Syy:
urllib.requestlähettää oletuksena tunnisteenPython-urllib/3.x, jonka Cloudflaren bottisuojaus torjuu. Tämä olisi kaatanut kaiken — tilauksen, vahvistuksen, peruutuksen ja viikkolähetyksen. - Korjaus: oma
User-Agent(jaAccept: application/json). Regressiotesti varmistaa, ettei urllibin oletustunniste palaa; testi kaatuu ilman otsaketta. - Huomionarvoista: lähetysdomain oli koko ajan verifioitu — ilman koetinta vika olisi jäänyt helposti domainin syyksi.
Päivitetty Resendin nykyiseen rajapintaan:
- Audienceja ei enää ole. Resend nimesi ne Segmenteiksi ja teki
yhteystiedoista globaaleja: yhteystieto tunnistetaan sähköpostiosoitteesta
eikä kuulu enää yhteen Audienceen. Toteutus oli kirjoitettu vanhaa rajapintaa
vasten (
/audiences/<id>/contacts), joka ei enää ole olemassa. - Päätepisteet nyt:
POST /contacts,PATCH /contacts/{email},GET /contacts. RESEND_AUDIENCE_IDpoistettu kokonaan — sitä ei ole enää mistä ottaa eikä sitä tarvita. Tilaajalista on yksinkertaisesti ne yhteystiedot, joidenunsubscribedonfalse. Käyttöönotossa ei siis luoda mitään listaa.- Sivutus toteutettu. Resendin listaus on kursoripohjainen ja palauttaa vain
osan kerralla. Ilman
has_more-seurantaa yli yhden sivun tilaajalista katkeaisi hiljaa: osa tilaajista jäisi ilman kirjettä eikä mikään näyttäisi rikkinäiseltä. Testi vartioi sivutusta ja silmukan katkaisua.
Uutiskirjeen korjaukset:
- Viesti on nyt PDF-keskeinen. Aiempi versio listasi nostojen otsikot viestin
rungossa, mikä oli käytännössä kooste tekstimuodossa. Nyt runko on lyhyt saate
(viikko + yksi tiivistysrivi + linkki verkkoversioon) ja varsinainen sisältö on
PDF-liitteessä. Testi vartioi, ettei runko ala kasvaa: ei luetteloita, alle 900
merkkiä.
highlight_titlespoistettu tarpeettomana. - PDF on nyt pakollinen. Koska liite on viestin sisältö, lähetys keskeytyy virheeseen jos PDF puuttuu — aiemmin viesti olisi lähtenyt ilman sitä.
- Korjattu funktioiden tuonti Vercelillä. Tuotannossa kaikki
/api/-päätepisteet palauttivat 500 — myös/api/subscribe:n GET, joka vain ohjaa/tilaa-sivulle eikä käytä yhtään ympäristömuuttujaa. Se sulki pois puuttuvat salaisuudet ja osoitti, ettei sisarmoduulin_commontuonti onnistu. Korjattu lisäämällä funktion oma hakemistosys.pathiin, pakottamallaincludeFiles: "api/**"ja kiinnittämällä Python-versio (3.12). - Lisätty väliaikainen
api/diag.py, joka kertoo ajoympäristön tosiasiat (Python-versio, hakupolku, tuonnin tulos, mitkä muuttujat on asetettu — ei arvoja). Poistetaan kun toiminta on todettu. - Opetus: Vercelin "Ready" kertoo vain että käännös onnistui, ei että funktiot ajautuvat. Päätepisteet on kutsuttava erikseen.
Uutiskirje: tilaus, vahvistus, peruutus ja viikkolähetys.
Mallina omistajan Sotekooste-projekti (README + siirrettävä toteutuskuvaus).
- Tilaus ilman JavaScriptiä. Tavallinen HTML-lomake (site/tilaa.html) POSTaa
/api/subscribe-funktiolle. Ainoa CSP-muutos on form-action 'none' →
'self'; script-src pysyy poissa, joten sivustolla ei edelleenkään ole JS:ää.
Mitattu selaimella: 'none' estää lähetyksen, 'self' sallii sen.
- Kaksivaiheinen tilaus ilman tietokantaa. Vahvistuslinkin tunniste on
HMAC-allekirjoitettu ja sisältää vanhenemisajan (7 vrk). Vahvistamatonta
osoitetta ei tallenneta minnekään; vasta vahvistus lisää sen Resend Audienceen.
- Vahvistus- ja peruutustunnisteet on eriytetty (käyttötarkoitus on osa
allekirjoitusta), joten vahvistuslinkillä ei voi peruuttaa tilausta eikä
päinvastoin.
- Kolme toisistaan riippumatonta lähetyssuojaa: SEND_EMAILS on oltava
täsmälleen "true"; koeajo ei kirjaa lähetyslokiin; TEST_EMAIL ohittaa
tilaajalistan. Lisäksi sisällötöntä koostetta (0 artikkelia) ei lähetetä.
Käsin käynnistetty workflow-ajo on oletuksena koeajo.
- One-Click Unsubscribe (List-Unsubscribe + List-Unsubscribe-Post,
RFC 8058) jokaisessa viestissä — Gmail ja Yahoo vaativat sen massalähettäjiltä.
- Idempotenssi ilman tietokantaa: data/<pvm>/newsletter_sent.json estää
saman koosteen lähettämisen samalle osoitteelle kahdesti.
- Sähköpostiosoitteita ei lokiteta selväkielisenä (m***@esimerkki.fi).
- Funktiot eivät tarvitse riippuvuuksia: Resendin REST-rajapintaa kutsutaan
urllib.requestilla. Tyhjä api/requirements.txt estää juuren weasyprintin
asentumisen funktioihin.
- 18 uutta testiä. Jokainen regressiotesti varmennettu palauttamalla vika
hetkeksi — kaikki viisi kaatuivat vialla ja menevät läpi korjattuina.
- CI laajennettu kattamaan api/ (ruff + mypy).
- Korjattu matkan varrella: mobiilissa tilauskentästä tuli 16rem korkea,
koska flex-basis tarkoittaa pystysuuntaisessa flex-kontissa korkeutta.
Painotusalat näkyviin ingresseihin:
- Etusivun hero-ingressiin ja tietoja-sivun ingressiin lisätty virke painotusaloista (lasten endokrinologia, aineenvaihduntataudit, lihavuus, harvinaissairaudet). Sivut kertoivat aiemmin vain kattavuuden, eivät painotusta. Sama sanamuoto molemmissa; etusivulla toisena virkkeenä, koska ensimmäinen päättyy jo kaksoispisteeseen.
- Painotusalat vastaavat HANDOFF:iin kirjattua toimituksellista painotusta.
Oma verkkotunnus pediatrics.fi käyttöön:
SITE_URL-oletusjulkaisut.vercel.app→pediatrics.fi. Tämä ohjaa RSS-syötteen absoluuttiset linkit jaog:url-tagit. Ympäristömuuttuja yliajaa edelleen (esikatseluympäristöt).- Lisätty
rel="canonical"kaikille sivuille. Kaksi hostnamea (oma domain ja Vercelin*.vercel.app) tarjoilee identtistä sisältöä, mikä näyttäisi hakukoneille duplikaatilta ilman kanonista osoitetta.tietoja.htmlon käsin ylläpidetty, joten sen osoite on kovakoodattu — uusi testi varmistaa, ettei se karkaaSITE_URL:stä. - Kaikki koosteet renderöity uudelleen uusilla osoitteilla.
- HANDOFF: uutiskirjeen este poistui — Resend vaatii verifioitavan lähetysdomainin,
mikä ei onnistunut
*.vercel.app-osoitteella mutta onnistuu nyt.
Visuaalinen kieli linjattu sote-uutiset/Sotekooste-sivuston mallin
mukaiseksi (omistajan toimittamat kuvakaappaukset referenssinä):
- Serif-otsikot takaisin, hallitusti. Otsikot, ingressit ja koostetiivistelmät
käyttävät järjestelmän serifiä (Charter → Iowan Old Style → Georgia); käyttöliittymä,
metatiedot ja versaalitunnisteet pysyvät sansissa. CSP kieltää fonttilataukset,
joten kaikki fontit tulevat järjestelmästä.
- Osiotunnisteet ovat nyt lyhyt värillinen viiva + harvennettu versaaliteksti
("— UUSIN KOOSTE"), ja niiden perässä voi olla pikkumerkki (artikkelimäärä).
Uusi toissijainen aksenttiväri --accent-2 (kontrasti 6,8:1 / 7,1:1).
- Litteä nosto. Nostokortin laatikko poistettu; erottelu tapahtuu viivoilla.
Järjestys mallin mukaan: tunniste → otsikko → julkaisupäivä → viiva → tiivistelmä
→ toiminnot. Painikkeet ovat suuremmat ja mobiilissa täysleveät ja pinotut.
- Lyhyt navigaatiotunnus. Koko nimi ei mahtunut navigaatiopalkkiin, joten
palkissa on nyt "Lastentaudit" + vaaleampi selite, joka piilotetaan kaikkein
kapeimmilla näytöillä. Aktiivinen sivu merkitään alleviivauspalkilla.
- Arkisto: vuosiotsikkoon koostemäärä, riviotsikot serifillä.
- Etusivun metarivi tiivistetty neljästä kohdasta kahteen ("Viikkokooste ·
Ilmestyy sunnuntaisin") — neljä versaalikohtaa rivittyi kahdelle riville.
- Korjattu vanha bugi: sisällysluettelon linkit alleviivautuivat, koska
.toc a ja .prose a ovat samalla spesifisyydellä ja jälkimmäinen tulee
tiedostossa myöhemmin. Aiempi korjaus ei siis koskaan purrut. Valitsin on nyt
.prose .toc a, ja regressiotesti lisätty.
Visuaalinen ilme yksinkertaisemmaksi ja sivuston nimi vaihdettu:
- Nimi: sivusto on nyt "Lastentautien artikkelien viikkokooste". Nimi tulee
yhdestä paikasta (
BRANDrender.py:ssä) ja välittyy otsikoihin, navigaatioon, alatunnisteeseen, PDF-nimiöön ja RSS-syötteeseen; yksikkötesti estää nimen kovakoodaamisen templateihin. - Korjattu näkyvä bugi: etusivun otsikossa oli
<br>, jonka mobiilisääntö piilotti — sanat valuivat yhteen ("Lastentautienviikkokatsaus") ja katkesivat kesken sanan.<br>poistettu ja regressiotesti lisätty. - Pienempi ja yksinkertaisempi typografia: perusfontti 17 px → 15 px, etusivun pääotsikko 54 px → 30 px, koostesivun otsikko 44 px → 27 px. Luovuttiin serif+sans-yhdistelmästä: koko sivusto käyttää nyt yhtä järjestelmäfonttia ja hierarkia syntyy koosta, painosta ja väristä.
- Rauhallisempi ilme: poistettu koristepilleri ja tilastolohko herosta (yksi hillitty metarivi tilalle), kevyemmät reunat ja pinnat, ei varjoja, pienempi pyöristys. Etusivu mahtuu nyt yhdelle mobiiliruudulle.
- Koostesivun otsikkona viikko (esim. "Viikko 30 / 2026") mallin joka viikko samanlaisen geneerisen otsikon sijaan; päällekkäinen viikkotunniste poistettu.
- Sanasto yhtenäistetty: "katsaus" → "kooste" käyttöliittymässä.
Sivuston rakenne, ulkoasu ja tyyli uudistettu ammattimaisemmaksi:
- Rakenne. Katsaussivulle sisällysluettelo ("Sisällys") ja ankkuri-id:t kaikkiin
osioihin. Korjattu vakava luettavuusongelma: "Muut julkaisut" -osiossa jopa 16
artikkelia niputtui yhdeksi kappaleeksi (markdown yhdistää peräkkäiset rivit) —
nyt jokainen artikkeli on oma merkintänsä ja sen otsikko omalla rivillään
(
split_entry_lines,classify_paragraphs). - Etusivu. Uusin katsaus nostettuna omaan korttiinsa; arkisto kompakteina vuosittain ryhmiteltyinä riveinä (skaalautuu, kun katsauksia kertyy).
- Tyyli.
site/styles.csskirjoitettu uudelleen design-järjestelmänä: tokenit, sujuva (clamp) tyyppiasteikko, serif-otsikot + sans-leipäteksti, hillitty paletti vaalealle ja tummalle tilalle. Kaikki tekstikontrastit läpäisevät WCAG AA:n ja interaktiivisten elementtien rajat 1.4.11:n (3:1). - Saavutettavuus. "Hyppää sisältöön" -linkki,
aria-currentnavigaatiossa,aria-labelit, näkyvät fokusrenkaat,scroll-paddingankkureille sekäprefers-reduced-motion-tuki. Ei vaakasuuntaista ylivuotoa 320–1280 px. - Tietoja-sivu jäsennelty: numeroidut vaiheet ja lehdet ryhmäkortteina.
- PDF ja tulostus. PDF-pohjaan nimiö, sivunumerot ja selkeämmät osio-otsikot;
selaimen tulostukselle omat
@media print-säännöt. - PDF luodaan nyt ennen HTML-sivua, joten sivulle ei jää kuollutta latauslinkkiä.
- Sivustotunnus (
site/favicon.svg) — selaimen välilehdessä ei enää oletuskuvaketta eikä palvelimelle turhaa 404-pyyntöä. - Yhdeksän uutta yksikkötestiä rakennefunktioille (43 testiä yhteensä).
Riippumattoman tarkastuksen (REVIEW.md, kierros 2) löydökset korjattu:
- CI-korjaus: lint-/testityökalut kiinnitetty versioon
tests.yml:ssä (kiinnittämätön ruff ajautui 0.16.0:aan ja kaatoi CI:n ilman koodimuutosta) ja ruff 0.16.0:n 9 löydöstä korjattu (mm. tz-tietoiset päiväykset,subprocess.run(check=False), suoritusoikeudet, dedup-lukuvirheen lokitus). - Poistettu
data/2026-07-23/— julkaisemattoman hakuajon artefaktit (tyhjäsummary.md) saastuttivat dedup-historian: 25 julkaisematonta artikkelia olisi pudonnut hiljaa seuraavasta katsauksesta. - CHANGELOGin 1.2.1-merkinnän asiavirheet korjattu (H12: Vercel, ei GitHub Pages; H1/H2-kuvaukset) ja AGENTS.md:n testausohjeen muotoilu siistitty.
- Vanhentunut ANALYYSIRAPORTTI.md poistettu jo aiemmin (commit
03355be; sisältö korvautunut ANALYSIS.md + REVIEW.md -tiedostoilla) — kirjataan tässä, koska poisto tehtiin alun perin korjausscopen ulkopuolella.
Viimeistely (Erä 3/3):
- PubMed-sivutus (E-utilities) korjattu ehkäisemään ikuissilmukka, kun palautettu lista sisältää vain aiemmin käsiteltyjä osumia (T2).
- Prompt-injektiosuoja lisätty AI-tiivistysohjeisiin, jotta malli ei tottele artikkelidatan sisäisiä käskyjä (H11).
- Testikattavuutta parannettu puuttuneiden yksikkötestien osalta (H13).
- tekoälymallin vastauksen tyyppi ja
finishReason="STOP"pakotettu ennen tiivistyksen hyväksymistä (T1).
Analyysiraportin korjaukset (Erät 1–4):
- XSS-sanitointi: markdownin renderöinti puhdistaa HTML-koodin haittakehyksiltä (K1).
- Retraktoitujen julkaisujen suodatus lisätty hakuun (K2).
- Tiivistyksen luotettavuus: finishReason tarkistus tekoälymallin vastauksessa (T1).
- Deduplikointi ei luota enää hakuvaiheen osumalohkoihin. Tuplien siivous yhdistetty ja viedään loppuun asti (T2).
- Merkkisuoja: changelogin päivitys estää merkintöjen poiston epävalidilla syötteellä (T3).
- HTML/URL-escapaus: linkkien osoitteet renderöidään turvallisesti (T4).
- Ylimääräinen
articles.json-vientikopio poistettu, JSON:in lataus vaindata/-kansion katsaushakemistosta (T5). - Workflow-timeoutit: GitHub Actionsiin lisätty aikarajat jumiutuvien ajojen varalta (T6).
- Lokirivin päätepisteoletus korjattu genai:ksi (H1); viikkoajon COUNT-arvon numeerisuus validoidaan (H2).
- macOS-yhteensopivuus viikon numeron laskentaan bash-skriptissä (H3).
- Alustariippumaton
strftime-korvausrender.py:ssä (H4). - DOI-täsmäykseen sanarajojen tarkistus regexillä täydellisyysraportointia varten (H5).
- RSS-syöte (
feed.xml) on nyt 100% standardin mukainen (H6). - Tietoturva/kestävyys: GitHub Actions -toiminnot kiinnitetty SHA-tiivisteillä (H7).
- Prompt-injektiosuoja lisätty AI-tiivistysohjeisiin CLI- ja CI-polkuja varten (H11).
- Turvaotsakkeet (CSP, nosniff, Referrer-Policy) lisätty
vercel.json-konfiguraatioon (H12). - Työkalujen laatu- ja tyyppitarkistukset: Ruff ja Mypy lisätty CI-putkeen, tyyppivirheet korjattu (H13).
- Dependabot konfiguroitu päivittämään riippuvuuksia viikottain (J2).
- OpenGraph-metatiedot HTML-pohjissa (J4).
Jatkokehitys analyysiraportin pohjalta:
- Recall-mittari: haku kirjoittaa
data/<pvm>/coverage.json:n (esearch-osumat vs. haetut vs. säilytetyt, pudotukset syyn ja lehden mukaan) ja varoittaa lehdistä, joista putosi enemmän kuin säilyi. Tekee näkymättömästä recall-riskistä mitattavan. - RSS 2.0 -syöte (
site/feed.xml), generoidaan manifestista; linkitSITE_URL-muuttujasta. Syötelinkki lisätty etu- ja katsaussivujen<head>:iin. - Etusivun arkistonavigointi: katsaukset ryhmitellään vuosittain omiin osioihinsa.
- Uudet yksikkötestit recall-mittarille, RSS-syötteelle ja vuosiryhmittelylle.
- Siivous (H3): poistettu orpo
data/2026-07-18/(viikko 29:n aiempi, sittemmin2026-07-19:ään yhdistetty katsaus). Varmistettu ensin, että kaikki sen 39 PMID:iä sisältyvät jo2026-07-19/articles.json:iin, joten deduplikointihistoria säilyy ehjänä.
Analyysiraportin (ANALYYSIRAPORTTI.md) pohjalta korjattu löydöksiä:
- Turvallisuus: heartbeat-ping-URL pois
config/journals.json:ista →HEARTBEAT_URL(Actions-salaisuus / plist). API-avain query-parametristax-goog-api-key-otsikkoon. - Luotettavuus: PubMed-haku sivuttaa nyt kaikki osumat (
retstart) turvarajaan asti ja varoittaa äänekkäästi katkaisusta (ei enää hiljaista >500 osuman katkaisua). - E-utilities-retry erottaa ohimenevät (429/5xx/verkko) pysyvistä (4xx) virheistä.
- Riippuvuudet kiinnitetty
requirements.txt:iin (weasyprint, requests, markdown); workflow jasetup_venv.shasentavat siitä. TurhaGOOGLE_CLOUD_PROJECT_IDpoistettu. - tiivistysskriptin oletuspäätepiste ja dokumentaatio korjattu genai:ksi (ei Vertex).
- Lisätty offline-yksikkötestit (
tests/) ja testi-CI (.github/workflows/tests.yml).
Versiointi käyttöön: VERSION (semver) + tämä CHANGELOG.md.
Viikkoajo päivittää "Julkaisut"-osion automaattisesti ja renderöinti leimaa version sivun/PDF:n alatunnisteeseen. - Viikko 29:n kaksi katsausta yhdistetty yhdeksi (2026-07-19). - PDF-fontit: pääotsikko 14 pt, muu teksti 10 pt. - Toisen tekoälypalvelun viittaukset poistettu; tekoälymalli on ainoa dokumentoitu tiivistin, pilviajo (GitHub Actions) oletus. - Tiivistin vaihdettu genai-päätepisteeseen (Vertex ei hyväksy tavallista API-avainta) ja viikkoajo siirretty GitHub Actionsiin.