{
  "policy": {
    "version": 5,
    "patterns": {
      "version": 1,
      "method": "CURATED_PRACTICE_TOPIC",
      "notice": "Pattern topics identify design choices to practice. Read the ticket's acceptance criteria and justify the simplest suitable approach. Tags are not capability or ownership evidence; an untagged ticket has no curated pattern topic assigned."
    },
    "fieldMix": {
      "version": 1,
      "method": "CURATED_ESTIMATE",
      "notice": "Field percentages are editorial estimates of the ticket's engineering focus. They total 100%; they are not measured time, proficiency scores, or ownership evidence."
    },
    "contentStatus": "PRACTICE_BRIEF",
    "assessmentStatus": "NOT_QUALIFIED",
    "evidenceUse": "NONE",
    "aiPolicy": "AI tools are welcome during implementation. Record assumptions, review the result, and verify its behavior.",
    "notice": "Fictional engineering practice briefs. Starter repositories, fixtures, automated grading, and verified ownership are not included.",
    "outcomeEvidence": "Tests, patches, and runbooks are requested deliverables. They become Outcome Evidence only through a qualified Mission and immutable Evidence IDs.",
    "ownershipEvidence": "Independent adaptation must be observed under a declared verification policy and cite immutable Evidence IDs. Completing a planning ticket establishes no Ownership Evidence."
  },
  "patternTopics": [
    {
      "id": "factory-method",
      "label": "Factory Method",
      "group": "Creational"
    },
    {
      "id": "abstract-factory",
      "label": "Abstract Factory",
      "group": "Creational"
    },
    {
      "id": "builder",
      "label": "Builder",
      "group": "Creational"
    },
    {
      "id": "prototype",
      "label": "Prototype",
      "group": "Creational"
    },
    {
      "id": "singleton",
      "label": "Singleton",
      "group": "Creational"
    },
    {
      "id": "adapter",
      "label": "Adapter",
      "group": "Structural"
    },
    {
      "id": "bridge",
      "label": "Bridge",
      "group": "Structural"
    },
    {
      "id": "composite",
      "label": "Composite",
      "group": "Structural"
    },
    {
      "id": "decorator",
      "label": "Decorator",
      "group": "Structural"
    },
    {
      "id": "facade",
      "label": "Facade",
      "group": "Structural"
    },
    {
      "id": "flyweight",
      "label": "Flyweight",
      "group": "Structural"
    },
    {
      "id": "proxy",
      "label": "Proxy",
      "group": "Structural"
    },
    {
      "id": "chain-of-responsibility",
      "label": "Chain of Responsibility",
      "group": "Behavioral"
    },
    {
      "id": "command",
      "label": "Command",
      "group": "Behavioral"
    },
    {
      "id": "interpreter",
      "label": "Interpreter",
      "group": "Behavioral"
    },
    {
      "id": "iterator",
      "label": "Iterator",
      "group": "Behavioral"
    },
    {
      "id": "mediator",
      "label": "Mediator",
      "group": "Behavioral"
    },
    {
      "id": "memento",
      "label": "Memento",
      "group": "Behavioral"
    },
    {
      "id": "observer",
      "label": "Observer",
      "group": "Behavioral"
    },
    {
      "id": "state",
      "label": "State",
      "group": "Behavioral"
    },
    {
      "id": "strategy",
      "label": "Strategy",
      "group": "Behavioral"
    },
    {
      "id": "template-method",
      "label": "Template Method",
      "group": "Behavioral"
    },
    {
      "id": "visitor",
      "label": "Visitor",
      "group": "Behavioral"
    },
    {
      "id": "ports-and-adapters",
      "label": "Ports and Adapters",
      "group": "Architectural"
    },
    {
      "id": "cqrs",
      "label": "CQRS",
      "group": "Architectural"
    },
    {
      "id": "strangler-fig",
      "label": "Strangler Fig",
      "group": "Architectural"
    },
    {
      "id": "saga",
      "label": "Saga",
      "group": "Distributed and reliability"
    },
    {
      "id": "transactional-outbox",
      "label": "Transactional Outbox",
      "group": "Distributed and reliability"
    },
    {
      "id": "circuit-breaker",
      "label": "Circuit Breaker",
      "group": "Distributed and reliability"
    },
    {
      "id": "bulkhead",
      "label": "Bulkhead",
      "group": "Distributed and reliability"
    }
  ],
  "projects": [
    {
      "id": "7686971b-e040-4dad-9c1f-ef8c3a641873",
      "key": "TRIP",
      "title": "A commuter planner that explains uncertainty",
      "field": "Mobile",
      "summary": "Make saved journeys, departures, and service alerts useful when feeds or device permissions change.",
      "context": "The fictional Northline commuter app plans bus and rail trips from synthetic timetable and alert feeds. Riders save journeys and choose optional reminders. The scope excludes ticket purchases, safety guarantees, and continuous location collection.",
      "stack": [
        "Kotlin",
        "Jetpack Compose",
        "Room",
        "JUnit",
        "Android emulator"
      ],
      "prerequisites": [
        "Synthetic timetable with explicit time zones and service dates",
        "Departure, alert, and permission adapters with failure fixtures"
      ],
      "developerValue": "Practice temporal modeling, feed reconciliation, battery-conscious refresh, and mobile permission design.",
      "companyValue": "Review how an engineer communicates uncertainty and keeps a consumer app useful under unreliable data.",
      "delivery": "Work from fixed fixtures and an emulator. Deliver individual issues or three-to-four focused phase increments.",
      "phases": [
        {
          "id": "journey",
          "title": "Make journeys readable",
          "goal": "Present stable saved trips and correct service times."
        },
        {
          "id": "feeds",
          "title": "Handle changing feeds",
          "goal": "Keep live data, cancellations, and stale results coherent."
        },
        {
          "id": "device",
          "title": "Respect device conditions",
          "goal": "Handle lifecycle, reminders, and permissions deliberately."
        },
        {
          "id": "launch",
          "title": "Prepare a measured release",
          "goal": "Explain limitations and verify the complete journey."
        }
      ],
      "tickets": [
        {
          "id": "ed34c934-1140-4842-9274-d388c6bd3929",
          "key": "TRIP-101",
          "title": "Give saved journeys names that survive station renames",
          "type": "STORY",
          "priority": "LOW",
          "difficulty": "FOUNDATIONAL",
          "estimateMinutes": 60,
          "phaseId": "journey",
          "dependsOn": [],
          "scenario": "Central Station becomes Central Exchange in a feed update and a saved commute disappears. Save journeys by station identity and let riders keep a personal label.",
          "acceptanceCriteria": [
            "Saved journeys use stable station IDs and optional rider labels.",
            "Renaming a station updates its display without duplicating the journey.",
            "A removed station shows an unavailable state with an edit action instead of silently deleting the journey."
          ],
          "implementationNotes": [
            "Limit labels and render them as text; no account sync is required."
          ],
          "verification": [
            "Save Home to Office and apply a station-name update; identity and label remain.",
            "Remove a referenced station and reload; the saved journey remains editable with an unavailable endpoint."
          ],
          "deliverables": [
            "Saved-journey model and station-update fixture tests"
          ],
          "rollout": "Migrate a copied local fixture database first; retain original IDs if label migration fails.",
          "skills": [
            "Data modeling",
            "Local persistence",
            "Migration"
          ],
          "fieldMix": [
            {
              "field": "Mobile",
              "percentage": 80
            },
            {
              "field": "Database engineering",
              "percentage": 20
            }
          ],
          "patterns": []
        },
        {
          "id": "44507917-89cc-4ad4-b2fe-5d0f9fa14037",
          "key": "TRIP-102",
          "title": "Keep the last train on the correct service day",
          "type": "BUG",
          "priority": "HIGH",
          "difficulty": "ADVANCED",
          "estimateMinutes": 165,
          "phaseId": "journey",
          "dependsOn": [],
          "scenario": "A Saturday service departing after midnight appears under Sunday and disappears from the late-night search. The feed expresses service-day times beyond 24:00.",
          "acceptanceCriteria": [
            "Service date and elapsed service-day time map to an instant using the feed time zone.",
            "Display uses rider-friendly local date/time while retaining the original service identity.",
            "Unsupported or ambiguous feed times produce an explicit data error rather than a guessed departure."
          ],
          "implementationNotes": [
            "Write the conversion policy for daylight-saving transitions; do not parse service times as ordinary clock-only strings."
          ],
          "verification": [
            "Resolve a Saturday 25:10 service to the expected next-calendar-day instant and keep Saturday service identity.",
            "Exercise the agreed clock-change fixtures and malformed time input; no negative journey duration or silent guess appears."
          ],
          "deliverables": [
            "Service-time conversion module and boundary fixture table"
          ],
          "rollout": "Compare converted times against curated synthetic examples; hide affected feed entries if conversion fails.",
          "skills": [
            "Time zones",
            "Domain modeling",
            "Boundary testing"
          ],
          "fieldMix": [
            {
              "field": "Mobile",
              "percentage": 80
            },
            {
              "field": "Integrations",
              "percentage": 20
            }
          ],
          "patterns": []
        },
        {
          "id": "ffa4e66f-611a-49c8-9628-a36ec72f2d3a",
          "key": "TRIP-103",
          "title": "Explain transfers without relying on line colors",
          "type": "TASK",
          "priority": "MEDIUM",
          "difficulty": "FOUNDATIONAL",
          "estimateMinutes": 75,
          "phaseId": "journey",
          "dependsOn": [
            "TRIP-102"
          ],
          "scenario": "The journey card distinguishes Line 4 from Line 8 only by color. Riders using grayscale or a screen reader cannot tell where to change.",
          "acceptanceCriteria": [
            "Each leg shows mode, line label, destination, and boarding/alighting stops in reading order.",
            "Transfers state walking and waiting time in text.",
            "Large text and grayscale preserve every required leg distinction."
          ],
          "implementationNotes": [
            "Keep decorative map graphics out of the essential accessible description."
          ],
          "verification": [
            "Read a two-transfer fixture with assistive navigation and compare spoken order to itinerary order.",
            "Use grayscale and large text; a canceled leg must remain identifiable without its color or icon."
          ],
          "deliverables": [
            "Accessible journey-leg card and device walkthrough"
          ],
          "rollout": "Release text semantics with the existing card layout first; revert visual changes independently if leg details become clipped.",
          "skills": [
            "Mobile accessibility",
            "Information design",
            "Compose"
          ],
          "fieldMix": [
            {
              "field": "Accessibility",
              "percentage": 60
            },
            {
              "field": "Mobile",
              "percentage": 40
            }
          ],
          "patterns": []
        },
        {
          "id": "4eb7717d-eba1-4e27-9230-45b566675793",
          "key": "TRIP-104",
          "title": "Label stale departures when the live feed stops responding",
          "type": "STORY",
          "priority": "HIGH",
          "difficulty": "INTERMEDIATE",
          "estimateMinutes": 105,
          "phaseId": "feeds",
          "dependsOn": [
            "TRIP-102"
          ],
          "scenario": "The departures screen keeps displaying an old prediction after a feed outage. A rider mistakes a 12-minute-old estimate for a live arrival.",
          "acceptanceCriteria": [
            "Predictions show source update time and become stale at the configured fixture threshold.",
            "Scheduled times remain available but are visibly distinguished from current predictions.",
            "A failed refresh preserves readable departures and provides a retry action."
          ],
          "implementationNotes": [
            "Calculate staleness from trusted feed timestamps with a documented clock-skew allowance."
          ],
          "verification": [
            "Advance a fake clock across the freshness threshold and inspect the label change.",
            "Return a future-dated update beyond allowed skew and then fail refresh; do not display it as trustworthy live data."
          ],
          "deliverables": [
            "Departure freshness policy and fake-clock tests"
          ],
          "rollout": "Enable stale labels for one synthetic feed; disable live prediction display if timestamps cannot be validated.",
          "skills": [
            "Freshness modeling",
            "Error recovery",
            "Time handling"
          ],
          "fieldMix": [
            {
              "field": "Mobile",
              "percentage": 60
            },
            {
              "field": "Real-time systems",
              "percentage": 40
            }
          ],
          "patterns": []
        },
        {
          "id": "9a566373-aee9-406d-91be-d238102af747",
          "key": "TRIP-105",
          "title": "Apply cancellation updates to the right departure instance",
          "type": "BUG",
          "priority": "HIGH",
          "difficulty": "ADVANCED",
          "estimateMinutes": 150,
          "phaseId": "feeds",
          "dependsOn": [
            "TRIP-104"
          ],
          "scenario": "Canceling the 08:10 Line 4 also marks tomorrow’s 08:10 as canceled because the cache key contains only the route. Reconcile alerts by departure identity.",
          "acceptanceCriteria": [
            "Cancellation identity includes service date and trip instance rather than route label alone.",
            "A newer correction can restore a departure while an older cancellation cannot overwrite it.",
            "Unknown alert references remain diagnosable without canceling unrelated trips."
          ],
          "implementationNotes": [
            "Define ordering from feed revision metadata, not arrival order on the device."
          ],
          "verification": [
            "Cancel today’s instance and verify tomorrow’s matching route stays available.",
            "Deliver correction and cancellation out of order; the newest revision wins and unknown IDs change no journey."
          ],
          "deliverables": [
            "Departure alert reducer and ordering regression tests"
          ],
          "rollout": "Shadow-reconcile synthetic alerts before display; fall back to a general service notice if precise mapping is unavailable.",
          "skills": [
            "Event ordering",
            "Cache identity",
            "Data reconciliation"
          ],
          "fieldMix": [
            {
              "field": "Mobile",
              "percentage": 50
            },
            {
              "field": "Real-time systems",
              "percentage": 30
            },
            {
              "field": "Data engineering",
              "percentage": 20
            }
          ],
          "patterns": []
        },
        {
          "id": "3367b22b-0ab5-4665-9ff0-9e29e527bf4a",
          "key": "TRIP-106",
          "title": "Stop refreshing a journey after the rider leaves it",
          "type": "BUG",
          "priority": "MEDIUM",
          "difficulty": "INTERMEDIATE",
          "estimateMinutes": 105,
          "phaseId": "device",
          "dependsOn": [
            "TRIP-104"
          ],
          "scenario": "Opening several saved journeys starts several permanent refresh loops. The app continues polling all of them in the background and occasionally displays the wrong result.",
          "acceptanceCriteria": [
            "Only the visible journey owns an active foreground refresh loop.",
            "Backgrounding pauses polling and foreground return performs one bounded freshness check.",
            "Late results from a departed journey cannot update the active journey."
          ],
          "implementationNotes": [
            "Use lifecycle-aware cancellation plus request identity; do not request a background location service for refresh."
          ],
          "verification": [
            "Switch among three journeys and inspect that only one loop remains active.",
            "Background during an in-flight request and return after expiry; exactly one fresh check occurs and stale results are ignored."
          ],
          "deliverables": [
            "Lifecycle-aware refresh coordinator and request-count tests"
          ],
          "rollout": "Pilot with a short foreground session trace; disable automatic refresh if loop ownership regresses and keep manual refresh.",
          "skills": [
            "Mobile lifecycle",
            "Resource management",
            "Async state"
          ],
          "fieldMix": [
            {
              "field": "Mobile",
              "percentage": 100
            }
          ],
          "patterns": []
        },
        {
          "id": "30f65da3-6197-49f2-9515-9a28f1558a31",
          "key": "TRIP-107",
          "title": "Make departure reminders honest about permission and schedule changes",
          "type": "STORY",
          "priority": "MEDIUM",
          "difficulty": "ADVANCED",
          "estimateMinutes": 165,
          "phaseId": "device",
          "dependsOn": [
            "TRIP-105",
            "TRIP-106"
          ],
          "scenario": "A rider enables a reminder after denying notifications, then assumes an alert will arrive. Rescheduled departures also leave the old reminder behind.",
          "acceptanceCriteria": [
            "Reminder state distinguishes requested, scheduled, denied, canceled, and expired.",
            "Changing the departure revision cancels the old schedule before confirming its replacement.",
            "The app offers an in-app alternative when device notification capability is unavailable."
          ],
          "implementationNotes": [
            "Request permission only after an explicit reminder action and disclose the limits of delivery timing."
          ],
          "verification": [
            "Allow notifications, schedule a reminder, then change departure time; one current schedule remains.",
            "Deny permission and cancel the trip during rescheduling; no successful reminder claim or orphan schedule remains."
          ],
          "deliverables": [
            "Reminder state coordinator and permission-change cases"
          ],
          "rollout": "Pilot opt-in reminders on emulators and test devices; disable new scheduling if duplicate reminders are observed.",
          "skills": [
            "Permissions",
            "Scheduling",
            "State machines"
          ],
          "fieldMix": [
            {
              "field": "Mobile",
              "percentage": 100
            }
          ],
          "patterns": []
        },
        {
          "id": "4f920124-7d04-4729-ab57-762067cebae5",
          "key": "TRIP-108",
          "title": "Recover a route search when the timetable changes mid-session",
          "type": "BUG",
          "priority": "HIGH",
          "difficulty": "EXPERT",
          "estimateMinutes": 210,
          "phaseId": "device",
          "dependsOn": [
            "TRIP-101",
            "TRIP-105",
            "TRIP-106"
          ],
          "scenario": "A timetable refresh replaces station and trip data while a route calculation still reads the old version. The result combines a new platform with a removed connection.",
          "acceptanceCriteria": [
            "A search reads one timetable snapshot/version for its entire calculation.",
            "A result from an obsolete snapshot is explicitly revalidated or replaced before being labeled current.",
            "Interrupted dataset replacement leaves either the old complete dataset or the new complete dataset available."
          ],
          "implementationNotes": [
            "Use synthetic small datasets and a local transaction; implementing a nationwide routing engine is excluded."
          ],
          "verification": [
            "Swap timetable versions during calculation and verify every returned leg belongs to one version.",
            "Fail replacement halfway through and restart; searches use a complete version and unavailable connections are not invented."
          ],
          "deliverables": [
            "Versioned timetable handoff and interrupted-update tests"
          ],
          "rollout": "Stage updates beside the active dataset; switch the active pointer back if validation fails and retain prior-version diagnostics.",
          "skills": [
            "Snapshot consistency",
            "Transactions",
            "Offline data"
          ],
          "fieldMix": [
            {
              "field": "Mobile",
              "percentage": 60
            },
            {
              "field": "Database engineering",
              "percentage": 40
            }
          ],
          "patterns": []
        },
        {
          "id": "1a1bc5bb-5020-444e-8b59-3f375ecf8189",
          "key": "TRIP-109",
          "title": "Offer nearby stops without making location mandatory",
          "type": "STORY",
          "priority": "MEDIUM",
          "difficulty": "INTERMEDIATE",
          "estimateMinutes": 105,
          "phaseId": "launch",
          "dependsOn": [
            "TRIP-101",
            "TRIP-103"
          ],
          "scenario": "The first screen demands location before showing any journeys. A rider who declines cannot search by station even though the timetable supports it.",
          "acceptanceCriteria": [
            "Station search and saved journeys work without location permission.",
            "Nearby stops request location only after an explicit action and show how approximate results are used.",
            "Denial, timeout, and unavailable location return to a usable manual search without repeated prompts."
          ],
          "implementationNotes": [
            "Use a one-shot coarse-location adapter and do not persist coordinates in generic analytics."
          ],
          "verification": [
            "Deny location on first use and complete a manual station search.",
            "Return an approximate fix and then a timeout; nearby results disclose approximation and manual search remains accessible."
          ],
          "deliverables": [
            "Optional nearby-stop flow and permission-free journey check"
          ],
          "rollout": "Release manual search before nearby suggestions; disable the location adapter if denial recovery or privacy checks fail.",
          "skills": [
            "Permission design",
            "Privacy",
            "Mobile UX"
          ],
          "fieldMix": [
            {
              "field": "Privacy engineering",
              "percentage": 60
            },
            {
              "field": "Mobile",
              "percentage": 40
            }
          ],
          "patterns": []
        },
        {
          "id": "54e2f5b1-99a4-468b-b05c-ff4e0a892825",
          "key": "TRIP-110",
          "title": "Rehearse a disrupted commute with fixed feed data",
          "type": "CHORE",
          "priority": "MEDIUM",
          "difficulty": "ADVANCED",
          "estimateMinutes": 135,
          "phaseId": "launch",
          "dependsOn": [
            "TRIP-107",
            "TRIP-108",
            "TRIP-109"
          ],
          "scenario": "Most checks cover a clean daytime trip. Before the pilot, rehearse a saved late-night commute through feed outage, cancellation correction, and app restart.",
          "acceptanceCriteria": [
            "A deterministic scenario records which timetable revision, departure identity, and freshness label appear at each step.",
            "Restart preserves the saved journey without restoring an obsolete reminder.",
            "The handoff states fixture coverage and unresolved live-feed assumptions explicitly."
          ],
          "implementationNotes": [
            "Use a controllable clock and adapters; do not depend on actual transit services or notification delivery."
          ],
          "verification": [
            "Run the rehearsal twice and compare the ordered visible states.",
            "Inject an invalid feed timestamp and an interrupted dataset update; no current-live label or mixed-version trip appears."
          ],
          "deliverables": [
            "Disruption rehearsal script and pilot support checklist"
          ],
          "rollout": "Require the fixed-data rehearsal before app updates; pause pilot expansion on misleading freshness or reminder state.",
          "skills": [
            "Scenario testing",
            "Release readiness",
            "Operational handoff"
          ],
          "fieldMix": [
            {
              "field": "Quality engineering",
              "percentage": 60
            },
            {
              "field": "Mobile",
              "percentage": 40
            }
          ],
          "patterns": []
        }
      ]
    }
  ]
}
