diff --git a/messages/en.json b/messages/en.json index f5b37fd7..b9014266 100644 --- a/messages/en.json +++ b/messages/en.json @@ -75,10 +75,10 @@ }, "5": { "name": "Cable tram", - "description": "Used for street-level rail cars where the cable runs beneath the vehicle (e.g., cable car in San Francisco)." + "description": "Used for street-level rail cars where the cable runs beneath the vehicle (e.g. cable car in San Francisco)." }, "6": { - "name": "Aerial lift/Suspended cable car (e.g., gondola lift, aerial tramway)", + "name": "Aerial lift/Suspended cable car (e.g. gondola lift, aerial tramway)", "description": "Cable transport where cabins, cars, gondolas or open chairs are suspended by means of one or more cables." }, "7": { @@ -609,7 +609,7 @@ "hero": { "title": "The Benchmark for Reliable GTFS Schedule Data", "paragraphs": [ - "It's hard to measure if data is trustworthy. Transit agencies struggle to know if their GTFS data works for riders; data vendors lack a baseline of quality to aspire to, and trip planning apps end up spending time pre-processing feeds rather than seamlessly consuming them", + "It's hard to measure if data is trustworthy. Transit agencies struggle to know if their GTFS data works for riders; data vendors lack a baseline of quality to aspire to, and trip planning apps end up spending too much time pre-processing feeds rather than seamlessly consuming them", "That's where the MobilityData Seal of Reliability comes in. As a clear measure of baseline quality, the Seal of Reliability is applied to feeds that meet five key criteria: they're official, stable, available, compliant, and fresh." ], "ctaButton": "See a feed measured against the standard (todo)", @@ -658,32 +658,32 @@ "official": { "title": "Official", "subtitle": "Authorized by the transit agency.", - "description": "The feed has been confirmed as an official source, published by or on behalf of the transit agency." + "description": "A feed is Official on the Mobility Database when it has been manually confirmed. Confirmation is based on the producer URL domain, submitter information, the agency's website, and the feed_info.txt files." }, "stable": { "title": "Stable", - "subtitle": "The download URL doesn't change", - "description": "For at least six uninterrupted months, the same URL serves the feed, and the URL appears to be permanent." + "subtitle": "The feed URL is stable and permanent.", + "description": "A feed is Stable when the same URL has served the feed for at least six uninterrupted months, and the URL does not contain date, time, or seasonal markers that indicate a temporary path." }, "available": { "title": "Available", - "subtitle": "Can be downloaded from the URL each day.", - "description": "Once a day, the Mobility Database fetches the feed and downloads it cleanly." + "subtitle": "Can be downloaded each day.", + "description": "Once per day, the Mobility Database attempts to fetch the feed. A fetch is considered successful when the server returns a valid response, including redirects that resolve to the feed." }, "compliant": { "title": "Compliant", - "subtitle": "Adheres to every \"must\" in the General Transit Feed Specification.", - "description": "The feed has 0 errors in the Official GTFS Schedule Validator. Warnings and info notices do not affect compliance." + "subtitle": "The feed passes the Official GTFS Schedule Validator with zero errors.", + "description": "The Validator checks conformance with every \"must\" requirement in the GTFS specification. A single error triggers a violation. Warnings and info notices do not affect compliance." }, "freshRolling": { "title": "Fresh: Rolling 7 Days of Coverage", - "subtitle": "Always includes 7 days or more of service coverage.", - "description": "Following GTFS Best Practices, the feed provides at least 7 days of rolling future service coverage. This means at least a full week of service is covered every day it is fetched." + "subtitle": "At least 7 days of future service coverage, checked daily.", + "description": "Following GTFS Best Practices, the feed should provide at least 7 days of rolling future service coverage. The last date in the calendar files must be at least 7 days beyond the download date." }, "freshContinuous": { "title": "Fresh: Continuous Coverage", "subtitle": "A realistic service window with no gaps between feed versions.", - "description": "Service coverage is continuous across subsequent feed versions with no gaps, and the service window spans two years or less." + "description": "Service coverage must be continuous across consecutive feed versions, and the total service window must not exceed two years. If v1 ends Aug 31, v2 must begin no later than Sept 1. A Sept 2 start is a gap." } }, "gracePeriods": { @@ -709,7 +709,7 @@ "consequence": "If a feed cannot be downloaded, producers have 14 days to restore access. After 14 days, if the feed remains unavailable, the feed loses its Seal." }, "freshCoverage": { - "criterion": "Fresh (7‑day coverage)", + "criterion": "Fresh: Rolling 7 Days of Coverage", "triggerCondition": "Coverage gap", "gracePeriod": "14 days", "consequence": "If a feed stops covering far enough into the future, producers have 14 days to publish an update before the feed loses its Seal." @@ -732,6 +732,14 @@ "riskAwareness": { "question": "As a data producer, how do I know if I'm at risk of losing the Seal?", "answer": "The best way is to check your feed on the Mobility Database 1-2 times a month to see your Seal status. Our team is working on email notifications, so in the future you can be notified when you've entered a grace period." + }, + "thresholds": { + "question": "How did you select the thresholds for each criterion and grace period?", + "answer": "Thresholds were determined based on a mix of GTFS Best Practices review, examining real world data, and consulting consumers. For example, we decided on the grace period lengths based on reviewing the average time it takes for producers to correct the data evaluated on the Mobility Database. We have aimed to strike a balance between setting an ambitious bar for producers that promotes higher quality data, and setting goals that transit agencies can feasibly reach." + }, + "freshRolling7Days": { + "question": "Why did you choose 7 days for the Fresh: Rolling Coverage criterion?", + "answer": "7 days was selected for two reasons: it is in the minimum threshold already specified in GTFS for service coverage, and upon reviewing international data, several high-ridership transit agencies failed higher test thresholds. This indicated that a higher threshold, like 14 or 30 days, may not be feasible with operational timelines for transit agencies." } }, "howItIsCalculated": { @@ -757,25 +765,46 @@ "official": { "title": "Official", "paragraphs": [ - "A feed is Official when MobilityData has confirmed it is authorized by the transit agency. This means it is published by, or on behalf of, the agency itself. MobilityData assesses this based on the domain of the producer URL, the feed submission contributor's personal information, the agency's website, and the `feed_info.txt` file. This determination is made manually and does not change based on automated checks.", - "Unlike the other five criteria, Official status is not subject to the six-month probation." + "A feed is Official when MobilityData has confirmed it is authorized by the transit agency. This means it is published by, or on behalf of, the agency itself.", + "This determination is made manually and does not change based on automated checks.", + "The Official status is not subject to the six-month probation." ], - "notice": "There is no grace period for Official. The designation is either confirmed or it is not." + "notice": "There is no grace period for Official. The designation is either confirmed or it is not.", + "assessmentIntro": "MobilityData assesses this based on:", + "assessmentItems": [ + "The domain of the producer URL", + "The feed submission contributor’s personal information", + "The agency’s website", + "The `feed_info.txt` file" + ] }, "stable": { "title": "Stable", "description": "The Mobility Database evaluates stability based on the producer URL. A feed is considered Stable when:", "items": [ - "The same URL has served the feed without redirects for at least six months", + "The same URL has served the feed for at least six months", "The URL appears to be permanent, meaning it does not look like a temporary file path with dates, times or seasons" ], - "notice": "There is no grace period for Stable. If the URL changes, the criterion fails immediately and the six-month probation restarts from the date the new URL is added as a new feed." + "notice": "There is no grace period for Stable. If the URL changes, the criterion fails immediately and the six-month probation restarts from the date the new URL is added as a new feed.", + "urlPatternsIntro": "Examples of URL patterns that would fail:", + "urlPatterns": [ + "Dates in any format (e.g. https://opendata.bratislava.sk/dataset/download/cestovny-poriadok-20220725/1)", + "Seasonal slugs (e.g. `/summer-2024/`, `/winter-schedule/`)", + "Timestamps in the filename" + ], + "redirectNote": "A permanent redirect (301) from an old URL to a new one passes the Stable criterion." }, "available": { "title": "Available", - "description": "Once per day, the Mobility Database attempts to download the feed from its producer URL. A fetch is considered successful when the response returns an HTTP response in the 200 range.", + "description": "Once per day, the Mobility Database attempts to download the feed from its producer URL.", "noticeTitle": "14-day grace period.", - "notice": "If a fetch fails, producers have 14 days to restore access. If the feed is still unreachable after 14 days, the Seal is revoked." + "notice": "If a fetch fails, producers have 14 days to restore access. If the feed is still unreachable after 14 days, the Seal is revoked.", + "descriptionFetchIntro": "A fetch is considered successful when the response returns:", + "descriptionFetchItems": [ + "An HTTP response in the 200 range", + "A redirect (301, 302, 303, 307, or 308) that resolves to the feed at another URL", + "A 304 Not Modified response indicating the feed content is unchanged" + ] }, "compliant": { "title": "Compliant", @@ -818,7 +847,7 @@ }, "serviceWindow": { "title": "Two-year service window", - "description": "The total span of service dates in a dataset must not exceed two years. Feeds covering more than two years ahead are considered to contain stale, unreliable future data." + "description": "The total span of service dates in a dataset must not exceed two years. Feeds covering more than two years ahead contain unreliable future data." } }, "feedInfo": { diff --git a/messages/fr.json b/messages/fr.json index 9d75bfa4..48f77ba7 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -732,6 +732,14 @@ "riskAwareness": { "question": "En tant que producteur de données, comment savoir si je risque de perdre le Sceau ?", "answer": "La meilleure façon est de consulter le statut de votre flux sur la Mobility Database 1 à 2 fois par mois. Notre équipe travaille sur des notifications par courriel, afin qu'à l'avenir vous puissiez être averti dès l'entrée en période de grâce." + }, + "thresholds": { + "question": "How did you select the thresholds for each criteria and grace period?", + "answer": "Placeholder answer for threshold selection." + }, + "freshRolling7Days": { + "question": "Why did you choose 7 days for the freshness: rolling coverage criteria?", + "answer": "Placeholder answer for the 7-day freshness rolling coverage threshold." } }, "howItIsCalculated": { @@ -757,10 +765,18 @@ "official": { "title": "Officiel", "paragraphs": [ - "Un flux est Officiel lorsque MobilityData a confirmé qu'il est autorisé par l'agence de transport. Cela signifie qu'il est publié par l'agence elle-même ou en son nom. MobilityData évalue ce point à partir du domaine de l'URL du producteur, des informations personnelles du contributeur ayant soumis le flux, du site web de l'agence et du fichier `feed_info.txt`. Cette détermination est faite manuellement et ne change pas en fonction de vérifications automatisées.", + "Un flux est Officiel lorsque MobilityData a confirmé qu'il est autorisé par l'agence de transport. Cela signifie qu'il est publié par l'agence elle-même ou en son nom.", + "Cette détermination est faite manuellement et ne change pas en fonction de vérifications automatisées.", "Contrairement aux cinq autres critères, le statut Officiel n'est pas soumis à la période probatoire de six mois." ], - "notice": "Il n'y a pas de période de grâce pour le critère Officiel. La désignation est soit confirmée, soit elle ne l'est pas." + "notice": "Il n'y a pas de période de grâce pour le critère Officiel. La désignation est soit confirmée, soit elle ne l'est pas.", + "assessmentIntro": "MobilityData évalue ce point à partir de :", + "assessmentItems": [ + "Le domaine de l'URL du producteur", + "Les informations personnelles du contributeur ayant soumis le flux", + "Le site web de l'agence", + "Le fichier `feed_info.txt`" + ] }, "stable": { "title": "Stable", @@ -769,13 +785,26 @@ "La même URL sert le flux sans redirection depuis au moins six mois", "L'URL semble permanente, c'est-à-dire qu'elle ne ressemble pas à un chemin de fichier temporaire comportant des dates, des heures ou des saisons" ], - "notice": "Il n'y a pas de période de grâce pour le critère Stable. Si l'URL change, le critère échoue immédiatement et la période probatoire de six mois repart à la date à laquelle la nouvelle URL est ajoutée en tant que nouveau flux." + "notice": "Il n'y a pas de période de grâce pour le critère Stable. Si l'URL change, le critère échoue immédiatement et la période probatoire de six mois repart à la date à laquelle la nouvelle URL est ajoutée en tant que nouveau flux.", + "urlPatternsIntro": "Exemples de modèles d'URL qui échoueraient :", + "urlPatterns": [ + "Des dates dans n'importe quel format (ex. : https://opendata.bratislava.sk/dataset/download/cestovny-poriadok-20220725/1)", + "Des slugs saisonniers (ex. : `/summer-2024/`, `/winter-schedule/`)", + "Des numéros de version ou des horodatages dans le nom du fichier" + ], + "redirectNote": "Une redirection permanente (301) d'une ancienne URL vers une nouvelle satisfait le critère Stable." }, "available": { "title": "Disponible", - "description": "Une fois par jour, la Mobility Database tente de télécharger le flux depuis l'URL de son producteur. Une récupération est considérée comme réussie lorsque la réponse HTTP se situe dans la plage 200.", + "description": "Une fois par jour, la Mobility Database tente de télécharger le flux depuis l'URL de son producteur.", "noticeTitle": "Période de grâce de 14 jours.", - "notice": "En cas d'échec de récupération, les producteurs disposent de 14 jours pour rétablir l'accès. Si le flux reste inaccessible après 14 jours, le Sceau est retiré." + "notice": "En cas d'échec de récupération, les producteurs disposent de 14 jours pour rétablir l'accès. Si le flux reste inaccessible après 14 jours, le Sceau est retiré.", + "descriptionFetchIntro": "Une récupération est considérée comme réussie lorsque la réponse retourne :", + "descriptionFetchItems": [ + "Une réponse HTTP dans la plage 200", + "Une redirection (301, 302, 303, 307 ou 308) qui résout vers le flux à une autre URL", + "Une réponse 304 Non Modifié indiquant que le contenu du flux est inchangé" + ] }, "compliant": { "title": "Conforme", diff --git a/src/app/[locale]/seal-of-reliability/components/SealOfReliabilityDescriptionPage.tsx b/src/app/[locale]/seal-of-reliability/components/SealOfReliabilityDescriptionPage.tsx index cbef1c91..33c2afb6 100644 --- a/src/app/[locale]/seal-of-reliability/components/SealOfReliabilityDescriptionPage.tsx +++ b/src/app/[locale]/seal-of-reliability/components/SealOfReliabilityDescriptionPage.tsx @@ -33,7 +33,7 @@ import { heroContent, } from '../lib/content'; -const FEEDS_MEETING_STANDARD_PERCENT = 41; +const FEEDS_MEETING_STANDARD_PERCENT = 36; const FEEDS_BELOW_STANDARD_PERCENT = 100 - FEEDS_MEETING_STANDARD_PERCENT; export default async function SealOfReliabilityDescriptionPage(): Promise { diff --git a/src/app/[locale]/seal-of-reliability/how-it-is-calculated/components/HowItIsCalculatedPage.tsx b/src/app/[locale]/seal-of-reliability/how-it-is-calculated/components/HowItIsCalculatedPage.tsx index acc132a6..86674447 100644 --- a/src/app/[locale]/seal-of-reliability/how-it-is-calculated/components/HowItIsCalculatedPage.tsx +++ b/src/app/[locale]/seal-of-reliability/how-it-is-calculated/components/HowItIsCalculatedPage.tsx @@ -158,7 +158,14 @@ export default async function HowItIsCalculatedPage(): Promise { {t('official.title')} - {officialParagraphs.map((paragraph) => ( + + + + + {t('official.assessmentIntro')} + + + {officialParagraphs.slice(1).map((paragraph) => ( @@ -175,6 +182,11 @@ export default async function HowItIsCalculatedPage(): Promise { {t('stable.description')} + + {t('stable.urlPatternsIntro')} + + + {t('stable.redirectNote')} {t('stable.notice')} @@ -188,6 +200,12 @@ export default async function HowItIsCalculatedPage(): Promise { {t('available.description')} + + {t('available.descriptionFetchIntro')} + + {t('available.noticeTitle')} {t('available.notice')} diff --git a/src/app/[locale]/seal-of-reliability/lib/content.tsx b/src/app/[locale]/seal-of-reliability/lib/content.tsx index 69239a8d..8c5a7065 100644 --- a/src/app/[locale]/seal-of-reliability/lib/content.tsx +++ b/src/app/[locale]/seal-of-reliability/lib/content.tsx @@ -150,4 +150,12 @@ export const faqEntries: FaqEntry[] = [ questionKey: 'faq.riskAwareness.question', answerKey: 'faq.riskAwareness.answer', }, + { + questionKey: 'faq.thresholds.question', + answerKey: 'faq.thresholds.answer', + }, + { + questionKey: 'faq.freshRolling7Days.question', + answerKey: 'faq.freshRolling7Days.answer', + }, ];